diff --git a/app/build.gradle b/app/build.gradle index 825e6a125a..a33001308a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { applicationId "chat.rocket.android" minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk - versionCode 2067 - versionName "3.3.3" + versionCode 2068 + versionName "3.4.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true @@ -204,4 +204,4 @@ task compileSdk(type: Exec) { preBuild.dependsOn compileSdk if (isPlay) { apply plugin: 'com.google.gms.google-services' -} +} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/10.json b/app/schemas/chat.rocket.android.db.RCDatabase/10.json deleted file mode 100644 index a17af93dbf..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/10.json +++ /dev/null @@ -1,1081 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 10, - "identityHash": "db46c12dbb8747200288f48d5dc5558b", - "entities": [ - { - "tableName": "users", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "username", - "columnName": "username", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "utcOffset", - "columnName": "utcOffset", - "affinity": "REAL", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_users_username", - "unique": false, - "columnNames": [ - "username" - ], - "createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "chatrooms", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "subscriptionId", - "columnName": "subscriptionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fullname", - "columnName": "fullname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ownerId", - "columnName": "ownerId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "readonly", - "columnName": "readonly", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDefault", - "columnName": "isDefault", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "open", - "columnName": "open", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "alert", - "columnName": "alert", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userMentions", - "columnName": "userMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "groupMentions", - "columnName": "groupMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastSeen", - "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastMessageText", - "columnName": "lastMessageText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageUserId", - "columnName": "lastMessageUserId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageTimestamp", - "columnName": "lastMessageTimestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "broadcast", - "columnName": "broadcast", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_chatrooms_userId", - "unique": false, - "columnNames": [ - "userId" - ], - "createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)" - }, - { - "name": "index_chatrooms_ownerId", - "unique": false, - "columnNames": [ - "ownerId" - ], - "createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)" - }, - { - "name": "index_chatrooms_subscriptionId", - "unique": true, - "columnNames": [ - "subscriptionId" - ], - "createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)" - }, - { - "name": "index_chatrooms_updatedAt", - "unique": false, - "columnNames": [ - "updatedAt" - ], - "createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)" - }, - { - "name": "index_chatrooms_lastMessageUserId", - "unique": false, - "columnNames": [ - "lastMessageUserId" - ], - "createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)" - } - ], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "ownerId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "lastMessageUserId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `roomId` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `senderId` TEXT, `updatedAt` INTEGER, `editedAt` INTEGER, `editedBy` TEXT, `senderAlias` TEXT, `avatar` TEXT, `type` TEXT, `groupable` INTEGER NOT NULL, `parseUrls` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `role` TEXT, `synced` INTEGER NOT NULL, `unread` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`senderId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`editedBy`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "message", - "columnName": "message", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "senderId", - "columnName": "senderId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedAt", - "columnName": "editedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedBy", - "columnName": "editedBy", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "senderAlias", - "columnName": "senderAlias", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "avatar", - "columnName": "avatar", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "groupable", - "columnName": "groupable", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "parseUrls", - "columnName": "parseUrls", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "pinned", - "columnName": "pinned", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "role", - "columnName": "role", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "synced", - "columnName": "synced", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "senderId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "editedBy" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_favorites", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_mentions", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_channels", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `roomId` TEXT NOT NULL, `roomName` TEXT, PRIMARY KEY(`messageId`, `roomId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomName", - "columnName": "roomName", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "roomId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "attachments", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `message_id` TEXT NOT NULL, `title` TEXT, `type` TEXT, `description` TEXT, `text` TEXT, `author_name` TEXT, `author_icon` TEXT, `author_link` TEXT, `thumb_url` TEXT, `color` TEXT, `fallback` TEXT, `title_link` TEXT, `title_link_download` INTEGER NOT NULL, `image_url` TEXT, `image_type` TEXT, `image_size` INTEGER, `video_url` TEXT, `video_type` TEXT, `video_size` INTEGER, `audio_url` TEXT, `audio_type` TEXT, `audio_size` INTEGER, `message_link` TEXT, `timestamp` INTEGER, `has_actions` INTEGER NOT NULL, `has_fields` INTEGER NOT NULL, `button_alignment` TEXT, PRIMARY KEY(`_id`), FOREIGN KEY(`message_id`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "_id", - "columnName": "_id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "messageId", - "columnName": "message_id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "title", - "columnName": "title", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "description", - "columnName": "description", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "text", - "columnName": "text", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "authorName", - "columnName": "author_name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "authorIcon", - "columnName": "author_icon", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "authorLink", - "columnName": "author_link", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "thumbUrl", - "columnName": "thumb_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "color", - "columnName": "color", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "fallback", - "columnName": "fallback", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "titleLink", - "columnName": "title_link", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "titleLinkDownload", - "columnName": "title_link_download", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "imageUrl", - "columnName": "image_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageType", - "columnName": "image_type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageSize", - "columnName": "image_size", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "videoUrl", - "columnName": "video_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "videoType", - "columnName": "video_type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "videoSize", - "columnName": "video_size", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "audioUrl", - "columnName": "audio_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "audioType", - "columnName": "audio_type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "audioSize", - "columnName": "audio_size", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "messageLink", - "columnName": "message_link", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "hasActions", - "columnName": "has_actions", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "hasFields", - "columnName": "has_fields", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "buttonAlignment", - "columnName": "button_alignment", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "_id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "message_id" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "attachment_fields", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `attachmentId` TEXT NOT NULL, `title` TEXT NOT NULL, `value` TEXT NOT NULL, FOREIGN KEY(`attachmentId`) REFERENCES `attachments`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "attachmentId", - "columnName": "attachmentId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "title", - "columnName": "title", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "value", - "columnName": "value", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "indices": [ - { - "name": "index_attachment_fields_attachmentId", - "unique": false, - "columnNames": [ - "attachmentId" - ], - "createSql": "CREATE INDEX `index_attachment_fields_attachmentId` ON `${TABLE_NAME}` (`attachmentId`)" - } - ], - "foreignKeys": [ - { - "table": "attachments", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "attachmentId" - ], - "referencedColumns": [ - "_id" - ] - } - ] - }, - { - "tableName": "attachment_action", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `attachmentId` TEXT NOT NULL, `type` TEXT NOT NULL, `text` TEXT, `url` TEXT, `isWebView` INTEGER, `webViewHeightRatio` TEXT, `imageUrl` TEXT, `message` TEXT, `isMessageInChatWindow` INTEGER, FOREIGN KEY(`attachmentId`) REFERENCES `attachments`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "attachmentId", - "columnName": "attachmentId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "text", - "columnName": "text", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "url", - "columnName": "url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isWebView", - "columnName": "isWebView", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "webViewHeightRatio", - "columnName": "webViewHeightRatio", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageUrl", - "columnName": "imageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "message", - "columnName": "message", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isMessageInChatWindow", - "columnName": "isMessageInChatWindow", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "indices": [ - { - "name": "index_attachment_action_attachmentId", - "unique": false, - "columnNames": [ - "attachmentId" - ], - "createSql": "CREATE INDEX `index_attachment_action_attachmentId` ON `${TABLE_NAME}` (`attachmentId`)" - } - ], - "foreignKeys": [ - { - "table": "attachments", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "attachmentId" - ], - "referencedColumns": [ - "_id" - ] - } - ] - }, - { - "tableName": "urls", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`urlId` INTEGER PRIMARY KEY AUTOINCREMENT, `messageId` TEXT NOT NULL, `url` TEXT NOT NULL, `hostname` TEXT, `title` TEXT, `description` TEXT, `imageUrl` TEXT, FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "urlId", - "columnName": "urlId", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "url", - "columnName": "url", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hostname", - "columnName": "hostname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "title", - "columnName": "title", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "description", - "columnName": "description", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageUrl", - "columnName": "imageUrl", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "urlId" - ], - "autoGenerate": true - }, - "indices": [ - { - "name": "index_urls_messageId", - "unique": false, - "columnNames": [ - "messageId" - ], - "createSql": "CREATE INDEX `index_urls_messageId` ON `${TABLE_NAME}` (`messageId`)" - } - ], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "reactions", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`reaction` TEXT NOT NULL, `messageId` TEXT NOT NULL, `count` INTEGER NOT NULL, `usernames` TEXT NOT NULL, PRIMARY KEY(`reaction`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "reaction", - "columnName": "reaction", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "count", - "columnName": "count", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "usernames", - "columnName": "usernames", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "reaction" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_reactions_messageId", - "unique": false, - "columnNames": [ - "messageId" - ], - "createSql": "CREATE INDEX `index_reactions_messageId` ON `${TABLE_NAME}` (`messageId`)" - } - ], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "messages_sync", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`roomId` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`roomId`))", - "fields": [ - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "roomId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"db46c12dbb8747200288f48d5dc5558b\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/12.json b/app/schemas/chat.rocket.android.db.RCDatabase/12.json deleted file mode 100644 index 8b1da2129d..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/12.json +++ /dev/null @@ -1,1111 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 12, - "identityHash": "1984b5661945bd83607d9a7043ed87b0", - "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, `topic` TEXT, `announcement` TEXT, `description` TEXT, `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": "topic", - "columnName": "topic", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "announcement", - "columnName": "announcement", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "description", - "columnName": "description", - "affinity": "TEXT", - "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, `names` 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 - }, - { - "fieldPath": "names", - "columnName": "names", - "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, \"1984b5661945bd83607d9a7043ed87b0\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/11.json b/app/schemas/chat.rocket.android.db.RCDatabase/13.json similarity index 96% rename from app/schemas/chat.rocket.android.db.RCDatabase/11.json rename to app/schemas/chat.rocket.android.db.RCDatabase/13.json index 0d58ffcfba..5127d02b4c 100644 --- a/app/schemas/chat.rocket.android.db.RCDatabase/11.json +++ b/app/schemas/chat.rocket.android.db.RCDatabase/13.json @@ -1,8 +1,8 @@ { "formatVersion": 1, "database": { - "version": 11, - "identityHash": "1984b5661945bd83607d9a7043ed87b0", + "version": 13, + "identityHash": "3bef73b44ae4edf2a74b48fd68f2c599", "entities": [ { "tableName": "users", @@ -59,7 +59,7 @@ }, { "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, `topic` TEXT, `announcement` TEXT, `description` TEXT, `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 )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `parentId` TEXT, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `topic` TEXT, `announcement` TEXT, `description` TEXT, `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", @@ -73,6 +73,12 @@ "affinity": "TEXT", "notNull": true }, + { + "fieldPath": "parentId", + "columnName": "parentId", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "type", "columnName": "type", @@ -1105,7 +1111,7 @@ ], "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, \"1984b5661945bd83607d9a7043ed87b0\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"3bef73b44ae4edf2a74b48fd68f2c599\")" ] } } \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/3.json b/app/schemas/chat.rocket.android.db.RCDatabase/3.json deleted file mode 100644 index 5b38241e5b..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/3.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 3, - "identityHash": "06359a8c2943365dd094bc5dff210203", - "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, 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 - } - ], - "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`)" - } - ], - "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" - ] - } - ] - } - ], - "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, \"06359a8c2943365dd094bc5dff210203\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/4.json b/app/schemas/chat.rocket.android.db.RCDatabase/4.json deleted file mode 100644 index e7485fd072..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/4.json +++ /dev/null @@ -1,279 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 4, - "identityHash": "e389d26bfb975f00c75dc6fc5d06d012", - "entities": [ - { - "tableName": "users", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "username", - "columnName": "username", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "utcOffset", - "columnName": "utcOffset", - "affinity": "REAL", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_users_username", - "unique": false, - "columnNames": [ - "username" - ], - "createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "chatrooms", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "subscriptionId", - "columnName": "subscriptionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fullname", - "columnName": "fullname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ownerId", - "columnName": "ownerId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "readonly", - "columnName": "readonly", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDefault", - "columnName": "isDefault", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "open", - "columnName": "open", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "alert", - "columnName": "alert", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userMentions", - "columnName": "userMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "groupMentions", - "columnName": "groupMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastSeen", - "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastMessageText", - "columnName": "lastMessageText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageUserId", - "columnName": "lastMessageUserId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageTimestamp", - "columnName": "lastMessageTimestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "broadcast", - "columnName": "broadcast", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_chatrooms_userId", - "unique": false, - "columnNames": [ - "userId" - ], - "createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)" - }, - { - "name": "index_chatrooms_ownerId", - "unique": false, - "columnNames": [ - "ownerId" - ], - "createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)" - }, - { - "name": "index_chatrooms_subscriptionId", - "unique": true, - "columnNames": [ - "subscriptionId" - ], - "createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)" - }, - { - "name": "index_chatrooms_updatedAt", - "unique": false, - "columnNames": [ - "updatedAt" - ], - "createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)" - } - ], - "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" - ] - } - ] - } - ], - "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, \"e389d26bfb975f00c75dc6fc5d06d012\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/5.json b/app/schemas/chat.rocket.android.db.RCDatabase/5.json deleted file mode 100644 index d09c9f0ca7..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/5.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 5, - "identityHash": "47a0c30e2696ae09bc86df16cc37279d", - "entities": [ - { - "tableName": "users", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "username", - "columnName": "username", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "utcOffset", - "columnName": "utcOffset", - "affinity": "REAL", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_users_username", - "unique": false, - "columnNames": [ - "username" - ], - "createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "chatrooms", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "subscriptionId", - "columnName": "subscriptionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fullname", - "columnName": "fullname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ownerId", - "columnName": "ownerId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "readonly", - "columnName": "readonly", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDefault", - "columnName": "isDefault", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "open", - "columnName": "open", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "alert", - "columnName": "alert", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userMentions", - "columnName": "userMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "groupMentions", - "columnName": "groupMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastSeen", - "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastMessageText", - "columnName": "lastMessageText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageUserId", - "columnName": "lastMessageUserId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageTimestamp", - "columnName": "lastMessageTimestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "broadcast", - "columnName": "broadcast", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_chatrooms_userId", - "unique": false, - "columnNames": [ - "userId" - ], - "createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)" - }, - { - "name": "index_chatrooms_ownerId", - "unique": false, - "columnNames": [ - "ownerId" - ], - "createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)" - }, - { - "name": "index_chatrooms_subscriptionId", - "unique": true, - "columnNames": [ - "subscriptionId" - ], - "createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)" - }, - { - "name": "index_chatrooms_updatedAt", - "unique": false, - "columnNames": [ - "updatedAt" - ], - "createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)" - }, - { - "name": "index_chatrooms_lastMessageUserId", - "unique": false, - "columnNames": [ - "lastMessageUserId" - ], - "createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)" - } - ], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "ownerId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "lastMessageUserId" - ], - "referencedColumns": [ - "id" - ] - } - ] - } - ], - "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, \"47a0c30e2696ae09bc86df16cc37279d\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/6.json b/app/schemas/chat.rocket.android.db.RCDatabase/6.json deleted file mode 100644 index b3f9e85a20..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/6.json +++ /dev/null @@ -1,946 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 6, - "identityHash": "03aec453cb4faec2d1357fdb673b151e", - "entities": [ - { - "tableName": "users", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "username", - "columnName": "username", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "utcOffset", - "columnName": "utcOffset", - "affinity": "REAL", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_users_username", - "unique": false, - "columnNames": [ - "username" - ], - "createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "chatrooms", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "subscriptionId", - "columnName": "subscriptionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fullname", - "columnName": "fullname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ownerId", - "columnName": "ownerId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "readonly", - "columnName": "readonly", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDefault", - "columnName": "isDefault", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "open", - "columnName": "open", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "alert", - "columnName": "alert", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userMentions", - "columnName": "userMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "groupMentions", - "columnName": "groupMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastSeen", - "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastMessageText", - "columnName": "lastMessageText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageUserId", - "columnName": "lastMessageUserId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageTimestamp", - "columnName": "lastMessageTimestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "broadcast", - "columnName": "broadcast", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_chatrooms_userId", - "unique": false, - "columnNames": [ - "userId" - ], - "createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)" - }, - { - "name": "index_chatrooms_ownerId", - "unique": false, - "columnNames": [ - "ownerId" - ], - "createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)" - }, - { - "name": "index_chatrooms_subscriptionId", - "unique": true, - "columnNames": [ - "subscriptionId" - ], - "createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)" - }, - { - "name": "index_chatrooms_updatedAt", - "unique": false, - "columnNames": [ - "updatedAt" - ], - "createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)" - }, - { - "name": "index_chatrooms_lastMessageUserId", - "unique": false, - "columnNames": [ - "lastMessageUserId" - ], - "createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)" - } - ], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "ownerId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "lastMessageUserId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `roomId` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `senderId` TEXT, `updatedAt` INTEGER, `editedAt` INTEGER, `editedBy` TEXT, `senderAlias` TEXT, `avatar` TEXT, `type` TEXT, `groupable` INTEGER NOT NULL, `parseUrls` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `role` TEXT, 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 - } - ], - "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, `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, 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": "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 - } - ], - "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": [], - "foreignKeys": [ - { - "table": "attachments", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "attachmentId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "urls", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` 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": "id", - "columnName": "id", - "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": [ - "id" - ], - "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, PRIMARY KEY(`reaction`))", - "fields": [ - { - "fieldPath": "reaction", - "columnName": "reaction", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "reaction" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - }, - { - "tableName": "reactions_message_relations", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `reactionId` TEXT NOT NULL, `messageId` TEXT NOT NULL, `count` INTEGER NOT NULL, FOREIGN KEY(`reactionId`) REFERENCES `reactions`(`reaction`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "reactionId", - "columnName": "reactionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "count", - "columnName": "count", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "indices": [ - { - "name": "index_reactions_message_relations_messageId", - "unique": false, - "columnNames": [ - "messageId" - ], - "createSql": "CREATE INDEX `index_reactions_message_relations_messageId` ON `${TABLE_NAME}` (`messageId`)" - } - ], - "foreignKeys": [ - { - "table": "reactions", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "reactionId" - ], - "referencedColumns": [ - "reaction" - ] - }, - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - } - ], - "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, \"03aec453cb4faec2d1357fdb673b151e\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/7.json b/app/schemas/chat.rocket.android.db.RCDatabase/7.json deleted file mode 100644 index c0f12cf494..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/7.json +++ /dev/null @@ -1,959 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 7, - "identityHash": "4d5ac4ae382ddb3aa3850842253e89d2", - "entities": [ - { - "tableName": "users", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "username", - "columnName": "username", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "utcOffset", - "columnName": "utcOffset", - "affinity": "REAL", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_users_username", - "unique": false, - "columnNames": [ - "username" - ], - "createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "chatrooms", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "subscriptionId", - "columnName": "subscriptionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fullname", - "columnName": "fullname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ownerId", - "columnName": "ownerId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "readonly", - "columnName": "readonly", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDefault", - "columnName": "isDefault", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "open", - "columnName": "open", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "alert", - "columnName": "alert", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userMentions", - "columnName": "userMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "groupMentions", - "columnName": "groupMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastSeen", - "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastMessageText", - "columnName": "lastMessageText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageUserId", - "columnName": "lastMessageUserId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageTimestamp", - "columnName": "lastMessageTimestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "broadcast", - "columnName": "broadcast", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_chatrooms_userId", - "unique": false, - "columnNames": [ - "userId" - ], - "createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)" - }, - { - "name": "index_chatrooms_ownerId", - "unique": false, - "columnNames": [ - "ownerId" - ], - "createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)" - }, - { - "name": "index_chatrooms_subscriptionId", - "unique": true, - "columnNames": [ - "subscriptionId" - ], - "createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)" - }, - { - "name": "index_chatrooms_updatedAt", - "unique": false, - "columnNames": [ - "updatedAt" - ], - "createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)" - }, - { - "name": "index_chatrooms_lastMessageUserId", - "unique": false, - "columnNames": [ - "lastMessageUserId" - ], - "createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)" - } - ], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "ownerId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "lastMessageUserId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `roomId` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `senderId` TEXT, `updatedAt` INTEGER, `editedAt` INTEGER, `editedBy` TEXT, `senderAlias` TEXT, `avatar` TEXT, `type` TEXT, `groupable` INTEGER NOT NULL, `parseUrls` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `role` TEXT, `synced` INTEGER NOT NULL, `unread` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`senderId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`editedBy`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "message", - "columnName": "message", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "senderId", - "columnName": "senderId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedAt", - "columnName": "editedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedBy", - "columnName": "editedBy", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "senderAlias", - "columnName": "senderAlias", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "avatar", - "columnName": "avatar", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "groupable", - "columnName": "groupable", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "parseUrls", - "columnName": "parseUrls", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "pinned", - "columnName": "pinned", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "role", - "columnName": "role", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "synced", - "columnName": "synced", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "senderId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "editedBy" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_favorites", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_mentions", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_channels", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `roomId` TEXT NOT NULL, `roomName` TEXT, PRIMARY KEY(`messageId`, `roomId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomName", - "columnName": "roomName", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "roomId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "attachments", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `message_id` TEXT NOT NULL, `title` TEXT, `type` TEXT, `description` TEXT, `text` TEXT, `author_name` TEXT, `author_icon` TEXT, `author_link` TEXT, `thumb_url` TEXT, `color` TEXT, `fallback` TEXT, `title_link` TEXT, `title_link_download` INTEGER NOT NULL, `image_url` TEXT, `image_type` TEXT, `image_size` INTEGER, `video_url` TEXT, `video_type` TEXT, `video_size` INTEGER, `audio_url` TEXT, `audio_type` TEXT, `audio_size` INTEGER, `message_link` TEXT, `timestamp` INTEGER, 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 - } - ], - "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": [], - "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, \"4d5ac4ae382ddb3aa3850842253e89d2\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/8.json b/app/schemas/chat.rocket.android.db.RCDatabase/8.json deleted file mode 100644 index 9874e166dc..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/8.json +++ /dev/null @@ -1,1075 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 8, - "identityHash": "48d41bd13698c29cc5e0810934187c0e", - "entities": [ - { - "tableName": "users", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "username", - "columnName": "username", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "utcOffset", - "columnName": "utcOffset", - "affinity": "REAL", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_users_username", - "unique": false, - "columnNames": [ - "username" - ], - "createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "chatrooms", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "subscriptionId", - "columnName": "subscriptionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fullname", - "columnName": "fullname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ownerId", - "columnName": "ownerId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "readonly", - "columnName": "readonly", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDefault", - "columnName": "isDefault", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "open", - "columnName": "open", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "alert", - "columnName": "alert", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userMentions", - "columnName": "userMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "groupMentions", - "columnName": "groupMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastSeen", - "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastMessageText", - "columnName": "lastMessageText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageUserId", - "columnName": "lastMessageUserId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageTimestamp", - "columnName": "lastMessageTimestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "broadcast", - "columnName": "broadcast", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_chatrooms_userId", - "unique": false, - "columnNames": [ - "userId" - ], - "createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)" - }, - { - "name": "index_chatrooms_ownerId", - "unique": false, - "columnNames": [ - "ownerId" - ], - "createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)" - }, - { - "name": "index_chatrooms_subscriptionId", - "unique": true, - "columnNames": [ - "subscriptionId" - ], - "createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)" - }, - { - "name": "index_chatrooms_updatedAt", - "unique": false, - "columnNames": [ - "updatedAt" - ], - "createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)" - }, - { - "name": "index_chatrooms_lastMessageUserId", - "unique": false, - "columnNames": [ - "lastMessageUserId" - ], - "createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)" - } - ], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "ownerId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "lastMessageUserId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `roomId` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `senderId` TEXT, `updatedAt` INTEGER, `editedAt` INTEGER, `editedBy` TEXT, `senderAlias` TEXT, `avatar` TEXT, `type` TEXT, `groupable` INTEGER NOT NULL, `parseUrls` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `role` TEXT, `synced` INTEGER NOT NULL, `unread` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`senderId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`editedBy`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "message", - "columnName": "message", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "senderId", - "columnName": "senderId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedAt", - "columnName": "editedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedBy", - "columnName": "editedBy", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "senderAlias", - "columnName": "senderAlias", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "avatar", - "columnName": "avatar", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "groupable", - "columnName": "groupable", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "parseUrls", - "columnName": "parseUrls", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "pinned", - "columnName": "pinned", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "role", - "columnName": "role", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "synced", - "columnName": "synced", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "senderId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "editedBy" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_favorites", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_mentions", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_channels", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `roomId` TEXT NOT NULL, `roomName` TEXT, PRIMARY KEY(`messageId`, `roomId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomName", - "columnName": "roomName", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "roomId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "attachments", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `message_id` TEXT NOT NULL, `title` TEXT, `type` TEXT, `description` TEXT, `text` TEXT, `author_name` TEXT, `author_icon` TEXT, `author_link` TEXT, `thumb_url` TEXT, `color` TEXT, `fallback` TEXT, `title_link` TEXT, `title_link_download` INTEGER NOT NULL, `image_url` TEXT, `image_type` TEXT, `image_size` INTEGER, `video_url` TEXT, `video_type` TEXT, `video_size` INTEGER, `audio_url` TEXT, `audio_type` TEXT, `audio_size` INTEGER, `message_link` TEXT, `timestamp` INTEGER, `has_actions` INTEGER NOT NULL, `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": "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, \"48d41bd13698c29cc5e0810934187c0e\")" - ] - } -} \ No newline at end of file diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/9.json b/app/schemas/chat.rocket.android.db.RCDatabase/9.json deleted file mode 100644 index 9724e557be..0000000000 --- a/app/schemas/chat.rocket.android.db.RCDatabase/9.json +++ /dev/null @@ -1,1081 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 9, - "identityHash": "db46c12dbb8747200288f48d5dc5558b", - "entities": [ - { - "tableName": "users", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "username", - "columnName": "username", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "utcOffset", - "columnName": "utcOffset", - "affinity": "REAL", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_users_username", - "unique": false, - "columnNames": [ - "username" - ], - "createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "chatrooms", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "subscriptionId", - "columnName": "subscriptionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fullname", - "columnName": "fullname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ownerId", - "columnName": "ownerId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "readonly", - "columnName": "readonly", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDefault", - "columnName": "isDefault", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "open", - "columnName": "open", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "alert", - "columnName": "alert", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userMentions", - "columnName": "userMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "groupMentions", - "columnName": "groupMentions", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastSeen", - "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "lastMessageText", - "columnName": "lastMessageText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageUserId", - "columnName": "lastMessageUserId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastMessageTimestamp", - "columnName": "lastMessageTimestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "broadcast", - "columnName": "broadcast", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_chatrooms_userId", - "unique": false, - "columnNames": [ - "userId" - ], - "createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)" - }, - { - "name": "index_chatrooms_ownerId", - "unique": false, - "columnNames": [ - "ownerId" - ], - "createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)" - }, - { - "name": "index_chatrooms_subscriptionId", - "unique": true, - "columnNames": [ - "subscriptionId" - ], - "createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)" - }, - { - "name": "index_chatrooms_updatedAt", - "unique": false, - "columnNames": [ - "updatedAt" - ], - "createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)" - }, - { - "name": "index_chatrooms_lastMessageUserId", - "unique": false, - "columnNames": [ - "lastMessageUserId" - ], - "createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)" - } - ], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "ownerId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "lastMessageUserId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `roomId` TEXT NOT NULL, `message` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `senderId` TEXT, `updatedAt` INTEGER, `editedAt` INTEGER, `editedBy` TEXT, `senderAlias` TEXT, `avatar` TEXT, `type` TEXT, `groupable` INTEGER NOT NULL, `parseUrls` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `role` TEXT, `synced` INTEGER NOT NULL, `unread` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`senderId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`editedBy`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "message", - "columnName": "message", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "senderId", - "columnName": "senderId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "updatedAt", - "columnName": "updatedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedAt", - "columnName": "editedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "editedBy", - "columnName": "editedBy", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "senderAlias", - "columnName": "senderAlias", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "avatar", - "columnName": "avatar", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "groupable", - "columnName": "groupable", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "parseUrls", - "columnName": "parseUrls", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "pinned", - "columnName": "pinned", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "role", - "columnName": "role", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "synced", - "columnName": "synced", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "unread", - "columnName": "unread", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "senderId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "editedBy" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_favorites", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_mentions", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `userId` TEXT NOT NULL, PRIMARY KEY(`messageId`, `userId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "userId", - "columnName": "userId", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "userId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "users", - "onDelete": "NO ACTION", - "onUpdate": "NO ACTION", - "columns": [ - "userId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "message_channels", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `roomId` TEXT NOT NULL, `roomName` TEXT, PRIMARY KEY(`messageId`, `roomId`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "roomName", - "columnName": "roomName", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "messageId", - "roomId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "attachments", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` TEXT NOT NULL, `message_id` TEXT NOT NULL, `title` TEXT, `type` TEXT, `description` TEXT, `text` TEXT, `author_name` TEXT, `author_icon` TEXT, `author_link` TEXT, `thumb_url` TEXT, `color` TEXT, `fallback` TEXT, `title_link` TEXT, `title_link_download` INTEGER NOT NULL, `image_url` TEXT, `image_type` TEXT, `image_size` INTEGER, `video_url` TEXT, `video_type` TEXT, `video_size` INTEGER, `audio_url` TEXT, `audio_type` TEXT, `audio_size` INTEGER, `message_link` TEXT, `timestamp` INTEGER, `has_actions` INTEGER NOT NULL, `has_fields` INTEGER NOT NULL, `button_alignment` TEXT, PRIMARY KEY(`_id`), FOREIGN KEY(`message_id`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "_id", - "columnName": "_id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "messageId", - "columnName": "message_id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "title", - "columnName": "title", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "description", - "columnName": "description", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "text", - "columnName": "text", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "authorName", - "columnName": "author_name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "authorIcon", - "columnName": "author_icon", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "authorLink", - "columnName": "author_link", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "thumbUrl", - "columnName": "thumb_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "color", - "columnName": "color", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "fallback", - "columnName": "fallback", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "titleLink", - "columnName": "title_link", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "titleLinkDownload", - "columnName": "title_link_download", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "imageUrl", - "columnName": "image_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageType", - "columnName": "image_type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageSize", - "columnName": "image_size", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "videoUrl", - "columnName": "video_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "videoType", - "columnName": "video_type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "videoSize", - "columnName": "video_size", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "audioUrl", - "columnName": "audio_url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "audioType", - "columnName": "audio_type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "audioSize", - "columnName": "audio_size", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "messageLink", - "columnName": "message_link", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "hasActions", - "columnName": "has_actions", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "hasFields", - "columnName": "has_fields", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "buttonAlignment", - "columnName": "button_alignment", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "_id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "message_id" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "attachment_fields", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `attachmentId` TEXT NOT NULL, `title` TEXT NOT NULL, `value` TEXT NOT NULL, FOREIGN KEY(`attachmentId`) REFERENCES `attachments`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "attachmentId", - "columnName": "attachmentId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "title", - "columnName": "title", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "value", - "columnName": "value", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "indices": [ - { - "name": "index_attachment_fields_attachmentId", - "unique": false, - "columnNames": [ - "attachmentId" - ], - "createSql": "CREATE INDEX `index_attachment_fields_attachmentId` ON `${TABLE_NAME}` (`attachmentId`)" - } - ], - "foreignKeys": [ - { - "table": "attachments", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "attachmentId" - ], - "referencedColumns": [ - "_id" - ] - } - ] - }, - { - "tableName": "attachment_action", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `attachmentId` TEXT NOT NULL, `type` TEXT NOT NULL, `text` TEXT, `url` TEXT, `isWebView` INTEGER, `webViewHeightRatio` TEXT, `imageUrl` TEXT, `message` TEXT, `isMessageInChatWindow` INTEGER, FOREIGN KEY(`attachmentId`) REFERENCES `attachments`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "attachmentId", - "columnName": "attachmentId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "text", - "columnName": "text", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "url", - "columnName": "url", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isWebView", - "columnName": "isWebView", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "webViewHeightRatio", - "columnName": "webViewHeightRatio", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageUrl", - "columnName": "imageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "message", - "columnName": "message", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isMessageInChatWindow", - "columnName": "isMessageInChatWindow", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "indices": [ - { - "name": "index_attachment_action_attachmentId", - "unique": false, - "columnNames": [ - "attachmentId" - ], - "createSql": "CREATE INDEX `index_attachment_action_attachmentId` ON `${TABLE_NAME}` (`attachmentId`)" - } - ], - "foreignKeys": [ - { - "table": "attachments", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "attachmentId" - ], - "referencedColumns": [ - "_id" - ] - } - ] - }, - { - "tableName": "urls", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`urlId` INTEGER PRIMARY KEY AUTOINCREMENT, `messageId` TEXT NOT NULL, `url` TEXT NOT NULL, `hostname` TEXT, `title` TEXT, `description` TEXT, `imageUrl` TEXT, FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "urlId", - "columnName": "urlId", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "url", - "columnName": "url", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hostname", - "columnName": "hostname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "title", - "columnName": "title", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "description", - "columnName": "description", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "imageUrl", - "columnName": "imageUrl", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "urlId" - ], - "autoGenerate": true - }, - "indices": [ - { - "name": "index_urls_messageId", - "unique": false, - "columnNames": [ - "messageId" - ], - "createSql": "CREATE INDEX `index_urls_messageId` ON `${TABLE_NAME}` (`messageId`)" - } - ], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "reactions", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`reaction` TEXT NOT NULL, `messageId` TEXT NOT NULL, `count` INTEGER NOT NULL, `usernames` TEXT NOT NULL, PRIMARY KEY(`reaction`), FOREIGN KEY(`messageId`) REFERENCES `messages`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "reaction", - "columnName": "reaction", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "messageId", - "columnName": "messageId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "count", - "columnName": "count", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "usernames", - "columnName": "usernames", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "reaction" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_reactions_messageId", - "unique": false, - "columnNames": [ - "messageId" - ], - "createSql": "CREATE INDEX `index_reactions_messageId` ON `${TABLE_NAME}` (`messageId`)" - } - ], - "foreignKeys": [ - { - "table": "messages", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "messageId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "messages_sync", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`roomId` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`roomId`))", - "fields": [ - { - "fieldPath": "roomId", - "columnName": "roomId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "timestamp", - "columnName": "timestamp", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "roomId" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"db46c12dbb8747200288f48d5dc5558b\")" - ] - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3a342f59ae..9bb4177bb3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -90,7 +90,7 @@ android:theme="@style/AppTheme" android:windowSoftInputMode="adjustResize|stateAlwaysHidden" /> - + diff --git a/app/src/main/java/chat/rocket/android/about/di/AboutFragmentProvider.kt b/app/src/main/java/chat/rocket/android/about/di/AboutFragmentProvider.kt deleted file mode 100644 index 9ad3f3eb50..0000000000 --- a/app/src/main/java/chat/rocket/android/about/di/AboutFragmentProvider.kt +++ /dev/null @@ -1,12 +0,0 @@ -package chat.rocket.android.about.di - -import chat.rocket.android.about.ui.AboutFragment -import dagger.Module -import dagger.android.ContributesAndroidInjector - -@Module -abstract class AboutFragmentProvider { - - @ContributesAndroidInjector() - abstract fun provideAboutFragment(): AboutFragment -} diff --git a/app/src/main/java/chat/rocket/android/about/ui/AboutFragment.kt b/app/src/main/java/chat/rocket/android/about/ui/AboutFragment.kt deleted file mode 100644 index 08047eb1bc..0000000000 --- a/app/src/main/java/chat/rocket/android/about/ui/AboutFragment.kt +++ /dev/null @@ -1,66 +0,0 @@ -package chat.rocket.android.about.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import chat.rocket.android.BuildConfig -import chat.rocket.android.R -import chat.rocket.android.analytics.AnalyticsManager -import chat.rocket.android.analytics.event.ScreenViewEvent -import chat.rocket.android.main.ui.MainActivity -import dagger.android.support.AndroidSupportInjection -import kotlinx.android.synthetic.main.app_bar.* -import kotlinx.android.synthetic.main.fragment_about.* -import javax.inject.Inject - -internal const val TAG_ABOUT_FRAGMENT = "AboutFragment" - -class AboutFragment : Fragment() { - @Inject - lateinit var analyticsManager: AnalyticsManager - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - AndroidSupportInjection.inject(this) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = inflater.inflate(R.layout.fragment_about, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setupViews() - - analyticsManager.logScreenView(ScreenViewEvent.About) - } - - override fun onResume() { - super.onResume() - setupToolbar() - } - - private fun setupViews() { - text_version_name.text = BuildConfig.VERSION_NAME - text_build_number.text = getString( - R.string.msg_build, BuildConfig.VERSION_CODE, - BuildConfig.GIT_SHA, BuildConfig.FLAVOR - ) - } - - private fun setupToolbar() { - with((activity as MainActivity).toolbar) { - title = getString(R.string.title_about) - setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) - setNavigationOnClickListener { activity?.onBackPressed() } - } - } - - companion object { - fun newInstance() = AboutFragment() - } -} diff --git a/app/src/main/java/chat/rocket/android/analytics/Analytics.kt b/app/src/main/java/chat/rocket/android/analytics/Analytics.kt index d1adf4ce95..90750184cb 100644 --- a/app/src/main/java/chat/rocket/android/analytics/Analytics.kt +++ b/app/src/main/java/chat/rocket/android/analytics/Analytics.kt @@ -10,7 +10,7 @@ interface Analytics { * Logs the login event. * * @param event The [AuthenticationEvent] used to log in. - * @param loginSucceeded True if successful logged in, false otherwise. + * @param loginSucceeded True if logged in successfully, false otherwise. */ fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) {} @@ -18,7 +18,7 @@ interface Analytics { * Logs the sign up event. * * @param event The [AuthenticationEvent] used to sign up. - * @param signUpSucceeded True if successful signed up, false otherwise. + * @param signUpSucceeded True if signed up successfully, false otherwise. */ fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) {} diff --git a/app/src/main/java/chat/rocket/android/analytics/event/ScreenViewEvent.kt b/app/src/main/java/chat/rocket/android/analytics/event/ScreenViewEvent.kt index b96de69855..a1f6fc6f8a 100644 --- a/app/src/main/java/chat/rocket/android/analytics/event/ScreenViewEvent.kt +++ b/app/src/main/java/chat/rocket/android/analytics/event/ScreenViewEvent.kt @@ -27,4 +27,5 @@ sealed class ScreenViewEvent(val screenName: String) { object Preferences : ScreenViewEvent("PreferencesFragment") object Profile : ScreenViewEvent("ProfileFragment") object Settings : ScreenViewEvent("SettingsFragment") + object Directory : ScreenViewEvent("DirectoryFragment") } diff --git a/app/src/main/java/chat/rocket/android/app/AppLifecycleObserver.kt b/app/src/main/java/chat/rocket/android/app/AppLifecycleObserver.kt index 749fab04ff..c236f3a769 100644 --- a/app/src/main/java/chat/rocket/android/app/AppLifecycleObserver.kt +++ b/app/src/main/java/chat/rocket/android/app/AppLifecycleObserver.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.common.model.UserStatus import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch diff --git a/app/src/main/java/chat/rocket/android/app/DrawableHelper.kt b/app/src/main/java/chat/rocket/android/app/DrawableHelper.kt index b808072efa..8b3220472c 100644 --- a/app/src/main/java/chat/rocket/android/app/DrawableHelper.kt +++ b/app/src/main/java/chat/rocket/android/app/DrawableHelper.kt @@ -1,8 +1,9 @@ import android.content.Context import android.graphics.drawable.Drawable +import android.view.View +import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat -import android.widget.TextView import chat.rocket.android.R import chat.rocket.common.model.UserStatus @@ -43,7 +44,7 @@ object DrawableHelper { /** * Tints an array of Drawable. * - * REMARK: you MUST always wrap the array of Drawable before tint it. + * REMARK: you MUST always wrap the array of Drawable before tinting it. * * @param drawables The array of Drawable to tint. * @param context The context. @@ -60,7 +61,7 @@ object DrawableHelper { /** * Tints a Drawable. * - * REMARK: you MUST always wrap the Drawable before tint it. + * REMARK: you MUST always wrap the Drawable before tinting it. * * @param drawable The Drawable to tint. * @param context The context. @@ -72,43 +73,74 @@ object DrawableHelper { DrawableCompat.setTint(drawable, ContextCompat.getColor(context, resId)) /** - * Compounds an array of Drawable (to appear to the left of the text) into an array of TextView. + * Compounds an array of Drawable (to appear on the start side of a text) into an array of TextView. * - * REMARK: the number of elements in both array of Drawable and EditText MUST be equal. + * REMARK: the number of elements in both arrays of Drawable and TextView MUST be equal. * * @param textView The array of TextView. * @param drawables The array of Drawable. - * @see compoundDrawable + * @see compoundStartDrawable */ fun compoundDrawables(textView: Array, drawables: Array) { if (textView.size != drawables.size) { return } else { for (i in textView.indices) { - textView[i].setCompoundDrawablesWithIntrinsicBounds(drawables[i], null, null, null) + if (textView[i].resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + textView[i].setCompoundDrawablesWithIntrinsicBounds(null, null, drawables[i], null) + } else { + textView[i].setCompoundDrawablesWithIntrinsicBounds(drawables[i], null, null, null) + } } } } /** - * Compounds a Drawable (to appear on the left side of a text) into a TextView. + * Compounds a Drawable (to appear on the start side of a text) into a TextView. * * @param textView The TextView. * @param drawable The Drawable. * @see compoundDrawables */ - fun compoundDrawable(textView: TextView, drawable: Drawable) = - textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + fun compoundStartDrawable(textView: TextView, drawable: Drawable) = + if (textView.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null) + } else { + textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + } /** - * Compounds a Drawable (to appear on the right side of a text) into a TextView. + * Compounds a Drawable (to appear on the end side of a text) into a TextView. * * @param textView The TextView. * @param drawable The Drawable. - * @see compoundDrawable + * @see compoundStartDrawable */ - fun compoundRightDrawable(textView: TextView, drawable: Drawable) = - textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null) + fun compoundEndDrawable(textView: TextView, drawable: Drawable) = + if (textView.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + } else { + textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null) + } + + /** + * Compounds a Drawable (to appear on the start and end side of a text) into a TextView. + * + * @param textView The TextView. + * @param startDrawable The start Drawable. + * @param endDrawable The end Drawable. + * @see compoundStartDrawable + */ + fun compoundStartAndEndDrawable( + textView: TextView, + startDrawable: Drawable, + endDrawable: Drawable + ) = + if (textView.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + textView.setCompoundDrawablesWithIntrinsicBounds(endDrawable, null, startDrawable, null) + } else { + textView.setCompoundDrawablesWithIntrinsicBounds(startDrawable, null, endDrawable, null) + } /** * Returns the user status drawable. @@ -126,4 +158,4 @@ object DrawableHelper { else -> getDrawableFromId(R.drawable.ic_status_invisible_12dp, context) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt b/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt index 08279a74cd..5410d3d7f2 100644 --- a/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt +++ b/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt @@ -24,7 +24,7 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.SITE_URL import chat.rocket.android.server.domain.TokenRepository -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.retryIO import chat.rocket.android.util.setupFabric import chat.rocket.common.RocketChatException @@ -115,7 +115,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje // TODO - remove this checkCurrentServer() - // TODO - FIXME - we need to proper inject the EmojiRepository and initialize it properly + // TODO - FIXME - we need to properly inject and initialize the EmojiRepository loadEmojis() } @@ -176,7 +176,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje val currentServer = getCurrentServerInteractor.get() currentServer?.let { server -> GlobalScope.launch { - val client = factory.create(server) + val client = factory.get(server) EmojiRepository.setCurrentServerUrl(server) val customEmojiList = mutableListOf() try { diff --git a/app/src/main/java/chat/rocket/android/authentication/infraestructure/SharedPreferencesMultiServerTokenRepository.kt b/app/src/main/java/chat/rocket/android/authentication/infrastructure/SharedPreferencesMultiServerTokenRepository.kt similarity index 94% rename from app/src/main/java/chat/rocket/android/authentication/infraestructure/SharedPreferencesMultiServerTokenRepository.kt rename to app/src/main/java/chat/rocket/android/authentication/infrastructure/SharedPreferencesMultiServerTokenRepository.kt index f4875cfdfe..23b9af7376 100644 --- a/app/src/main/java/chat/rocket/android/authentication/infraestructure/SharedPreferencesMultiServerTokenRepository.kt +++ b/app/src/main/java/chat/rocket/android/authentication/infrastructure/SharedPreferencesMultiServerTokenRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.authentication.infraestructure +package chat.rocket.android.authentication.infrastructure import chat.rocket.android.authentication.domain.model.TokenModel import chat.rocket.android.dagger.scope.PerActivity diff --git a/app/src/main/java/chat/rocket/android/authentication/infraestructure/SharedPreferencesTokenRepository.kt b/app/src/main/java/chat/rocket/android/authentication/infrastructure/SharedPreferencesTokenRepository.kt similarity index 97% rename from app/src/main/java/chat/rocket/android/authentication/infraestructure/SharedPreferencesTokenRepository.kt rename to app/src/main/java/chat/rocket/android/authentication/infrastructure/SharedPreferencesTokenRepository.kt index b2348662e1..cb7bead7be 100644 --- a/app/src/main/java/chat/rocket/android/authentication/infraestructure/SharedPreferencesTokenRepository.kt +++ b/app/src/main/java/chat/rocket/android/authentication/infrastructure/SharedPreferencesTokenRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.authentication.infraestructure +package chat.rocket.android.authentication.infrastructure import android.content.SharedPreferences import androidx.core.content.edit diff --git a/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginPresenter.kt index b560c1c034..d3be18ae94 100644 --- a/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/login/presentation/LoginPresenter.kt @@ -15,8 +15,9 @@ import chat.rocket.android.server.domain.favicon import chat.rocket.android.server.domain.isLdapAuthenticationEnabled import chat.rocket.android.server.domain.isPasswordResetEnabled import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.server.domain.siteName import chat.rocket.android.server.domain.wideTile -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.isEmail @@ -50,6 +51,7 @@ class LoginPresenter @Inject constructor( ) { // TODO - we should validate the current server when opening the app, and have a nonnull get() private var currentServer = serverInteractor.get()!! + private val token = tokenRepository.get(currentServer) private lateinit var client: RocketChatClient private lateinit var settings: PublicSettings @@ -60,7 +62,7 @@ class LoginPresenter @Inject constructor( private fun setupConnectionInfo(serverUrl: String) { currentServer = serverUrl - client = factory.create(currentServer) + client = factory.get(currentServer) settings = settingsInteractor.get(currentServer) } @@ -140,8 +142,15 @@ class LoginPresenter @Inject constructor( val logo = settings.wideTile()?.let { currentServer.serverLogoUrl(it) } - val thumb = currentServer.avatarUrl(username) - val account = Account(currentServer, icon, logo, username, thumb) + val thumb = currentServer.avatarUrl(username, token?.userId, token?.authToken) + val account = Account( + settings.siteName() ?: currentServer, + currentServer, + icon, + logo, + username, + thumb + ) saveAccountInteractor.save(account) } diff --git a/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt index be36edab69..c9650cf273 100644 --- a/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsPresenter.kt @@ -14,8 +14,9 @@ import chat.rocket.android.server.domain.SaveCurrentServerInteractor import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.favicon import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.server.domain.siteName import chat.rocket.android.server.domain.wideTile -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.serverLogoUrl @@ -54,6 +55,7 @@ class LoginOptionsPresenter @Inject constructor( ) { // TODO - we should validate the current server when opening the app, and have a nonnull get() private var currentServer = serverInteractor.get()!! + private val token = tokenRepository.get(currentServer) private lateinit var client: RocketChatClient private lateinit var settings: PublicSettings private lateinit var credentialToken: String @@ -169,7 +171,7 @@ class LoginOptionsPresenter @Inject constructor( private fun setupConnectionInfo(serverUrl: String) { currentServer = serverUrl - client = factory.create(currentServer) + client = factory.get(currentServer) settings = settingsInteractor.get(currentServer) } @@ -180,8 +182,15 @@ class LoginOptionsPresenter @Inject constructor( val logo = settings.wideTile()?.let { currentServer.serverLogoUrl(it) } - val thumb = currentServer.avatarUrl(username) - val account = Account(currentServer, icon, logo, username, thumb) + val thumb = currentServer.avatarUrl(username, token?.userId, token?.authToken) + val account = Account( + settings.siteName() ?: currentServer, + currentServer, + icon, + logo, + username, + thumb + ) saveAccountInteractor.save(account) } diff --git a/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsView.kt b/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsView.kt index 5364c7a0c4..45d6a2e748 100644 --- a/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsView.kt +++ b/app/src/main/java/chat/rocket/android/authentication/loginoptions/presentation/LoginOptionsView.kt @@ -25,18 +25,18 @@ interface LoginOptionsView : LoadingView, MessageView { fun setupFacebookButtonListener(facebookOauthUrl: String, state: String) /** - * Shows the "login by Github" view if it is enabled by the server settings. + * Shows the "login by GitHub" view if it is enabled by the server settings. * - * REMARK: We must set up the Github button listener before enabling it + * REMARK: We must set up the GitHub button listener before enabling it * [setupGithubButtonListener]. * @see [showAccountsView] */ fun enableLoginByGithub() /** - * Setups the Github button. + * Setups the GitHub button. * - * @param githubUrl The Github OAuth URL to authenticate with. + * @param githubUrl The GitHub OAuth URL to authenticate with. * @param state A random string generated by the app, which you'll verify later * (to protect against forgery attacks). */ @@ -61,36 +61,36 @@ interface LoginOptionsView : LoadingView, MessageView { fun setupGoogleButtonListener(googleUrl: String, state: String) /** - * Shows the "login by Linkedin" view if it is enabled by the server settings. + * Shows the "login by LinkedIn" view if it is enabled by the server settings. * - * REMARK: We must set up the Linkedin button listener before enabling it + * REMARK: We must set up the LinkedIn button listener before enabling it * [setupLinkedinButtonListener]. * @see [showAccountsView] */ fun enableLoginByLinkedin() /** - * Setups the Linkedin button. + * Setups the LinkedIn button. * - * @param linkedinUrl The Linkedin OAuth URL to authenticate with. + * @param linkedinUrl The LinkedIn OAuth URL to authenticate with. * @param state A random string generated by the app, which you'll verify later * (to protect against forgery attacks). */ fun setupLinkedinButtonListener(linkedinUrl: String, state: String) /** - * Shows the "login by Gitlab" view if it is enabled by the server settings. + * Shows the "login by GitLab" view if it is enabled by the server settings. * - * REMARK: We must set up the Gitlab button listener before enabling it + * REMARK: We must set up the GitLab button listener before enabling it * [setupGitlabButtonListener]. * @see [showAccountsView] */ fun enableLoginByGitlab() /** - * Setups the Gitlab button. + * Setups the GitLab button. * - * @param gitlabUrl The Gitlab OAuth URL to authenticate with. + * @param gitlabUrl The GitLab OAuth URL to authenticate with. * @param state A random string generated by the app, which you'll verify later * (to protect against forgery attacks). */ @@ -99,7 +99,7 @@ interface LoginOptionsView : LoadingView, MessageView { /** * Shows the "login by WordPress" view if it is enabled by the server settings. * - * REMARK: We must set up the Gitlab button listener before enabling it [setupWordpressButtonListener]. + * REMARK: We must set up the GitLab button listener before enabling it [setupWordpressButtonListener]. */ fun enableLoginByWordpress() diff --git a/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt b/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt index 33bbb00593..d0d1abd971 100644 --- a/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt +++ b/app/src/main/java/chat/rocket/android/authentication/loginoptions/ui/LoginOptionsFragment.kt @@ -1,5 +1,8 @@ package chat.rocket.android.authentication.loginoptions.ui +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator import android.app.Activity import android.content.Intent import android.graphics.PorterDuff @@ -7,10 +10,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.AlphaAnimation +import android.view.animation.Animation import android.widget.Button import android.widget.LinearLayout import androidx.appcompat.view.ContextThemeWrapper import androidx.core.view.isVisible +import androidx.core.view.marginTop import androidx.fragment.app.Fragment import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager @@ -28,6 +34,7 @@ import chat.rocket.android.webview.sso.ui.ssoWebViewIntent import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.fragment_authentication_login_options.* +import timber.log.Timber import javax.inject.Inject private const val SERVER_NAME = "server_name" @@ -61,6 +68,8 @@ internal const val REQUEST_CODE_FOR_OAUTH = 1 internal const val REQUEST_CODE_FOR_CAS = 2 internal const val REQUEST_CODE_FOR_SAML = 3 +private const val DEFAULT_ANIMATION_DURATION = 400L + fun newInstance( serverName: String, state: String? = null, @@ -238,7 +247,6 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { enableLoginByLinkedin() } - if (gitlabOauthUrl != null && state != null) { setupGitlabButtonListener(gitlabOauthUrl.toString(), state.toString()) enableLoginByGitlab() @@ -390,11 +398,11 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { var isAccountsCollapsed = true button_expand_collapse_accounts.setOnClickListener { isAccountsCollapsed = if (isAccountsCollapsed) { - button_expand_collapse_accounts.rotateBy(180F, 400) + button_expand_collapse_accounts.rotateBy(180F, DEFAULT_ANIMATION_DURATION) expandAccountsView() false } else { - button_expand_collapse_accounts.rotateBy(180F, 400) + button_expand_collapse_accounts.rotateBy(180F, DEFAULT_ANIMATION_DURATION) collapseAccountsView() true } @@ -532,17 +540,73 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { } private fun expandAccountsView() { - (0..accounts_container.childCount) + val buttons = (0..accounts_container.childCount) .mapNotNull { accounts_container.getChildAt(it) as? Button } .filter { it.isClickable && !it.isVisible } - .forEach { it.isVisible = true } + val optionHeight = accounts_container.getChildAt(1).height + + accounts_container.getChildAt(1).marginTop + val collapsedHeight = accounts_container.height + val expandedHeight = collapsedHeight + optionHeight * buttons.size + + with(ValueAnimator.ofInt(collapsedHeight, expandedHeight)) { + addUpdateListener { + val params = accounts_container.layoutParams + params.height = animatedValue as Int + accounts_container.layoutParams = params + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animator: Animator) { + buttons.forEach { + it.isVisible = true + val anim = AlphaAnimation(0.0f, 1.0f) + anim.duration = DEFAULT_ANIMATION_DURATION + it.startAnimation(anim) + } + } + }) + setDuration(DEFAULT_ANIMATION_DURATION).start() + } } private fun collapseAccountsView() { - (0..accounts_container.childCount) + val buttons = (0..accounts_container.childCount) .mapNotNull { accounts_container.getChildAt(it) as? Button } .filter { it.isClickable && it.isVisible } .drop(3) - .forEach { it.isVisible = false } + val optionHeight = accounts_container.getChildAt(1).height + + accounts_container.getChildAt(1).marginTop + val expandedHeight = accounts_container.height + val collapsedHeight = expandedHeight - optionHeight * buttons.size + + with(ValueAnimator.ofInt(expandedHeight, collapsedHeight)) { + addUpdateListener { + val params = accounts_container.layoutParams + params.height = animatedValue as Int + accounts_container.layoutParams = params + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animator: Animator) { + buttons.forEach { + val anim = AlphaAnimation(1.0f, 0.0f) + anim.duration = DEFAULT_ANIMATION_DURATION + anim.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + Timber.d("Animation starts: $animation") + } + + override fun onAnimationEnd(animation: Animation) { + it.isVisible = false + } + + override fun onAnimationRepeat(animation: Animation) { + Timber.d("Animation repeats: $animation") + } + }) + it.startAnimation(anim) + } + } + }) + setDuration(DEFAULT_ANIMATION_DURATION).start() + } } } diff --git a/app/src/main/java/chat/rocket/android/authentication/onboarding/presentation/OnBoardingPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/onboarding/presentation/OnBoardingPresenter.kt index 532c6f56d9..37979843ad 100644 --- a/app/src/main/java/chat/rocket/android/authentication/onboarding/presentation/OnBoardingPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/onboarding/presentation/OnBoardingPresenter.kt @@ -7,7 +7,7 @@ import chat.rocket.android.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.RefreshSettingsInteractor import chat.rocket.android.server.domain.SaveConnectingServerInteractor -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.util.extension.launchUI import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/chat/rocket/android/authentication/registerusername/presentation/RegisterUsernamePresenter.kt b/app/src/main/java/chat/rocket/android/authentication/registerusername/presentation/RegisterUsernamePresenter.kt index f50137136e..a5d21a8762 100644 --- a/app/src/main/java/chat/rocket/android/authentication/registerusername/presentation/RegisterUsernamePresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/registerusername/presentation/RegisterUsernamePresenter.kt @@ -12,8 +12,9 @@ import chat.rocket.android.server.domain.SaveCurrentServerInteractor import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.favicon import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.server.domain.siteName import chat.rocket.android.server.domain.wideTile -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.serverLogoUrl @@ -38,8 +39,9 @@ class RegisterUsernamePresenter @Inject constructor( val settingsInteractor: GetSettingsInteractor ) { private val currentServer = serverInteractor.get()!! - private val client: RocketChatClient = factory.create(currentServer) - private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) + private val client: RocketChatClient = factory.get(currentServer) + private var settings: PublicSettings = settingsInteractor.get(currentServer) + private val token = tokenRepository.get(currentServer) fun registerUsername(username: String, userId: String, authToken: String) { launchUI(strategy) { @@ -72,15 +74,22 @@ class RegisterUsernamePresenter @Inject constructor( } } - private suspend fun saveAccount(username: String) { + private fun saveAccount(username: String) { val icon = settings.favicon()?.let { currentServer.serverLogoUrl(it) } val logo = settings.wideTile()?.let { currentServer.serverLogoUrl(it) } - val thumb = currentServer.avatarUrl(username) - val account = Account(currentServer, icon, logo, username, thumb) + val thumb = currentServer.avatarUrl(username, token?.userId, token?.authToken) + val account = Account( + settings.siteName() ?: currentServer, + currentServer, + icon, + logo, + username, + thumb + ) saveAccountInteractor.save(account) } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/registerusername/ui/RegisterUsernameFragment.kt b/app/src/main/java/chat/rocket/android/authentication/registerusername/ui/RegisterUsernameFragment.kt index 2ec6b49119..0ddd217bd4 100644 --- a/app/src/main/java/chat/rocket/android/authentication/registerusername/ui/RegisterUsernameFragment.kt +++ b/app/src/main/java/chat/rocket/android/authentication/registerusername/ui/RegisterUsernameFragment.kt @@ -140,7 +140,7 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_20dp, it) DrawableHelper.wrapDrawable(atDrawable) DrawableHelper.tintDrawable(atDrawable, it, R.color.colorDrawableTintGrey) - DrawableHelper.compoundDrawable(text_username, atDrawable) + DrawableHelper.compoundStartDrawable(text_username, atDrawable) } } diff --git a/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordPresenter.kt index ab750d4a42..c129db9f49 100644 --- a/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordPresenter.kt @@ -3,7 +3,7 @@ package chat.rocket.android.authentication.resetpassword.presentation import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.server.domain.GetConnectingServerInteractor -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException @@ -21,7 +21,7 @@ class ResetPasswordPresenter @Inject constructor( serverInteractor: GetConnectingServerInteractor ) { private val currentServer = serverInteractor.get()!! - private val client: RocketChatClient = factory.create(currentServer) + private val client: RocketChatClient = factory.get(currentServer) fun resetPassword(email: String) { launchUI(strategy) { diff --git a/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordView.kt b/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordView.kt index 6dd2e51518..fb72f60f63 100644 --- a/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordView.kt +++ b/app/src/main/java/chat/rocket/android/authentication/resetpassword/presentation/ResetPasswordView.kt @@ -21,7 +21,7 @@ interface ResetPasswordView : LoadingView, MessageView { fun enableButtonConnect() /** - * Disables the button to reset the password when the user entered an invalid email address + * Disables the button to reset the password when the user has entered an invalid email address */ fun disableButtonConnect() } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt index 415ace7783..d6192390cc 100644 --- a/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/server/presentation/ServerPresenter.kt @@ -8,7 +8,7 @@ import chat.rocket.android.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.RefreshSettingsInteractor import chat.rocket.android.server.domain.SaveConnectingServerInteractor -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.isValidUrl diff --git a/app/src/main/java/chat/rocket/android/authentication/server/presentation/VersionCheckView.kt b/app/src/main/java/chat/rocket/android/authentication/server/presentation/VersionCheckView.kt index 89711b6e96..0b93cf0ac2 100644 --- a/app/src/main/java/chat/rocket/android/authentication/server/presentation/VersionCheckView.kt +++ b/app/src/main/java/chat/rocket/android/authentication/server/presentation/VersionCheckView.kt @@ -26,7 +26,7 @@ interface VersionCheckView { fun versionOk() {} /** - * Alters the user this protocol is invalid. This is optional. + * Alters the user that this protocol is invalid. This is optional. */ fun errorInvalidProtocol() {} diff --git a/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt index 4d6b7d16f9..1633e36228 100644 --- a/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/signup/presentation/SignupPresenter.kt @@ -10,10 +10,12 @@ import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.SaveAccountInteractor import chat.rocket.android.server.domain.SaveCurrentServerInteractor +import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.favicon import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.server.domain.siteName import chat.rocket.android.server.domain.wideTile -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.privacyPolicyUrl @@ -38,13 +40,15 @@ class SignupPresenter @Inject constructor( private val analyticsManager: AnalyticsManager, private val factory: RocketChatClientFactory, private val saveAccountInteractor: SaveAccountInteractor, + tokenRepository: TokenRepository, settingsInteractor: GetSettingsInteractor ) { private val currentServer = serverInteractor.get()!! - private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) + private var settings: PublicSettings = settingsInteractor.get(currentServer) + private val token = tokenRepository.get(currentServer) fun signup(name: String, username: String, password: String, email: String) { - val client = factory.create(currentServer) + val client = factory.get(currentServer) launchUI(strategy) { view.showLoading() try { @@ -98,8 +102,15 @@ class SignupPresenter @Inject constructor( val logo = settings.wideTile()?.let { currentServer.serverLogoUrl(it) } - val thumb = currentServer.avatarUrl(me.username!!) - val account = Account(currentServer, icon, logo, me.username!!, thumb) + val thumb = currentServer.avatarUrl(me.username!!, token?.userId, token?.authToken) + val account = Account( + settings.siteName() ?: currentServer, + currentServer, + icon, + logo, + me.username!!, + thumb + ) saveAccountInteractor.save(account) } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/twofactor/presentation/TwoFAPresenter.kt b/app/src/main/java/chat/rocket/android/authentication/twofactor/presentation/TwoFAPresenter.kt index c4c6dbace0..a6e15b5ca9 100644 --- a/app/src/main/java/chat/rocket/android/authentication/twofactor/presentation/TwoFAPresenter.kt +++ b/app/src/main/java/chat/rocket/android/authentication/twofactor/presentation/TwoFAPresenter.kt @@ -13,8 +13,9 @@ import chat.rocket.android.server.domain.SaveCurrentServerInteractor import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.favicon import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.server.domain.siteName import chat.rocket.android.server.domain.wideTile -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.isEmail @@ -43,7 +44,8 @@ class TwoFAPresenter @Inject constructor( val settingsInteractor: GetSettingsInteractor ) { private val currentServer = serverInteractor.get()!! - private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) + private var settings: PublicSettings = settingsInteractor.get(currentServer) + private val token = tokenRepository.get(currentServer) fun authenticate( usernameOrEmail: String, @@ -51,7 +53,7 @@ class TwoFAPresenter @Inject constructor( twoFactorAuthenticationCode: String ) { launchUI(strategy) { - val client = factory.create(currentServer) + val client = factory.get(currentServer) view.showLoading() try { // The token is saved via the client TokenProvider @@ -101,8 +103,15 @@ class TwoFAPresenter @Inject constructor( val logo = settings.wideTile()?.let { currentServer.serverLogoUrl(it) } - val thumb = currentServer.avatarUrl(me.username!!) - val account = Account(currentServer, icon, logo, me.username!!, thumb) + val thumb = currentServer.avatarUrl(me.username!!, token?.userId, token?.authToken) + val account = Account( + settings.siteName() ?: currentServer, + currentServer, + icon, + logo, + me.username!!, + thumb + ) saveAccountInteractor.save(account) } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt b/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt index ae8c3b8ea5..0e03a35c28 100644 --- a/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatdetails/presentation/ChatDetailsPresenter.kt @@ -4,7 +4,7 @@ import chat.rocket.android.chatdetails.domain.ChatDetails import chat.rocket.android.chatroom.presentation.ChatRoomNavigator import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException diff --git a/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt b/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt index 87de3850e0..2e957adfb4 100644 --- a/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatdetails/ui/ChatDetailsFragment.kt @@ -195,7 +195,7 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { val wrappedDrawable = DrawableHelper.wrapDrawable(it) val mutableDrawable = wrappedDrawable.mutate() DrawableHelper.tintDrawable(mutableDrawable, context!!, R.color.colorPrimary) - DrawableHelper.compoundDrawable(name, mutableDrawable) + DrawableHelper.compoundStartDrawable(name, mutableDrawable) } } diff --git a/app/src/main/java/chat/rocket/android/chatdetails/ui/Menu.kt b/app/src/main/java/chat/rocket/android/chatdetails/ui/Menu.kt index d1f9a58060..c8961173a1 100644 --- a/app/src/main/java/chat/rocket/android/chatdetails/ui/Menu.kt +++ b/app/src/main/java/chat/rocket/android/chatdetails/ui/Menu.kt @@ -13,7 +13,7 @@ internal fun ChatDetailsFragment.setupMenu(menu: Menu) { with(settings.get(it)) { if (isJitsiEnabled()) { if (roomTypeOf(chatRoomType) !is RoomType.DirectMessage && !isJitsiEnabledForChannels()) { - return + return@let } menu.add( Menu.NONE, diff --git a/app/src/main/java/chat/rocket/android/chatinformation/presentation/MessageInfoPresenter.kt b/app/src/main/java/chat/rocket/android/chatinformation/presentation/MessageInfoPresenter.kt index 3f1ecc41f8..2b8b2b1d88 100644 --- a/app/src/main/java/chat/rocket/android/chatinformation/presentation/MessageInfoPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatinformation/presentation/MessageInfoPresenter.kt @@ -3,7 +3,7 @@ package chat.rocket.android.chatinformation.presentation import chat.rocket.android.chatroom.uimodel.UiModelMapper import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException diff --git a/app/src/main/java/chat/rocket/android/chatinformation/ui/MessageInfoFragment.kt b/app/src/main/java/chat/rocket/android/chatinformation/ui/MessageInfoFragment.kt index 8f6d881a6d..986f9f3ec1 100644 --- a/app/src/main/java/chat/rocket/android/chatinformation/ui/MessageInfoFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatinformation/ui/MessageInfoFragment.kt @@ -42,7 +42,6 @@ class MessageInfoFragment : Fragment(), MessageInfoView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AndroidSupportInjection.inject(this) - setHasOptionsMenu(true) val bundle = arguments if (bundle != null) { @@ -50,6 +49,8 @@ class MessageInfoFragment : Fragment(), MessageInfoView { } else { requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } } + + setHasOptionsMenu(true) } override fun onCreateView( diff --git a/app/src/main/java/chat/rocket/android/chatroom/di/ChatRoomFragmentModule.kt b/app/src/main/java/chat/rocket/android/chatroom/di/ChatRoomFragmentModule.kt index 260daba587..c14782ece9 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/di/ChatRoomFragmentModule.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/di/ChatRoomFragmentModule.kt @@ -56,6 +56,7 @@ class ChatRoomFragmentModule { context: Application, repository: SettingsRepository, userInteractor: GetCurrentUserInteractor, + tokenRepository: TokenRepository, @Named("currentServer") serverUrl: String, permissionsInteractor: PermissionsInteractor ): RoomUiModelMapper { @@ -63,6 +64,7 @@ class ChatRoomFragmentModule { context, repository.get(serverUrl), userInteractor, + tokenRepository, serverUrl, permissionsInteractor ) diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index 324c337a37..cf856debd1 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -29,12 +29,13 @@ import chat.rocket.android.server.domain.JobSchedulerInteractor import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.PermissionsInteractor import chat.rocket.android.server.domain.PublicSettings +import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.UsersRepository import chat.rocket.android.server.domain.uploadMaxFileSize import chat.rocket.android.server.domain.uploadMimeTypeFilter import chat.rocket.android.server.domain.useRealName -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory -import chat.rocket.android.server.infraestructure.state +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.state import chat.rocket.android.util.extension.getByteArray import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.avatarUrl @@ -101,6 +102,7 @@ class ChatRoomPresenter @Inject constructor( private val jobSchedulerInteractor: JobSchedulerInteractor, private val messageHelper: MessageHelper, private val dbManager: DatabaseManager, + tokenRepository: TokenRepository, getSettingsInteractor: GetSettingsInteractor, serverInteractor: GetCurrentServerInteractor, factory: ConnectionManagerFactory @@ -109,18 +111,21 @@ class ChatRoomPresenter @Inject constructor( private val manager = factory.create(currentServer) private val client = manager.client private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!) + private val token = tokenRepository.get(currentServer) private val currentLoggedUsername = userHelper.username() private val messagesChannel = Channel() private var chatRoomId: String? = null private lateinit var chatRoomType: String - private var chatIsBroadcast: Boolean = false + private lateinit var chatRoomName: String + private var isBroadcast: Boolean = false private var chatRoles = emptyList() private val stateChannel = Channel() private var typingStatusSubscriptionId: String? = null private var lastState = manager.state private var typingStatusList = arrayListOf() private val roomChangesChannel = Channel(Channel.CONFLATED) + private var lastMessageId: String? = null private lateinit var draftKey: String fun setupChatRoom( @@ -132,47 +137,77 @@ class ChatRoomPresenter @Inject constructor( draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId" chatRoomId = roomId chatRoomType = roomType + chatRoomName = roomName + chatRoles = emptyList() + var canModerate = isOwnerOrMod() + GlobalScope.launch(Dispatchers.IO + strategy.jobs) { - try { - chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) { - client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName) - } else { - emptyList() - } - } catch (ex: Exception) { - Timber.e(ex) - chatRoles = emptyList() - } finally { - // User has at least an 'owner' or 'moderator' role. - val canModerate = isOwnerOrMod() - // Can post anyway if has the 'post-readonly' permission on server. - val room = dbManager.getRoom(roomId) - room?.let { - chatIsBroadcast = it.chatRoom.broadcast ?: false - val roomUiModel = roomMapper.map(it, true) - launchUI(strategy) { - view.onRoomUpdated(roomUiModel = roomUiModel.copy( - broadcast = chatIsBroadcast, + // Can post anyway if has the 'post-readonly' permission on server. + val room = dbManager.getRoom(roomId) + room?.let { + isBroadcast = it.chatRoom.broadcast ?: false + val roomUiModel = roomMapper.map(it, true) + launchUI(strategy) { + view.onRoomUpdated( + roomUiModel = roomUiModel.copy( + broadcast = isBroadcast, canModerate = canModerate, writable = roomUiModel.writable || canModerate - )) - } + ) + ) } + } - loadMessages(roomId, roomType, clearDataSet = true) - chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) } - ?.let { messageId -> - val name = messageHelper.roomNameFromPermalink(chatRoomMessage) - citeMessage( - name!!, - messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, - messageId, - true - ) - } - subscribeRoomChanges() + loadMessages(roomId, chatRoomType, clearDataSet = true) + loadActiveMembers(roomId, chatRoomType, filterSelfOut = true) + + chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) } + ?.let { messageId -> + val name = messageHelper.roomNameFromPermalink(chatRoomMessage) + citeMessage( + name!!, + messageHelper.roomTypeFromPermalink(chatRoomMessage)!!, + messageId, + true + ) + } + + + /*FIXME: Get chat role can cause unresponsive problems especially on slower connections + We are updating the room again after the first step so that initial messages + get loaded in and the system appears more responsive. Something should be + done to either fix the load in speed of moderator roles or store the + information locally*/ + if (getChatRole()) { + canModerate = isOwnerOrMod() + if (canModerate) { + //FIXME: add this in when moderator page is actually created + //view.updateModeration() + } + } + + subscribeRoomChanges() + } + } + + private suspend fun getChatRole(): Boolean { + try { + if (roomTypeOf(chatRoomType) !is RoomType.DirectMessage) { + chatRoles = withContext(Dispatchers.IO + strategy.jobs) { + client.chatRoomRoles( + roomType = roomTypeOf(chatRoomType), + roomName = chatRoomName + ) + } + return true + } else { + chatRoles = emptyList() } + } catch (ex: Exception) { + Timber.e(ex) + chatRoles = emptyList() } + return false } private suspend fun subscribeRoomChanges() { @@ -181,7 +216,12 @@ class ChatRoomPresenter @Inject constructor( manager.addRoomChannel(it, roomChangesChannel) for (room in roomChangesChannel) { dbManager.getRoom(room.id)?.let { chatRoom -> - view.onRoomUpdated(roomMapper.map(chatRoom = chatRoom, showLastMessage = true)) + view.onRoomUpdated( + roomMapper.map( + chatRoom = chatRoom, + showLastMessage = true + ) + ) } } } @@ -206,8 +246,8 @@ class ChatRoomPresenter @Inject constructor( ) { this.chatRoomId = chatRoomId this.chatRoomType = chatRoomType - launchUI(strategy) { - view.showLoading() + + GlobalScope.launch(Dispatchers.IO + strategy.jobs) { try { if (offset == 0L) { // FIXME - load just 50 messages from DB to speed up. We will reload from Network after that @@ -215,11 +255,12 @@ class ChatRoomPresenter @Inject constructor( val localMessages = messagesRepository.getRecentMessages(chatRoomId, 50) val oldMessages = mapper.map( localMessages, RoomUiModel( - roles = chatRoles, - // FIXME: Why are we fixing isRoom attribute to true here? - isBroadcast = chatIsBroadcast, isRoom = true - ) + roles = chatRoles, + // FIXME: Why are we fixing isRoom attribute to true here? + isBroadcast = isBroadcast, isRoom = true + ) ) + lastMessageId = localMessages.firstOrNull()?.id val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId) if (oldMessages.isNotEmpty() && lastSyncDate != null) { view.showMessages(oldMessages, clearDataSet) @@ -232,7 +273,7 @@ class ChatRoomPresenter @Inject constructor( } // TODO: For now we are marking the room as read if we can get the messages (I mean, no exception occurs) - // but should mark only when the user see the first unread message. + // but should mark only when the user sees the first unread message. markRoomAsRead(chatRoomId) subscribeMessages(chatRoomId) @@ -243,8 +284,6 @@ class ChatRoomPresenter @Inject constructor( }.ifNull { view.showGenericErrorMessage() } - } finally { - view.hideLoading() } subscribeTypingStatus() @@ -279,7 +318,7 @@ class ChatRoomPresenter @Inject constructor( view.showMessages( mapper.map( messages, - RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true) + RoomUiModel(roles = chatRoles, isBroadcast = isBroadcast, isRoom = true) ), clearDataSet ) @@ -295,7 +334,7 @@ class ChatRoomPresenter @Inject constructor( view.showSearchedMessages( mapper.map( messages, - RoomUiModel(chatRoles, chatIsBroadcast, true) + RoomUiModel(chatRoles, isBroadcast, true) ) ) } catch (ex: Exception) { @@ -327,7 +366,11 @@ class ChatRoomPresenter @Inject constructor( timestamp = Instant.now().toEpochMilli(), sender = SimpleUser(user?.id, user?.username ?: username, user?.name), attachments = null, - avatar = currentServer.avatarUrl(username ?: ""), + avatar = currentServer.avatarUrl( + username!!, + token?.userId, + token?.authToken + ), channels = null, editedAt = null, editedBy = null, @@ -349,7 +392,7 @@ class ChatRoomPresenter @Inject constructor( view.showNewMessage( mapper.map( newMessage, - RoomUiModel(roles = chatRoles, isBroadcast = chatIsBroadcast) + RoomUiModel(roles = chatRoles, isBroadcast = isBroadcast) ), false ) client.sendMessage(id, chatRoomId, text) @@ -368,6 +411,7 @@ class ChatRoomPresenter @Inject constructor( throw ex } } + lastMessageId = id } else { client.updateMessage(chatRoomId, messageId, text) } @@ -599,16 +643,21 @@ class ChatRoomPresenter @Inject constructor( Timber.d("History: $messages") if (messages.result.isNotEmpty()) { - val models = mapper.map(messages.result, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast, - // FIXME: Why are we fixing isRoom attribute to true here? - isRoom = true - )) + val models = mapper.map( + messages.result, RoomUiModel( + roles = chatRoles, + isBroadcast = isBroadcast, + // FIXME: Why are we fixing isRoom attribute to true here? + isRoom = true + ) + ) messagesRepository.saveAll(messages.result) //if success - saving last synced time //assume that BE returns ordered messages, the first message is the latest one - messagesRepository.saveLastSyncDate(chatRoomId, messages.result.first().timestamp) + messagesRepository.saveLastSyncDate( + chatRoomId, + messages.result.first().timestamp + ) launchUI(strategy) { view.showNewMessage(models, true) @@ -685,9 +734,9 @@ class ChatRoomPresenter @Inject constructor( replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", quotedMessage = mapper.map( message, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast - ) + roles = chatRoles, + isBroadcast = isBroadcast + ) ).last().preview?.message ?: "" ) } @@ -790,13 +839,15 @@ class ChatRoomPresenter @Inject constructor( } } - fun loadActiveMembers( + suspend fun loadActiveMembers( chatRoomId: String, chatRoomType: String, offset: Long = 0, filterSelfOut: Boolean = false ) { - launchUI(strategy) { + val activeUsers = mutableListOf() + + withContext(Dispatchers.IO + strategy.jobs) { try { val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") { client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 50).result @@ -807,12 +858,12 @@ class ChatRoomPresenter @Inject constructor( // Take at most the 100 most recent messages distinguished by user. Can return less. val recentMessages = messagesRepository.getRecentMessages(chatRoomId, 100) .filterNot { filterSelfOut && it.sender?.username == self } - val activeUsers = mutableListOf() recentMessages.forEach { val sender = it.sender val username = sender?.username ?: "" val name = sender?.name ?: "" - val avatarUrl = currentServer.avatarUrl(username) + val avatarUrl = + currentServer.avatarUrl(username, token?.userId, token?.authToken) val found = members.firstOrNull { member -> member.username == username } val status = if (found != null) found.status else UserStatus.Offline() val searchList = mutableListOf(username, name) @@ -833,7 +884,8 @@ class ChatRoomPresenter @Inject constructor( activeUsers.addAll(others.map { val username = it.username ?: "" val name = it.name ?: "" - val avatarUrl = currentServer.avatarUrl(username) + val avatarUrl = + currentServer.avatarUrl(username, token?.userId, token?.authToken) val searchList = mutableListOf(username, name) PeopleSuggestionUiModel( avatarUrl, @@ -846,11 +898,13 @@ class ChatRoomPresenter @Inject constructor( ) }) - view.populatePeopleSuggestions(activeUsers) } catch (e: RocketChatException) { Timber.e(e) } } + launchUI(strategy) { + view.populatePeopleSuggestions(activeUsers) + } } fun spotlight(query: String, @AutoCompleteType type: Int, filterSelfOut: Boolean = false) { @@ -869,7 +923,7 @@ class ChatRoomPresenter @Inject constructor( val searchList = mutableListOf(username, name) it.emails?.forEach { email -> searchList.add(email.address) } PeopleSuggestionUiModel( - currentServer.avatarUrl(username), + currentServer.avatarUrl(username, token?.userId, token?.authToken), username, username, name, it.status, false, searchList ) }.filterNot { filterSelfOut && self != null && self == it.text }) @@ -931,6 +985,7 @@ class ChatRoomPresenter @Inject constructor( ChatRoom( id = id, subscriptionId = subscriptionId, + parentId = parentId, type = roomTypeOf(type), unread = unread, broadcast = broadcast ?: false, @@ -962,47 +1017,49 @@ class ChatRoomPresenter @Inject constructor( } // TODO: move this to new interactor or FetchChatRoomsInteractor? - private suspend fun getChatRoomsAsync(name: String? = null): List = withContext(Dispatchers.IO) { - retryDB("getAllSync()") { - dbManager.chatRoomDao().getAllSync().filter { - if (name == null) { - return@filter true - } - it.chatRoom.name == name || it.chatRoom.fullname == name - }.map { - with(it.chatRoom) { - ChatRoom( - id = id, - subscriptionId = subscriptionId, - type = roomTypeOf(type), - unread = unread, - broadcast = broadcast ?: false, - alert = alert, - fullName = fullname, - name = name ?: "", - favorite = favorite ?: false, - default = isDefault ?: false, - readonly = readonly, - open = open, - lastMessage = null, - archived = false, - status = null, - user = null, - userMentions = userMentions, - client = client, - announcement = null, - description = null, - groupMentions = groupMentions, - roles = null, - topic = null, - lastSeen = this.lastSeen, - timestamp = timestamp, - updatedAt = updatedAt - ) + private suspend fun getChatRoomsAsync(name: String? = null): List = + withContext(Dispatchers.IO) { + retryDB("getAllSync()") { + dbManager.chatRoomDao().getAllSync().filter { + if (name == null) { + return@filter true + } + it.chatRoom.name == name || it.chatRoom.fullname == name + }.map { + with(it.chatRoom) { + ChatRoom( + id = id, + subscriptionId = subscriptionId, + parentId = parentId, + type = roomTypeOf(type), + unread = unread, + broadcast = broadcast ?: false, + alert = alert, + fullName = fullname, + name = name ?: "", + favorite = favorite ?: false, + default = isDefault ?: false, + readonly = readonly, + open = open, + lastMessage = null, + archived = false, + status = null, + user = null, + userMentions = userMentions, + client = client, + announcement = null, + description = null, + groupMentions = groupMentions, + roles = null, + topic = null, + lastSeen = this.lastSeen, + timestamp = timestamp, + updatedAt = updatedAt + ) + } } } } - } fun joinChat(chatRoomId: String) { launchUI(strategy) { @@ -1011,7 +1068,8 @@ class ChatRoomPresenter @Inject constructor( val canPost = permissions.canPostToReadOnlyChannels() dbManager.getRoom(chatRoomId)?.let { val roomUiModel = roomMapper.map(it, true).copy( - writable = canPost) + writable = canPost + ) view.onJoined(roomUiModel = roomUiModel) view.onRoomUpdated(roomUiModel = roomUiModel) } @@ -1098,6 +1156,31 @@ class ChatRoomPresenter @Inject constructor( } } + fun reactToLastMessage(text: String, roomId: String) { + launchUI(strategy) { + lastMessageId?.let { messageId -> + val emoji = text.substring(1).trimEnd() + if (emoji.length >= 2 && emoji.startsWith(":") && emoji.endsWith(":")) { + try { + retryIO("toggleEmoji($messageId, $emoji)") { + client.toggleReaction(messageId, emoji.removeSurrounding(":")) + } + logReactionEvent() + view.clearMessageComposition(true) + } catch (ex: RocketChatException) { + Timber.e(ex) + // emoji is not valid, post it + sendMessage(roomId, text, null) + } + } else { + sendMessage(roomId, text, null) + } + }.ifNull { + sendMessage(roomId, text, null) + } + } + } + private fun logReactionEvent() { when { roomTypeOf(chatRoomType) is RoomType.DirectMessage -> @@ -1171,18 +1254,19 @@ class ChatRoomPresenter @Inject constructor( sendMessage(roomId, text, null) } else { view.disableSendMessageButton() - val command = text.split(" ") - val name = command[0].substring(1) + val index = text.indexOf(" ") + var name = "" var params = "" - command.forEachIndexed { index, param -> - if (index > 0) { - params += "$param " - } + if (index >= 1) { + name = text.substring(1, index) + params = text.substring(index + 1).trim() } val result = retryIO("runCommand($name, $params, $roomId)") { client.runCommand(Command(name, params), roomId) } - if (!result) { + if (result) { + view.clearMessageComposition(true) + } else { // failed, command is not valid so post it sendMessage(roomId, text, null) } @@ -1260,7 +1344,7 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { val viewModelStreamedMessage = mapper.map( streamedMessage, RoomUiModel( - roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true + roles = chatRoles, isBroadcast = isBroadcast, isRoom = true ) ) val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId) @@ -1295,6 +1379,7 @@ class ChatRoomPresenter @Inject constructor( fun clearDraftMessage() { localRepository.clear(draftKey) } + /** * Get unfinished message from local repository, when user left chat room without * sending a message and now the user is back. diff --git a/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt b/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt index 14bdb6dbb7..53fbb16f2e 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/service/MessageService.kt @@ -4,9 +4,9 @@ import android.app.job.JobParameters import android.app.job.JobService import chat.rocket.android.db.DatabaseManagerFactory import chat.rocket.android.server.domain.GetAccountsInteractor -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory -import chat.rocket.android.server.infraestructure.DatabaseMessageMapper -import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.DatabaseMessageMapper +import chat.rocket.android.server.infrastructure.DatabaseMessagesRepository import chat.rocket.core.internal.rest.sendMessage import chat.rocket.core.model.Message import dagger.android.AndroidInjection diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt index c881a45621..207a9f8a07 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt @@ -10,7 +10,7 @@ import androidx.fragment.app.Fragment import chat.rocket.android.R import chat.rocket.android.chatroom.presentation.ChatRoomNavigator import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.android.util.extensions.addFragment import chat.rocket.android.util.extensions.textContent import dagger.android.AndroidInjection @@ -139,7 +139,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { } fun setupExpandMoreForToolbar(listener: (View) -> Unit) { - DrawableHelper.compoundRightDrawable( + DrawableHelper.compoundEndDrawable( text_toolbar_title, DrawableHelper.getDrawableFromId(R.drawable.ic_chatroom_toolbar_expand_more_20dp, this) ) diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt index a597ebe238..9f60804723 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt @@ -5,6 +5,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle @@ -28,14 +29,17 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.ScreenViewEvent +import chat.rocket.android.chatroom.adapter.AttachmentViewHolder import chat.rocket.android.chatroom.adapter.ChatRoomAdapter import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter import chat.rocket.android.chatroom.adapter.EmojiSuggestionsAdapter +import chat.rocket.android.chatroom.adapter.MessageViewHolder import chat.rocket.android.chatroom.adapter.PEOPLE import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter @@ -61,11 +65,16 @@ import chat.rocket.android.emoji.EmojiPickerPopup import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.internal.isCustom import chat.rocket.android.helper.EndlessRecyclerViewScrollListener -import chat.rocket.android.helper.ImageHelper import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.MessageParser +import chat.rocket.android.helper.AndroidPermissionsHelper +import chat.rocket.android.helper.AndroidPermissionsHelper.getCameraPermission +import chat.rocket.android.helper.AndroidPermissionsHelper.getWriteExternalStoragePermission +import chat.rocket.android.helper.AndroidPermissionsHelper.hasCameraPermission +import chat.rocket.android.helper.AndroidPermissionsHelper.hasWriteExternalStoragePermission import chat.rocket.android.util.extension.asObservable import chat.rocket.android.util.extension.createImageFile +import chat.rocket.android.util.extension.orFalse import chat.rocket.android.util.extensions.circularRevealOrUnreveal import chat.rocket.android.util.extensions.clearLightStatusBar import chat.rocket.android.util.extensions.fadeIn @@ -81,6 +90,7 @@ import chat.rocket.common.model.RoomType import chat.rocket.common.model.roomTypeOf import chat.rocket.core.internal.realtime.socket.model.State import com.bumptech.glide.Glide +import com.google.android.material.snackbar.Snackbar import dagger.android.support.AndroidSupportInjection import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable @@ -149,7 +159,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR lateinit var analyticsManager: AnalyticsManager @Inject lateinit var navigator: ChatRoomNavigator - private lateinit var adapter: ChatRoomAdapter + private lateinit var chatRoomAdapter: ChatRoomAdapter internal lateinit var chatRoomId: String private lateinit var chatRoomName: String internal lateinit var chatRoomType: String @@ -268,7 +278,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AndroidSupportInjection.inject(this) - setHasOptionsMenu(true) arguments?.run { chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "") @@ -283,7 +292,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } - adapter = ChatRoomAdapter( + chatRoomAdapter = ChatRoomAdapter( roomId = chatRoomId, roomType = chatRoomType, roomName = chatRoomName, @@ -292,6 +301,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR navigator = navigator, analyticsManager = analyticsManager ) + + setHasOptionsMenu(true) } override fun onCreateView( @@ -380,7 +391,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun showMessages(dataSet: List>, clearDataSet: Boolean) { ui { if (clearDataSet) { - adapter.clearData() + chatRoomAdapter.clearData() } if (dataSet.isNotEmpty()) { @@ -421,35 +432,22 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } - if (recycler_view.adapter == null) { - recycler_view.adapter = adapter - if (dataSet.size >= 30) { - recycler_view.addOnScrollListener(endlessRecyclerViewScrollListener) - } - recycler_view.addOnLayoutChangeListener(layoutChangeListener) - recycler_view.addOnScrollListener(onScrollListener) - - // Load just once, on the first page... - presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true) - } - - val oldMessagesCount = adapter.itemCount - adapter.appendData(dataSet) + val oldMessagesCount = chatRoomAdapter.itemCount + chatRoomAdapter.appendData(dataSet) if (oldMessagesCount == 0 && dataSet.isNotEmpty()) { recycler_view.scrollToPosition(0) verticalScrollOffset.set(0) } - presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true) - empty_chat_view.isVisible = adapter.itemCount == 0 + empty_chat_view.isVisible = chatRoomAdapter.itemCount == 0 dismissEmojiKeyboard() } } override fun showSearchedMessages(dataSet: List>) { recycler_view.removeOnScrollListener(endlessRecyclerViewScrollListener) - adapter.clearData() - adapter.prependData(dataSet) - empty_chat_view.isVisible = adapter.itemCount == 0 + chatRoomAdapter.clearData() + chatRoomAdapter.prependData(dataSet) + empty_chat_view.isVisible = chatRoomAdapter.itemCount == 0 dismissEmojiKeyboard() } @@ -460,21 +458,21 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR setupToolbar(roomUiModel.name.toString()) setupMessageComposer(roomUiModel) isBroadcastChannel = roomUiModel.broadcast - if (isBroadcastChannel && !roomUiModel.canModerate) { - disableMenu = true - activity?.invalidateOptionsMenu() - } + isFavorite = roomUiModel.favorite.orFalse() + disableMenu = (roomUiModel.broadcast && !roomUiModel.canModerate) + activity?.invalidateOptionsMenu() } } - override fun sendMessage(text: String) { ui { if (!text.isBlank()) { - if (!text.startsWith("/")) { - presenter.sendMessage(chatRoomId, text, editingMessageId) - } else { + if (text.startsWith("/")) { presenter.runCommand(text, chatRoomId) + } else if (text.startsWith("+")) { + presenter.reactToLastMessage(text, chatRoomId) + } else { + presenter.sendMessage(chatRoomId, text, editingMessageId) } } } @@ -519,7 +517,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } - override fun clearMessageComposition(deleteMessage: Boolean) { ui { citation = null @@ -533,7 +530,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun showNewMessage(message: List>, isMessageReceived: Boolean) { ui { - adapter.prependData(message) + chatRoomAdapter.prependData(message) if (isMessageReceived && button_fab.isVisible) { newMessageCount++ if (newMessageCount <= 99) { @@ -546,7 +543,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR recycler_view.scrollToPosition(0) } verticalScrollOffset.set(0) - empty_chat_view.isVisible = adapter.itemCount == 0 + empty_chat_view.isVisible = chatRoomAdapter.itemCount == 0 dismissEmojiKeyboard() } } @@ -556,9 +553,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR // TODO - investigate WHY we get a empty list here if (message.isEmpty()) return@ui - if (adapter.updateItem(message.last())) { + if (chatRoomAdapter.updateItem(message.last())) { if (message.size > 1) { - adapter.prependData(listOf(message.first())) + chatRoomAdapter.prependData(listOf(message.first())) } } else { showNewMessage(message, true) @@ -569,7 +566,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun dispatchDeleteMessage(msgId: String) { ui { - adapter.removeItem(msgId) + chatRoomAdapter.removeItem(msgId) } } @@ -596,45 +593,31 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } override fun showMessage(message: String) { - ui { - showToast(message) - } + ui { showToast(message) } } override fun showMessage(resId: Int) { - ui { - showToast(resId) - } + ui { showToast(resId) } } override fun showGenericErrorMessage() { - ui { - showMessage(getString(R.string.msg_generic_error)) - } + ui { showMessage(getString(R.string.msg_generic_error)) } } override fun populatePeopleSuggestions(members: List) { - ui { - suggestions_view.addItems("@", members) - } + ui { suggestions_view.addItems("@", members) } } override fun populateRoomSuggestions(chatRooms: List) { - ui { - suggestions_view.addItems("#", chatRooms) - } + ui { suggestions_view.addItems("#", chatRooms) } } override fun populateCommandSuggestions(commands: List) { - ui { - suggestions_view.addItems("/", commands) - } + ui { suggestions_view.addItems("/", commands) } } override fun populateEmojiSuggestions(emojis: List) { - ui { - suggestions_view.addItems(":", emojis) - } + ui { suggestions_view.addItems(":", emojis) } } override fun copyToClipboard(message: String) { @@ -812,7 +795,56 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR presenter.loadMessages(chatRoomId, chatRoomType, page * 30L) } } - recycler_view.addOnScrollListener(fabScrollListener) + + with (recycler_view) { + adapter = chatRoomAdapter + addOnScrollListener(endlessRecyclerViewScrollListener) + addOnLayoutChangeListener(layoutChangeListener) + addOnScrollListener(onScrollListener) + addOnScrollListener(fabScrollListener) + } + if (!isReadOnly) { + val touchCallback: ItemTouchHelper.SimpleCallback = + object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + var replyId: String? = null + + when (viewHolder) { + is MessageViewHolder -> replyId = viewHolder.data?.messageId + is AttachmentViewHolder -> replyId = viewHolder.data?.messageId + } + + replyId?.let { + citeMessage(chatRoomName, chatRoomType, it, true) + } + + chatRoomAdapter.notifyItemChanged(viewHolder.adapterPosition) + } + + override fun getSwipeDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + // Currently enable swipes for text and attachment messages only + + if (viewHolder is MessageViewHolder || viewHolder is AttachmentViewHolder) { + return super.getSwipeDirs(recyclerView, viewHolder) + } + + return 0 + } + } + + ItemTouchHelper(touchCallback).attachToRecyclerView(recycler_view) + } } private fun setupFab() { @@ -903,8 +935,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR button_add_reaction_or_show_keyboard.setOnClickListener { toggleKeyboard() } button_take_a_photo.setOnClickListener { - dispatchTakePictureIntent() - + // Check for camera permission + context?.let { + if (hasCameraPermission(it)) { + dispatchTakePictureIntent() + } else { + getCameraPermission(this) + } + } handler.postDelayed({ hideAttachmentOptions() }, 400) @@ -922,11 +960,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR button_drawing.setOnClickListener { activity?.let { fragmentActivity -> - if (!ImageHelper.canWriteToExternalStorage(fragmentActivity)) { - ImageHelper.checkWritingPermission(fragmentActivity) + if (!hasWriteExternalStoragePermission(fragmentActivity)) { + getWriteExternalStoragePermission(this) } else { - val intent = Intent(fragmentActivity, DrawingActivity::class.java) - startActivityForResult(intent, REQUEST_CODE_FOR_DRAW) + dispatchDrawingIntent() } } @@ -937,6 +974,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } + private fun dispatchDrawingIntent() { + val intent = Intent(activity, DrawingActivity::class.java) + startActivityForResult(intent, REQUEST_CODE_FOR_DRAW) + } + private fun dispatchTakePictureIntent() { Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> // Create the File where the photo should go @@ -957,6 +999,44 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + when (requestCode) { + AndroidPermissionsHelper.CAMERA_CODE -> { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted + dispatchTakePictureIntent() + } else { + // permission denied + Snackbar.make( + root_layout, + R.string.msg_camera_permission_denied, + Snackbar.LENGTH_SHORT + ).show() + } + return + } + AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE -> { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted + dispatchDrawingIntent() + } else { + // permission denied + Snackbar.make( + root_layout, + R.string.msg_storage_permission_denied, + Snackbar.LENGTH_SHORT + ).show() + } + return + } + } + } + private fun getDraftMessage() { val unfinishedMessage = presenter.getDraftUnfinishedMessage() if (unfinishedMessage.isNotNullNorEmpty()) { diff --git a/app/src/main/java/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt b/app/src/main/java/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt index 3cdd187d40..4a1be35286 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt @@ -30,7 +30,7 @@ import chat.rocket.android.server.domain.baseUrl import chat.rocket.android.server.domain.messageReadReceiptEnabled import chat.rocket.android.server.domain.messageReadReceiptStoreUsers import chat.rocket.android.server.domain.useRealName -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.android.util.extension.isImage import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.ifNotNullNorEmpty @@ -108,7 +108,7 @@ class UiModelMapper @Inject constructor( readReceipts.forEach { list.add( ReadReceiptViewModel( - avatar = baseUrl.avatarUrl(it.user.username ?: ""), + avatar = baseUrl.avatarUrl(it.user.username!!, token?.userId, token?.authToken), name = userHelper.displayName(it.user), time = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(it.timestamp)) ) @@ -173,6 +173,7 @@ class UiModelMapper @Inject constructor( ChatRoom( id = id, subscriptionId = subscriptionId, + parentId = parentId, type = roomTypeOf(type), unread = unread, broadcast = broadcast ?: false, @@ -527,7 +528,7 @@ class UiModelMapper @Inject constructor( val username = message.sender?.username ?: "?" return baseUrl.let { - baseUrl.avatarUrl(username) + baseUrl.avatarUrl(username, token?.userId, token?.authToken) } } diff --git a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomUiModelMapper.kt b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomUiModelMapper.kt index 0dc298d137..739f4adbc6 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomUiModelMapper.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomUiModelMapper.kt @@ -8,11 +8,13 @@ import chat.rocket.android.db.model.ChatRoom 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.TokenRepository import chat.rocket.android.server.domain.showLastMessage import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useSpecialCharsOnRoom import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.date +import chat.rocket.android.util.extensions.isNotNullNorEmpty import chat.rocket.android.util.extensions.localDateTime import chat.rocket.common.model.RoomType import chat.rocket.common.model.User @@ -26,10 +28,12 @@ class RoomUiModelMapper( private val context: Application, private val settings: PublicSettings, private val userInteractor: GetCurrentUserInteractor, + private val tokenRepository: TokenRepository, private val serverUrl: String, private val permissions: PermissionsInteractor ) { private val currentUser by lazy { userInteractor.get() } + private val token by lazy { tokenRepository.get(serverUrl) } fun map( rooms: List, @@ -80,7 +84,7 @@ class RoomUiModelMapper( private fun mapUser(user: User): RoomUiModel = with(user) { val name = mapName(user.username!!, user.name) val status = user.status - val avatar = serverUrl.avatarUrl(user.username!!) + val avatar = serverUrl.avatarUrl(user.username!!, token?.userId, token?.authToken) val username = user.username!! RoomUiModel( @@ -98,7 +102,7 @@ class RoomUiModelMapper( id = id, name = name!!, type = type, - avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true), + avatar = serverUrl.avatarUrl(name!!, token?.userId, token?.authToken, isGroupOrChannel = true), lastMessage = if (showLastMessage) { mapLastMessage( lastMessage?.sender?.id, @@ -115,36 +119,40 @@ class RoomUiModelMapper( ) } - fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel = with(chatRoom.chatRoom) { - val isUnread = alert || unread > 0 - val type = roomTypeOf(type) - val status = chatRoom.status?.let { userStatusOf(it) } - val roomName = mapName(name, fullname) - val favorite = favorite - val timestamp = mapDate(lastMessageTimestamp ?: updatedAt) - val avatar = if (type is RoomType.DirectMessage) { - serverUrl.avatarUrl(name) - } else { - serverUrl.avatarUrl(name, isGroupOrChannel = true) - } - val unread = mapUnread(unread) - val lastMessage = if (showLastMessage) { - mapLastMessage( + fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel = + with(chatRoom.chatRoom) { + val isUnread = alert || unread > 0 + val type = roomTypeOf(type) + val status = chatRoom.status?.let { userStatusOf(it) } + val roomName = mapName(name, fullname) + val favorite = favorite + val timestamp = mapDate(lastMessageTimestamp ?: updatedAt) + val avatar = + if (type is RoomType.DirectMessage) { + serverUrl.avatarUrl(name, token?.userId, token?.authToken) + } else { + serverUrl.avatarUrl(name, token?.userId, token?.authToken, isGroupOrChannel = true) + } + val unread = mapUnread(unread) + val lastMessage = if (showLastMessage) { + mapLastMessage( lastMessageUserId, chatRoom.lastMessageUserName, chatRoom.lastMessageUserFullName, lastMessageText, type is RoomType.DirectMessage - ) - } else { - null - } - val hasMentions = mapMentions(userMentions, groupMentions) - val open = open - val lastMessageMarkdown = lastMessage?.let { Markwon.markdown(context, it.toString()).toString() } + ) + } else { + null + } + val hasMentions = mapMentions(userMentions, groupMentions) + val open = open + val lastMessageMarkdown = + lastMessage?.let { Markwon.markdown(context, it.toString()).toString() } - RoomUiModel( + RoomUiModel( id = id, + isDiscussion = parentId.isNotNullNorEmpty(), name = roomName, type = type, avatar = avatar, @@ -159,8 +167,8 @@ class RoomUiModelMapper( username = if (type is RoomType.DirectMessage) name else null, muted = muted.orEmpty(), writable = isChannelWritable(muted) - ) - } + ) + } private fun isChannelWritable(muted: List?): Boolean { val canWriteToReadOnlyChannels = permissions.canPostToReadOnlyChannels() @@ -169,7 +177,7 @@ class RoomUiModelMapper( private fun roomType(type: String): String = with(context.resources) { when (type) { - RoomType.CHANNEL -> getString(R.string.header_channel) + RoomType.CHANNEL -> getString(R.string.msg_channels) RoomType.PRIVATE_GROUP -> getString(R.string.header_private_groups) RoomType.DIRECT_MESSAGE -> getString(R.string.header_direct_messages) RoomType.LIVECHAT -> getString(R.string.header_live_chats) diff --git a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt index 2317178adf..9a4da09a36 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/adapter/RoomViewHolder.kt @@ -3,6 +3,7 @@ package chat.rocket.android.chatrooms.adapter import android.content.res.Resources import android.graphics.drawable.Drawable import android.view.View +import android.widget.TextView import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -11,14 +12,17 @@ import chat.rocket.android.chatrooms.adapter.model.RoomUiModel import chat.rocket.android.util.extension.setTextViewAppearance import chat.rocket.common.model.RoomType import chat.rocket.common.model.UserStatus +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions import kotlinx.android.synthetic.main.item_chat.view.* -import kotlinx.android.synthetic.main.unread_messages_badge.view.* class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit) : ViewHolder(itemView) { private val resources: Resources = itemView.resources private val channelIcon: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp, null) private val groupIcon: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp, null) + private val discussionIcon: Drawable = resources.getDrawable(R.drawable.ic_discussion_20dp, null) private val onlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp, null) private val awayIcon: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp, null) private val busyIcon: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp, null) @@ -27,10 +31,16 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit override fun bindViews(data: RoomItemHolder) { val room = data.data with(itemView) { - image_avatar.setImageURI(room.avatar) + Glide.with(image_avatar.context) + .load(room.avatar) + .apply(RequestOptions.bitmapTransform(RoundedCorners(10))) + .into(image_avatar) + text_chat_name.text = room.name - if (room.status != null && room.type is RoomType.DirectMessage) { + if (room.isDiscussion) { + image_chat_icon.setImageDrawable(discussionIcon) + } else if (room.status != null && room.type is RoomType.DirectMessage) { image_chat_icon.setImageDrawable(getStatusDrawable(room.status)) } else { image_chat_icon.setImageDrawable(getRoomDrawable(room.type)) @@ -51,6 +61,7 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit } if (room.alert) { + val text_total_unread_messages = text_total_unread_messages as TextView if (room.unread == null) text_total_unread_messages.text = "!" if (room.unread != null) text_total_unread_messages.text = room.unread if (room.mentions) text_total_unread_messages.text = "@${room.unread}" diff --git a/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt b/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt index 8531d41a70..4b536b3d4f 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/adapter/model/RoomUiModel.kt @@ -5,6 +5,7 @@ import chat.rocket.common.model.UserStatus data class RoomUiModel( val id: String, + val isDiscussion: Boolean = false, val type: RoomType, val name: CharSequence, val avatar: String, diff --git a/app/src/main/java/chat/rocket/android/chatrooms/di/ChatRoomsFragmentModule.kt b/app/src/main/java/chat/rocket/android/chatrooms/di/ChatRoomsFragmentModule.kt index f231837cd1..bea2578b3a 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/di/ChatRoomsFragmentModule.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/di/ChatRoomsFragmentModule.kt @@ -10,15 +10,14 @@ import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.db.ChatRoomDao import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.UserDao -import chat.rocket.android.infrastructure.LocalRepository 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.SettingsRepository import chat.rocket.android.server.domain.TokenRepository -import chat.rocket.android.server.infraestructure.ConnectionManager -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.ConnectionManager +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.core.RocketChatClient import dagger.Module import dagger.Provides @@ -45,7 +44,7 @@ class ChatRoomsFragmentModule { factory: RocketChatClientFactory, @Named("currentServer") currentServer: String ): RocketChatClient { - return factory.create(currentServer) + return factory.get(currentServer) } @Provides @@ -87,12 +86,20 @@ class ChatRoomsFragmentModule { @PerFragment fun provideRoomMapper( context: Application, - repository: SettingsRepository, + settingsRepository: SettingsRepository, userInteractor: GetCurrentUserInteractor, + tokenRepository: TokenRepository, @Named("currentServer") serverUrl: String, permissionsInteractor: PermissionsInteractor ): RoomUiModelMapper { - return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl, permissionsInteractor) + return RoomUiModelMapper( + context, + settingsRepository.get(serverUrl), + userInteractor, + tokenRepository, + serverUrl, + permissionsInteractor + ) } @Provides diff --git a/app/src/main/java/chat/rocket/android/chatrooms/infrastructure/ChatRoomsRepository.kt b/app/src/main/java/chat/rocket/android/chatrooms/infrastructure/ChatRoomsRepository.kt index 41850a4cb1..96695c4039 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/infrastructure/ChatRoomsRepository.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/infrastructure/ChatRoomsRepository.kt @@ -15,6 +15,10 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao) { Order.GROUPED_ACTIVITY -> dao.getAllGrouped() Order.NAME -> dao.getAllAlphabetically() Order.GROUPED_NAME -> dao.getAllAlphabeticallyGrouped() + Order.UNREAD_ON_TOP_ACTIVITY -> dao.getAllUnread(); + Order.UNREAD_ON_TOP_NAME -> dao.getAllAlphabeticallyUnread(); + Order.UNREAD_ON_TOP_GROUPED_ACTIVITY -> dao.getAllGroupedUnread(); + Order.UNREAD_ON_TOP_GROUPED_NAME -> dao.getAllAlphabeticallyGroupedUnread(); } } @@ -28,8 +32,16 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao) { GROUPED_ACTIVITY, NAME, GROUPED_NAME, + UNREAD_ON_TOP_ACTIVITY, + UNREAD_ON_TOP_NAME, + UNREAD_ON_TOP_GROUPED_ACTIVITY, + UNREAD_ON_TOP_GROUPED_NAME } } fun ChatRoomsRepository.Order.isGrouped(): Boolean = this == ChatRoomsRepository.Order.GROUPED_ACTIVITY - || this == ChatRoomsRepository.Order.GROUPED_NAME \ No newline at end of file + || this == ChatRoomsRepository.Order.GROUPED_NAME + +fun ChatRoomsRepository.Order.isUnreadOnTop(): Boolean = this == ChatRoomsRepository.Order.UNREAD_ON_TOP_ACTIVITY + || this == ChatRoomsRepository.Order.UNREAD_ON_TOP_NAME + diff --git a/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt b/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt index 4f1ee432d2..5646f96bb3 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt @@ -10,9 +10,11 @@ import chat.rocket.android.helper.UserHelper import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.server.domain.SettingsRepository +import chat.rocket.android.server.domain.SortingAndGroupingInteractor +import chat.rocket.android.server.domain.siteName import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useSpecialCharsOnRoom -import chat.rocket.android.server.infraestructure.ConnectionManager +import chat.rocket.android.server.infrastructure.ConnectionManager import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.retryDB import chat.rocket.android.util.retryIO @@ -35,6 +37,7 @@ class ChatRoomsPresenter @Inject constructor( private val strategy: CancelStrategy, private val navigator: MainNavigator, @Named("currentServer") private val currentServer: String, + private val sortingAndGroupingInteractor: SortingAndGroupingInteractor, private val dbManager: DatabaseManager, manager: ConnectionManager, private val localRepository: LocalRepository, @@ -44,29 +47,44 @@ class ChatRoomsPresenter @Inject constructor( private val client = manager.client private val settings = settingsRepository.get(currentServer) + fun toCreateChannel() = navigator.toCreateChannel() + + fun toSettings() = navigator.toSettings() + + fun toDirectory() = navigator.toDirectory() + + fun getCurrentServerName() = view.setupToolbar(settings.siteName() ?: currentServer) + + fun getSortingAndGroupingPreferences() { + with(sortingAndGroupingInteractor) { + view.setupSortingAndGrouping( + getSortByName(currentServer), + getUnreadOnTop(currentServer), + getGroupByType(currentServer), + getGroupByFavorites(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") + Timber.e("Error loading channel") view.showGenericErrorMessage() } } catch (ex: Exception) { - Timber.d(ex, "Error loading channel") + Timber.e(ex, "Error loading channel") view.showGenericErrorMessage() - } finally { - view.hideLoadingRoom() } } } fun loadChatRoom(chatRoom: RoomUiModel) { launchUI(strategy) { - view.showLoadingRoom(chatRoom.name) try { val room = retryDB("getRoom(${chatRoom.id}") { dbManager.getRoom(chatRoom.id) } if (room != null) { @@ -76,6 +94,7 @@ class ChatRoomsPresenter @Inject constructor( val entity = ChatRoomEntity( id = id, subscriptionId = "", + parentId = null, type = type.toString(), name = username ?: name.toString(), fullname = name.toString(), @@ -86,10 +105,8 @@ class ChatRoomsPresenter @Inject constructor( } } } catch (ex: Exception) { - Timber.d(ex, "Error loading channel") + Timber.e(ex, "Error loading channel") view.showGenericErrorMessage() - } finally { - view.hideLoadingRoom() } } } @@ -97,11 +114,12 @@ class ChatRoomsPresenter @Inject constructor( suspend fun loadChatRoom(chatRoom: ChatRoomEntity, local: Boolean = false) { with(chatRoom) { val isDirectMessage = roomTypeOf(type) is RoomType.DirectMessage - val roomName = if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) { - fullname ?: name - } else { - name - } + val roomName = + if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) { + fullname ?: name + } else { + name + } val myself = getCurrentUser() if (myself?.username == null) { @@ -131,14 +149,14 @@ class ChatRoomsPresenter @Inject constructor( } navigator.toChatRoom( - chatRoomId = id, - chatRoomName = roomName, - chatRoomType = type, - isReadOnly = readonly ?: false, - chatRoomLastSeen = lastSeen ?: -1, - isSubscribed = open, - isCreator = ownerId == myself.id || isDirectMessage, - isFavorite = favorite ?: false + chatRoomId = id, + chatRoomName = roomName, + chatRoomType = type, + isReadOnly = readonly ?: false, + chatRoomLastSeen = lastSeen ?: -1, + isSubscribed = open, + isCreator = ownerId == myself.id || isDirectMessage, + isFavorite = favorite ?: false ) } } diff --git a/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsView.kt b/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsView.kt index 638abd4234..e40130f71f 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsView.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsView.kt @@ -4,7 +4,27 @@ import chat.rocket.android.core.behaviours.LoadingView import chat.rocket.android.core.behaviours.MessageView interface ChatRoomsView : LoadingView, MessageView { - fun showLoadingRoom(name: CharSequence) - fun hideLoadingRoom() + /** + * Setups the toolbar with the current logged in server name. + * + * @param serverName The current logged in server name to show on Toolbar. + */ + fun setupToolbar(serverName: String) + + /** + * Setups the sorting and grouping in the bases of the user preference for + * the current logged in server. + * + * @param isSortByName True if sorting by name, false otherwise. + * @param isUnreadOnTop True if grouping by unread on top, false otherwise. + * @param isGroupByType True if grouping by type , false otherwise. + * @param isGroupByFavorites True if grouping by favorites, false otherwise. + */ + fun setupSortingAndGrouping( + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt index 58f3355722..33bb481171 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt @@ -1,19 +1,15 @@ package chat.rocket.android.chatrooms.ui -import androidx.appcompat.app.AlertDialog -import android.app.ProgressDialog import android.os.Bundle -import android.os.Handler import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.CheckBox -import android.widget.RadioGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Observer @@ -30,12 +26,9 @@ import chat.rocket.android.chatrooms.viewmodel.ChatRoomsViewModel import chat.rocket.android.chatrooms.viewmodel.ChatRoomsViewModelFactory import chat.rocket.android.chatrooms.viewmodel.LoadingState import chat.rocket.android.chatrooms.viewmodel.Query -import chat.rocket.android.helper.ChatRoomsSortOrder -import chat.rocket.android.helper.Constants -import chat.rocket.android.helper.SharedPreferenceHelper +import chat.rocket.android.servers.ui.ServersBottomSheetFragment +import chat.rocket.android.sortingandgrouping.ui.SortingAndGroupingBottomSheetFragment import chat.rocket.android.util.extension.onQueryTextListener -import chat.rocket.android.util.extensions.fadeIn -import chat.rocket.android.util.extensions.fadeOut import chat.rocket.android.util.extensions.ifNotNullNotEmpty import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.showToast @@ -43,6 +36,7 @@ import chat.rocket.android.util.extensions.ui import chat.rocket.android.widget.DividerItemDecoration import chat.rocket.core.internal.realtime.socket.model.State import dagger.android.support.AndroidSupportInjection +import kotlinx.android.synthetic.main.app_bar_chat_rooms.* import kotlinx.android.synthetic.main.fragment_chat_rooms.* import timber.log.Timber import javax.inject.Inject @@ -51,46 +45,37 @@ internal const val TAG_CHAT_ROOMS_FRAGMENT = "ChatRoomsFragment" private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID" -class ChatRoomsFragment : Fragment(), ChatRoomsView { - @Inject - lateinit var presenter: ChatRoomsPresenter - @Inject - lateinit var factory: ChatRoomsViewModelFactory - @Inject - lateinit var analyticsManager: AnalyticsManager +fun newInstance(chatRoomId: String?): Fragment = ChatRoomsFragment().apply { + arguments = Bundle(1).apply { + putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) + } +} +class ChatRoomsFragment : Fragment(), ChatRoomsView { + @Inject lateinit var presenter: ChatRoomsPresenter + @Inject lateinit var factory: ChatRoomsViewModelFactory + @Inject lateinit var analyticsManager: AnalyticsManager private lateinit var viewModel: ChatRoomsViewModel - - private var searchView: SearchView? = null - private var sortView: MenuItem? = null - private val handler = Handler() private var chatRoomId: String? = null - private var progressDialog: ProgressDialog? = null - - companion object { - fun newInstance(chatRoomId: String? = null): ChatRoomsFragment = ChatRoomsFragment().apply { - arguments = Bundle(1).apply { - putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) - } - } - } + private var isSortByName = false + private var isUnreadOnTop = false + private var isGroupByType = false + private var isGroupByFavorites = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AndroidSupportInjection.inject(this) - setHasOptionsMenu(true) + arguments?.run { chatRoomId = getString(BUNDLE_CHAT_ROOM_ID) - chatRoomId.ifNotNullNotEmpty { roomId -> - presenter.loadChatRoom(roomId) + + chatRoomId.ifNotNullNotEmpty { + presenter.loadChatRoom(it) chatRoomId = null } } - } - override fun onDestroy() { - handler.removeCallbacks(dismissStatus) - super.onDestroy() + setHasOptionsMenu(true) } override fun onCreateView( @@ -102,62 +87,48 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + with(presenter) { + getCurrentServerName() + getSortingAndGroupingPreferences() + } + viewModel = ViewModelProviders.of(this, factory).get(ChatRoomsViewModel::class.java) subscribeUi() - - setupToolbar() + setupListeners() analyticsManager.logScreenView(ScreenViewEvent.ChatRooms) } - private fun subscribeUi() { - ui { - val adapter = RoomsAdapter { room -> - presenter.loadChatRoom(room) + override fun setupToolbar(serverName: String) { + with((activity as AppCompatActivity)) { + with(toolbar) { + setSupportActionBar(this) + supportActionBar?.setDisplayShowTitleEnabled(false) + setNavigationOnClickListener { presenter.toSettings() } } + } + text_server_name.text = serverName + } - recycler_view.layoutManager = LinearLayoutManager(it) - recycler_view.addItemDecoration( - DividerItemDecoration( - it, - resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_start), - resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end) - ) + override fun setupSortingAndGrouping( + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) { + this.isSortByName = isSortByName + this.isUnreadOnTop = isUnreadOnTop + this.isGroupByType = isGroupByType + this.isGroupByFavorites = isGroupByFavorites + + if (isSortByName) { + text_sort_by.text = + getString(R.string.msg_sort_by_placeholder, getString(R.string.msg_sort_by_name).toLowerCase()) + } else { + text_sort_by.text = getString( + R.string.msg_sort_by_placeholder, + getString(R.string.msg_sort_by_activity).toLowerCase() ) - recycler_view.itemAnimator = DefaultItemAnimator() - - viewModel.getChatRooms().observe(viewLifecycleOwner, Observer { rooms -> - rooms?.let { - Timber.d("Got items: $it") - adapter.values = it - if (recycler_view.adapter != adapter) { - recycler_view.adapter = adapter - } - if (rooms.isNotEmpty()) { - text_no_data_to_display.isVisible = false - } - } - }) - - viewModel.loadingState.observe(viewLifecycleOwner, Observer { state -> - when (state) { - is LoadingState.Loading -> if (state.count == 0L) showLoading() - is LoadingState.Loaded -> { - hideLoading() - if (state.count == 0L) showNoChatRoomsToDisplay() - } - is LoadingState.Error -> { - hideLoading() - showGenericErrorMessage() - } - } - }) - - viewModel.getStatus().observe(viewLifecycleOwner, Observer { status -> - status?.let { showConnectionState(status) } - }) - - updateSort() } } @@ -165,120 +136,42 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.chatrooms, menu) - sortView = menu.findItem(R.id.action_sort) + val searchMenuItem = menu.findItem(R.id.action_search) + val searchView = searchMenuItem?.actionView as SearchView - val searchItem = menu.findItem(R.id.action_search) - searchView = searchItem?.actionView as? SearchView - searchView?.setIconifiedByDefault(false) - searchView?.maxWidth = Integer.MAX_VALUE - searchView?.onQueryTextListener { queryChatRoomsByName(it) } + with(searchView) { + setIconifiedByDefault(false) + maxWidth = Integer.MAX_VALUE + onQueryTextListener { queryChatRoomsByName(it) } + } - val expandListener = object : MenuItem.OnActionExpandListener { + searchMenuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - // Simply setting sortView to visible won't work, so we invalidate the options - // to recreate the entire menu... - viewModel.showLastMessage = true + // We need to show all the menu items here by invalidating the options to recreate the entire menu. activity?.invalidateOptionsMenu() queryChatRoomsByName(null) + hideDirectoryView() return true } override fun onMenuItemActionExpand(item: MenuItem): Boolean { - viewModel.showLastMessage = false - sortView?.isVisible = false + // We need to hide the all the menu items here. + menu.findItem(R.id.action_new_channel).isVisible = false + showDirectoryView() return true } - } - searchItem?.setOnActionExpandListener(expandListener) + }) } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - // TODO - simplify this - R.id.action_sort -> { - val dialogLayout = layoutInflater.inflate(R.layout.chatroom_sort_dialog, null) - val sortType = SharedPreferenceHelper.getInt( - Constants.CHATROOM_SORT_TYPE_KEY, - ChatRoomsSortOrder.ACTIVITY - ) - val groupByType = - SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false) - - val radioGroup = dialogLayout.findViewById(R.id.radio_group_sort) - val groupByTypeCheckBox = - dialogLayout.findViewById(R.id.checkbox_group_by_type) - - radioGroup.check( - when (sortType) { - 0 -> R.id.radio_sort_alphabetical - else -> R.id.radio_sort_activity - } - ) - radioGroup.setOnCheckedChangeListener { _, checkedId -> - run { - SharedPreferenceHelper.putInt( - Constants.CHATROOM_SORT_TYPE_KEY, when (checkedId) { - R.id.radio_sort_alphabetical -> 0 - R.id.radio_sort_activity -> 1 - else -> 1 - } - ) - } - } - - groupByTypeCheckBox.isChecked = groupByType - groupByTypeCheckBox.setOnCheckedChangeListener { _, isChecked -> - SharedPreferenceHelper.putBoolean( - Constants.CHATROOM_GROUP_BY_TYPE_KEY, - isChecked - ) - } - - context?.let { - AlertDialog.Builder(it) - .setTitle(R.string.dialog_sort_title) - .setView(dialogLayout) - .setPositiveButton(R.string.msg_sort) { dialog, _ -> - invalidateQueryOnSearch() - updateSort() - dialog.dismiss() - }.show() - } - } + R.id.action_new_channel -> presenter.toCreateChannel() } return super.onOptionsItemSelected(item) } - private fun updateSort() { - val sortType = SharedPreferenceHelper.getInt( - Constants.CHATROOM_SORT_TYPE_KEY, - ChatRoomsSortOrder.ACTIVITY - ) - val grouped = SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false) - - val query = when (sortType) { - ChatRoomsSortOrder.ALPHABETICAL -> { - Query.ByName(grouped) - } - ChatRoomsSortOrder.ACTIVITY -> { - Query.ByActivity(grouped) - } - else -> Query.ByActivity() - } - - viewModel.setQuery(query) - } - - private fun invalidateQueryOnSearch() { - searchView?.let { - if (!searchView!!.isIconified) { - queryChatRoomsByName(searchView!!.query.toString()) - } - } - } - private fun showNoChatRoomsToDisplay() { - ui { text_no_data_to_display.isVisible = true } +// ui { text_no_data_to_display.isVisible = true } } override fun showLoading() { @@ -303,55 +196,147 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) - override fun showLoadingRoom(name: CharSequence) { - ui { - progressDialog = ProgressDialog.show(activity, "Rocket.Chat", "Loading room $name") - } - } - - override fun hideLoadingRoom() { - progressDialog?.dismiss() - } - private fun showConnectionState(state: State) { Timber.d("Got new state: $state") +// ui { +// text_connection_status.fadeIn() +// handler.removeCallbacks(dismissStatus) +// text_connection_status.text = when (state) { +// is State.Connected -> { +// handler.postDelayed(dismissStatus, 2000) +// getString(R.string.status_connected) +// } +// is State.Disconnected -> getString(R.string.status_disconnected) +// is State.Connecting -> getString(R.string.status_connecting) +// is State.Authenticating -> getString(R.string.status_authenticating) +// is State.Disconnecting -> getString(R.string.status_disconnecting) +// is State.Waiting -> getString(R.string.status_waiting, state.seconds) +// else -> { +// handler.postDelayed(dismissStatus, 500) +// "" +// } +// } +// } + } + + private fun subscribeUi() { ui { - text_connection_status.fadeIn() - handler.removeCallbacks(dismissStatus) - text_connection_status.text = when (state) { - is State.Connected -> { - handler.postDelayed(dismissStatus, 2000) - getString(R.string.status_connected) + val adapter = RoomsAdapter { room -> + presenter.loadChatRoom(room) + } + + with(recycler_view) { + layoutManager = LinearLayoutManager(it) + addItemDecoration( + DividerItemDecoration( + it, + resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_start), + resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end) + ) + ) + itemAnimator = DefaultItemAnimator() + } + + viewModel.getChatRooms().observe(viewLifecycleOwner, Observer { rooms -> + rooms?.let { + adapter.values = it + if (recycler_view.adapter != adapter) { + recycler_view.adapter = adapter + } + if (rooms.isNotEmpty()) { +// text_no_data_to_display.isVisible = false + } } - is State.Disconnected -> getString(R.string.status_disconnected) - is State.Connecting -> getString(R.string.status_connecting) - is State.Authenticating -> getString(R.string.status_authenticating) - is State.Disconnecting -> getString(R.string.status_disconnecting) - is State.Waiting -> getString(R.string.status_waiting, state.seconds) - else -> { - handler.postDelayed(dismissStatus, 500) - "" + }) + + viewModel.loadingState.observe(viewLifecycleOwner, Observer { state -> + when (state) { + is LoadingState.Loading -> if (state.count == 0L) showLoading() + is LoadingState.Loaded -> { + hideLoading() + if (state.count == 0L) showNoChatRoomsToDisplay() + } + is LoadingState.Error -> { + hideLoading() + showGenericErrorMessage() + } } - } + }) + + viewModel.getStatus().observe(viewLifecycleOwner, Observer { status -> + status?.let { showConnectionState(status) } + }) + + showAllChats() } } - private val dismissStatus = { - if (text_connection_status != null) { - text_connection_status.fadeOut() + private fun setupListeners() { + text_server_name.setOnClickListener { + ServersBottomSheetFragment().show( + activity?.supportFragmentManager, + chat.rocket.android.servers.ui.TAG + ) + } + + text_sort_by.setOnClickListener { + SortingAndGroupingBottomSheetFragment().show( + activity?.supportFragmentManager, + chat.rocket.android.sortingandgrouping.ui.TAG + ) + } + + text_directory.setOnClickListener { presenter.toDirectory() } + } + + fun sortChatRoomsList( + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) { + this.isSortByName = isSortByName + this.isUnreadOnTop = isUnreadOnTop + this.isGroupByType = isGroupByType + this.isGroupByFavorites = isGroupByFavorites + + if (isSortByName) { + viewModel.setQuery(Query.ByName(isGroupByType, isUnreadOnTop)) + changeSortByTitle(getString(R.string.msg_sort_by_name)) + } else { + viewModel.setQuery(Query.ByActivity(isGroupByType, isUnreadOnTop)) + changeSortByTitle(getString(R.string.msg_sort_by_activity)) } } - private fun setupToolbar() { - (activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_chats) + private fun changeSortByTitle(text: String) { + text_sort_by.text = getString(R.string.msg_sort_by_placeholder, text.toLowerCase()) } private fun queryChatRoomsByName(name: String?): Boolean { if (name.isNullOrEmpty()) { - updateSort() + showAllChats() } else { - viewModel.setQuery(Query.Search(name!!)) + viewModel.setQuery(Query.Search(name)) } return true } + + private fun showAllChats() { + if (isSortByName) { + viewModel.setQuery(Query.ByName(isGroupByType, isUnreadOnTop)) + } else { + viewModel.setQuery(Query.ByActivity(isGroupByType, isUnreadOnTop)) + } + } + + private fun showDirectoryView() { + text_directory.isVisible = true + text_sort_by.isGone = true + } + + private fun hideDirectoryView() { + text_directory.isGone = true + text_sort_by.isVisible = true + } } diff --git a/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModel.kt b/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModel.kt index 6f4532e7a2..c5c0952894 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModel.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModel.kt @@ -9,7 +9,7 @@ import chat.rocket.android.chatrooms.adapter.LoadingItemHolder import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor import chat.rocket.android.chatrooms.infrastructure.ChatRoomsRepository -import chat.rocket.android.server.infraestructure.ConnectionManager +import chat.rocket.android.server.infrastructure.ConnectionManager import chat.rocket.android.util.livedata.transform import chat.rocket.android.util.livedata.wrap import chat.rocket.android.util.retryIO @@ -140,8 +140,10 @@ sealed class LoadingState { } sealed class Query { - data class ByActivity(val grouped: Boolean = false) : Query() - data class ByName(val grouped: Boolean = false) : Query() + + data class ByActivity(val grouped: Boolean = false, val unreadOnTop: Boolean = false) : Query() + data class ByName(val grouped: Boolean = false, val unreadOnTop: Boolean = false ) : Query() + data class Search(val query: String) : Query() } @@ -155,22 +157,44 @@ fun Query.isGrouped(): Boolean { } } +fun Query.isUnreadOnTop(): Boolean { + return when(this) { + is Query.Search -> false + is Query.ByName -> unreadOnTop + is Query.ByActivity -> unreadOnTop + } +} + fun Query.asSortingOrder(): ChatRoomsRepository.Order { return when(this) { is Query.ByName -> { - if (grouped) { + if (grouped && !unreadOnTop) { ChatRoomsRepository.Order.GROUPED_NAME - } else { + } + else if(unreadOnTop && !grouped){ + ChatRoomsRepository.Order.UNREAD_ON_TOP_NAME + } + else if(unreadOnTop && grouped){ + ChatRoomsRepository.Order.UNREAD_ON_TOP_GROUPED_NAME + } + else { ChatRoomsRepository.Order.NAME } } is Query.ByActivity -> { - if (grouped) { + if (grouped && !unreadOnTop) { ChatRoomsRepository.Order.GROUPED_ACTIVITY - } else { + } + else if(unreadOnTop && !grouped){ + ChatRoomsRepository.Order.UNREAD_ON_TOP_ACTIVITY + } + else if(unreadOnTop && grouped){ + ChatRoomsRepository.Order.UNREAD_ON_TOP_GROUPED_ACTIVITY + } + else { ChatRoomsRepository.Order.ACTIVITY } } else -> throw IllegalArgumentException("Should be ByName or ByActivity") } -} +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModelFactory.kt b/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModelFactory.kt index db5c6ccce3..77edce808c 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModelFactory.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/viewmodel/ChatRoomsViewModelFactory.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModelProvider import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor import chat.rocket.android.chatrooms.infrastructure.ChatRoomsRepository -import chat.rocket.android.server.infraestructure.ConnectionManager +import chat.rocket.android.server.infrastructure.ConnectionManager import javax.inject.Inject class ChatRoomsViewModelFactory @Inject constructor( diff --git a/app/src/main/java/chat/rocket/android/core/behaviours/AppLanguageView.kt b/app/src/main/java/chat/rocket/android/core/behaviours/AppLanguageView.kt new file mode 100644 index 0000000000..56044e8f12 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/core/behaviours/AppLanguageView.kt @@ -0,0 +1,12 @@ +package chat.rocket.android.core.behaviours + +interface AppLanguageView { + + /** + * Updates the app language + * + * @param language The app language to be updated. + * @param country Opcional. The country code to be updated. + */ + fun updateLanguage(language: String, country: String? = null) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelPresenter.kt b/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelPresenter.kt index 354aaa1f3d..fd031ddedd 100644 --- a/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelPresenter.kt +++ b/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelPresenter.kt @@ -4,7 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.members.uimodel.MemberUiModelMapper import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.common.RocketChatException import chat.rocket.common.model.RoomType @@ -22,7 +22,7 @@ class CreateChannelPresenter @Inject constructor( val serverInteractor: GetCurrentServerInteractor, val factory: RocketChatClientFactory ) { - private val client: RocketChatClient = factory.create(serverInteractor.get()!!) + private val client: RocketChatClient = factory.get(serverInteractor.get()!!) fun createChannel( roomType: RoomType, @@ -35,7 +35,6 @@ class CreateChannelPresenter @Inject constructor( view.disableUserInput() try { client.createChannel(roomType, channelName, usersList, readOnly) - view.prepareToShowChatList() view.showChannelCreatedSuccessfullyMessage() toChatList() } catch (exception: RocketChatException) { diff --git a/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelView.kt b/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelView.kt index eb29f5f0cd..463fb760ba 100644 --- a/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelView.kt +++ b/app/src/main/java/chat/rocket/android/createchannel/presentation/CreateChannelView.kt @@ -28,12 +28,6 @@ interface CreateChannelView : LoadingView, MessageView { */ fun hideSuggestionViewInProgress() - /** - * Shows the navigation drawer with the chat item checked before showing the chat list. - * This function is invoked after successfully created the channel. - */ - fun prepareToShowChatList() - /** * Shows a message that a channel was successfully created. */ diff --git a/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt b/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt index 57f60a6f18..4baf9af6ab 100644 --- a/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt +++ b/app/src/main/java/chat/rocket/android/createchannel/ui/CreateChannelFragment.kt @@ -9,7 +9,6 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible -import androidx.core.view.postDelayed import androidx.fragment.app.Fragment import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -19,7 +18,6 @@ import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.createchannel.presentation.CreateChannelPresenter import chat.rocket.android.createchannel.presentation.CreateChannelView -import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.members.adapter.MembersAdapter import chat.rocket.android.members.uimodel.MemberUiModel import chat.rocket.android.util.extension.asObservable @@ -32,17 +30,18 @@ import com.google.android.material.chip.Chip import dagger.android.support.AndroidSupportInjection import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.fragment_create_channel.* import java.util.concurrent.TimeUnit import javax.inject.Inject internal const val TAG_CREATE_CHANNEL_FRAGMENT = "CreateChannelFragment" +fun newInstance() = CreateChannelFragment() + class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback { - @Inject - lateinit var createChannelPresenter: CreateChannelPresenter - @Inject - lateinit var analyticsManager: AnalyticsManager + @Inject lateinit var presenter: CreateChannelPresenter + @Inject lateinit var analyticsManager: AnalyticsManager private var actionMode: ActionMode? = null private val adapter: MembersAdapter = MembersAdapter { it.username?.run { processSelectedMember(this) } @@ -52,10 +51,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback private var isChannelReadOnly: Boolean = false private var memberList = arrayListOf() - companion object { - fun newInstance() = CreateChannelFragment() - } - override fun onCreate(savedInstanceState: Bundle?) { AndroidSupportInjection.inject(this) super.onCreate(savedInstanceState) @@ -93,7 +88,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem): Boolean { return when (menuItem.itemId) { R.id.action_create_channel -> { - createChannelPresenter.createChannel( + presenter.createChannel( roomTypeOf(channelType), text_channel_name.text.toString(), memberList, @@ -165,17 +160,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback view_member_suggestion_loading.isVisible = false } - override fun prepareToShowChatList() { - with(activity as MainActivity) { - setCheckedNavDrawerItem(R.id.menu_action_chats) - openDrawer() - getDrawerLayout().postDelayed(1000) { - closeDrawer() - createChannelPresenter.toChatList() - } - } - } - override fun showChannelCreatedSuccessfullyMessage() { showMessage(getString(R.string.msg_channel_created_successfully)) } @@ -191,8 +175,14 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback } private fun setupToolBar() { - (activity as AppCompatActivity?)?.supportActionBar?.title = - getString(R.string.title_create_channel) + with((activity as AppCompatActivity)) { + with(toolbar) { + setSupportActionBar(this) + title = getString(R.string.title_create_channel) + setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) + setNavigationOnClickListener { activity?.onBackPressed() } + } + } } private fun setupViewListeners() { @@ -247,7 +237,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback .filter { t -> t.isNotBlank() } .subscribe { if (it.length >= 3) { - createChannelPresenter.searchUser(it.toString()) + presenter.searchUser(it.toString()) } else { view_member_suggestion.isVisible = false } diff --git a/app/src/main/java/chat/rocket/android/dagger/AppComponent.kt b/app/src/main/java/chat/rocket/android/dagger/AppComponent.kt index 1812840193..e9963c8da5 100644 --- a/app/src/main/java/chat/rocket/android/dagger/AppComponent.kt +++ b/app/src/main/java/chat/rocket/android/dagger/AppComponent.kt @@ -14,9 +14,15 @@ import dagger.android.support.AndroidSupportInjectionModule import javax.inject.Singleton @Singleton -@Component(modules = [AndroidSupportInjectionModule::class, - AppModule::class, ActivityBuilder::class, ServiceBuilder::class, ReceiverBuilder::class, - AndroidWorkerInjectionModule::class]) +@Component( + modules = [ + AndroidSupportInjectionModule::class, + AppModule::class, + ActivityBuilder::class, + ServiceBuilder::class, + ReceiverBuilder::class, + AndroidWorkerInjectionModule::class] +) interface AppComponent { @Component.Builder diff --git a/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt b/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt index bea265f32d..2893e634dd 100644 --- a/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt +++ b/app/src/main/java/chat/rocket/android/dagger/module/ActivityBuilder.kt @@ -1,6 +1,5 @@ package chat.rocket.android.dagger.module -import chat.rocket.android.about.di.AboutFragmentProvider import chat.rocket.android.authentication.di.AuthenticationModule import chat.rocket.android.authentication.login.di.LoginFragmentProvider import chat.rocket.android.authentication.loginoptions.di.LoginOptionsFragmentProvider @@ -20,6 +19,7 @@ import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider import chat.rocket.android.createchannel.di.CreateChannelProvider import chat.rocket.android.dagger.scope.PerActivity +import chat.rocket.android.directory.di.DirectoryFragmentProvider import chat.rocket.android.draw.main.di.DrawModule import chat.rocket.android.draw.main.ui.DrawingActivity import chat.rocket.android.favoritemessages.di.FavoriteMessagesFragmentProvider @@ -29,13 +29,14 @@ import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.members.di.MembersFragmentProvider import chat.rocket.android.mentions.di.MentionsFragmentProvider import chat.rocket.android.pinnedmessages.di.PinnedMessagesFragmentProvider -import chat.rocket.android.preferences.di.PreferencesFragmentProvider import chat.rocket.android.profile.di.ProfileFragmentProvider import chat.rocket.android.server.di.ChangeServerModule import chat.rocket.android.server.ui.ChangeServerActivity +import chat.rocket.android.servers.di.ServersBottomSheetFragmentProvider import chat.rocket.android.settings.di.SettingsFragmentProvider import chat.rocket.android.settings.password.di.PasswordFragmentProvider import chat.rocket.android.settings.password.ui.PasswordActivity +import chat.rocket.android.sortingandgrouping.di.SortingAndGroupingBottomSheetFragmentProvider import chat.rocket.android.userdetails.di.UserDetailsFragmentProvider import chat.rocket.android.videoconference.di.VideoConferenceModule import chat.rocket.android.videoconference.ui.VideoConferenceActivity @@ -65,12 +66,13 @@ abstract class ActivityBuilder { @ContributesAndroidInjector( modules = [MainModule::class, ChatRoomsFragmentProvider::class, + ServersBottomSheetFragmentProvider::class, + SortingAndGroupingBottomSheetFragmentProvider::class, CreateChannelProvider::class, ProfileFragmentProvider::class, SettingsFragmentProvider::class, - AboutFragmentProvider::class, - PreferencesFragmentProvider::class, - AdminPanelWebViewFragmentProvider::class + AdminPanelWebViewFragmentProvider::class, + DirectoryFragmentProvider::class ] ) abstract fun bindMainActivity(): MainActivity diff --git a/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt b/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt index c06b4c4cf9..536192cd8d 100644 --- a/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt +++ b/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt @@ -12,8 +12,8 @@ import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnswersAnalytics import chat.rocket.android.analytics.GoogleAnalyticsForFirebase -import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository -import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository +import chat.rocket.android.authentication.infrastructure.SharedPreferencesMultiServerTokenRepository +import chat.rocket.android.authentication.infrastructure.SharedPreferencesTokenRepository import chat.rocket.android.chatroom.service.MessageService import chat.rocket.android.dagger.qualifier.ForAuthentication import chat.rocket.android.dagger.qualifier.ForMessages @@ -27,37 +27,41 @@ import chat.rocket.android.push.PushManager import chat.rocket.android.server.domain.AccountsRepository import chat.rocket.android.server.domain.AnalyticsTrackingInteractor import chat.rocket.android.server.domain.AnalyticsTrackingRepository +import chat.rocket.android.server.domain.BasicAuthRepository import chat.rocket.android.server.domain.ChatRoomsRepository import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.GetAccountInteractor import chat.rocket.android.server.domain.GetAccountsInteractor +import chat.rocket.android.server.domain.GetBasicAuthInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.JobSchedulerInteractor import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MultiServerTokenRepository import chat.rocket.android.server.domain.PermissionsRepository +import chat.rocket.android.server.domain.SaveBasicAuthInteractor import chat.rocket.android.server.domain.SettingsRepository +import chat.rocket.android.server.domain.SortingAndGroupingRepository import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.UsersRepository -import chat.rocket.android.server.domain.BasicAuthRepository -import chat.rocket.android.server.domain.GetBasicAuthInteractor -import chat.rocket.android.server.domain.SaveBasicAuthInteractor -import chat.rocket.android.server.infraestructure.SharedPrefsBasicAuthRepository -import chat.rocket.android.server.infraestructure.DatabaseMessageMapper -import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository -import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl -import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository -import chat.rocket.android.server.infraestructure.MemoryUsersRepository -import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepository -import chat.rocket.android.server.infraestructure.SharedPreferencesPermissionsRepository -import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository -import chat.rocket.android.server.infraestructure.SharedPrefsAnalyticsTrackingRepository -import chat.rocket.android.server.infraestructure.SharedPrefsConnectingServerRepository -import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository +import chat.rocket.android.server.infrastructure.CurrentLanguageRepository +import chat.rocket.android.server.infrastructure.SharedPrefsCurrentLanguageRepository +import chat.rocket.android.server.infrastructure.DatabaseMessageMapper +import chat.rocket.android.server.infrastructure.DatabaseMessagesRepository +import chat.rocket.android.server.infrastructure.JobSchedulerInteractorImpl +import chat.rocket.android.server.infrastructure.MemoryChatRoomsRepository +import chat.rocket.android.server.infrastructure.MemoryUsersRepository +import chat.rocket.android.server.infrastructure.SharedPreferencesAccountsRepository +import chat.rocket.android.server.infrastructure.SharedPreferencesPermissionsRepository +import chat.rocket.android.server.infrastructure.SharedPreferencesSettingsRepository +import chat.rocket.android.server.infrastructure.SharedPrefsAnalyticsTrackingRepository +import chat.rocket.android.server.infrastructure.SharedPrefsBasicAuthRepository +import chat.rocket.android.server.infrastructure.SharedPrefsConnectingServerRepository +import chat.rocket.android.server.infrastructure.SharedPrefsCurrentServerRepository +import chat.rocket.android.server.infrastructure.SharedPrefsSortingAndGroupingRepository import chat.rocket.android.util.AppJsonAdapterFactory -import chat.rocket.android.util.HttpLoggingInterceptor import chat.rocket.android.util.BasicAuthenticatorInterceptor +import chat.rocket.android.util.HttpLoggingInterceptor import chat.rocket.android.util.TimberLogger import chat.rocket.common.internal.FallbackSealedClassJsonAdapter import chat.rocket.common.internal.ISO8601Date @@ -123,7 +127,10 @@ class AppModule { @Provides @Singleton - fun provideOkHttpClient(logger: HttpLoggingInterceptor, basicAuthenticator: BasicAuthenticatorInterceptor): OkHttpClient { + fun provideOkHttpClient( + logger: HttpLoggingInterceptor, + basicAuthenticator: BasicAuthenticatorInterceptor + ): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(logger) .addInterceptor(basicAuthenticator) @@ -170,7 +177,6 @@ class AppModule { fun provideSharedPreferences(context: Application) = context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE) - @Provides @ForMessages fun provideMessagesSharedPreferences(context: Application) = @@ -194,12 +200,24 @@ class AppModule { return SharedPrefsAnalyticsTrackingRepository(prefs) } + @Provides + @Singleton + fun provideSortingAndGroupingRepository(prefs: SharedPreferences): SortingAndGroupingRepository { + return SharedPrefsSortingAndGroupingRepository(prefs) + } + @Provides @ForAuthentication fun provideConnectingServerRepository(prefs: SharedPreferences): CurrentServerRepository { return SharedPrefsConnectingServerRepository(prefs) } + @Provides + @Singleton + fun provideCurrentLanguageRepository(prefs: SharedPreferences): CurrentLanguageRepository { + return SharedPrefsCurrentLanguageRepository(prefs) + } + @Provides @Singleton fun provideSettingsRepository(localRepository: LocalRepository): SettingsRepository { @@ -293,10 +311,10 @@ class AppModule { @Provides @Singleton - fun provideBasicAuthRepository ( + fun provideBasicAuthRepository( preferences: SharedPreferences, moshi: Moshi - ): BasicAuthRepository = + ): BasicAuthRepository = SharedPrefsBasicAuthRepository(preferences, moshi) @Provides diff --git a/app/src/main/java/chat/rocket/android/dagger/module/LocalModule.kt b/app/src/main/java/chat/rocket/android/dagger/module/LocalModule.kt index e6e08f148e..26df5c5f4c 100644 --- a/app/src/main/java/chat/rocket/android/dagger/module/LocalModule.kt +++ b/app/src/main/java/chat/rocket/android/dagger/module/LocalModule.kt @@ -6,7 +6,7 @@ import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository +import chat.rocket.android.server.infrastructure.SharedPrefsCurrentServerRepository import chat.rocket.android.util.AppJsonAdapterFactory import chat.rocket.android.util.TimberLogger import chat.rocket.common.internal.FallbackSealedClassJsonAdapter diff --git a/app/src/main/java/chat/rocket/android/db/ChatRoomDao.kt b/app/src/main/java/chat/rocket/android/db/ChatRoomDao.kt index 11bc281296..6b95702243 100644 --- a/app/src/main/java/chat/rocket/android/db/ChatRoomDao.kt +++ b/app/src/main/java/chat/rocket/android/db/ChatRoomDao.kt @@ -65,11 +65,59 @@ abstract class ChatRoomDao : BaseDao { """) abstract fun getAllGrouped(): LiveData> + @Transaction + @Query(""" + $BASE_QUERY + $FILTER_NOT_OPENED + ORDER BY + $UNREAD, + CASE + WHEN lastMessageTimeStamp IS NOT NULL THEN lastMessageTimeStamp + ELSE updatedAt + END DESC + """) + abstract fun getAllUnread(): LiveData> + + @Transaction + @Query(""" + $BASE_QUERY + $FILTER_NOT_OPENED + ORDER BY + $UNREAD, + name COLLATE NOCASE + """) + abstract fun getAllAlphabeticallyUnread(): LiveData> + @Transaction @Query(""" $BASE_QUERY $FILTER_NOT_OPENED - ORDER BY name + ORDER BY + $TYPE_ORDER, + $UNREAD, + name COLLATE NOCASE + """) + abstract fun getAllAlphabeticallyGroupedUnread(): LiveData> + + @Transaction + @Query(""" + $BASE_QUERY + $FILTER_NOT_OPENED + ORDER BY + $TYPE_ORDER, + $UNREAD, + CASE + WHEN lastMessageTimeStamp IS NOT NULL THEN lastMessageTimeStamp + ELSE updatedAt + END DESC + """) + abstract fun getAllGroupedUnread(): LiveData> + + @Transaction + @Query(""" + $BASE_QUERY + $FILTER_NOT_OPENED + ORDER BY name COLLATE NOCASE """) abstract fun getAllAlphabetically(): LiveData> @@ -79,7 +127,7 @@ abstract class ChatRoomDao : BaseDao { $FILTER_NOT_OPENED ORDER BY $TYPE_ORDER, - name + name COLLATE NOCASE """) abstract fun getAllAlphabeticallyGrouped(): LiveData> @@ -139,5 +187,11 @@ abstract class ChatRoomDao : BaseDao { ELSE 5 END """ + const val UNREAD = """ + CASE + WHEN alert OR unread > 0 THEN 1 + ELSE 2 + END + """ } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/db/DatabaseManager.kt b/app/src/main/java/chat/rocket/android/db/DatabaseManager.kt index 0dffbf8f16..8363352372 100644 --- a/app/src/main/java/chat/rocket/android/db/DatabaseManager.kt +++ b/app/src/main/java/chat/rocket/android/db/DatabaseManager.kt @@ -1,6 +1,7 @@ package chat.rocket.android.db import android.app.Application +import androidx.core.net.toUri import chat.rocket.android.R import chat.rocket.android.db.model.BaseMessageEntity import chat.rocket.android.db.model.BaseUserEntity @@ -15,6 +16,7 @@ import chat.rocket.android.db.model.UrlEntity import chat.rocket.android.db.model.UserEntity import chat.rocket.android.db.model.UserStatus import chat.rocket.android.db.model.asEntity +import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.exhaustive import chat.rocket.android.util.extensions.removeTrailingSlash import chat.rocket.android.util.extensions.toEntity @@ -23,6 +25,7 @@ import chat.rocket.android.util.retryDB import chat.rocket.common.model.BaseRoom import chat.rocket.common.model.RoomType import chat.rocket.common.model.SimpleUser +import chat.rocket.common.model.Token import chat.rocket.common.model.User import chat.rocket.core.internal.model.Subscription import chat.rocket.core.internal.realtime.socket.model.StreamMessage @@ -32,6 +35,7 @@ import chat.rocket.core.model.Message import chat.rocket.core.model.Myself import chat.rocket.core.model.Room import chat.rocket.core.model.userId +import com.facebook.drawee.backends.pipeline.Fresco import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel @@ -48,8 +52,7 @@ import kotlin.collections.component2 import kotlin.collections.set import kotlin.system.measureTimeMillis -class DatabaseManager(val context: Application, val serverUrl: String) { - +class DatabaseManager(val context: Application, val serverUrl: String, val token: Token) { private val database: RCDatabase = androidx.room.Room.databaseBuilder( context, RCDatabase::class.java, serverUrl.databaseName() @@ -58,23 +61,23 @@ class DatabaseManager(val context: Application, val serverUrl: String) { .build() private val dbContext = newSingleThreadContext("$serverUrl-db-context") private val dbManagerContext = newSingleThreadContext("$serverUrl-db-manager-context") - private val writeChannel = Channel(Channel.UNLIMITED) private var dbJob: Job? = null - private val insertSubs = HashMap() private val insertRooms = HashMap() private val updateSubs = LinkedHashMap() private val updateRooms = LinkedHashMap() - fun chatRoomDao(): ChatRoomDao = database.chatRoomDao() - fun userDao(): UserDao = database.userDao() - fun messageDao(): MessageDao = database.messageDao() - init { start() } + fun chatRoomDao(): ChatRoomDao = database.chatRoomDao() + + fun userDao(): UserDao = database.userDao() + + fun messageDao(): MessageDao = database.messageDao() + fun start() { dbJob?.cancel() dbJob = GlobalScope.launch(dbContext) { @@ -138,7 +141,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { } /* - * Creates a list of data base operations + * Creates a list of database operations */ fun processChatRoomsBatch(batch: List>) { GlobalScope.launch(dbManagerContext) { @@ -189,6 +192,20 @@ class DatabaseManager(val context: Application, val serverUrl: String) { status = myself.status?.toString() ?: user.status ) ?: myself.asUser().toEntity() + if (myself.avatarOrigin != null && myself.active == null && + myself.name == null && myself.username == null + ) { + user?.username?.let { + Fresco.getImagePipeline().evictFromCache( + serverUrl.avatarUrl( + it, + token.userId, + token.authToken + ).toUri() + ) + } + } + Timber.d("UPDATING SELF: $entity") entity?.let { sendOperation(Operation.UpsertUser(it)) } } @@ -472,6 +489,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { return ChatRoomEntity( id = room.id, subscriptionId = subscription.id, + parentId = subscription.parentId, type = room.type.toString(), name = room.name ?: subscription.name ?: throw NullPointerException(), // this should be filtered on the SDK @@ -513,6 +531,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { return ChatRoomEntity( id = id, subscriptionId = subscriptionId, + parentId = parentId, type = type.toString(), name = name, fullname = fullName, diff --git a/app/src/main/java/chat/rocket/android/db/DatabaseManagerFactory.kt b/app/src/main/java/chat/rocket/android/db/DatabaseManagerFactory.kt index 19a67975f6..08e3936de7 100644 --- a/app/src/main/java/chat/rocket/android/db/DatabaseManagerFactory.kt +++ b/app/src/main/java/chat/rocket/android/db/DatabaseManagerFactory.kt @@ -1,12 +1,16 @@ package chat.rocket.android.db import android.app.Application +import chat.rocket.android.server.domain.TokenRepository import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @Singleton -class DatabaseManagerFactory @Inject constructor(private val context: Application) { +class DatabaseManagerFactory @Inject constructor( + private val context: Application, + private val tokenRepository: TokenRepository +) { private val cache = HashMap() fun create(serverUrl: String): DatabaseManager { @@ -15,9 +19,10 @@ class DatabaseManagerFactory @Inject constructor(private val context: Applicatio return it } - Timber.d("Returning FRESH database for $serverUrl") - val db = DatabaseManager(context, serverUrl) - cache[serverUrl] = db - return db + Timber.d("Returning fresh database for $serverUrl") + with(DatabaseManager(context, serverUrl, tokenRepository.get(serverUrl)!!)) { + cache[serverUrl] = this + return this + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/db/RCDatabase.kt b/app/src/main/java/chat/rocket/android/db/RCDatabase.kt index 96084c7ff6..78178a34a6 100644 --- a/app/src/main/java/chat/rocket/android/db/RCDatabase.kt +++ b/app/src/main/java/chat/rocket/android/db/RCDatabase.kt @@ -32,7 +32,7 @@ import chat.rocket.android.emoji.internal.db.StringListConverter ReactionEntity::class, MessagesSync::class ], - version = 12, + version = 13, exportSchema = true ) @TypeConverters(StringListConverter::class) diff --git a/app/src/main/java/chat/rocket/android/db/model/ChatRoomEntity.kt b/app/src/main/java/chat/rocket/android/db/model/ChatRoomEntity.kt index f3d0aed820..e709003705 100644 --- a/app/src/main/java/chat/rocket/android/db/model/ChatRoomEntity.kt +++ b/app/src/main/java/chat/rocket/android/db/model/ChatRoomEntity.kt @@ -26,6 +26,7 @@ import chat.rocket.android.emoji.internal.db.StringListConverter data class ChatRoomEntity( @PrimaryKey var id: String, var subscriptionId: String, + var parentId: String?, var type: String, var name: String, var fullname: String? = null, diff --git a/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryAdapter.kt b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryAdapter.kt new file mode 100644 index 0000000000..44e6aacda7 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryAdapter.kt @@ -0,0 +1,108 @@ +package chat.rocket.android.directory.adapter + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.R +import chat.rocket.android.directory.uimodel.DirectoryUiModel +import chat.rocket.android.util.extensions.inflate + +private const val VIEW_TYPE_CHANNELS = 0 +private const val VIEW_TYPE_USERS = 1 +private const val VIEW_TYPE_GLOBAL_USERS = 2 + +class DirectoryAdapter(private val selector: Selector) : + RecyclerView.Adapter() { + private var isSortByChannels: Boolean = true + private var isSearchForGlobalUsers: Boolean = true + private var dataSet: List = ArrayList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = + when (viewType) { + VIEW_TYPE_CHANNELS -> DirectoryChannelViewHolder( + parent.inflate(R.layout.item_directory_channel) + ) + VIEW_TYPE_USERS -> DirectoryUsersViewHolder( + parent.inflate(R.layout.item_directory_user) + ) + VIEW_TYPE_GLOBAL_USERS -> DirectoryGlobalUsersViewHolder( + parent.inflate(R.layout.item_directory_user) + ) + else -> throw IllegalStateException("viewType must be either VIEW_TYPE_CHANNELS, VIEW_TYPE_USERS or VIEW_TYPE_GLOBAL_USERS") + } + + override fun getItemCount(): Int = dataSet.size + + override fun getItemViewType(position: Int): Int { + return if (isSortByChannels) { + VIEW_TYPE_CHANNELS + } else { + if (isSearchForGlobalUsers) { + VIEW_TYPE_GLOBAL_USERS + } else { + VIEW_TYPE_USERS + } + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = + when (holder) { + is DirectoryChannelViewHolder -> bindDirectoryChannelViewHolder(holder, position) + is DirectoryUsersViewHolder -> bindDirectoryUsersViewHolder(holder, position) + is DirectoryGlobalUsersViewHolder -> bindDirectoryGlobalUsersViewHolder( + holder, + position + ) + else -> throw IllegalStateException("Unable to bind ViewHolder. ViewHolder must be either DirectoryChannelViewHolder, DirectoryUsersViewHolder or DirectoryGlobalUsersViewHolder") + } + + private fun bindDirectoryChannelViewHolder(holder: DirectoryChannelViewHolder, position: Int) { + with(dataSet[position]) { + holder.bind(this) + holder.itemView.setOnClickListener { selector.onChannelSelected(id, name) } + } + } + + private fun bindDirectoryUsersViewHolder(holder: DirectoryUsersViewHolder, position: Int) { + with(dataSet[position]) { + holder.bind(this) + holder.itemView.setOnClickListener { selector.onUserSelected(username, name) } + } + } + + private fun bindDirectoryGlobalUsersViewHolder( + holder: DirectoryGlobalUsersViewHolder, + position: Int + ) { + with(dataSet[position]) { + holder.bind(this) + holder.itemView.setOnClickListener { selector.onGlobalUserSelected(username, name) } + } + } + + fun clearData() { + dataSet = emptyList() + notifyDataSetChanged() + } + + fun setSorting(isSortByChannels: Boolean, isSearchForGlobalUsers: Boolean) { + this.isSortByChannels = isSortByChannels + this.isSearchForGlobalUsers = isSearchForGlobalUsers + } + + fun prependData(dataSet: List) { + this.dataSet = dataSet + notifyItemRangeInserted(0, dataSet.size) + } + + fun appendData(dataSet: List) { + val previousDataSetSize = this.dataSet.size + this.dataSet += dataSet + notifyItemRangeInserted(previousDataSetSize, dataSet.size) + } +} + +interface Selector { + fun onChannelSelected(channelId: String, channelName: String) + fun onUserSelected(username: String, name: String) + fun onGlobalUserSelected(username: String, name: String) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryChannelViewHolder.kt b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryChannelViewHolder.kt new file mode 100644 index 0000000000..1156008b9e --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryChannelViewHolder.kt @@ -0,0 +1,17 @@ +package chat.rocket.android.directory.adapter + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.directory.uimodel.DirectoryUiModel +import com.bumptech.glide.Glide +import kotlinx.android.synthetic.main.item_directory_channel.view.* + +class DirectoryChannelViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun bind(directoryChannelUiModel: DirectoryUiModel) = with(itemView) { + Glide.with(image_avatar).load(directoryChannelUiModel.channelAvatarUri).into(image_avatar) + text_channel_name.text = directoryChannelUiModel.name + text_channel_description.text = directoryChannelUiModel.description + text_channel_total_members.text = directoryChannelUiModel.totalMembers + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryGlobalUsersViewHolder.kt b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryGlobalUsersViewHolder.kt new file mode 100644 index 0000000000..57c19fb952 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryGlobalUsersViewHolder.kt @@ -0,0 +1,21 @@ +package chat.rocket.android.directory.adapter + +import android.view.View +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.directory.uimodel.DirectoryUiModel +import com.bumptech.glide.Glide +import kotlinx.android.synthetic.main.item_directory_user.view.* + +class DirectoryGlobalUsersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun bind(directoryChannelUiModel: DirectoryUiModel) = with(itemView) { + Glide.with(image_avatar).load(directoryChannelUiModel.userAvatarUri).into(image_avatar) + text_user_name.text = directoryChannelUiModel.name + text_user_username.text = directoryChannelUiModel.username + with(text_server_url) { + text = directoryChannelUiModel.serverUrl + isVisible = true + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryUsersViewHolder.kt b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryUsersViewHolder.kt new file mode 100644 index 0000000000..e2351a16cb --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/adapter/DirectoryUsersViewHolder.kt @@ -0,0 +1,18 @@ +package chat.rocket.android.directory.adapter + +import android.view.View +import androidx.core.view.isGone +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.directory.uimodel.DirectoryUiModel +import com.bumptech.glide.Glide +import kotlinx.android.synthetic.main.item_directory_user.view.* + +class DirectoryUsersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun bind(directoryChannelUiModel: DirectoryUiModel) = with(itemView) { + Glide.with(image_avatar).load(directoryChannelUiModel.userAvatarUri).into(image_avatar) + text_user_name.text = directoryChannelUiModel.name + text_user_username.text = directoryChannelUiModel.username + text_server_url.isGone = true + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/di/DirectoryFragmentModule.kt b/app/src/main/java/chat/rocket/android/directory/di/DirectoryFragmentModule.kt new file mode 100644 index 0000000000..75e71e55aa --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/di/DirectoryFragmentModule.kt @@ -0,0 +1,15 @@ +package chat.rocket.android.directory.di + +import chat.rocket.android.dagger.scope.PerFragment +import chat.rocket.android.directory.presentation.DirectoryView +import chat.rocket.android.directory.ui.DirectoryFragment +import dagger.Module +import dagger.Provides + +@Module +class DirectoryFragmentModule { + + @Provides + @PerFragment + fun directoryView(frag: DirectoryFragment): DirectoryView = frag +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/di/DirectoryFragmentProvider.kt b/app/src/main/java/chat/rocket/android/directory/di/DirectoryFragmentProvider.kt new file mode 100644 index 0000000000..38ad5bb288 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/di/DirectoryFragmentProvider.kt @@ -0,0 +1,15 @@ +package chat.rocket.android.directory.di + +import chat.rocket.android.dagger.scope.PerFragment +import chat.rocket.android.directory.ui.DirectoryFragment +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class DirectoryFragmentProvider { + + @ContributesAndroidInjector(modules = [DirectoryFragmentModule::class]) + @PerFragment + abstract fun provideDirectoryFragment(): DirectoryFragment + +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/presentation/DirectoryPresenter.kt b/app/src/main/java/chat/rocket/android/directory/presentation/DirectoryPresenter.kt new file mode 100644 index 0000000000..8063e6c897 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/presentation/DirectoryPresenter.kt @@ -0,0 +1,189 @@ +package chat.rocket.android.directory.presentation + +import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor +import chat.rocket.android.core.lifecycle.CancelStrategy +import chat.rocket.android.db.DatabaseManager +import chat.rocket.android.db.model.ChatRoomEntity +import chat.rocket.android.directory.uimodel.DirectoryUiModelMapper +import chat.rocket.android.helper.UserHelper +import chat.rocket.android.main.presentation.MainNavigator +import chat.rocket.android.server.infrastructure.RocketChatClientFactory +import chat.rocket.android.util.extension.launchUI +import chat.rocket.common.model.RoomType +import chat.rocket.common.model.roomTypeOf +import chat.rocket.common.util.ifNull +import chat.rocket.core.RocketChatClient +import chat.rocket.core.internal.rest.DirectoryRequestType +import chat.rocket.core.internal.rest.DirectoryWorkspaceType +import chat.rocket.core.internal.rest.createDirectMessage +import chat.rocket.core.internal.rest.directory +import chat.rocket.core.internal.rest.getInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Named + +class DirectoryPresenter @Inject constructor( + private val view: DirectoryView, + private val navigator: MainNavigator, + private val strategy: CancelStrategy, + @Named("currentServer") private val currentServer: String, + private val dbManager: DatabaseManager, + private val userHelper: UserHelper, + val factory: RocketChatClientFactory, + private val mapper: DirectoryUiModelMapper +) { + private val client: RocketChatClient = factory.get(currentServer) + private var offset: Long = 0 + + fun loadAllDirectoryChannels(query: String? = null) { + launchUI(strategy) { + try { + view.showLoading() + val directoryResult = client.directory( + text = query, + directoryRequestType = DirectoryRequestType.Channels(), + offset = offset, + count = 60 + ) + val directoryUiModels = mapper.mapToUiModelList(directoryResult.result) + view.showChannels(directoryUiModels) + offset += 1 * 60L + } catch (exception: Exception) { + exception.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + } finally { + view.hideLoading() + } + } + } + + fun loadAllDirectoryUsers(isSearchForGlobalUsers: Boolean, query: String? = null) { + launchUI(strategy) { + try { + view.showLoading() + val directoryResult = client.directory( + text = query, + directoryRequestType = DirectoryRequestType.Users(), + directoryWorkspaceType = if (isSearchForGlobalUsers) { + DirectoryWorkspaceType.All() + } else { + DirectoryWorkspaceType.Local() + }, + offset = offset, + count = 60 + ) + val directoryUiModels = mapper.mapToUiModelList(directoryResult.result) + view.showUsers(directoryUiModels) + offset += 1 * 60L + } catch (exception: Exception) { + exception.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + } finally { + view.hideLoading() + } + } + } + + fun updateSorting( + isSortByChannels: Boolean, + isSearchForGlobalUsers: Boolean, + query: String? = null + ) { + resetOffset() + if (isSortByChannels) { + loadAllDirectoryChannels(query) + } else { + loadAllDirectoryUsers(isSearchForGlobalUsers, query) + } + } + + fun toChannel(channelId: String, name: String) { + launchUI(strategy) { + try { + view.showLoading() + withContext(Dispatchers.Default) { + val chatRoom = client.getInfo(channelId, name, roomTypeOf(RoomType.CHANNEL)) + navigator.toChatRoom( + chatRoomId = channelId, + chatRoomName = name, + chatRoomType = RoomType.CHANNEL, + isReadOnly = chatRoom.readonly, + chatRoomLastSeen = -1, + isSubscribed = false, + isCreator = false, + isFavorite = false + ) + + } + } catch (ex: Exception) { + Timber.e(ex) + ex.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + } finally { + view.hideLoading() + } + } + } + + fun tiDirectMessage(username: String, name: String) { + launchUI(strategy) { + try { + view.showLoading() + + withContext(Dispatchers.Default) { + val directMessage = client.createDirectMessage(username) + + val chatRoomEntity = ChatRoomEntity( + id = directMessage.id, + parentId = null, + name = username, + description = null, + type = RoomType.DIRECT_MESSAGE, + fullname = name, + subscriptionId = "", + updatedAt = directMessage.updatedAt + ) + + dbManager.insertOrReplaceRoom(chatRoomEntity) + + FetchChatRoomsInteractor(client, dbManager).refreshChatRooms() + + navigator.toChatRoom( + chatRoomId = chatRoomEntity.id, + chatRoomName = chatRoomEntity.name, + chatRoomType = chatRoomEntity.type, + isReadOnly = false, + chatRoomLastSeen = -1, + isSubscribed = chatRoomEntity.open, + isCreator = true, + isFavorite = false + ) + } + } catch (ex: Exception) { + Timber.e(ex) + ex.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + } finally { + view.hideLoading() + } + } + } + + private fun resetOffset() { + offset = 0 + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/presentation/DirectoryView.kt b/app/src/main/java/chat/rocket/android/directory/presentation/DirectoryView.kt new file mode 100644 index 0000000000..9b7d03ee92 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/presentation/DirectoryView.kt @@ -0,0 +1,22 @@ +package chat.rocket.android.directory.presentation + +import chat.rocket.android.core.behaviours.LoadingView +import chat.rocket.android.core.behaviours.MessageView +import chat.rocket.android.directory.uimodel.DirectoryUiModel + +interface DirectoryView : MessageView, LoadingView { + + /** + * Shows the list of directory channels. + * + * @param dataSet The data set to show. + */ + fun showChannels(dataSet: List) + + /** + * Shows the list of directory users. + * + * @param dataSet The data set to show. + */ + fun showUsers(dataSet: List) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/ui/DirectoryFragment.kt b/app/src/main/java/chat/rocket/android/directory/ui/DirectoryFragment.kt new file mode 100644 index 0000000000..5df73e7c99 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/ui/DirectoryFragment.kt @@ -0,0 +1,250 @@ +package chat.rocket.android.directory.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.R +import chat.rocket.android.analytics.AnalyticsManager +import chat.rocket.android.analytics.event.ScreenViewEvent +import chat.rocket.android.directory.adapter.DirectoryAdapter +import chat.rocket.android.directory.adapter.Selector +import chat.rocket.android.directory.presentation.DirectoryPresenter +import chat.rocket.android.directory.presentation.DirectoryView +import chat.rocket.android.directory.uimodel.DirectoryUiModel +import chat.rocket.android.helper.EndlessRecyclerViewScrollListener +import chat.rocket.android.util.extension.onQueryTextListener +import chat.rocket.android.util.extensions.inflate +import chat.rocket.android.util.extensions.isNotNullNorBlank +import chat.rocket.android.util.extensions.showToast +import chat.rocket.android.util.extensions.ui +import dagger.android.support.AndroidSupportInjection +import kotlinx.android.synthetic.main.app_bar.* +import kotlinx.android.synthetic.main.fragment_directory.* +import kotlinx.android.synthetic.main.fragment_settings.view_loading +import javax.inject.Inject + +internal const val TAG_DIRECTORY_FRAGMENT = "DirectoryFragment" + +fun newInstance(): Fragment = DirectoryFragment() + +class DirectoryFragment : Fragment(), DirectoryView { + @Inject lateinit var analyticsManager: AnalyticsManager + @Inject lateinit var presenter: DirectoryPresenter + private var isSortByChannels: Boolean = true + private var isSearchForGlobalUsers: Boolean = false + private val linearLayoutManager = LinearLayoutManager(context) + private val directoryAdapter = DirectoryAdapter(object : Selector { + override fun onChannelSelected(channelId: String, channelName: String) { + presenter.toChannel(channelId, channelName) + } + override fun onUserSelected(username: String, name: String) { + presenter.tiDirectMessage(username, name) + } + override fun onGlobalUserSelected(username: String, name: String) { + presenter.tiDirectMessage(username, name) + } + }) + private val hashtagDrawable by lazy { + DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_16dp, text_sort_by.context) + } + private val userDrawable by lazy { + DrawableHelper.getDrawableFromId(R.drawable.ic_user_16dp, text_sort_by.context) + } + private val arrowDownDrawable by lazy { + DrawableHelper.getDrawableFromId(R.drawable.ic_arrow_down, text_sort_by.context) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + AndroidSupportInjection.inject(this) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = container?.inflate(R.layout.fragment_directory) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupToolbar() + setupRecyclerView() + setupListeners() + presenter.loadAllDirectoryChannels() + analyticsManager.logScreenView(ScreenViewEvent.Directory) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.directory, menu) + + val searchMenuItem = menu.findItem(R.id.action_search) + val searchView = searchMenuItem?.actionView as SearchView + + with(searchView) { + setIconifiedByDefault(false) + maxWidth = Integer.MAX_VALUE + onQueryTextListener { updateSorting(isSortByChannels, isSearchForGlobalUsers, it) } + } + + searchMenuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + updateSorting(isSortByChannels, isSearchForGlobalUsers, reload = true) + return true + } + + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + return true + } + }) + + } + + + override fun showChannels(dataSet: List) { + ui { + if (directoryAdapter.itemCount == 0) { + directoryAdapter.prependData(dataSet) + if (dataSet.size >= 60) { + recycler_view.addOnScrollListener(object : + EndlessRecyclerViewScrollListener(linearLayoutManager) { + override fun onLoadMore( + page: Int, + totalItemsCount: Int, + recyclerView: RecyclerView + ) { + presenter.loadAllDirectoryChannels() + } + }) + } + } else { + directoryAdapter.appendData(dataSet) + } + } + } + + override fun showUsers(dataSet: List) { + ui { + if (directoryAdapter.itemCount == 0) { + directoryAdapter.prependData(dataSet) + if (dataSet.size >= 60) { + recycler_view.addOnScrollListener(object : + EndlessRecyclerViewScrollListener(linearLayoutManager) { + override fun onLoadMore( + page: Int, + totalItemsCount: Int, + recyclerView: RecyclerView + ) { + presenter.loadAllDirectoryUsers(isSearchForGlobalUsers) + } + }) + } + } else { + directoryAdapter.appendData(dataSet) + } + } + } + + override fun showMessage(resId: Int) { + ui { showToast(resId) } + } + + override fun showMessage(message: String) { + ui { showToast(message) } + } + + override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) + + override fun showLoading() { + view_loading.isVisible = true + } + + override fun hideLoading() { + view_loading.isVisible = false + } + + fun updateSorting( + isSortByChannels: Boolean, + isSearchForGlobalUsers: Boolean, + query: String? = null, + reload: Boolean = false + ) { + if (query.isNotNullNorBlank() || reload) { + directoryAdapter.clearData() + presenter.updateSorting(isSortByChannels, isSearchForGlobalUsers, query) + } + + if (this.isSortByChannels != isSortByChannels || + this.isSearchForGlobalUsers != isSearchForGlobalUsers + ) { + this.isSortByChannels = isSortByChannels + this.isSearchForGlobalUsers = isSearchForGlobalUsers + updateSortByTitle() + with(directoryAdapter) { + clearData() + setSorting(isSortByChannels, isSearchForGlobalUsers) + } + presenter.updateSorting(isSortByChannels, isSearchForGlobalUsers, query) + } + } + + private fun setupToolbar() { + with((activity as AppCompatActivity)) { + with(toolbar) { + setSupportActionBar(this) + title = getString(R.string.msg_directory) + setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) + setNavigationOnClickListener { activity?.onBackPressed() } + } + } + } + + private fun setupRecyclerView() { + ui { + with(recycler_view) { + layoutManager = linearLayoutManager + addItemDecoration(DividerItemDecoration(it, DividerItemDecoration.HORIZONTAL)) + adapter = directoryAdapter + } + } + } + + private fun setupListeners() { + text_sort_by.setOnClickListener { + activity?.supportFragmentManager?.let { + showDirectorySortingBottomSheetFragment(isSortByChannels, isSearchForGlobalUsers, it) + } + } + } + + + private fun updateSortByTitle() { + if (isSortByChannels) { + text_sort_by.text = getString(R.string.msg_channels) + DrawableHelper.compoundStartAndEndDrawable( + text_sort_by, + hashtagDrawable, + arrowDownDrawable + ) + } else { + text_sort_by.text = getString(R.string.msg_users) + DrawableHelper.compoundStartAndEndDrawable( + text_sort_by, + userDrawable, + arrowDownDrawable + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/ui/DirectorySortingBottomSheetFragment.kt b/app/src/main/java/chat/rocket/android/directory/ui/DirectorySortingBottomSheetFragment.kt new file mode 100644 index 0000000000..31ca732f3a --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/ui/DirectorySortingBottomSheetFragment.kt @@ -0,0 +1,123 @@ +package chat.rocket.android.directory.ui + +import DrawableHelper +import android.content.DialogInterface +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.FragmentManager +import chat.rocket.android.R +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.bottom_sheet_fragment_directory_sorting.* + +fun showDirectorySortingBottomSheetFragment( + isSortByChannels: Boolean, + isSearchForGlobalUsers: Boolean, + supportFragmentManager: FragmentManager +) = DirectorySortingBottomSheetFragment().apply { + arguments = Bundle(2).apply { + putBoolean(BUNDLE_IS_SORT_BY_CHANNELS, isSortByChannels) + putBoolean(BUNDLE_IS_SEARCH_FOR_GLOBAL_USERS, isSearchForGlobalUsers) + } +}.show(supportFragmentManager, TAG) + +internal const val TAG = "DirectorySortingBottomSheetFragment" + +private const val BUNDLE_IS_SORT_BY_CHANNELS = "is_sort_by_channels" +private const val BUNDLE_IS_SEARCH_FOR_GLOBAL_USERS = "is_search_for_global_users" + +class DirectorySortingBottomSheetFragment : BottomSheetDialogFragment() { + private var isSortByChannels = true + private var isSearchForGlobalUsers = false + private val hashtagDrawable by lazy { + DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_16dp, requireContext()) + } + private val userDrawable by lazy { + DrawableHelper.getDrawableFromId(R.drawable.ic_user_16dp, requireContext()) + } + private val checkDrawable by lazy { + DrawableHelper.getDrawableFromId(R.drawable.ic_check, requireContext()) + } + private val directoryFragment by lazy { + activity?.supportFragmentManager?.findFragmentByTag(TAG_DIRECTORY_FRAGMENT) as DirectoryFragment + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.run { + isSortByChannels = getBoolean(BUNDLE_IS_SORT_BY_CHANNELS) + isSearchForGlobalUsers = getBoolean(BUNDLE_IS_SEARCH_FOR_GLOBAL_USERS) + } + ?: requireNotNull(arguments) { "no arguments supplied when the bottom sheet fragment was instantiated" } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = + inflater.inflate(R.layout.bottom_sheet_fragment_directory_sorting, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupView() + setupListeners() + } + + override fun onCancel(dialog: DialogInterface?) { + super.onCancel(dialog) + } + + private fun setupView() { + if (isSortByChannels) { + checkSelection(text_channels, hashtagDrawable) + } else { + checkSelection(text_users, userDrawable) + } + + switch_global_users.isChecked = isSearchForGlobalUsers + } + + private fun setupListeners() { + text_channels.setOnClickListener { + checkSelection(text_channels, hashtagDrawable) + uncheckSelection(text_users, userDrawable) + isSortByChannels = true + directoryFragment.updateSorting(isSortByChannels, isSearchForGlobalUsers) + } + + text_users.setOnClickListener { + checkSelection(text_users, userDrawable) + uncheckSelection(text_channels, hashtagDrawable) + isSortByChannels = false + directoryFragment.updateSorting(isSortByChannels, isSearchForGlobalUsers) + } + + switch_global_users.setOnCheckedChangeListener { _, isChecked -> + isSearchForGlobalUsers = isChecked + directoryFragment.updateSorting(isSortByChannels, isSearchForGlobalUsers) + } + } + + private fun checkSelection(textView: TextView, startDrawable: Drawable) { + context?.let { + DrawableHelper.compoundStartAndEndDrawable( + textView, + startDrawable, + checkDrawable + ) + } + } + + private fun uncheckSelection(textView: TextView, startDrawable: Drawable) { + context?.let { + DrawableHelper.compoundStartDrawable( + textView, + startDrawable + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/directory/uimodel/DirectoryUiModel.kt b/app/src/main/java/chat/rocket/android/directory/uimodel/DirectoryUiModel.kt new file mode 100644 index 0000000000..df586f9dcd --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/uimodel/DirectoryUiModel.kt @@ -0,0 +1,35 @@ +package chat.rocket.android.directory.uimodel + +import chat.rocket.android.util.extensions.avatarUrl +import chat.rocket.common.model.Token +import chat.rocket.core.model.DirectoryResult + +class DirectoryUiModel( + private val directoryResult: DirectoryResult, + private val baseUrl: String?, + private val token: Token? +) { + val id: String = directoryResult.id + val channelAvatarUri: String? + val userAvatarUri: String? + val name: String = directoryResult.name + val username: String = "@${directoryResult.username}" + val serverUrl: String = "" // TODO + val description: String = "" // TODO + val totalMembers: String = "" // TODO + + init { + channelAvatarUri = getChannelAvatar() + userAvatarUri = getUserAvatar() + } + + private fun getChannelAvatar(): String? { + return baseUrl?.avatarUrl(name, token?.userId, token?.authToken, isGroupOrChannel = true) + } + + private fun getUserAvatar(): String? { + return directoryResult.username?.let { + baseUrl?.avatarUrl(it, token?.userId, token?.authToken) + } + } +} diff --git a/app/src/main/java/chat/rocket/android/directory/uimodel/DirectoryUiModelMapper.kt b/app/src/main/java/chat/rocket/android/directory/uimodel/DirectoryUiModelMapper.kt new file mode 100644 index 0000000000..47564b9120 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/directory/uimodel/DirectoryUiModelMapper.kt @@ -0,0 +1,23 @@ +package chat.rocket.android.directory.uimodel + +import chat.rocket.android.server.domain.GetSettingsInteractor +import chat.rocket.android.server.domain.TokenRepository +import chat.rocket.android.server.domain.baseUrl +import chat.rocket.core.model.DirectoryResult +import chat.rocket.core.model.Value +import javax.inject.Inject +import javax.inject.Named + +class DirectoryUiModelMapper @Inject constructor( + getSettingsInteractor: GetSettingsInteractor, + @Named("currentServer") private val currentServer: String, + tokenRepository: TokenRepository +) { + private var settings: Map> = getSettingsInteractor.get(currentServer) + private val baseUrl = settings.baseUrl() + private val token = tokenRepository.get(currentServer) + + fun mapToUiModelList(directoryList: List): List { + return directoryList.map { DirectoryUiModel(it, baseUrl, token) } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt b/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt index 3d6a655dea..3f13d09d89 100644 --- a/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt +++ b/app/src/main/java/chat/rocket/android/favoritemessages/presentation/FavoriteMessagesPresenter.kt @@ -3,9 +3,8 @@ package chat.rocket.android.favoritemessages.presentation import chat.rocket.android.chatroom.uimodel.UiModelMapper import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.db.DatabaseManager -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI -import chat.rocket.android.util.retryDB import chat.rocket.common.RocketChatException import chat.rocket.common.model.roomTypeOf import chat.rocket.common.util.ifNull @@ -23,7 +22,7 @@ class FavoriteMessagesPresenter @Inject constructor( private val mapper: UiModelMapper, val factory: RocketChatClientFactory ) { - private val client: RocketChatClient = factory.create(currentServer) + private val client: RocketChatClient = factory.get(currentServer) private var offset: Int = 0 /** diff --git a/app/src/main/java/chat/rocket/android/files/presentation/FilesPresenter.kt b/app/src/main/java/chat/rocket/android/files/presentation/FilesPresenter.kt index 38275e4015..be0bb30f61 100644 --- a/app/src/main/java/chat/rocket/android/files/presentation/FilesPresenter.kt +++ b/app/src/main/java/chat/rocket/android/files/presentation/FilesPresenter.kt @@ -5,9 +5,8 @@ import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.db.DatabaseManager import chat.rocket.android.files.uimodel.FileUiModel import chat.rocket.android.files.uimodel.FileUiModelMapper -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI -import chat.rocket.android.util.retryDB import chat.rocket.common.RocketChatException import chat.rocket.common.model.roomTypeOf import chat.rocket.common.util.ifNull @@ -25,7 +24,7 @@ class FilesPresenter @Inject constructor( private val mapper: FileUiModelMapper, val factory: RocketChatClientFactory ) { - private val client: RocketChatClient = factory.create(currentServer) + private val client: RocketChatClient = factory.get(currentServer) private var offset: Int = 0 /** diff --git a/app/src/main/java/chat/rocket/android/helper/AndroidPermissionsHelper.kt b/app/src/main/java/chat/rocket/android/helper/AndroidPermissionsHelper.kt index 8effca8c56..8b12ba0908 100644 --- a/app/src/main/java/chat/rocket/android/helper/AndroidPermissionsHelper.kt +++ b/app/src/main/java/chat/rocket/android/helper/AndroidPermissionsHelper.kt @@ -1,23 +1,60 @@ package chat.rocket.android.helper +import android.Manifest import android.app.Activity import android.content.Context import android.content.pm.PackageManager +import android.view.ContextThemeWrapper import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment object AndroidPermissionsHelper { const val WRITE_EXTERNAL_STORAGE_CODE = 1 + const val CAMERA_CODE = 2 - fun checkPermission(context: Context, permission: String): Boolean { + private fun checkPermission(context: Context, permission: String): Boolean { return ContextCompat.checkSelfPermission( context, permission ) == PackageManager.PERMISSION_GRANTED } - fun requestPermission(context: Activity, permission: String, requestCode: Int) { + private fun requestPermission(context: Activity, permission: String, requestCode: Int) { ActivityCompat.requestPermissions(context, arrayOf(permission), requestCode) } + + fun hasCameraPermission(context: Context): Boolean { + return checkPermission(context, Manifest.permission.CAMERA) + } + + fun getCameraPermission(fragment: Fragment) { + fragment.requestPermissions( + arrayOf(Manifest.permission.CAMERA), + CAMERA_CODE + ) + } + + fun hasWriteExternalStoragePermission(context: Context): Boolean { + return checkPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + + fun getWriteExternalStoragePermission(fragment: Fragment) { + fragment.requestPermissions( + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + WRITE_EXTERNAL_STORAGE_CODE + ) + } + + fun checkWritingPermission(context: Context) { + if (context is ContextThemeWrapper) { + val activity = if (context.baseContext is Activity) context.baseContext as Activity else context as Activity + requestPermission( + activity, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + WRITE_EXTERNAL_STORAGE_CODE + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/helper/Constants.kt b/app/src/main/java/chat/rocket/android/helper/Constants.kt deleted file mode 100644 index 7dbeb08ec2..0000000000 --- a/app/src/main/java/chat/rocket/android/helper/Constants.kt +++ /dev/null @@ -1,18 +0,0 @@ -package chat.rocket.android.helper - -object Constants { - const val CHATROOM_SORT_TYPE_KEY: String = "chatroom_sort_type" - const val CHATROOM_GROUP_BY_TYPE_KEY: String = "chatroom_group_by_type" - const val CHATROOM_GROUP_FAVOURITES_KEY: String = "chatroom_group_favourites" - - //Used to sort chat rooms - const val CHATROOM_CHANNEL = 0 - const val CHATROOM_PRIVATE_GROUP = 1 - const val CHATROOM_DM = 2 - const val CHATROOM_LIVE_CHAT = 3 -} - -object ChatRoomsSortOrder { - const val ALPHABETICAL: Int = 0 - const val ACTIVITY: Int = 1 -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/helper/ImageHelper.kt b/app/src/main/java/chat/rocket/android/helper/ImageHelper.kt index 0d48ed25d5..5fe5944860 100644 --- a/app/src/main/java/chat/rocket/android/helper/ImageHelper.kt +++ b/app/src/main/java/chat/rocket/android/helper/ImageHelper.kt @@ -1,7 +1,5 @@ package chat.rocket.android.helper -import android.Manifest -import android.app.Activity import android.content.Context import android.graphics.Color import android.graphics.Typeface @@ -9,7 +7,6 @@ import android.media.MediaScannerConnection import android.os.Environment import android.text.TextUtils import android.util.TypedValue -import android.view.ContextThemeWrapper import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView @@ -18,6 +15,8 @@ import androidx.appcompat.widget.Toolbar import androidx.core.net.toUri import androidx.core.view.setPadding import chat.rocket.android.R +import chat.rocket.android.helper.AndroidPermissionsHelper.checkWritingPermission +import chat.rocket.android.helper.AndroidPermissionsHelper.hasWriteExternalStoragePermission import com.facebook.binaryresource.FileBinaryResource import com.facebook.cache.common.CacheKey import com.facebook.imageformat.ImageFormatChecker @@ -117,7 +116,7 @@ object ImageHelper { } private fun saveImage(context: Context): Boolean { - if (!canWriteToExternalStorage(context)) { + if (!hasWriteExternalStoragePermission(context)) { checkWritingPermission(context) return false } @@ -152,22 +151,4 @@ object ImageHelper { } return true } - - fun canWriteToExternalStorage(context: Context): Boolean { - return AndroidPermissionsHelper.checkPermission( - context, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } - - fun checkWritingPermission(context: Context) { - if (context is ContextThemeWrapper) { - val activity = if (context.baseContext is Activity) context.baseContext as Activity else context as Activity - AndroidPermissionsHelper.requestPermission( - activity, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE - ) - } - } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/helper/JitsiHelper.kt b/app/src/main/java/chat/rocket/android/helper/JitsiHelper.kt index b123cfe547..37a746d471 100644 --- a/app/src/main/java/chat/rocket/android/helper/JitsiHelper.kt +++ b/app/src/main/java/chat/rocket/android/helper/JitsiHelper.kt @@ -3,7 +3,7 @@ package chat.rocket.android.helper object JitsiHelper { /** - * Returns the for the Jitsi video conferencing URL. + * Returns the Jitsi video conferencing URL. * * @param isSecureProtocol True if using SSL, false otherwise - from the public settings. * @param domain The Jitsi domain - from public settings. diff --git a/app/src/main/java/chat/rocket/android/helper/MessageParser.kt b/app/src/main/java/chat/rocket/android/helper/MessageParser.kt index bdfad6bcc5..655691eabf 100644 --- a/app/src/main/java/chat/rocket/android/helper/MessageParser.kt +++ b/app/src/main/java/chat/rocket/android/helper/MessageParser.kt @@ -94,10 +94,16 @@ class MessageParser @Inject constructor( } private fun getMention(user: SimpleUser): String { + user.id?.let { + if (SYSTEM_MENTIONS.contains(it)) { + return "@$it" + } + } + return if (settings.useRealName()) { - user.name ?: "@${user.username}" + user.name ?: user.username.orEmpty() } else { - "@${user.username}" + user.username.orEmpty() } } @@ -527,5 +533,7 @@ class MessageParser @Inject constructor( */ private val WEB_URL = Pattern.compile( "($WEB_URL_WITH_PROTOCOL|$WEB_URL_WITHOUT_PROTOCOL)") + + private val SYSTEM_MENTIONS = arrayOf("all", "here") } } diff --git a/app/src/main/java/chat/rocket/android/helper/OauthHelper.kt b/app/src/main/java/chat/rocket/android/helper/OauthHelper.kt index 241529214e..952dab1454 100644 --- a/app/src/main/java/chat/rocket/android/helper/OauthHelper.kt +++ b/app/src/main/java/chat/rocket/android/helper/OauthHelper.kt @@ -15,11 +15,11 @@ object OauthHelper { "\"isCordova\":true}").encodeToBase64() /** - * Returns the Github Oauth URL. + * Returns the GitHub Oauth URL. * * @param clientId The GitHub client ID. * @param state An unguessable random string used to protect against forgery attacks. - * @return The Github Oauth URL. + * @return The GitHub Oauth URL. */ fun getGithubOauthUrl(clientId: String, state: String): String { return "https://github.com/login/oauth/authorize" + @@ -46,12 +46,12 @@ object OauthHelper { } /** - * Returns the Linkedin Oauth URL. + * Returns the LinkedIn Oauth URL. * - * @param clientId The Linkedin client ID. + * @param clientId The LinkedIn client ID. * @param serverUrl The server URL. * @param state An unguessable random string used to protect against forgery attacks. - * @return The Linkedin Oauth URL. + * @return The LinkedIn Oauth URL. */ fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String): String { return "https://linkedin.com/oauth/v2/authorization" + @@ -62,13 +62,13 @@ object OauthHelper { } /** - * Returns the Gitlab Oauth URL. + * Returns the GitLab Oauth URL. * - * @param host The Gitlab host. - * @param clientId The Gitlab client ID. + * @param host The GitLab host. + * @param clientId The GitLab client ID. * @param serverUrl The server URL. * @param state An unguessable random string used to protect against forgery attacks. - * @return The Gitlab Oauth URL. + * @return The GitLab Oauth URL. */ fun getGitlabOauthUrl( host: String? = "https://gitlab.com", diff --git a/app/src/main/java/chat/rocket/android/helper/SharedPreferenceHelper.kt b/app/src/main/java/chat/rocket/android/helper/SharedPreferenceHelper.kt deleted file mode 100644 index 61e789af81..0000000000 --- a/app/src/main/java/chat/rocket/android/helper/SharedPreferenceHelper.kt +++ /dev/null @@ -1,48 +0,0 @@ -package chat.rocket.android.helper - -import android.content.SharedPreferences -import android.preference.PreferenceManager -import chat.rocket.android.app.RocketChatApplication - -object SharedPreferenceHelper { - private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(RocketChatApplication.getAppContext()) - private var editor: SharedPreferences.Editor? = sharedPreferences.edit() - - //Add more methods for other types if needed - - fun putInt(key: String, value: Int) { - editor!!.putInt(key, value).apply() - } - - fun getInt(key: String, defaultValue: Int): Int { - return sharedPreferences.getInt(key, defaultValue) - } - - fun putLong(key: String, value: Long) { - editor!!.putLong(key, value).apply() - } - - fun getLong(key: String, defaultValue: Long): Long { - return sharedPreferences.getLong(key, defaultValue) - } - - fun putString(key: String, value: String) { - editor!!.putString(key, value).apply() - } - - fun getString(key: String, defaultValue: String): String? { - return sharedPreferences.getString(key, defaultValue) - } - - fun putBoolean(key: String, value: Boolean) { - editor!!.putBoolean(key, value).apply() - } - - fun getBoolean(key: String, defaultValue: Boolean): Boolean { - return sharedPreferences.getBoolean(key, defaultValue) - } - - fun remove(key: String) { - editor!!.remove(key).apply() - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/helper/UserHelper.kt b/app/src/main/java/chat/rocket/android/helper/UserHelper.kt index 90e9ede0e2..06d5289e73 100644 --- a/app/src/main/java/chat/rocket/android/helper/UserHelper.kt +++ b/app/src/main/java/chat/rocket/android/helper/UserHelper.kt @@ -24,6 +24,24 @@ class UserHelper @Inject constructor( */ fun username(): String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY, null) + /** + * Return the name for the current logged [User]. + */ + fun name(): String? = user()?.name + + /** + * Return the display name for the given [user]. + * If setting 'Use_Real_Name' is true then the real name will be given, otherwise the username + * without the '@' is yielded. + */ + fun displayName(user: User) = getCurrentServerInteractor.get()?.let { + if (settingsRepository.get(it).useRealName()) { + user.name + } else { + user.username + } + } + /** * Return the display name for the given [user]. * If setting 'Use_Real_Name' is true then the real name will be given, otherwise the username diff --git a/app/src/main/java/chat/rocket/android/main/adapter/AccountViewHolder.kt b/app/src/main/java/chat/rocket/android/main/adapter/AccountViewHolder.kt deleted file mode 100644 index 6c9e79dc4a..0000000000 --- a/app/src/main/java/chat/rocket/android/main/adapter/AccountViewHolder.kt +++ /dev/null @@ -1,17 +0,0 @@ -package chat.rocket.android.main.adapter - -import androidx.recyclerview.widget.RecyclerView -import android.view.View -import chat.rocket.android.server.domain.model.Account -import kotlinx.android.synthetic.main.item_account.view.* - -class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - - fun bind(account: Account) { - with(itemView) { - server_logo.setImageURI(account.serverLogo) - text_server_url.text = account.serverUrl - text_username.text = account.userName - } - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/adapter/AccountsAdapter.kt b/app/src/main/java/chat/rocket/android/main/adapter/AccountsAdapter.kt deleted file mode 100644 index d3b8683263..0000000000 --- a/app/src/main/java/chat/rocket/android/main/adapter/AccountsAdapter.kt +++ /dev/null @@ -1,68 +0,0 @@ -package chat.rocket.android.main.adapter - -import androidx.recyclerview.widget.RecyclerView -import android.view.ViewGroup -import chat.rocket.android.R -import chat.rocket.android.server.domain.model.Account -import chat.rocket.android.util.extensions.inflate -import chat.rocket.common.model.UserStatus - -private const val VIEW_TYPE_CHANGE_STATUS = 0 -private const val VIEW_TYPE_ACCOUNT = 1 -private const val VIEW_TYPE_ADD_ACCOUNT = 2 - -class AccountsAdapter( - private val accounts: List, - private val selector: Selector -) : RecyclerView.Adapter() { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (viewType) { - VIEW_TYPE_CHANGE_STATUS -> StatusViewHolder(parent.inflate(R.layout.item_change_status)) - VIEW_TYPE_ACCOUNT -> AccountViewHolder(parent.inflate(R.layout.item_account)) - else -> AddAccountViewHolder(parent.inflate(R.layout.item_add_account)) - } - } - - override fun getItemCount() = accounts.size + 2 - - override fun getItemViewType(position: Int): Int { - return when { - position == 0 -> VIEW_TYPE_CHANGE_STATUS - position <= accounts.size -> VIEW_TYPE_ACCOUNT - else -> VIEW_TYPE_ADD_ACCOUNT - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is StatusViewHolder -> bindStatusViewHolder(holder) - is AccountViewHolder -> bindAccountViewHolder(holder, position) - is AddAccountViewHolder -> bindAddAccountViewHolder(holder) - } - } - - private fun bindStatusViewHolder(holder: StatusViewHolder) { - holder.bind { userStatus -> selector.onStatusSelected(userStatus) } - } - - private fun bindAccountViewHolder(holder: AccountViewHolder, position: Int) { - val account = accounts[position - 1] - holder.bind(account) - holder.itemView.setOnClickListener { - selector.onAccountSelected(account.serverUrl) - } - } - - private fun bindAddAccountViewHolder(holder: AddAccountViewHolder) { - holder.itemView.setOnClickListener { - selector.onAddedAccountSelected() - } - } -} - -interface Selector { - fun onStatusSelected(userStatus: UserStatus) - fun onAccountSelected(serverUrl: String) - fun onAddedAccountSelected() -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/adapter/AddAccountViewHolder.kt b/app/src/main/java/chat/rocket/android/main/adapter/AddAccountViewHolder.kt deleted file mode 100644 index 4660dadb12..0000000000 --- a/app/src/main/java/chat/rocket/android/main/adapter/AddAccountViewHolder.kt +++ /dev/null @@ -1,6 +0,0 @@ -package chat.rocket.android.main.adapter - -import androidx.recyclerview.widget.RecyclerView -import android.view.View - -class AddAccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/adapter/StatusViewHolder.kt b/app/src/main/java/chat/rocket/android/main/adapter/StatusViewHolder.kt deleted file mode 100644 index 86fae29681..0000000000 --- a/app/src/main/java/chat/rocket/android/main/adapter/StatusViewHolder.kt +++ /dev/null @@ -1,18 +0,0 @@ -package chat.rocket.android.main.adapter - -import androidx.recyclerview.widget.RecyclerView -import android.view.View -import chat.rocket.common.model.UserStatus -import kotlinx.android.synthetic.main.item_change_status.view.* - -class StatusViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - - fun bind(listener: (UserStatus) -> Unit) { - with(itemView) { - text_online.setOnClickListener { listener(UserStatus.Online()) } - text_away.setOnClickListener { listener(UserStatus.Away()) } - text_busy.setOnClickListener { listener(UserStatus.Busy()) } - text_invisible.setOnClickListener { listener(UserStatus.Offline()) } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/di/MainModule.kt b/app/src/main/java/chat/rocket/android/main/di/MainModule.kt index 91ae6a7ad4..ab8819e171 100644 --- a/app/src/main/java/chat/rocket/android/main/di/MainModule.kt +++ b/app/src/main/java/chat/rocket/android/main/di/MainModule.kt @@ -1,10 +1,10 @@ package chat.rocket.android.main.di import androidx.lifecycle.LifecycleOwner +import chat.rocket.android.core.behaviours.AppLanguageView import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.main.presentation.MainNavigator -import chat.rocket.android.main.presentation.MainView import chat.rocket.android.main.ui.MainActivity import dagger.Module import dagger.Provides @@ -15,14 +15,17 @@ class MainModule { @Provides @PerActivity - fun provideJob() = Job() + fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity) @Provides @PerActivity - fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity) + fun appLanguageView(activity: MainActivity): AppLanguageView { + return activity + } @Provides - fun provideMainView(activity: MainActivity): MainView = activity + @PerActivity + fun provideJob() = Job() @Provides fun provideLifecycleOwner(activity: MainActivity): LifecycleOwner = activity diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt index c149448922..49b8da299f 100644 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt +++ b/app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt @@ -3,51 +3,60 @@ package chat.rocket.android.main.presentation import chat.rocket.android.R import chat.rocket.android.authentication.ui.newServerIntent import chat.rocket.android.chatroom.ui.chatRoomIntent -import chat.rocket.android.chatrooms.ui.ChatRoomsFragment import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT -import chat.rocket.android.createchannel.ui.CreateChannelFragment import chat.rocket.android.createchannel.ui.TAG_CREATE_CHANNEL_FRAGMENT +import chat.rocket.android.directory.ui.TAG_DIRECTORY_FRAGMENT import chat.rocket.android.main.ui.MainActivity -import chat.rocket.android.profile.ui.ProfileFragment import chat.rocket.android.profile.ui.TAG_PROFILE_FRAGMENT import chat.rocket.android.server.ui.changeServerIntent -import chat.rocket.android.settings.ui.SettingsFragment import chat.rocket.android.settings.ui.TAG_SETTINGS_FRAGMENT import chat.rocket.android.util.extensions.addFragment -import chat.rocket.android.webview.adminpanel.ui.AdminPanelWebViewFragment +import chat.rocket.android.util.extensions.addFragmentBackStack +import chat.rocket.android.webview.adminpanel.ui.TAG_ADMIN_PANEL_WEB_VIEW_FRAGMENT +import chat.rocket.android.webview.ui.webViewIntent class MainNavigator(internal val activity: MainActivity) { fun toChatList(chatRoomId: String? = null) { activity.addFragment(TAG_CHAT_ROOMS_FRAGMENT, R.id.fragment_container) { - ChatRoomsFragment.newInstance(chatRoomId) + chat.rocket.android.chatrooms.ui.newInstance(chatRoomId) } } - fun toCreateChannel() { - activity.addFragment(TAG_CREATE_CHANNEL_FRAGMENT, R.id.fragment_container) { - CreateChannelFragment.newInstance() + fun toSettings() { + activity.addFragmentBackStack(TAG_SETTINGS_FRAGMENT, R.id.fragment_container) { + chat.rocket.android.settings.ui.newInstance() } } - fun toUserProfile() { - activity.addFragment(TAG_PROFILE_FRAGMENT, R.id.fragment_container) { - ProfileFragment.newInstance() + fun toDirectory() { + activity.addFragmentBackStack(TAG_DIRECTORY_FRAGMENT, R.id.fragment_container) { + chat.rocket.android.directory.ui.newInstance() } } - fun toSettings() { - activity.addFragment(TAG_SETTINGS_FRAGMENT, R.id.fragment_container) { - SettingsFragment.newInstance() + fun toCreateChannel() { + activity.addFragmentBackStack(TAG_CREATE_CHANNEL_FRAGMENT, R.id.fragment_container) { + chat.rocket.android.createchannel.ui.newInstance() + } + } + + fun toProfile() { + activity.addFragmentBackStack(TAG_PROFILE_FRAGMENT, R.id.fragment_container) { + chat.rocket.android.profile.ui.newInstance() } } fun toAdminPanel(webPageUrl: String, userToken: String) { - activity.addFragment("AdminPanelWebViewFragment", R.id.fragment_container) { - AdminPanelWebViewFragment.newInstance(webPageUrl, userToken) + activity.addFragmentBackStack(TAG_ADMIN_PANEL_WEB_VIEW_FRAGMENT, R.id.fragment_container) { + chat.rocket.android.webview.adminpanel.ui.newInstance(webPageUrl, userToken) } } + fun toLicense(licenseUrl: String, licenseTitle: String) { + activity.startActivity(activity.webViewIntent(licenseUrl, licenseTitle)) + } + fun toChatRoom( chatRoomId: String, chatRoomName: String, diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt index d192b466b1..f0732314fd 100644 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt +++ b/app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt @@ -1,244 +1,47 @@ package chat.rocket.android.main.presentation -import android.content.Context -import chat.rocket.android.core.lifecycle.CancelStrategy -import chat.rocket.android.db.DatabaseManagerFactory -import chat.rocket.android.emoji.Emoji -import chat.rocket.android.emoji.EmojiRepository -import chat.rocket.android.emoji.Fitzpatrick -import chat.rocket.android.emoji.internal.EmojiCategory -import chat.rocket.android.infrastructure.LocalRepository -import chat.rocket.android.main.uimodel.NavHeaderUiModel -import chat.rocket.android.main.uimodel.NavHeaderUiModelMapper +import chat.rocket.android.core.behaviours.AppLanguageView import chat.rocket.android.push.GroupedPush -import chat.rocket.android.server.domain.GetAccountsInteractor -import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.domain.GetSettingsInteractor -import chat.rocket.android.server.domain.PublicSettings -import chat.rocket.android.server.domain.RefreshSettingsInteractor +import chat.rocket.android.server.domain.GetCurrentLanguageInteractor import chat.rocket.android.server.domain.RefreshPermissionsInteractor -import chat.rocket.android.server.domain.RemoveAccountInteractor -import chat.rocket.android.server.domain.SaveAccountInteractor -import chat.rocket.android.server.domain.TokenRepository -import chat.rocket.android.server.domain.favicon -import chat.rocket.android.server.domain.model.Account -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory -import chat.rocket.android.server.infraestructure.RocketChatClientFactory -import chat.rocket.android.server.presentation.CheckServerPresenter -import chat.rocket.android.util.extension.launchUI -import chat.rocket.android.util.extensions.adminPanelUrl -import chat.rocket.android.util.extensions.serverLogoUrl -import chat.rocket.android.util.retryIO -import chat.rocket.common.RocketChatAuthException -import chat.rocket.common.RocketChatException -import chat.rocket.common.model.UserStatus -import chat.rocket.common.util.ifNull -import chat.rocket.core.RocketChatClient -import chat.rocket.core.internal.rest.getCustomEmojis -import chat.rocket.core.internal.rest.me -import chat.rocket.core.model.Myself -import kotlinx.coroutines.channels.Channel -import timber.log.Timber +import chat.rocket.android.server.domain.RefreshSettingsInteractor +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import javax.inject.Inject +import javax.inject.Named class MainPresenter @Inject constructor( - private val view: MainView, - private val strategy: CancelStrategy, - private val navigator: MainNavigator, - private val tokenRepository: TokenRepository, + @Named("currentServer") private val currentServerUrl: String, + private val mainNavigator: MainNavigator, + private val appLanguageView: AppLanguageView, private val refreshSettingsInteractor: RefreshSettingsInteractor, private val refreshPermissionsInteractor: RefreshPermissionsInteractor, - private val navHeaderMapper: NavHeaderUiModelMapper, - private val saveAccountInteractor: SaveAccountInteractor, - private val getAccountsInteractor: GetAccountsInteractor, - private val groupedPush: GroupedPush, - serverInteractor: GetCurrentServerInteractor, - localRepository: LocalRepository, - removeAccountInteractor: RemoveAccountInteractor, - factory: RocketChatClientFactory, - dbManagerFactory: DatabaseManagerFactory, - getSettingsInteractor: GetSettingsInteractor, - managerFactory: ConnectionManagerFactory -) : CheckServerPresenter( - strategy = strategy, - factory = factory, - serverInteractor = serverInteractor, - localRepository = localRepository, - removeAccountInteractor = removeAccountInteractor, - tokenRepository = tokenRepository, - managerFactory = managerFactory, - dbManagerFactory = dbManagerFactory, - tokenView = view, - navigator = navigator + private val connectionManagerFactory: ConnectionManagerFactory, + private var getLanguageInteractor: GetCurrentLanguageInteractor, + private val groupedPush: GroupedPush ) { - private val currentServer = serverInteractor.get()!! - private val manager = managerFactory.create(currentServer) - private val client: RocketChatClient = factory.create(currentServer) - private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!) - private val userDataChannel = Channel() - - fun toChatList(chatRoomId: String? = null) = navigator.toChatList(chatRoomId) - - fun toUserProfile() = navigator.toUserProfile() - - fun toSettings() = navigator.toSettings() - - fun toAdminPanel() = tokenRepository.get(currentServer)?.let { - navigator.toAdminPanel(currentServer.adminPanelUrl(), it.authToken) - } - - fun toCreateChannel() = navigator.toCreateChannel() - - fun loadServerAccounts() { - launchUI(strategy) { - try { - view.setupServerAccountList(getAccountsInteractor.get()) - } catch (ex: Exception) { - when (ex) { - is RocketChatAuthException -> logout() - else -> { - Timber.d(ex, "Error loading serve accounts") - ex.message?.let { - view.showMessage(it) - }.ifNull { - view.showGenericErrorMessage() - } - } - } - } - } - } - - fun loadCurrentInfo() { - setupConnectionInfo(currentServer) - checkServerInfo(currentServer) - launchUI(strategy) { - try { - val me = retryIO("me") { client.me() } - val model = navHeaderMapper.mapToUiModel(me) - saveAccount(model) - view.setupUserAccountInfo(model) - } catch (ex: Exception) { - when (ex) { - is RocketChatAuthException -> logout() - else -> { - Timber.d(ex, "Error loading my information for navheader") - ex.message?.let { - view.showMessage(it) - }.ifNull { - view.showGenericErrorMessage() - } - } - } - } - subscribeMyselfUpdates() - } - } - - /** - * Load all emojis for the current server. Simple emojis are always the same for every server, - * but custom emojis vary according to the its url. - */ - fun loadEmojis() { - launchUI(strategy) { - EmojiRepository.setCurrentServerUrl(currentServer) - val customEmojiList = mutableListOf() - try { - for (customEmoji in retryIO("getCustomEmojis()") { client.getCustomEmojis() }) { - customEmojiList.add(Emoji( - shortname = ":${customEmoji.name}:", - category = EmojiCategory.CUSTOM.name, - url = "$currentServer/emoji-custom/${customEmoji.name}.${customEmoji.extension}", - count = 0, - fitzpatrick = Fitzpatrick.Default.type, - keywords = customEmoji.aliases, - shortnameAlternates = customEmoji.aliases, - siblings = mutableListOf(), - unicode = "", - isDefault = true - )) - } - - EmojiRepository.load(view as Context, customEmojis = customEmojiList) - } catch (ex: RocketChatException) { - Timber.e(ex) - EmojiRepository.load(view as Context) - } - } - } - - fun logout() { - setupConnectionInfo(currentServer) - super.logout(userDataChannel) - } fun connect() { - refreshSettingsInteractor.refreshAsync(currentServer) - refreshPermissionsInteractor.refreshAsync(currentServer) - manager.connect() - } - - fun disconnect() { - setupConnectionInfo(currentServer) - super.disconnect(userDataChannel) - } - - fun changeServer(serverUrl: String) { - if (currentServer != serverUrl) { - navigator.switchOrAddNewServer(serverUrl) - } else { - view.closeServerSelection() - } - } - - fun addNewServer() { - navigator.toServerScreen() + refreshSettingsInteractor.refreshAsync(currentServerUrl) + refreshPermissionsInteractor.refreshAsync(currentServerUrl) + connectionManagerFactory.create(currentServerUrl).connect() } - fun changeDefaultStatus(userStatus: UserStatus) { - launchUI(strategy) { - try { - manager.setDefaultStatus(userStatus) - view.showUserStatus(userStatus) - } catch (ex: RocketChatException) { - ex.message?.let { - view.showMessage(it) - }.ifNull { - view.showGenericErrorMessage() - } - } - } - } - - private fun saveAccount(uiModel: NavHeaderUiModel) { - val icon = settings.favicon()?.let { - currentServer.serverLogoUrl(it) - } - val account = Account( - currentServer, - icon, - uiModel.serverLogo, - uiModel.userDisplayName!!, - uiModel.userAvatar - ) - saveAccountInteractor.save(account) - } + fun clearNotificationsForChatRoom(chatRoomId: String?) { + if (chatRoomId == null) return - private suspend fun subscribeMyselfUpdates() { - manager.addUserDataChannel(userDataChannel) - for (myself in userDataChannel) { - updateMyself(myself) + groupedPush.hostToPushMessageList[currentServerUrl].let { list -> + list?.removeAll { it.info.roomId == chatRoomId } } } - private fun updateMyself(myself: Myself) = - view.setupUserAccountInfo(navHeaderMapper.mapToUiModel(myself)) + fun showChatList(chatRoomId: String? = null) = mainNavigator.toChatList(chatRoomId) - fun clearNotificationsForChatroom(chatRoomId: String?) { - if (chatRoomId == null) return - groupedPush.hostToPushMessageList[currentServer]?.let { list -> - list.removeAll { it.info.roomId == chatRoomId } + fun getAppLanguage() { + with(getLanguageInteractor) { + getLanguage()?.let { language -> + appLanguageView.updateLanguage(language, getCountry()) + } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/presentation/MainView.kt b/app/src/main/java/chat/rocket/android/main/presentation/MainView.kt deleted file mode 100644 index 2047f21bf8..0000000000 --- a/app/src/main/java/chat/rocket/android/main/presentation/MainView.kt +++ /dev/null @@ -1,38 +0,0 @@ -package chat.rocket.android.main.presentation - -import chat.rocket.android.authentication.server.presentation.VersionCheckView -import chat.rocket.android.core.behaviours.MessageView -import chat.rocket.android.main.uimodel.NavHeaderUiModel -import chat.rocket.android.server.domain.model.Account -import chat.rocket.android.server.presentation.TokenView -import chat.rocket.common.model.UserStatus - -interface MainView : MessageView, VersionCheckView, TokenView { - - /** - * Shows the current user status. - * - * @see [UserStatus] - */ - fun showUserStatus(userStatus: UserStatus) - - /** - * Setups the user account info (displayed in the nav. header) - * - * @param uiModel The [NavHeaderUiModel]. - */ - fun setupUserAccountInfo(uiModel: NavHeaderUiModel) - - /** - * Setups the server account list. - * - * @param serverAccountList The list of server accounts. - */ - fun setupServerAccountList(serverAccountList: List) - - fun closeServerSelection() - - fun showProgress() - - fun hideProgress() -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt b/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt index b7b9476740..1dde86a3e4 100644 --- a/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt +++ b/app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt @@ -1,282 +1,85 @@ package chat.rocket.android.main.ui -import DrawableHelper import android.app.Activity -import androidx.appcompat.app.AlertDialog -import android.app.ProgressDialog +import android.app.NotificationManager +import android.content.Context +import android.content.res.Configuration +import android.os.Build import android.os.Bundle -import androidx.annotation.IdRes +import android.os.LocaleList import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.GravityCompat -import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import chat.rocket.android.BuildConfig import chat.rocket.android.R -import chat.rocket.android.chatrooms.ui.ChatRoomsFragment -import chat.rocket.android.main.adapter.AccountsAdapter -import chat.rocket.android.main.adapter.Selector +import chat.rocket.android.core.behaviours.AppLanguageView import chat.rocket.android.main.presentation.MainPresenter -import chat.rocket.android.main.presentation.MainView -import chat.rocket.android.main.uimodel.NavHeaderUiModel import chat.rocket.android.push.refreshPushToken -import chat.rocket.android.server.domain.PermissionsInteractor -import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.ui.INTENT_CHAT_ROOM_ID -import chat.rocket.android.util.extensions.fadeIn -import chat.rocket.android.util.extensions.fadeOut -import chat.rocket.android.util.extensions.rotateBy -import chat.rocket.android.util.extensions.showToast -import chat.rocket.android.util.invalidateFirebaseToken -import chat.rocket.common.model.UserStatus import dagger.android.AndroidInjection import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.HasActivityInjector import dagger.android.support.HasSupportFragmentInjector -import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.android.synthetic.main.app_bar.* -import kotlinx.android.synthetic.main.nav_header.view.* +import java.util.* import javax.inject.Inject -import android.app.NotificationManager -import android.content.Context - -private const val CURRENT_STATE = "current_state" - -class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, - HasSupportFragmentInjector { +class MainActivity : AppCompatActivity(), HasActivityInjector, + HasSupportFragmentInjector, AppLanguageView { @Inject lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector @Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector @Inject lateinit var presenter: MainPresenter - @Inject - lateinit var permissions: PermissionsInteractor - private var isFragmentAdded: Boolean = false - private var expanded = false - private val headerLayout by lazy { view_navigation.getHeaderView(0) } - private var chatRoomId: String? = null - private var progressDialog: ProgressDialog? = null override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - refreshPushToken() - chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) - presenter.clearNotificationsForChatroom(chatRoomId) - presenter.connect() - presenter.loadServerAccounts() - presenter.loadCurrentInfo() - presenter.loadEmojis() - setupToolbar() - setupNavigationView() - } - - override fun onSaveInstanceState(outState: Bundle?) { - super.onSaveInstanceState(outState) - outState?.putBoolean(CURRENT_STATE, isFragmentAdded) - } - - override fun onRestoreInstanceState(savedInstanceState: Bundle?) { - super.onRestoreInstanceState(savedInstanceState) - isFragmentAdded = savedInstanceState?.getBoolean(CURRENT_STATE) ?: false + with(presenter) { + connect() + getAppLanguage() + intent.getStringExtra(INTENT_CHAT_ROOM_ID).let { + clearNotificationsForChatRoom(it) + showChatList(it) + } + } } override fun onResume() { super.onResume() - if (!isFragmentAdded) { - presenter.toChatList(chatRoomId) - isFragmentAdded = true - } - val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) - as NotificationManager - notificationManager.cancelAll() - } - - override fun onDestroy() { - super.onDestroy() - if (isFinishing) { - presenter.disconnect() - } + clearAppNotifications() } - override fun onBackPressed() { - if (drawer_layout.isDrawerOpen(GravityCompat.START)) { - closeDrawer() - } else { - supportFragmentManager.findFragmentById(R.id.fragment_container)?.let { - if (it !is ChatRoomsFragment && supportFragmentManager.backStackEntryCount == 0) { - presenter.toChatList(chatRoomId) - setCheckedNavDrawerItem(R.id.menu_action_chats) - } else { - super.onBackPressed() - } - } - } - } - - override fun activityInjector(): AndroidInjector = activityDispatchingAndroidInjector + override fun activityInjector(): AndroidInjector = + activityDispatchingAndroidInjector override fun supportFragmentInjector(): AndroidInjector = fragmentDispatchingAndroidInjector - - override fun showUserStatus(userStatus: UserStatus) { - headerLayout.apply { - image_user_status.setImageDrawable( - DrawableHelper.getUserStatusDrawable(userStatus, this.context) - ) - } - } - - override fun setupUserAccountInfo(uiModel: NavHeaderUiModel) { - with(headerLayout) { - with(uiModel) { - if (userStatus != null) { - image_user_status.setImageDrawable( - DrawableHelper.getUserStatusDrawable(userStatus, context) - ) - } - if (userDisplayName != null) { - text_user_name.text = userDisplayName - } - if (userAvatar != null) { - setAvatar(userAvatar) - } - if (serverLogo != null) { - server_logo.setImageURI(serverLogo) - } - text_server_url.text = uiModel.serverUrl - } - } - } - - override fun setupServerAccountList(serverAccountList: List) { - accounts_list.layoutManager = LinearLayoutManager(this) - accounts_list.adapter = AccountsAdapter(serverAccountList, object : Selector { - override fun onStatusSelected(userStatus: UserStatus) { - presenter.changeDefaultStatus(userStatus) - } - - override fun onAccountSelected(serverUrl: String) { - presenter.changeServer(serverUrl) - } - - override fun onAddedAccountSelected() { - presenter.addNewServer() - } - }) - - headerLayout.account_container.setOnClickListener { - it.image_account_expand.rotateBy(180f) - if (expanded) { - accounts_list.fadeOut() - } else { - accounts_list.fadeIn() - } - expanded = !expanded - } - - headerLayout.image_avatar.setOnClickListener { - view_navigation.menu.findItem(R.id.menu_action_profile).isChecked = true - presenter.toUserProfile() - drawer_layout.closeDrawer(GravityCompat.START) + override fun updateLanguage(language: String, country: String?) { + val locale: Locale = if (country != null) { + Locale(language, country) + } else { + Locale(language) } - } - - - override fun closeServerSelection() { - view_navigation.getHeaderView(0).account_container.performClick() - } - - override fun alertNotRecommendedVersion() { - AlertDialog.Builder(this) - .setMessage( - getString( - R.string.msg_ver_not_recommended, - BuildConfig.RECOMMENDED_SERVER_VERSION - ) - ) - .setPositiveButton(android.R.string.ok, null) - .create() - .show() - } - - override fun blockAndAlertNotRequiredVersion() { - AlertDialog.Builder(this) - .setMessage( - getString( - R.string.msg_ver_not_minimum, - BuildConfig.REQUIRED_SERVER_VERSION - ) - ) - .setOnDismissListener { presenter.logout() } - .setPositiveButton(android.R.string.ok, null) - .create() - .show() - } - - override fun invalidateToken(token: String) = invalidateFirebaseToken(token) - - override fun showMessage(resId: Int) = showToast(resId) - override fun showMessage(message: String) = showToast(message) + Locale.setDefault(locale) - override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) + val config = Configuration() - private fun setupToolbar() { - setSupportActionBar(toolbar) - } - - fun setupNavigationView() { - with (view_navigation.menu) { - clear() - setupMenu(this) - } - - view_navigation.setNavigationItemSelectedListener { - it.isChecked = true - closeDrawer() - onNavDrawerItemSelected(it) - true + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + config.locales = LocaleList(locale) + } else { + config.locale = locale } - toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp) - toolbar.setNavigationOnClickListener { openDrawer() } - } - - fun showLogoutDialog() { - val builder = AlertDialog.Builder(this) - builder.setTitle(R.string.title_are_you_sure) - .setPositiveButton(R.string.action_logout) { _, _ -> presenter.logout()} - .setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() } - .create() - .show() - } - - fun setAvatar(avatarUrl: String) { - headerLayout.image_avatar.setImageURI(avatarUrl) - } - - fun getDrawerLayout(): DrawerLayout = drawer_layout - - fun openDrawer() = drawer_layout.openDrawer(GravityCompat.START) - - fun closeDrawer() = drawer_layout.closeDrawer(GravityCompat.START) - - fun setCheckedNavDrawerItem(@IdRes item: Int) = view_navigation.setCheckedItem(item) - - override fun showProgress() { - progressDialog = ProgressDialog.show(this, getString(R.string.app_name), getString(R.string.msg_log_out), true, false) + // TODO We need to check out a better way to use createConfigurationContext + // instead of updateConfiguration here since it is deprecated. + resources.updateConfiguration(config, resources.displayMetrics) } - override fun hideProgress() { - progressDialog?.dismiss() - progressDialog = null - } + private fun clearAppNotifications() = + (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancelAll() } diff --git a/app/src/main/java/chat/rocket/android/main/ui/Menu.kt b/app/src/main/java/chat/rocket/android/main/ui/Menu.kt deleted file mode 100644 index d0cdf4ca92..0000000000 --- a/app/src/main/java/chat/rocket/android/main/ui/Menu.kt +++ /dev/null @@ -1,69 +0,0 @@ -package chat.rocket.android.main.ui - -import android.view.Menu -import android.view.MenuItem -import chat.rocket.android.R - -internal fun MainActivity.setupMenu(menu: Menu) { - with(menu) { - add( - R.id.menu_section_one, - R.id.menu_action_chats, - Menu.NONE, - R.string.title_chats - ).setIcon(R.drawable.ic_chat_bubble_black_24dp) - .isChecked = true - - add( - R.id.menu_section_one, - R.id.menu_action_create_channel, - Menu.NONE, - R.string.action_create_channel - ).setIcon(R.drawable.ic_create_black_24dp) - - add( - R.id.menu_section_two, - R.id.menu_action_profile, - Menu.NONE, - R.string.title_profile - ).setIcon(R.drawable.ic_person_black_20dp) - - add( - R.id.menu_section_two, - R.id.menu_action_settings, - Menu.NONE, - R.string.title_settings - ).setIcon(R.drawable.ic_settings_black_24dp) - - if (permissions.canSeeTheAdminPanel()) { - add( - R.id.menu_section_two, - R.id.menu_action_admin_panel, - Menu.NONE, - R.string.title_admin_panel - ).setIcon(R.drawable.ic_settings_black_24dp) - } - - add( - R.id.menu_section_three, - R.id.menu_action_logout, - Menu.NONE, - R.string.action_logout - ).setIcon(R.drawable.ic_logout_black_24dp) - - setGroupCheckable(R.id.menu_section_one, true, true) - setGroupCheckable(R.id.menu_section_two, true, true) - setGroupCheckable(R.id.menu_section_three, true, true) - } -} - -internal fun MainActivity.onNavDrawerItemSelected(menuItem: MenuItem) { - when (menuItem.itemId) { - R.id.menu_action_chats-> presenter.toChatList() - R.id.menu_action_create_channel -> presenter.toCreateChannel() - R.id.menu_action_profile -> presenter.toUserProfile() - R.id.menu_action_settings -> presenter.toSettings() - R.id.menu_action_admin_panel -> presenter.toAdminPanel() - R.id.menu_action_logout -> showLogoutDialog() - } -} diff --git a/app/src/main/java/chat/rocket/android/main/uimodel/NavHeaderUiModel.kt b/app/src/main/java/chat/rocket/android/main/uimodel/NavHeaderUiModel.kt deleted file mode 100644 index 1b616dfc15..0000000000 --- a/app/src/main/java/chat/rocket/android/main/uimodel/NavHeaderUiModel.kt +++ /dev/null @@ -1,11 +0,0 @@ -package chat.rocket.android.main.uimodel - -import chat.rocket.common.model.UserStatus - -data class NavHeaderUiModel( - val userDisplayName: String?, - val userStatus: UserStatus?, - val userAvatar: String?, - val serverUrl: String, - val serverLogo: String? -) \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/main/uimodel/NavHeaderUiModelMapper.kt b/app/src/main/java/chat/rocket/android/main/uimodel/NavHeaderUiModelMapper.kt deleted file mode 100644 index 6a25e76f99..0000000000 --- a/app/src/main/java/chat/rocket/android/main/uimodel/NavHeaderUiModelMapper.kt +++ /dev/null @@ -1,32 +0,0 @@ -package chat.rocket.android.main.uimodel - -import chat.rocket.android.server.domain.* -import chat.rocket.android.util.extensions.avatarUrl -import chat.rocket.android.util.extensions.serverLogoUrl -import chat.rocket.core.model.Myself -import javax.inject.Inject - -class NavHeaderUiModelMapper @Inject constructor( - serverInteractor: GetCurrentServerInteractor, - getSettingsInteractor: GetSettingsInteractor -) { - private val currentServer = serverInteractor.get()!! - private var settings: PublicSettings = getSettingsInteractor.get(currentServer) - - fun mapToUiModel(me: Myself): NavHeaderUiModel { - val displayName = mapDisplayName(me) - val status = me.status - val avatar = me.username?.let { currentServer.avatarUrl(it) } - val image = settings.wideTile() ?: settings.faviconLarge() - val logo = image?.let { currentServer.serverLogoUrl(it) } - - return NavHeaderUiModel(displayName, status, avatar, currentServer, logo) - } - - private fun mapDisplayName(me: Myself): String? { - val username = me.username - val realName = me.name - val senderName = if (settings.useRealName()) realName else username - return senderName ?: username - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt b/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt index a8dbdcc8cd..68095d2768 100644 --- a/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt +++ b/app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt @@ -15,10 +15,10 @@ class MembersAdapter( ) : RecyclerView.Adapter() { private var dataSet: List = ArrayList() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MembersAdapter.ViewHolder = + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_member)) - override fun onBindViewHolder(holder: MembersAdapter.ViewHolder, position: Int) = + override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position], listener) override fun getItemCount(): Int = dataSet.size diff --git a/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt b/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt index 882648bad2..94e0858f75 100644 --- a/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt +++ b/app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt @@ -6,7 +6,7 @@ import chat.rocket.android.db.DatabaseManager import chat.rocket.android.helper.UserHelper import chat.rocket.android.members.uimodel.MemberUiModel import chat.rocket.android.members.uimodel.MemberUiModelMapper -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.common.RocketChatException import chat.rocket.common.model.roomTypeOf @@ -27,7 +27,7 @@ class MembersPresenter @Inject constructor( val factory: RocketChatClientFactory, private val userHelper: UserHelper ) { - private val client: RocketChatClient = factory.create(currentServer) + private val client: RocketChatClient = factory.get(currentServer) private var offset: Long = 0 /** diff --git a/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt b/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt index 261f6eab7d..5c57bd98f1 100644 --- a/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt +++ b/app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt @@ -102,15 +102,11 @@ class MembersFragment : Fragment(), MembersView { } override fun showMessage(resId: Int) { - ui { - showToast(resId) - } + ui { showToast(resId) } } override fun showMessage(message: String) { - ui { - showToast(message) - } + ui { showToast(message) } } override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) diff --git a/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt b/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt index 1487578230..16e78b8096 100644 --- a/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt +++ b/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModel.kt @@ -2,6 +2,7 @@ package chat.rocket.android.members.uimodel import chat.rocket.android.server.domain.useRealName import chat.rocket.android.util.extensions.avatarUrl +import chat.rocket.common.model.Token import chat.rocket.common.model.User import chat.rocket.common.model.UserStatus import chat.rocket.core.model.Value @@ -9,7 +10,8 @@ import chat.rocket.core.model.Value class MemberUiModel( private val member: User, private val settings: Map>, - private val baseUrl: String? + private val baseUrl: String?, + private val token: Token? ) { val userId: String = member.id val avatarUri: String? @@ -33,7 +35,7 @@ class MemberUiModel( private fun getUserAvatar(): String? { val username = member.username ?: "?" return baseUrl?.let { - baseUrl.avatarUrl(username, format = "png") + baseUrl.avatarUrl(username, token?.userId, token?.authToken, format = "png") } } diff --git a/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModelMapper.kt b/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModelMapper.kt index 6cf69f12cb..587aa02ca3 100644 --- a/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModelMapper.kt +++ b/app/src/main/java/chat/rocket/android/members/uimodel/MemberUiModelMapper.kt @@ -2,19 +2,24 @@ package chat.rocket.android.members.uimodel import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetSettingsInteractor +import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.baseUrl import chat.rocket.common.model.User import chat.rocket.core.model.Value import javax.inject.Inject +import javax.inject.Named class MemberUiModelMapper @Inject constructor( serverInteractor: GetCurrentServerInteractor, - getSettingsInteractor: GetSettingsInteractor + getSettingsInteractor: GetSettingsInteractor, + @Named("currentServer") private val currentServer: String, + tokenRepository: TokenRepository ) { private var settings: Map> = getSettingsInteractor.get(serverInteractor.get()!!) private val baseUrl = settings.baseUrl() + private val token = tokenRepository.get(currentServer) fun mapToUiModelList(memberList: List): List { - return memberList.map { MemberUiModel(it, settings, baseUrl) } + return memberList.map { MemberUiModel(it, settings, baseUrl, token) } } } diff --git a/app/src/main/java/chat/rocket/android/mentions/presentention/MentionsPresenter.kt b/app/src/main/java/chat/rocket/android/mentions/presentention/MentionsPresenter.kt index aa21f438ff..c985522a67 100644 --- a/app/src/main/java/chat/rocket/android/mentions/presentention/MentionsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/mentions/presentention/MentionsPresenter.kt @@ -2,7 +2,7 @@ package chat.rocket.android.mentions.presentention import chat.rocket.android.chatroom.uimodel.UiModelMapper import chat.rocket.android.core.lifecycle.CancelStrategy -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.common.RocketChatException import chat.rocket.common.util.ifNull @@ -18,7 +18,7 @@ class MentionsPresenter @Inject constructor( private val mapper: UiModelMapper, val factory: RocketChatClientFactory ) { - private val client = factory.create(currentServer) + private val client = factory.get(currentServer) private var offset: Long = 0 /** diff --git a/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt b/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt index fa9f4020ee..d5b030f49a 100644 --- a/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt +++ b/app/src/main/java/chat/rocket/android/pinnedmessages/presentation/PinnedMessagesPresenter.kt @@ -3,9 +3,8 @@ package chat.rocket.android.pinnedmessages.presentation import chat.rocket.android.chatroom.uimodel.UiModelMapper import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.db.DatabaseManager -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI -import chat.rocket.android.util.retryDB import chat.rocket.common.RocketChatException import chat.rocket.common.model.roomTypeOf import chat.rocket.common.util.ifNull @@ -23,7 +22,7 @@ class PinnedMessagesPresenter @Inject constructor( private val mapper: UiModelMapper, val factory: RocketChatClientFactory ) { - private val client: RocketChatClient = factory.create(currentServer) + private val client: RocketChatClient = factory.get(currentServer) private var offset: Int = 0 /** diff --git a/app/src/main/java/chat/rocket/android/preferences/di/PreferencesFragmentModule.kt b/app/src/main/java/chat/rocket/android/preferences/di/PreferencesFragmentModule.kt deleted file mode 100644 index 218a2f9a4c..0000000000 --- a/app/src/main/java/chat/rocket/android/preferences/di/PreferencesFragmentModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package chat.rocket.android.preferences.di - -import chat.rocket.android.dagger.scope.PerFragment -import chat.rocket.android.preferences.presentation.PreferencesView -import chat.rocket.android.preferences.ui.PreferencesFragment -import dagger.Module -import dagger.Provides - -@Module -class PreferencesFragmentModule { - - @Provides - @PerFragment - fun preferencesView(frag: PreferencesFragment): PreferencesView { - return frag - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/preferences/di/PreferencesFragmentProvider.kt b/app/src/main/java/chat/rocket/android/preferences/di/PreferencesFragmentProvider.kt deleted file mode 100644 index 083beb6768..0000000000 --- a/app/src/main/java/chat/rocket/android/preferences/di/PreferencesFragmentProvider.kt +++ /dev/null @@ -1,14 +0,0 @@ -package chat.rocket.android.preferences.di - -import chat.rocket.android.dagger.scope.PerFragment -import chat.rocket.android.preferences.ui.PreferencesFragment -import dagger.Module -import dagger.android.ContributesAndroidInjector - -@Module -abstract class PreferencesFragmentProvider { - - @ContributesAndroidInjector(modules = [PreferencesFragmentModule::class]) - @PerFragment - abstract fun providePreferencesFragment(): PreferencesFragment -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesPresenter.kt b/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesPresenter.kt deleted file mode 100644 index 38eba6e6db..0000000000 --- a/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesPresenter.kt +++ /dev/null @@ -1,22 +0,0 @@ -package chat.rocket.android.preferences.presentation - -import chat.rocket.android.server.domain.AnalyticsTrackingInteractor -import javax.inject.Inject - -class PreferencesPresenter @Inject constructor( - private val view: PreferencesView, - private val analyticsTrackingInteractor: AnalyticsTrackingInteractor -) { - - fun loadAnalyticsTrackingInformation() { - view.setupAnalyticsTrackingView(analyticsTrackingInteractor.get()) - } - - fun enableAnalyticsTracking() { - analyticsTrackingInteractor.save(true) - } - - fun disableAnalyticsTracking() { - analyticsTrackingInteractor.save(false) - } -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesView.kt b/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesView.kt deleted file mode 100644 index 6c3d4248ec..0000000000 --- a/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesView.kt +++ /dev/null @@ -1,11 +0,0 @@ -package chat.rocket.android.preferences.presentation - -interface PreferencesView { - - /** - * Setups the analytics tracking view. - * - * @param isAnalyticsTrackingEnabled Whether the analytics tracking is enabled - */ - fun setupAnalyticsTrackingView(isAnalyticsTrackingEnabled: Boolean) -} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/preferences/ui/PreferencesFragment.kt b/app/src/main/java/chat/rocket/android/preferences/ui/PreferencesFragment.kt deleted file mode 100644 index dcf3b136e4..0000000000 --- a/app/src/main/java/chat/rocket/android/preferences/ui/PreferencesFragment.kt +++ /dev/null @@ -1,96 +0,0 @@ -package chat.rocket.android.preferences.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import chat.rocket.android.BuildConfig -import chat.rocket.android.R -import chat.rocket.android.analytics.AnalyticsManager -import chat.rocket.android.analytics.event.ScreenViewEvent -import chat.rocket.android.main.ui.MainActivity -import chat.rocket.android.preferences.presentation.PreferencesPresenter -import chat.rocket.android.preferences.presentation.PreferencesView -import dagger.android.support.AndroidSupportInjection -import kotlinx.android.synthetic.main.app_bar.* -import kotlinx.android.synthetic.main.fragment_preferences.* -import javax.inject.Inject - -internal const val TAG_PREFERENCES_FRAGMENT = "PreferencesFragment" - -class PreferencesFragment : Fragment(), PreferencesView { - @Inject - lateinit var presenter: PreferencesPresenter - @Inject - lateinit var analyticsManager: AnalyticsManager - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - AndroidSupportInjection.inject(this) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = inflater.inflate(R.layout.fragment_preferences, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setupListeners() - presenter.loadAnalyticsTrackingInformation() - - analyticsManager.logScreenView(ScreenViewEvent.Preferences) - } - - override fun onResume() { - setupToolbar() - super.onResume() - } - - override fun setupAnalyticsTrackingView(isAnalyticsTrackingEnabled: Boolean) { - if (BuildConfig.FLAVOR == "foss") { - switch_analytics_tracking.isChecked = false - switch_analytics_tracking.isEnabled = false - text_analytics_tracking_description.text = - getString(R.string.msg_not_applicable_since_it_is_a_foss_version) - return - } - - if (isAnalyticsTrackingEnabled) { - text_analytics_tracking_description.text = - getString(R.string.msg_send_analytics_tracking) - } else { - text_analytics_tracking_description.text = - getString(R.string.msg_do_not_send_analytics_tracking) - } - switch_analytics_tracking.isChecked = isAnalyticsTrackingEnabled - } - - private fun setupToolbar() { - with((activity as MainActivity).toolbar) { - title = getString(R.string.title_preferences) - setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) - setNavigationOnClickListener { activity?.onBackPressed() } - } - } - - private fun setupListeners() { - switch_analytics_tracking.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - text_analytics_tracking_description.text = - getString(R.string.msg_send_analytics_tracking) - presenter.enableAnalyticsTracking() - } else { - text_analytics_tracking_description.text = - getString(R.string.msg_do_not_send_analytics_tracking) - presenter.disableAnalyticsTracking() - } - } - } - - companion object { - fun newInstance() = PreferencesFragment() - } -} diff --git a/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt b/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt index 826dbdaa86..8212c88323 100644 --- a/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt +++ b/app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt @@ -11,24 +11,23 @@ import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.RemoveAccountInteractor import chat.rocket.android.server.domain.TokenRepository -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.util.extension.compressImageAndGetByteArray -import chat.rocket.android.util.extension.gethash import chat.rocket.android.util.extension.launchUI -import chat.rocket.android.util.extension.toHex import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException +import chat.rocket.common.model.UserStatus +import chat.rocket.common.model.userStatusOf import chat.rocket.common.util.ifNull import chat.rocket.core.RocketChatClient -import chat.rocket.core.internal.rest.deleteOwnAccount +import chat.rocket.core.internal.realtime.setDefaultStatus +import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.resetAvatar import chat.rocket.core.internal.rest.setAvatar import chat.rocket.core.internal.rest.updateProfile -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import java.util.* import javax.inject.Inject @@ -56,18 +55,24 @@ class ProfilePresenter @Inject constructor( navigator = navigator ) { private val serverUrl = serverInteractor.get()!! - private val client: RocketChatClient = factory.create(serverUrl) + private val client: RocketChatClient = factory.get(serverUrl) private val user = userHelper.user() + private val token = tokenRepository.get(serverUrl) fun loadUserProfile() { launchUI(strategy) { view.showLoading() try { + val me = retryIO(description = "serverInfo", times = 5) { + client.me() + } + view.showProfile( - serverUrl.avatarUrl(user?.username ?: ""), - user?.name ?: "", - user?.username ?: "", - user?.emails?.getOrNull(0)?.address ?: "" + me.status.toString(), + serverUrl.avatarUrl(me.username!!, token?.userId, token?.authToken), + me.name ?: "", + me.username ?: "", + me.emails?.getOrNull(0)?.address ?: "" ) } catch (exception: RocketChatException) { view.showMessage(exception) @@ -82,10 +87,18 @@ class ProfilePresenter @Inject constructor( view.showLoading() try { user?.id?.let { id -> - retryIO { client.updateProfile(userId = id, email = email, name = name, username = username) } + retryIO { + client.updateProfile( + userId = id, + email = email, + name = name, + username = username + ) + } view.showProfileUpdateSuccessfullyMessage() view.showProfile( - serverUrl.avatarUrl(user.username ?: ""), + user.status.toString(), + serverUrl.avatarUrl(user.username!!, token?.userId, token?.authToken), name, username, email @@ -115,7 +128,15 @@ class ProfilePresenter @Inject constructor( uriInteractor.getInputStream(uri) } } - user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) } + user?.username?.let { + view.reloadUserAvatar( + serverUrl.avatarUrl( + it, + token?.userId, + token?.authToken + ) + ) + } } catch (exception: RocketChatException) { exception.message?.let { view.showMessage(it) @@ -143,7 +164,15 @@ class ProfilePresenter @Inject constructor( } } - user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) } + user?.username?.let { + view.reloadUserAvatar( + serverUrl.avatarUrl( + it, + token?.userId, + token?.authToken + ) + ) + } } catch (exception: RocketChatException) { exception.message?.let { view.showMessage(it) @@ -163,7 +192,15 @@ class ProfilePresenter @Inject constructor( user?.id?.let { id -> retryIO { client.resetAvatar(id) } } - user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) } + user?.username?.let { + view.reloadUserAvatar( + serverUrl.avatarUrl( + it, + token?.userId, + token?.authToken + ) + ) + } } catch (exception: RocketChatException) { exception.message?.let { view.showMessage(it) @@ -176,26 +213,17 @@ class ProfilePresenter @Inject constructor( } } - fun deleteAccount(password: String) { + fun updateStatus(status: UserStatus) { launchUI(strategy) { - view.showLoading() try { - withContext(Dispatchers.Default) { - // REMARK: Backend API is only working with a lowercase hash. - // https://github.com/RocketChat/Rocket.Chat/issues/12573 - retryIO { client.deleteOwnAccount(password.gethash().toHex().toLowerCase()) } - setupConnectionInfo(serverUrl) - logout(null) - } - } catch (exception: Exception) { + client.setDefaultStatus(status) + } catch (exception: RocketChatException) { exception.message?.let { view.showMessage(it) }.ifNull { view.showGenericErrorMessage() } - } finally { - view.hideLoading() } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/profile/presentation/ProfileView.kt b/app/src/main/java/chat/rocket/android/profile/presentation/ProfileView.kt index 86c8f97620..0ac7abffa5 100644 --- a/app/src/main/java/chat/rocket/android/profile/presentation/ProfileView.kt +++ b/app/src/main/java/chat/rocket/android/profile/presentation/ProfileView.kt @@ -9,12 +9,19 @@ interface ProfileView : TokenView, LoadingView, MessageView { /** * Shows the user profile. * + * @param status The user status. * @param avatarUrl The user avatar URL. * @param name The user display name. * @param username The user username. * @param email The user email. */ - fun showProfile(avatarUrl: String, name: String, username: String, email: String?) + fun showProfile( + status: String, + avatarUrl: String, + name: String, + username: String, + email: String? + ) /** * Reloads the user avatar (after successfully updating it). diff --git a/app/src/main/java/chat/rocket/android/profile/ui/ProfileFragment.kt b/app/src/main/java/chat/rocket/android/profile/ui/ProfileFragment.kt index 2eeff8b2c1..a8d337c7d7 100644 --- a/app/src/main/java/chat/rocket/android/profile/ui/ProfileFragment.kt +++ b/app/src/main/java/chat/rocket/android/profile/ui/ProfileFragment.kt @@ -2,8 +2,8 @@ package chat.rocket.android.profile.ui import DrawableHelper import android.app.Activity -import androidx.appcompat.app.AlertDialog import android.content.Intent +import android.content.pm.PackageManager import android.graphics.Bitmap import android.os.Build import android.os.Bundle @@ -12,8 +12,8 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.view.MenuInflater -import android.widget.EditText +import android.widget.RadioGroup +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.net.toUri @@ -22,6 +22,9 @@ import androidx.fragment.app.Fragment import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.ScreenViewEvent +import chat.rocket.android.helper.AndroidPermissionsHelper +import chat.rocket.android.helper.AndroidPermissionsHelper.getCameraPermission +import chat.rocket.android.helper.AndroidPermissionsHelper.hasCameraPermission import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.profile.presentation.ProfilePresenter import chat.rocket.android.profile.presentation.ProfileView @@ -33,12 +36,18 @@ import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.invalidateFirebaseToken +import chat.rocket.common.model.UserStatus +import chat.rocket.common.model.userStatusOf import com.facebook.drawee.backends.pipeline.Fresco +import com.google.android.material.snackbar.Snackbar import dagger.android.support.AndroidSupportInjection import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.Observables +import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.avatar_profile.* import kotlinx.android.synthetic.main.fragment_profile.* +import kotlinx.android.synthetic.main.fragment_profile.view_dim +import kotlinx.android.synthetic.main.fragment_profile.view_loading import kotlinx.android.synthetic.main.update_avatar_options.* import javax.inject.Inject @@ -47,24 +56,23 @@ internal const val TAG_PROFILE_FRAGMENT = "ProfileFragment" private const val REQUEST_CODE_FOR_PERFORM_SAF = 1 private const val REQUEST_CODE_FOR_PERFORM_CAMERA = 2 +fun newInstance() = ProfileFragment() + class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { @Inject lateinit var presenter: ProfilePresenter @Inject lateinit var analyticsManager: AnalyticsManager + private var currentStatus = "" private var currentName = "" private var currentUsername = "" private var currentEmail = "" private var actionMode: ActionMode? = null private val editTextsDisposable = CompositeDisposable() - companion object { - fun newInstance() = ProfileFragment() - } - override fun onCreate(savedInstanceState: Bundle?) { - AndroidSupportInjection.inject(this) super.onCreate(savedInstanceState) + AndroidSupportInjection.inject(this) setHasOptionsMenu(true) } @@ -78,11 +86,12 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { super.onViewCreated(view, savedInstanceState) setupToolbar() - setupListeners() if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { tintEditTextDrawableStart() } + presenter.loadUserProfile() + setupListeners() subscribeEditTexts() analyticsManager.logScreenView(ScreenViewEvent.Profile) @@ -112,25 +121,21 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { super.onPrepareOptionsMenu(menu) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.profile, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_delete_account -> showDeleteAccountDialog() - } - return true - } - - override fun showProfile(avatarUrl: String, name: String, username: String, email: String?) { + override fun showProfile( + status: String, + avatarUrl: String, + name: String, + username: String, + email: String? + ) { ui { + text_status.text = getString(R.string.status, status.capitalize()) image_avatar.setImageURI(avatarUrl) text_name.textContent = name text_username.textContent = username text_email.textContent = email ?: "" + currentStatus = status currentName = name currentUsername = username currentEmail = email ?: "" @@ -142,11 +147,10 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { override fun reloadUserAvatar(avatarUrl: String) { Fresco.getImagePipeline().evictFromCache(avatarUrl.toUri()) image_avatar.setImageURI(avatarUrl) - (activity as MainActivity).setAvatar(avatarUrl) } override fun showProfileUpdateSuccessfullyMessage() { - showMessage(getString(R.string.msg_profile_update_successfully)) + showMessage(getString(R.string.msg_profile_updated_successfully)) } override fun invalidateToken(token: String) = invalidateFirebaseToken(token) @@ -205,10 +209,19 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { } private fun setupToolbar() { - (activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_profile) + with((activity as AppCompatActivity)) { + with(toolbar) { + setSupportActionBar(this) + title = getString(R.string.title_profile) + setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) + setNavigationOnClickListener { activity?.onBackPressed() } + } + } } private fun setupListeners() { + text_status.setOnClickListener { showStatusDialog(currentStatus) } + image_avatar.setOnClickListener { showUpdateAvatarOptions() } view_dim.setOnClickListener { hideUpdateAvatarOptions() } @@ -219,7 +232,13 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { } button_take_a_photo.setOnClickListener { - dispatchTakePicture(REQUEST_CODE_FOR_PERFORM_CAMERA) + context?.let { + if (hasCameraPermission(it)) { + dispatchTakePicture(REQUEST_CODE_FOR_PERFORM_CAMERA) + } else { + getCameraPermission(this) + } + } hideUpdateAvatarOptions() } @@ -263,8 +282,8 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { text_email.asObservable() ) { text_name, text_username, text_email -> return@combineLatest (text_name.toString() != currentName || - text_username.toString() != currentUsername || - text_email.toString() != currentEmail) + text_username.toString() != currentUsername || + text_email.toString() != currentEmail) }.subscribe { isValid -> activity?.invalidateOptionsMenu() if (isValid) { @@ -293,15 +312,62 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { } } - fun showDeleteAccountDialog() { + private fun showStatusDialog(currentStatus: String) { + val dialogLayout = layoutInflater.inflate(R.layout.dialog_status, null) + val radioGroup = dialogLayout.findViewById(R.id.radio_group_status) + + radioGroup.check( + when (userStatusOf(currentStatus)) { + is UserStatus.Online -> R.id.radio_button_online + is UserStatus.Away -> R.id.radio_button_away + is UserStatus.Busy -> R.id.radio_button_busy + else -> R.id.radio_button_invisible + } + ) + + var newStatus: UserStatus = userStatusOf(currentStatus) + radioGroup.setOnCheckedChangeListener { _, checkId -> + when (checkId) { + R.id.radio_button_online -> newStatus = UserStatus.Online() + R.id.radio_button_away -> newStatus = UserStatus.Away() + R.id.radio_button_busy -> newStatus = UserStatus.Busy() + else -> newStatus = UserStatus.Offline() + } + } + context?.let { - val passwordEText = EditText(context); - val mDialogView = LayoutInflater.from(it).inflate(R.layout.item_account_delete, null) - val mBuilder = AlertDialog.Builder(it) + AlertDialog.Builder(it) + .setView(dialogLayout) + .setPositiveButton(R.string.msg_change_status) { dialog, _ -> + presenter.updateStatus(newStatus) + text_status.text = getString(R.string.status, newStatus.toString().capitalize()) + this.currentStatus = newStatus.toString() + dialog.dismiss() + }.show() + } + } - mBuilder.setView(mDialogView).setPositiveButton(R.string.action_delete_account) { _, _ -> - presenter.deleteAccount(passwordEText.text.toString()) - }.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }.create().show() + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + when (requestCode) { + AndroidPermissionsHelper.CAMERA_CODE -> { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // permission was granted + dispatchTakePicture(REQUEST_CODE_FOR_PERFORM_CAMERA) + } else { + // permission denied + Snackbar.make( + relative_layout, + R.string.msg_camera_permission_denied, + Snackbar.LENGTH_SHORT + ).show() + } + return + } } } } diff --git a/app/src/main/java/chat/rocket/android/push/DirectReplyReceiver.kt b/app/src/main/java/chat/rocket/android/push/DirectReplyReceiver.kt index 8e2f9754b0..6fa3170c2c 100644 --- a/app/src/main/java/chat/rocket/android/push/DirectReplyReceiver.kt +++ b/app/src/main/java/chat/rocket/android/push/DirectReplyReceiver.kt @@ -7,7 +7,7 @@ import android.content.Intent import androidx.core.app.RemoteInput import android.widget.Toast import chat.rocket.android.R -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.common.RocketChatException import chat.rocket.core.internal.rest.sendMessage import dagger.android.AndroidInjection diff --git a/app/src/main/java/chat/rocket/android/server/domain/GetCurrentLanguageInteractor.kt b/app/src/main/java/chat/rocket/android/server/domain/GetCurrentLanguageInteractor.kt new file mode 100644 index 0000000000..e0e475df05 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/server/domain/GetCurrentLanguageInteractor.kt @@ -0,0 +1,12 @@ +package chat.rocket.android.server.domain + +import chat.rocket.android.server.infrastructure.CurrentLanguageRepository +import javax.inject.Inject + +class GetCurrentLanguageInteractor @Inject constructor( + private val repository: CurrentLanguageRepository +) { + + fun getLanguage(): String? = repository.getLanguage() + fun getCountry(): String? = repository.getCountry() +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/domain/PermissionsInteractor.kt b/app/src/main/java/chat/rocket/android/server/domain/PermissionsInteractor.kt index c04c0f8124..0b90b6460e 100644 --- a/app/src/main/java/chat/rocket/android/server/domain/PermissionsInteractor.kt +++ b/app/src/main/java/chat/rocket/android/server/domain/PermissionsInteractor.kt @@ -69,7 +69,7 @@ class PermissionsInteractor @Inject constructor( } - fun canSeeTheAdminPanel(): Boolean { + fun isAdministrationEnabled(): Boolean { currentServerUrl()?.let { serverUrl -> val viewStatistics = permissionsRepository.get(serverUrl, VIEW_STATISTICS) diff --git a/app/src/main/java/chat/rocket/android/server/domain/RefreshPermissionsInteractor.kt b/app/src/main/java/chat/rocket/android/server/domain/RefreshPermissionsInteractor.kt index e1f1571923..feaa110ba8 100644 --- a/app/src/main/java/chat/rocket/android/server/domain/RefreshPermissionsInteractor.kt +++ b/app/src/main/java/chat/rocket/android/server/domain/RefreshPermissionsInteractor.kt @@ -1,6 +1,6 @@ package chat.rocket.android.server.domain -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.retryIO import chat.rocket.core.internal.rest.permissions import kotlinx.coroutines.Dispatchers @@ -20,7 +20,7 @@ class RefreshPermissionsInteractor @Inject constructor( fun refreshAsync(server: String) { GlobalScope.launch(Dispatchers.IO) { try { - factory.create(server).let { client -> + factory.get(server).let { client -> val permissions = retryIO( description = "permissions", times = 5, diff --git a/app/src/main/java/chat/rocket/android/server/domain/RefreshSettingsInteractor.kt b/app/src/main/java/chat/rocket/android/server/domain/RefreshSettingsInteractor.kt index 348fe84120..d50536767a 100644 --- a/app/src/main/java/chat/rocket/android/server/domain/RefreshSettingsInteractor.kt +++ b/app/src/main/java/chat/rocket/android/server/domain/RefreshSettingsInteractor.kt @@ -1,6 +1,6 @@ package chat.rocket.android.server.domain -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.retryIO import chat.rocket.core.internal.rest.settings import kotlinx.coroutines.Dispatchers @@ -74,7 +74,7 @@ class RefreshSettingsInteractor @Inject constructor( suspend fun refresh(server: String) { withContext(Dispatchers.IO) { - factory.create(server).let { client -> + factory.get(server).let { client -> val settings = retryIO( description = "settings", times = 5, diff --git a/app/src/main/java/chat/rocket/android/server/domain/SaveCurrentLanguageInteractor.kt b/app/src/main/java/chat/rocket/android/server/domain/SaveCurrentLanguageInteractor.kt new file mode 100644 index 0000000000..3bff12f8de --- /dev/null +++ b/app/src/main/java/chat/rocket/android/server/domain/SaveCurrentLanguageInteractor.kt @@ -0,0 +1,10 @@ +package chat.rocket.android.server.domain + +import chat.rocket.android.server.infrastructure.CurrentLanguageRepository +import javax.inject.Inject + +class SaveCurrentLanguageInteractor @Inject constructor( + private val repository: CurrentLanguageRepository +) { + fun save(language: String, country: String?) = repository.save(language, country) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/domain/SortingAndGroupingInteractor.kt b/app/src/main/java/chat/rocket/android/server/domain/SortingAndGroupingInteractor.kt new file mode 100644 index 0000000000..ada5d45250 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/server/domain/SortingAndGroupingInteractor.kt @@ -0,0 +1,32 @@ +package chat.rocket.android.server.domain + +import javax.inject.Inject + +class SortingAndGroupingInteractor @Inject constructor(val repository: SortingAndGroupingRepository) { + + fun save( + currentServerUrl: String, + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) = repository.save( + currentServerUrl, + isSortByName, + isUnreadOnTop, + isGroupByType, + isGroupByFavorites + ) + + fun getSortByName(currentServerUrl: String): Boolean = + repository.getSortByName(currentServerUrl) + + fun getUnreadOnTop(currentServerUrl: String): Boolean = + repository.getUnreadOnTop(currentServerUrl) + + fun getGroupByType(currentServerUrl: String): Boolean = + repository.getGroupByType(currentServerUrl) + + fun getGroupByFavorites(currentServerUrl: String): Boolean = + repository.getGroupByFavorites(currentServerUrl) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/domain/SortingAndGroupingRepository.kt b/app/src/main/java/chat/rocket/android/server/domain/SortingAndGroupingRepository.kt new file mode 100644 index 0000000000..2977e47758 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/server/domain/SortingAndGroupingRepository.kt @@ -0,0 +1,20 @@ +package chat.rocket.android.server.domain + +interface SortingAndGroupingRepository { + + fun save( + currentServerUrl: String, + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) + + fun getSortByName(currentServerUrl: String): Boolean + + fun getUnreadOnTop(currentServerUrl: String): Boolean + + fun getGroupByType(currentServerUrl: String): Boolean + + fun getGroupByFavorites(currentServerUrl: String): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/domain/model/Account.kt b/app/src/main/java/chat/rocket/android/server/domain/model/Account.kt index 143eaced55..ebb4c0d328 100644 --- a/app/src/main/java/chat/rocket/android/server/domain/model/Account.kt +++ b/app/src/main/java/chat/rocket/android/server/domain/model/Account.kt @@ -4,6 +4,7 @@ import se.ansman.kotshi.JsonSerializable @JsonSerializable data class Account( + val serverName: String?, val serverUrl: String, val serverLogo: String?, val serverBg: String?, diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/ConnectionManager.kt similarity index 99% rename from app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/ConnectionManager.kt index 070b270987..e2d7e1b745 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/ConnectionManager.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import androidx.lifecycle.MutableLiveData import chat.rocket.android.db.DatabaseManager diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManagerFactory.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/ConnectionManagerFactory.kt similarity index 78% rename from app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManagerFactory.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/ConnectionManagerFactory.kt index d1f04094ee..b4d32e63ea 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManagerFactory.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/ConnectionManagerFactory.kt @@ -1,7 +1,6 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import chat.rocket.android.db.DatabaseManagerFactory -import chat.rocket.android.infrastructure.LocalRepository import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -20,7 +19,7 @@ class ConnectionManagerFactory @Inject constructor( } Timber.d("Returning FRESH Manager for: $url") - val manager = ConnectionManager(factory.create(url), dbFactory.create(url)) + val manager = ConnectionManager(factory.get(url), dbFactory.create(url)) cache[url] = manager return manager } diff --git a/app/src/main/java/chat/rocket/android/server/infrastructure/CurrentLanguageRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/CurrentLanguageRepository.kt new file mode 100644 index 0000000000..e877579234 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/CurrentLanguageRepository.kt @@ -0,0 +1,8 @@ +package chat.rocket.android.server.infrastructure + +interface CurrentLanguageRepository { + + fun save(language: String, country: String? = null) + fun getLanguage(): String? + fun getCountry(): String? +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/DatabaseMessageMapper.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/DatabaseMessageMapper.kt similarity index 99% rename from app/src/main/java/chat/rocket/android/server/infraestructure/DatabaseMessageMapper.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/DatabaseMessageMapper.kt index 75eb8ea8fe..4ba37af7c3 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/DatabaseMessageMapper.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/DatabaseMessageMapper.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.model.* diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/DatabaseMessagesRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/DatabaseMessagesRepository.kt similarity index 98% rename from app/src/main/java/chat/rocket/android/server/infraestructure/DatabaseMessagesRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/DatabaseMessagesRepository.kt index 144fe5f8d8..959f2c4aca 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/DatabaseMessagesRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/DatabaseMessagesRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.Operation diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/JobSchedulerInteractorImpl.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/JobSchedulerInteractorImpl.kt similarity index 91% rename from app/src/main/java/chat/rocket/android/server/infraestructure/JobSchedulerInteractorImpl.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/JobSchedulerInteractorImpl.kt index 809d1efb2b..f590008473 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/JobSchedulerInteractorImpl.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/JobSchedulerInteractorImpl.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import android.app.job.JobInfo import android.app.job.JobScheduler diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/MemoryChatRoomsRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/MemoryChatRoomsRepository.kt similarity index 90% rename from app/src/main/java/chat/rocket/android/server/infraestructure/MemoryChatRoomsRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/MemoryChatRoomsRepository.kt index 17f30b3e32..b2d92b44f9 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/MemoryChatRoomsRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/MemoryChatRoomsRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import chat.rocket.android.server.domain.ChatRoomsRepository import chat.rocket.core.model.ChatRoom diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/MemoryUsersRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/MemoryUsersRepository.kt similarity index 95% rename from app/src/main/java/chat/rocket/android/server/infraestructure/MemoryUsersRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/MemoryUsersRepository.kt index a7287a404f..d8bac8001e 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/MemoryUsersRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/MemoryUsersRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import chat.rocket.android.server.domain.UsersRepository import chat.rocket.android.server.domain.UsersRepository.Query diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/RocketChatClientFactory.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/RocketChatClientFactory.kt similarity index 92% rename from app/src/main/java/chat/rocket/android/server/infraestructure/RocketChatClientFactory.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/RocketChatClientFactory.kt index 3f0bb6ce77..0c5ab18cbb 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/RocketChatClientFactory.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/RocketChatClientFactory.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import android.os.Build import chat.rocket.android.BuildConfig @@ -18,7 +18,7 @@ class RocketChatClientFactory @Inject constructor( ) { private val cache = HashMap() - fun create(url: String): RocketChatClient { + fun get(url: String): RocketChatClient { cache[url]?.let { Timber.d("Returning CACHED client for: $url") return it diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesAccountsRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesAccountsRepository.kt similarity index 96% rename from app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesAccountsRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesAccountsRepository.kt index 2765210eda..86f84a64b1 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesAccountsRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesAccountsRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import android.content.SharedPreferences import androidx.core.content.edit diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesPermissionsRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesPermissionsRepository.kt similarity index 95% rename from app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesPermissionsRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesPermissionsRepository.kt index 946f9f44fc..3341c2a1e1 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesPermissionsRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesPermissionsRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.server.domain.PermissionsRepository diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesSettingsRepository.kt similarity index 96% rename from app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesSettingsRepository.kt index 9826b2d3fd..5b7969b61e 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPreferencesSettingsRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository.Companion.SETTINGS_KEY diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsAnalyticsTrackingRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsAnalyticsTrackingRepository.kt similarity index 91% rename from app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsAnalyticsTrackingRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsAnalyticsTrackingRepository.kt index 6856f7ff73..2408de857c 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsAnalyticsTrackingRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsAnalyticsTrackingRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import android.content.SharedPreferences import chat.rocket.android.server.domain.AnalyticsTrackingRepository diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsBasicAuthRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsBasicAuthRepository.kt similarity index 96% rename from app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsBasicAuthRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsBasicAuthRepository.kt index 6c586200db..fd35c8df50 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsBasicAuthRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsBasicAuthRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import android.content.SharedPreferences import androidx.core.content.edit diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsConnectingServerRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsConnectingServerRepository.kt similarity index 92% rename from app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsConnectingServerRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsConnectingServerRepository.kt index bf8b3a6ffa..f4e90a4e3a 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsConnectingServerRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsConnectingServerRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import android.content.SharedPreferences import chat.rocket.android.server.domain.CurrentServerRepository diff --git a/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsCurrentLanguageRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsCurrentLanguageRepository.kt new file mode 100644 index 0000000000..06224302e7 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsCurrentLanguageRepository.kt @@ -0,0 +1,26 @@ +package chat.rocket.android.server.infrastructure + +import android.content.SharedPreferences +import java.util.* + +private const val CURRENT_LANGUAGE = "current_language" +private const val CURRENT_LANGUAGE_COUNTRY = "current_language_country" + +class SharedPrefsCurrentLanguageRepository(private val preferences: SharedPreferences) : + CurrentLanguageRepository { + + override fun save(language: String, country: String?) { + with(preferences) { + edit().putString(CURRENT_LANGUAGE, language).apply() + edit().putString(CURRENT_LANGUAGE_COUNTRY, country).apply() + } + } + + override fun getLanguage(): String? { + return preferences.getString(CURRENT_LANGUAGE, Locale.getDefault().language) + } + + override fun getCountry(): String? { + return preferences.getString(CURRENT_LANGUAGE_COUNTRY, Locale.getDefault().country) + } +} diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsCurrentServerRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsCurrentServerRepository.kt similarity index 92% rename from app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsCurrentServerRepository.kt rename to app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsCurrentServerRepository.kt index 8967865dcf..449801ce1a 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPrefsCurrentServerRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsCurrentServerRepository.kt @@ -1,4 +1,4 @@ -package chat.rocket.android.server.infraestructure +package chat.rocket.android.server.infrastructure import android.content.SharedPreferences import chat.rocket.android.server.domain.CurrentServerRepository diff --git a/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsSortingAndGroupingRepository.kt b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsSortingAndGroupingRepository.kt new file mode 100644 index 0000000000..91aecd17bd --- /dev/null +++ b/app/src/main/java/chat/rocket/android/server/infrastructure/SharedPrefsSortingAndGroupingRepository.kt @@ -0,0 +1,39 @@ +package chat.rocket.android.server.infrastructure + +import android.content.SharedPreferences +import chat.rocket.android.server.domain.SortingAndGroupingRepository + +private const val SORT_BY_NAME_KEY = "SORT_BY_NAME_KEY" +private const val UNREAD_ON_TOP_KEY = "UNREAD_ON_TOP_KEY" +private const val GROUP_BY_TYPE_KEY = "GROUP_BY_TYPE_KEY" +private const val GROUP_BY_FAVORITES_KEY = "GROUP_BY_FAVORITES_KEY" + +class SharedPrefsSortingAndGroupingRepository(private val preferences: SharedPreferences) : + SortingAndGroupingRepository { + + override fun save( + currentServerUrl: String, + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) { + preferences.edit().putBoolean(SORT_BY_NAME_KEY + currentServerUrl, isSortByName).apply() + preferences.edit().putBoolean(UNREAD_ON_TOP_KEY + currentServerUrl, isUnreadOnTop).apply() + preferences.edit().putBoolean(GROUP_BY_TYPE_KEY + currentServerUrl, isGroupByType).apply() + preferences.edit().putBoolean(GROUP_BY_FAVORITES_KEY + currentServerUrl, isGroupByFavorites) + .apply() + } + + override fun getSortByName(currentServerUrl: String): Boolean = + preferences.getBoolean(SORT_BY_NAME_KEY + currentServerUrl, false) + + override fun getUnreadOnTop(currentServerUrl: String): Boolean = + preferences.getBoolean(UNREAD_ON_TOP_KEY + currentServerUrl, false) + + override fun getGroupByType(currentServerUrl: String): Boolean = + preferences.getBoolean(GROUP_BY_TYPE_KEY + currentServerUrl, false) + + override fun getGroupByFavorites(currentServerUrl: String): Boolean = + preferences.getBoolean(GROUP_BY_FAVORITES_KEY + currentServerUrl, false) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt b/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt index dbfc912a3f..cbe8718e37 100644 --- a/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt +++ b/app/src/main/java/chat/rocket/android/server/presentation/ChangeServerPresenter.kt @@ -9,7 +9,7 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.SaveCurrentServerInteractor import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.TokenRepository -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.common.util.ifNull import javax.inject.Inject diff --git a/app/src/main/java/chat/rocket/android/server/presentation/CheckServerPresenter.kt b/app/src/main/java/chat/rocket/android/server/presentation/CheckServerPresenter.kt index d11c28819c..8b807ab39b 100644 --- a/app/src/main/java/chat/rocket/android/server/presentation/CheckServerPresenter.kt +++ b/app/src/main/java/chat/rocket/android/server/presentation/CheckServerPresenter.kt @@ -26,9 +26,9 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.RemoveAccountInteractor import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.RefreshSettingsInteractor -import chat.rocket.android.server.infraestructure.ConnectionManager -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.ConnectionManager +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.VersionInfo import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.casUrl @@ -105,7 +105,7 @@ abstract class CheckServerPresenter constructor( internal fun setupConnectionInfo(serverUrl: String) { currentServer = serverUrl - client = factory.create(serverUrl) + client = factory.get(serverUrl) managerFactory?.create(serverUrl)?.let { manager = it } @@ -452,7 +452,7 @@ abstract class CheckServerPresenter constructor( /** * Returns the OAuth client ID of a [serviceMap]. - * REMARK: This function works for common OAuth providers (Google, Facebook, Github and so on) + * REMARK: This function works for common OAuth providers (Google, Facebook, GitHub and so on) * as well as custom OAuth. * * @param serviceMap The service map to get the OAuth client ID. diff --git a/app/src/main/java/chat/rocket/android/servers/adapter/AddNewServerViewHolder.kt b/app/src/main/java/chat/rocket/android/servers/adapter/AddNewServerViewHolder.kt new file mode 100644 index 0000000000..ee2c6fec7c --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/adapter/AddNewServerViewHolder.kt @@ -0,0 +1,6 @@ +package chat.rocket.android.servers.adapter + +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class AddNewServerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/servers/adapter/ServerViewHolder.kt b/app/src/main/java/chat/rocket/android/servers/adapter/ServerViewHolder.kt new file mode 100644 index 0000000000..6ea75b314d --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/adapter/ServerViewHolder.kt @@ -0,0 +1,21 @@ +package chat.rocket.android.servers.adapter + +import android.view.View +import androidx.core.view.isInvisible +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.server.domain.model.Account +import com.bumptech.glide.Glide +import kotlinx.android.synthetic.main.item_server.view.* + +class ServerViewHolder(itemView: View, private val currentServerUrl: String) : + RecyclerView.ViewHolder(itemView) { + + fun bind(account: Account) { + with(itemView) { + Glide.with(context).load(account.serverLogo).into(image_server) + text_server_name.text = account.serverName ?: account.serverUrl + text_server_url.text = account.serverUrl + image_check.isInvisible = currentServerUrl != account.serverUrl + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/servers/adapter/ServersAdapter.kt b/app/src/main/java/chat/rocket/android/servers/adapter/ServersAdapter.kt new file mode 100644 index 0000000000..d71f540313 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/adapter/ServersAdapter.kt @@ -0,0 +1,57 @@ +package chat.rocket.android.servers.adapter + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import chat.rocket.android.R +import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.util.extensions.inflate + +private const val VIEW_TYPE_SERVER = 0 +private const val VIEW_TYPE_ADD_NEW_SERVER = 1 + +class ServersAdapter( + private val servers: List, + private val currentServerUrl: String, + private val selector: Selector +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + VIEW_TYPE_SERVER -> ServerViewHolder( + parent.inflate(R.layout.item_server), currentServerUrl + ) + else -> AddNewServerViewHolder(parent.inflate(R.layout.item_add_new_server)) + } + } + + override fun getItemCount() = servers.size + 1 + + override fun getItemViewType(position: Int): Int { + return when { + position < servers.size -> VIEW_TYPE_SERVER + else -> VIEW_TYPE_ADD_NEW_SERVER + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is ServerViewHolder -> bindServerViewHolder(holder, position) + is AddNewServerViewHolder -> bindAddNewServerViewHolder(holder) + } + } + + private fun bindServerViewHolder(holder: ServerViewHolder, position: Int) { + val account = servers[position] + holder.bind(account) + holder.itemView.setOnClickListener { selector.onServerSelected(account.serverUrl) } + } + + private fun bindAddNewServerViewHolder(holder: AddNewServerViewHolder) { + holder.itemView.setOnClickListener { selector.onAddNewServerSelected() } + } +} + +interface Selector { + fun onServerSelected(serverUrl: String) + fun onAddNewServerSelected() +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/servers/di/ServersBottomSheetFragmentModule.kt b/app/src/main/java/chat/rocket/android/servers/di/ServersBottomSheetFragmentModule.kt new file mode 100644 index 0000000000..64d1c91b36 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/di/ServersBottomSheetFragmentModule.kt @@ -0,0 +1,15 @@ +package chat.rocket.android.servers.di + +import chat.rocket.android.dagger.scope.PerFragment +import chat.rocket.android.servers.presentation.ServersView +import chat.rocket.android.servers.ui.ServersBottomSheetFragment +import dagger.Module +import dagger.Provides + +@Module +class ServersBottomSheetFragmentModule { + + @Provides + @PerFragment + fun serversView(frag: ServersBottomSheetFragment): ServersView = frag +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/servers/di/ServersBottomSheetFragmentProvider.kt b/app/src/main/java/chat/rocket/android/servers/di/ServersBottomSheetFragmentProvider.kt new file mode 100644 index 0000000000..d7c9ca56ff --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/di/ServersBottomSheetFragmentProvider.kt @@ -0,0 +1,14 @@ +package chat.rocket.android.servers.di + +import chat.rocket.android.dagger.scope.PerFragment +import chat.rocket.android.servers.ui.ServersBottomSheetFragment +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class ServersBottomSheetFragmentProvider { + + @ContributesAndroidInjector(modules = [ServersBottomSheetFragmentModule::class]) + @PerFragment + abstract fun provideServersBottomSheetFragment(): ServersBottomSheetFragment +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/servers/presentation/ServersPresenter.kt b/app/src/main/java/chat/rocket/android/servers/presentation/ServersPresenter.kt new file mode 100644 index 0000000000..b7768edf37 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/presentation/ServersPresenter.kt @@ -0,0 +1,47 @@ +package chat.rocket.android.servers.presentation + +import chat.rocket.android.core.lifecycle.CancelStrategy +import chat.rocket.android.main.presentation.MainNavigator +import chat.rocket.android.server.domain.GetAccountsInteractor +import chat.rocket.android.util.extension.launchUI +import chat.rocket.common.util.ifNull +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Named + +class ServersPresenter @Inject constructor( + private val view: ServersView, + private val navigator: MainNavigator, + private val strategy: CancelStrategy, + private val getAccountsInteractor: GetAccountsInteractor, + @Named("currentServer") private val currentServerUrl: String +) { + + fun getAllServers() { + launchUI(strategy) { + try { + view.showServerList(getAccountsInteractor.get(), currentServerUrl) + } catch (exception: Exception) { + Timber.e(exception, "Error loading servers") + exception.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + } + } + } + + fun changeServer(serverUrl: String) { + if (currentServerUrl != serverUrl) { + navigator.switchOrAddNewServer(serverUrl) + } else { + view.hideServerView() + } + } + + fun addNewServer() { + view.hideServerView() + navigator.toServerScreen() + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/servers/presentation/ServersView.kt b/app/src/main/java/chat/rocket/android/servers/presentation/ServersView.kt new file mode 100644 index 0000000000..742dca4b11 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/presentation/ServersView.kt @@ -0,0 +1,20 @@ +package chat.rocket.android.servers.presentation + +import chat.rocket.android.core.behaviours.MessageView +import chat.rocket.android.server.domain.model.Account + +interface ServersView : MessageView { + + /** + * Shows the server list. + * + * @param serverList The list of server to show. + * @param currentServerUrl The current logged in server url. + */ + fun showServerList(serverList: List, currentServerUrl: String) + + /** + * Hides the servers view. + */ + fun hideServerView() +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/servers/ui/ServersBottomSheetFragment.kt b/app/src/main/java/chat/rocket/android/servers/ui/ServersBottomSheetFragment.kt new file mode 100644 index 0000000000..717a113e04 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/servers/ui/ServersBottomSheetFragment.kt @@ -0,0 +1,67 @@ +package chat.rocket.android.servers.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import chat.rocket.android.R +import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.servers.adapter.Selector +import chat.rocket.android.servers.adapter.ServersAdapter +import chat.rocket.android.servers.presentation.ServersPresenter +import chat.rocket.android.servers.presentation.ServersView +import chat.rocket.android.util.extensions.showToast +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dagger.android.support.AndroidSupportInjection +import kotlinx.android.synthetic.main.bottom_sheet_fragment_servers.* +import javax.inject.Inject + +const val TAG = "ServersBottomSheetFragment" + +class ServersBottomSheetFragment : BottomSheetDialogFragment(), ServersView { + @Inject + lateinit var presenter: ServersPresenter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + AndroidSupportInjection.inject(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = + inflater.inflate(R.layout.bottom_sheet_fragment_servers, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.getAllServers() + } + + override fun showServerList(serverList: List, currentServerUrl: String) { + recycler_view.layoutManager = LinearLayoutManager(context) + recycler_view.adapter = ServersAdapter(serverList, currentServerUrl, object : Selector { + override fun onServerSelected(serverUrl: String) { + presenter.changeServer(serverUrl) + } + + override fun onAddNewServerSelected() { + presenter.addNewServer() + } + }) + } + + override fun hideServerView() = dismiss() + + override fun showMessage(resId: Int) { + showToast(resId) + } + + override fun showMessage(message: String) { + showToast(message) + } + + override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentModule.kt b/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentModule.kt index beb4ba79b4..1928f7b7ef 100644 --- a/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentModule.kt +++ b/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentModule.kt @@ -1,13 +1,11 @@ package chat.rocket.android.settings.di import androidx.lifecycle.LifecycleOwner -import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.settings.presentation.SettingsView import chat.rocket.android.settings.ui.SettingsFragment import dagger.Module import dagger.Provides -import kotlinx.coroutines.Job @Module class SettingsFragmentModule { @@ -20,13 +18,7 @@ class SettingsFragmentModule { @Provides @PerFragment - fun settingsLifecycleOwner(frag: SettingsFragment): LifecycleOwner { - return frag - } - - @Provides - @PerFragment - fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { - return CancelStrategy(owner, jobs) + fun settingsLifecycleOwner(fragment: SettingsFragment): LifecycleOwner { + return fragment } } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentProvider.kt b/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentProvider.kt index 8d5ed61590..cfb74551a2 100644 --- a/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentProvider.kt +++ b/app/src/main/java/chat/rocket/android/settings/di/SettingsFragmentProvider.kt @@ -1,5 +1,6 @@ package chat.rocket.android.settings.di +import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.settings.ui.SettingsFragment import dagger.Module import dagger.android.ContributesAndroidInjector @@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector abstract class SettingsFragmentProvider { @ContributesAndroidInjector(modules = [SettingsFragmentModule::class]) + @PerFragment abstract fun provideSettingsFragment(): SettingsFragment } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/settings/password/presentation/PasswordPresenter.kt b/app/src/main/java/chat/rocket/android/settings/password/presentation/PasswordPresenter.kt index 2731855a45..e6d86c2ed7 100644 --- a/app/src/main/java/chat/rocket/android/settings/password/presentation/PasswordPresenter.kt +++ b/app/src/main/java/chat/rocket/android/settings/password/presentation/PasswordPresenter.kt @@ -4,11 +4,10 @@ import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.helper.UserHelper import chat.rocket.android.server.domain.GetCurrentServerInteractor -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.retryIO import chat.rocket.common.RocketChatException -import chat.rocket.common.util.ifNull import chat.rocket.core.RocketChatClient import chat.rocket.core.internal.rest.updateProfile import javax.inject.Inject @@ -22,7 +21,7 @@ class PasswordPresenter @Inject constructor( factory: RocketChatClientFactory ) { private val serverUrl = serverInteractor.get()!! - private val client: RocketChatClient = factory.create(serverUrl) + private val client: RocketChatClient = factory.get(serverUrl) fun updatePassword(password: String) { launchUI(strategy) { diff --git a/app/src/main/java/chat/rocket/android/settings/presentation/SettingsPresenter.kt b/app/src/main/java/chat/rocket/android/settings/presentation/SettingsPresenter.kt new file mode 100644 index 0000000000..c311b3e5f9 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/settings/presentation/SettingsPresenter.kt @@ -0,0 +1,151 @@ +package chat.rocket.android.settings.presentation + +import android.content.Context +import android.os.Build +import chat.rocket.android.core.lifecycle.CancelStrategy +import chat.rocket.android.db.DatabaseManagerFactory +import chat.rocket.android.helper.UserHelper +import chat.rocket.android.main.presentation.MainNavigator +import chat.rocket.android.server.domain.AnalyticsTrackingInteractor +import chat.rocket.android.server.domain.GetCurrentServerInteractor +import chat.rocket.android.server.domain.PermissionsInteractor +import chat.rocket.android.server.domain.RemoveAccountInteractor +import chat.rocket.android.server.domain.SaveCurrentLanguageInteractor +import chat.rocket.android.server.domain.TokenRepository +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory +import chat.rocket.android.server.presentation.CheckServerPresenter +import chat.rocket.android.util.extension.gethash +import chat.rocket.android.util.extension.launchUI +import chat.rocket.android.util.extension.toHex +import chat.rocket.android.util.extensions.adminPanelUrl +import chat.rocket.android.util.extensions.avatarUrl +import chat.rocket.android.util.retryIO +import chat.rocket.common.util.ifNull +import chat.rocket.core.internal.rest.deleteOwnAccount +import chat.rocket.core.internal.rest.me +import chat.rocket.core.internal.rest.serverInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber +import java.util.* +import javax.inject.Inject +import javax.inject.Named + +class SettingsPresenter @Inject constructor( + private val view: SettingsView, + private val strategy: CancelStrategy, + private val navigator: MainNavigator, + @Named("currentServer") private val currentServer: String, + private val userHelper: UserHelper, + private val analyticsTrackingInteractor: AnalyticsTrackingInteractor, + private val tokenRepository: TokenRepository, + private val permissions: PermissionsInteractor, + private val rocketChatClientFactory: RocketChatClientFactory, + private val saveLanguageInteractor: SaveCurrentLanguageInteractor, + getCurrentServerInteractor: GetCurrentServerInteractor, + removeAccountInteractor: RemoveAccountInteractor, + databaseManagerFactory: DatabaseManagerFactory, + connectionManagerFactory: ConnectionManagerFactory +) : CheckServerPresenter( + strategy = strategy, + factory = rocketChatClientFactory, + serverInteractor = getCurrentServerInteractor, + removeAccountInteractor = removeAccountInteractor, + tokenRepository = tokenRepository, + dbManagerFactory = databaseManagerFactory, + managerFactory = connectionManagerFactory, + tokenView = view, + navigator = navigator +) { + private val token = tokenRepository.get(currentServer) + + fun setupView() { + launchUI(strategy) { + try { + val serverInfo = retryIO(description = "serverInfo", times = 5) { + rocketChatClientFactory.get(currentServer).serverInfo() + } + + val me = retryIO(description = "serverInfo", times = 5) { + rocketChatClientFactory.get(currentServer).me() + } + + userHelper.user()?.let { user -> + view.setupSettingsView( + currentServer.avatarUrl(me.username!!, token?.userId, token?.authToken), + userHelper.displayName(user) ?: me.username ?: "", + me.status.toString(), + permissions.isAdministrationEnabled(), + analyticsTrackingInteractor.get(), + true, + serverInfo.version + ) + } + } catch (exception: Exception) { + Timber.d(exception, "Error getting server info") + exception.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + } + } + } + + fun enableAnalyticsTracking(isEnabled: Boolean) { + analyticsTrackingInteractor.save(isEnabled) + } + + fun logout() { + setupConnectionInfo(currentServer) + super.logout(null) // TODO null? + } + + fun deleteAccount(password: String) { + launchUI(strategy) { + view.showLoading() + try { + withContext(Dispatchers.Default) { + // REMARK: Backend API is only working with a lowercase hash. + // https://github.com/RocketChat/Rocket.Chat/issues/12573 + retryIO { + rocketChatClientFactory.get(currentServer) + .deleteOwnAccount(password.gethash().toHex().toLowerCase()) + } + setupConnectionInfo(currentServer) + logout(null) + } + } catch (exception: Exception) { + exception.message?.let { + view.showMessage(it) + }.ifNull { + view.showGenericErrorMessage() + } + } finally { + view.hideLoading() + } + } + } + + fun getCurrentLocale(context: Context): Locale { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + context.resources.configuration.locales.get(0) + } else { + context.resources.configuration.locale + } + } + + fun saveLocale(language: String, country: String? = null) { + saveLanguageInteractor.save(language, country) + } + + fun toProfile() = navigator.toProfile() + + fun toAdmin() = tokenRepository.get(currentServer)?.let { + navigator.toAdminPanel(currentServer.adminPanelUrl(), it.authToken) + } + + fun toLicense(licenseUrl: String, licenseTitle: String) = + navigator.toLicense(licenseUrl, licenseTitle) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/settings/presentation/SettingsView.kt b/app/src/main/java/chat/rocket/android/settings/presentation/SettingsView.kt index a62bb4f884..3fc3b8e413 100644 --- a/app/src/main/java/chat/rocket/android/settings/presentation/SettingsView.kt +++ b/app/src/main/java/chat/rocket/android/settings/presentation/SettingsView.kt @@ -1,3 +1,29 @@ package chat.rocket.android.settings.presentation -interface SettingsView +import chat.rocket.android.core.behaviours.LoadingView +import chat.rocket.android.core.behaviours.MessageView +import chat.rocket.android.server.presentation.TokenView + +interface SettingsView : TokenView, LoadingView, MessageView { + + /** + * Setups the settings view. + * + * @param avatar The user avatar. + * @param displayName The user display name. + * @param status The user status. + * @param isAdministrationEnabled True if the administration is enabled, false otherwise. + * @param isAnalyticsTrackingEnabled True if the analytics tracking is enabled, false otherwise. + * @param isDeleteAccountEnabled True if the delete account is enabled, false otherwise. + * @param serverVersion The version of the current logged in server. + */ + fun setupSettingsView( + avatar: String, + displayName: String, + status: String, + isAdministrationEnabled: Boolean, + isAnalyticsTrackingEnabled: Boolean, + isDeleteAccountEnabled: Boolean, + serverVersion: String + ) +} diff --git a/app/src/main/java/chat/rocket/android/settings/ui/SettingsFragment.kt b/app/src/main/java/chat/rocket/android/settings/ui/SettingsFragment.kt index 9daab340c1..ad66900c9d 100644 --- a/app/src/main/java/chat/rocket/android/settings/ui/SettingsFragment.kt +++ b/app/src/main/java/chat/rocket/android/settings/ui/SettingsFragment.kt @@ -7,35 +7,56 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.AdapterView +import android.widget.EditText +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toUri +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import chat.rocket.android.BuildConfig import chat.rocket.android.R -import chat.rocket.android.about.ui.AboutFragment -import chat.rocket.android.about.ui.TAG_ABOUT_FRAGMENT import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.ScreenViewEvent +import chat.rocket.android.core.behaviours.AppLanguageView import chat.rocket.android.helper.TextHelper.getDeviceAndAppInformation -import chat.rocket.android.main.ui.MainActivity -import chat.rocket.android.preferences.ui.PreferencesFragment -import chat.rocket.android.preferences.ui.TAG_PREFERENCES_FRAGMENT -import chat.rocket.android.settings.password.ui.PasswordActivity +import chat.rocket.android.settings.presentation.SettingsPresenter import chat.rocket.android.settings.presentation.SettingsView -import chat.rocket.android.util.extensions.addFragmentBackStack import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.showToast -import chat.rocket.android.webview.ui.webViewIntent +import chat.rocket.android.util.invalidateFirebaseToken import dagger.android.support.AndroidSupportInjection +import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.fragment_settings.* import timber.log.Timber import javax.inject.Inject internal const val TAG_SETTINGS_FRAGMENT = "SettingsFragment" -class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListener { +fun newInstance(): Fragment = SettingsFragment() + +class SettingsFragment : Fragment(), SettingsView, AppLanguageView { @Inject lateinit var analyticsManager: AnalyticsManager + @Inject + lateinit var presenter: SettingsPresenter + private val locales = arrayListOf( + "en", + "ar", + "de", + "es", + "fa", + "fr", + "hi,IN", + "it", + "ja", + "pt,BR", + "pt,PT", + "ru,RU", + "tr", + "uk", + "zh,CN", + "zh,TW" + ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -51,86 +72,104 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar() - setupListView() + presenter.setupView() analyticsManager.logScreenView(ScreenViewEvent.Settings) } - override fun onResume() { - // FIXME - gambiarra ahead. will fix when moving to new androidx Navigation - (activity as? MainActivity)?.setupNavigationView() - super.onResume() - } + override fun setupSettingsView( + avatar: String, + displayName: String, + status: String, + isAdministrationEnabled: Boolean, + isAnalyticsTrackingEnabled: Boolean, + isDeleteAccountEnabled: Boolean, + serverVersion: String + ) { + image_avatar.setImageURI(avatar) - override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - when (parent?.getItemAtPosition(position).toString()) { - resources.getStringArray(R.array.settings_actions)[0] -> { - (activity as AppCompatActivity).addFragmentBackStack( - TAG_PREFERENCES_FRAGMENT, - R.id.fragment_container - ) { - PreferencesFragment.newInstance() - } - } + text_display_name.text = displayName - resources.getStringArray(R.array.settings_actions)[1] -> - activity?.startActivity(Intent(activity, PasswordActivity::class.java)) + text_status.text = status - // TODO (https://github.com/RocketChat/Rocket.Chat.Android/pull/1918) - resources.getStringArray(R.array.settings_actions)[2] -> showToast("Coming soon") + profile_container.setOnClickListener { presenter.toProfile() } - resources.getStringArray(R.array.settings_actions)[3] -> shareApp() + text_contact_us.setOnClickListener { contactSupport() } - resources.getStringArray(R.array.settings_actions)[4] -> showAppOnStore() + text_language.setOnClickListener { changeLanguage() } - resources.getStringArray(R.array.settings_actions)[5] -> contactSupport() + text_review_this_app.setOnClickListener { showAppOnStore() } - resources.getStringArray(R.array.settings_actions)[6] -> activity?.startActivity( - context?.webViewIntent( - getString(R.string.license_url), - getString(R.string.title_licence) - ) - ) + text_share_this_app.setOnClickListener { shareApp() } - resources.getStringArray(R.array.settings_actions)[7] -> { - (activity as AppCompatActivity).addFragmentBackStack( - TAG_ABOUT_FRAGMENT, - R.id.fragment_container - ) { - AboutFragment.newInstance() - } + text_license.setOnClickListener { + presenter.toLicense(getString(R.string.license_url), getString(R.string.title_license)) + } + + text_app_version.text = getString(R.string.msg_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) + + text_server_version.text = getString(R.string.msg_server_version, serverVersion) + + text_logout.setOnClickListener { showLogoutDialog() } + + with(text_administration) { + isVisible = isAdministrationEnabled + setOnClickListener { presenter.toAdmin() } + } + + with(switch_crash_report) { + isChecked = isAnalyticsTrackingEnabled + isEnabled = BuildConfig.FLAVOR == "play" + setOnCheckedChangeListener { _, isChecked -> + presenter.enableAnalyticsTracking(isChecked) } } - } - 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())) + with(text_delete_account) { + isVisible = isDeleteAccountEnabled + setOnClickListener { showDeleteAccountDialog() } } } - private fun setupListView() { - settings_list.onItemClickListener = this + override fun updateLanguage(language: String, country: String?) { + presenter.saveLocale(language, country) + activity?.recreate() } - private fun setupToolbar() { - (activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_settings) + override fun invalidateToken(token: String) = invalidateFirebaseToken(token) + + override fun showLoading() { + view_loading.isVisible = true } - private fun shareApp() { - with(Intent(Intent.ACTION_SEND)) { - type = "text/plain" - putExtra(Intent.EXTRA_SUBJECT, getString(R.string.msg_check_this_out)) - putExtra(Intent.EXTRA_TEXT, getString(R.string.play_store_link)) - startActivity(Intent.createChooser(this, getString(R.string.msg_share_using))) + override fun hideLoading() { + view_loading.isVisible = false + } + + override fun showMessage(resId: Int) { + showToast(resId) + } + + override fun showMessage(message: String) { + showToast(message) + } + + override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) + + private fun setupToolbar() { + with((activity as AppCompatActivity)) { + with(toolbar) { + setSupportActionBar(this) + title = getString(R.string.title_settings) + setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) + setNavigationOnClickListener { activity?.onBackPressed() } + } } } private fun contactSupport() { val uriText = "mailto:${"support@rocket.chat"}" + - "?subject=" + Uri.encode(getString(R.string.msg_android_app_support)) + - "&body=" + Uri.encode(getDeviceAndAppInformation()) + "?subject=" + Uri.encode(getString(R.string.msg_android_app_support)) + + "&body=" + Uri.encode(getDeviceAndAppInformation()) with(Intent(Intent.ACTION_SENDTO)) { data = uriText.toUri() @@ -142,7 +181,79 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen } } - companion object { - fun newInstance() = SettingsFragment() + private fun changeLanguage() { + context?.let { + val selectedLocale = presenter.getCurrentLocale(it) + var localeIndex = -1 + locales.forEachIndexed { index, locale -> + val array = locale.split(",") + val language = array[0] + val country = if (array.size > 1) array[1] else "" + // If language and country are specified, return the respective locale, else return + // the first locale found if the language is as specified regardless of the country. + if (language == selectedLocale.language) { + if (country == selectedLocale.country) { + localeIndex = index + return@forEachIndexed + } else if (localeIndex == -1) { + localeIndex = index + } + } + } + AlertDialog.Builder(it) + .setTitle(R.string.title_choose_language) + .setSingleChoiceItems( + resources.getStringArray(R.array.languages), localeIndex + ) { dialog, option -> + val array = locales[option].split(",") + if (array.size > 1) { + updateLanguage(array[0], array[1]) + } else { + updateLanguage(array[0]) + } + dialog.dismiss() + } + .create() + .show() + } + } + + 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())) + } + } + + private fun shareApp() { + with(Intent(Intent.ACTION_SEND)) { + type = "text/plain" + putExtra(Intent.EXTRA_SUBJECT, getString(R.string.msg_check_this_out)) + putExtra(Intent.EXTRA_TEXT, getString(R.string.play_store_link)) + startActivity(Intent.createChooser(this, getString(R.string.msg_share_using))) + } + } + + private fun showLogoutDialog() { + context?.let { + val builder = AlertDialog.Builder(it) + builder.setTitle(R.string.title_are_you_sure) + .setPositiveButton(R.string.action_logout) { _, _ -> presenter.logout() } + .setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() } + .create() + .show() + } + } + + private fun showDeleteAccountDialog() { + context?.let { + AlertDialog.Builder(it) + .setView(LayoutInflater.from(it).inflate(R.layout.dialog_delete_account, null)) + .setPositiveButton(R.string.msg_delete_account) { _, _ -> + presenter.deleteAccount(EditText(context).text.toString()) + }.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }.create() + .show() + } } -} +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/sortingandgrouping/di/SortingAndGroupingBottomSheetFragmentModule.kt b/app/src/main/java/chat/rocket/android/sortingandgrouping/di/SortingAndGroupingBottomSheetFragmentModule.kt new file mode 100644 index 0000000000..fced1df63e --- /dev/null +++ b/app/src/main/java/chat/rocket/android/sortingandgrouping/di/SortingAndGroupingBottomSheetFragmentModule.kt @@ -0,0 +1,16 @@ +package chat.rocket.android.sortingandgrouping.di + +import chat.rocket.android.dagger.scope.PerFragment +import chat.rocket.android.sortingandgrouping.presentation.SortingAndGroupingView +import chat.rocket.android.sortingandgrouping.ui.SortingAndGroupingBottomSheetFragment +import dagger.Module +import dagger.Provides + +@Module +class SortingAndGroupingBottomSheetFragmentModule { + + @Provides + @PerFragment + fun sortingAndGroupingView(frag: SortingAndGroupingBottomSheetFragment): SortingAndGroupingView = + frag +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/sortingandgrouping/di/SortingAndGroupingBottomSheetFragmentProvider.kt b/app/src/main/java/chat/rocket/android/sortingandgrouping/di/SortingAndGroupingBottomSheetFragmentProvider.kt new file mode 100644 index 0000000000..783812cef3 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/sortingandgrouping/di/SortingAndGroupingBottomSheetFragmentProvider.kt @@ -0,0 +1,15 @@ +package chat.rocket.android.sortingandgrouping.di + +import chat.rocket.android.dagger.scope.PerFragment +import chat.rocket.android.sortingandgrouping.ui.SortingAndGroupingBottomSheetFragment +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class SortingAndGroupingBottomSheetFragmentProvider { + + @ContributesAndroidInjector(modules = [SortingAndGroupingBottomSheetFragmentModule::class]) + @PerFragment + abstract fun provideSortingAndGroupingBottomSheetFragment(): SortingAndGroupingBottomSheetFragment + +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/sortingandgrouping/presentation/SortingAndGroupingPresenter.kt b/app/src/main/java/chat/rocket/android/sortingandgrouping/presentation/SortingAndGroupingPresenter.kt new file mode 100644 index 0000000000..1dadf303b8 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/sortingandgrouping/presentation/SortingAndGroupingPresenter.kt @@ -0,0 +1,38 @@ +package chat.rocket.android.sortingandgrouping.presentation + +import chat.rocket.android.server.domain.SortingAndGroupingInteractor +import javax.inject.Inject +import javax.inject.Named + +class SortingAndGroupingPresenter @Inject constructor( + private val view: SortingAndGroupingView, + private val sortingAndGroupingInteractor: SortingAndGroupingInteractor, + @Named("currentServer") private val currentServerUrl: String +) { + + fun getSortingAndGroupingPreferences() { + with(sortingAndGroupingInteractor) { + view.showSortingAndGroupingPreferences( + getSortByName(currentServerUrl), + getUnreadOnTop(currentServerUrl), + getGroupByType(currentServerUrl), + getGroupByFavorites(currentServerUrl) + ) + } + } + + fun saveSortingAndGroupingPreferences( + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) { + sortingAndGroupingInteractor.save( + currentServerUrl, + isSortByName, + isUnreadOnTop, + isGroupByType, + isGroupByFavorites + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/sortingandgrouping/presentation/SortingAndGroupingView.kt b/app/src/main/java/chat/rocket/android/sortingandgrouping/presentation/SortingAndGroupingView.kt new file mode 100644 index 0000000000..c8627e22e7 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/sortingandgrouping/presentation/SortingAndGroupingView.kt @@ -0,0 +1,19 @@ +package chat.rocket.android.sortingandgrouping.presentation + +interface SortingAndGroupingView { + + /** + * Shows the sorting and grouping preferences for the current logged in server. + * + * @param isSortByName True if sorting by name, false otherwise. + * @param isUnreadOnTop True if grouping by unread on top, false otherwise. + * @param isGroupByType True if grouping by type , false otherwise. + * @param isGroupByFavorites True if grouping by favorites, false otherwise. + */ + fun showSortingAndGroupingPreferences( + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/sortingandgrouping/ui/SortingAndGroupingBottomSheetFragment.kt b/app/src/main/java/chat/rocket/android/sortingandgrouping/ui/SortingAndGroupingBottomSheetFragment.kt new file mode 100644 index 0000000000..4ab319310f --- /dev/null +++ b/app/src/main/java/chat/rocket/android/sortingandgrouping/ui/SortingAndGroupingBottomSheetFragment.kt @@ -0,0 +1,173 @@ +package chat.rocket.android.sortingandgrouping.ui + +import DrawableHelper +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.DrawableRes +import chat.rocket.android.R +import chat.rocket.android.chatrooms.ui.ChatRoomsFragment +import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT +import chat.rocket.android.sortingandgrouping.presentation.SortingAndGroupingPresenter +import chat.rocket.android.sortingandgrouping.presentation.SortingAndGroupingView +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dagger.android.support.AndroidSupportInjection +import kotlinx.android.synthetic.main.bottom_sheet_fragment_sort_by.* +import javax.inject.Inject + +const val TAG = "SortingAndGroupingBottomSheetFragment" + +class SortingAndGroupingBottomSheetFragment : BottomSheetDialogFragment(), SortingAndGroupingView { + @Inject + lateinit var presenter: SortingAndGroupingPresenter + private var isSortByName = false + private var isUnreadOnTop = false + private var isGroupByType = false + private var isGroupByFavorites = false + private val chatRoomFragment by lazy { + activity?.supportFragmentManager?.findFragmentByTag(TAG_CHAT_ROOMS_FRAGMENT) as ChatRoomsFragment + } + private val filterDrawable by lazy { R.drawable.ic_filter_20dp } + private val activityDrawable by lazy { R.drawable.ic_activity_20dp } + private val unreadOnTopDrawable by lazy { R.drawable.ic_unread_20dp } + private val groupByTypeDrawable by lazy { R.drawable.ic_group_by_type_20dp } + private val groupByFavoritesDrawable by lazy { R.drawable.ic_favorites_20dp } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + AndroidSupportInjection.inject(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = + inflater.inflate(R.layout.bottom_sheet_fragment_sort_by, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + presenter.getSortingAndGroupingPreferences() + setupListeners() + } + + override fun onCancel(dialog: DialogInterface?) { + super.onCancel(dialog) + presenter.saveSortingAndGroupingPreferences( + isSortByName, + isUnreadOnTop, + isGroupByType, + isGroupByFavorites + ) + } + + override fun showSortingAndGroupingPreferences( + isSortByName: Boolean, + isUnreadOnTop: Boolean, + isGroupByType: Boolean, + isGroupByFavorites: Boolean + ) { + this.isSortByName = isSortByName + this.isUnreadOnTop = isUnreadOnTop + this.isGroupByType = isGroupByType + this.isGroupByFavorites = isGroupByFavorites + + if (isSortByName) { + changeSortByTitle(getString(R.string.msg_sort_by_name)) + checkSelection(text_name, filterDrawable) + } else { + changeSortByTitle(getString(R.string.msg_sort_by_activity)) + checkSelection(text_activity, activityDrawable) + } + + if (isUnreadOnTop) checkSelection(text_unread_on_top, unreadOnTopDrawable) + if (isGroupByType) checkSelection(text_group_by_type, groupByTypeDrawable) + if (isGroupByFavorites) checkSelection(text_group_by_favorites, groupByFavoritesDrawable) + } + + private fun setupListeners() { + text_name.setOnClickListener { + changeSortByTitle(getString(R.string.msg_sort_by_name)) + checkSelection(text_name, filterDrawable) + uncheckSelection(text_activity, activityDrawable) + isSortByName = true + sortChatRoomsList() + } + + text_activity.setOnClickListener { + changeSortByTitle(getString(R.string.msg_sort_by_activity)) + checkSelection(text_activity, activityDrawable) + uncheckSelection(text_name, filterDrawable) + isSortByName = false + sortChatRoomsList() + } + + text_unread_on_top.setOnClickListener { + isUnreadOnTop = if (isUnreadOnTop) { + uncheckSelection(text_unread_on_top, unreadOnTopDrawable) + false + } else { + checkSelection(text_unread_on_top, unreadOnTopDrawable) + true + } + sortChatRoomsList() + } + + text_group_by_type.setOnClickListener { + isGroupByType = if (isGroupByType) { + uncheckSelection(text_group_by_type, groupByTypeDrawable) + false + } else { + checkSelection(text_group_by_type, groupByTypeDrawable) + true + } + sortChatRoomsList() + } + + text_group_by_favorites.setOnClickListener { + isGroupByFavorites = if (isGroupByFavorites) { + uncheckSelection(text_group_by_favorites, groupByFavoritesDrawable) + false + } else { + checkSelection(text_group_by_favorites, groupByFavoritesDrawable) + true + } + sortChatRoomsList() + } + } + + private fun changeSortByTitle(text: String) { + text_sort_by.text = getString(R.string.msg_sort_by_placeholder, text.toLowerCase()) + } + + private fun checkSelection(textView: TextView, @DrawableRes startDrawable: Int) { + context?.let { + DrawableHelper.compoundStartAndEndDrawable( + textView, + DrawableHelper.getDrawableFromId(startDrawable, it), + DrawableHelper.getDrawableFromId(R.drawable.ic_check, it) + ) + } + } + + private fun uncheckSelection(textView: TextView, @DrawableRes startDrawable: Int) { + context?.let { + DrawableHelper.compoundStartDrawable( + textView, + DrawableHelper.getDrawableFromId(startDrawable, it) + ) + } + } + + private fun sortChatRoomsList() { + chatRoomFragment.sortChatRoomsList( + isSortByName, + isUnreadOnTop, + isGroupByType, + isGroupByFavorites + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsPresenter.kt b/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsPresenter.kt index c6aa059b4b..48d2776739 100644 --- a/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsPresenter.kt +++ b/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsPresenter.kt @@ -8,12 +8,14 @@ import chat.rocket.android.db.model.ChatRoomEntity import chat.rocket.android.db.model.UserEntity import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.GetSettingsInteractor +import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.isJitsiEnabled -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.retryIO import chat.rocket.common.model.RoomType +import chat.rocket.common.model.Token import chat.rocket.common.util.ifNull import chat.rocket.core.internal.rest.createDirectMessage import kotlinx.coroutines.Dispatchers @@ -26,6 +28,7 @@ class UserDetailsPresenter @Inject constructor( private val dbManager: DatabaseManager, private val strategy: CancelStrategy, private val navigator: ChatRoomNavigator, + tokenRepository: TokenRepository, settingsInteractor: GetSettingsInteractor, serverInteractor: CurrentServerRepository, factory: ConnectionManagerFactory @@ -35,6 +38,7 @@ class UserDetailsPresenter @Inject constructor( private val client = manager.client private val interactor = FetchChatRoomsInteractor(client, dbManager) private val settings = settingsInteractor.get(currentServer) + private val token = tokenRepository.get(currentServer) private lateinit var userEntity: UserEntity fun loadUserDetails(userId: String) { @@ -43,25 +47,21 @@ class UserDetailsPresenter @Inject constructor( view.showLoading() dbManager.getUser(userId)?.let { userEntity = it - val avatarUrl = - userEntity.username?.let { username -> currentServer.avatarUrl(avatar = username) } + val avatarUrl = userEntity.username?.let { username -> + currentServer.avatarUrl(username, token?.userId, token?.authToken) + } val username = userEntity.username val name = userEntity.name - val utcOffset = - userEntity.utcOffset // TODO Convert UTC and display like the mockup + val utcOffset = userEntity.utcOffset // FIXME Convert UTC - if (avatarUrl != null && username != null && name != null && utcOffset != null) { - view.showUserDetailsAndActions( - avatarUrl = avatarUrl, - name = name, - username = username, - status = userEntity.status, - utcOffset = utcOffset.toString(), - isVideoCallAllowed = settings.isJitsiEnabled() - ) - } else { - throw Exception() - } + view.showUserDetailsAndActions( + avatarUrl = avatarUrl, + name = name, + username = username, + status = userEntity.status, + utcOffset = utcOffset.toString(), + isVideoCallAllowed = settings.isJitsiEnabled() + ) } } catch (ex: Exception) { Timber.e(ex) @@ -89,6 +89,7 @@ class UserDetailsPresenter @Inject constructor( val chatRoomEntity = ChatRoomEntity( id = directMessage.id, name = userEntity.username ?: userEntity.name.orEmpty(), + parentId = null, description = null, type = RoomType.DIRECT_MESSAGE, fullname = userEntity.name, diff --git a/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsView.kt b/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsView.kt index a1cb70b6b7..aa8187abec 100644 --- a/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsView.kt +++ b/app/src/main/java/chat/rocket/android/userdetails/presentation/UserDetailsView.kt @@ -16,11 +16,11 @@ interface UserDetailsView : LoadingView, MessageView { * @param isVideoCallAllowed True if the video call is allowed, false otherwise. */ fun showUserDetailsAndActions( - avatarUrl: String, - name: String, - username: String, - status: String, - utcOffset: String, + avatarUrl: String?, + name: String?, + username: String?, + status: String?, + utcOffset: String?, isVideoCallAllowed: Boolean ) } diff --git a/app/src/main/java/chat/rocket/android/userdetails/ui/UserDetailsFragment.kt b/app/src/main/java/chat/rocket/android/userdetails/ui/UserDetailsFragment.kt index 7ec9463449..bfd829b88a 100644 --- a/app/src/main/java/chat/rocket/android/userdetails/ui/UserDetailsFragment.kt +++ b/app/src/main/java/chat/rocket/android/userdetails/ui/UserDetailsFragment.kt @@ -20,6 +20,7 @@ import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.ui import com.bumptech.glide.Glide import com.bumptech.glide.load.MultiTransformation +import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions @@ -78,14 +79,17 @@ class UserDetailsFragment : Fragment(), UserDetailsView { } override fun showUserDetailsAndActions( - avatarUrl: String, - name: String, - username: String, - status: String, - utcOffset: String, + avatarUrl: String?, + name: String?, + username: String?, + status: String?, + utcOffset: String?, isVideoCallAllowed: Boolean ) { - val requestBuilder = Glide.with(this).load(avatarUrl) + val requestBuilder = Glide.with(this) + .load(avatarUrl) + .apply(RequestOptions.skipMemoryCacheOf(true)) + .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.NONE)) requestBuilder.apply( RequestOptions.bitmapTransform(MultiTransformation(BlurTransformation(), CenterCrop())) @@ -94,19 +98,21 @@ class UserDetailsFragment : Fragment(), UserDetailsView { requestBuilder.apply(RequestOptions.bitmapTransform(RoundedCorners(14))) .into(image_avatar) - text_name.text = name - text_username.text = username - text_description_status.text = status.substring(0, 1).toUpperCase() + status.substring(1) - text_description_timezone.text = utcOffset + text_name.text = name ?: getString(R.string.msg_unknown) + text_username.text = username ?: getString(R.string.msg_unknown) - // We should also setup the user details listeners. - text_message.setOnClickListener { presenter.createDirectMessage(username) } + text_description_status.text = status?.capitalize() ?: getString(R.string.msg_unknown) + + text_description_timezone.text = utcOffset ?: getString(R.string.msg_unknown) + + text_video_call.isVisible = isVideoCallAllowed - if (isVideoCallAllowed) { - text_video_call.isVisible = true - text_video_call.setOnClickListener { presenter.toVideoConference(username) } - } else { - text_video_call.isVisible = false + // We should also setup the user details listeners. + username?.run { + text_message.setOnClickListener { presenter.createDirectMessage(this) } + if (isVideoCallAllowed) { + text_video_call.setOnClickListener { presenter.toVideoConference(this) } + } } } @@ -147,4 +153,4 @@ class UserDetailsFragment : Fragment(), UserDetailsView { private fun setupListeners() { image_arrow_back.setOnClickListener { activity?.onBackPressed() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/chat/rocket/android/util/extensions/RocketChatClient.kt b/app/src/main/java/chat/rocket/android/util/extensions/RocketChatClient.kt index 41ba306134..69654be5e8 100644 --- a/app/src/main/java/chat/rocket/android/util/extensions/RocketChatClient.kt +++ b/app/src/main/java/chat/rocket/android/util/extensions/RocketChatClient.kt @@ -2,7 +2,7 @@ package chat.rocket.android.util.extensions import chat.rocket.android.db.model.MessageEntity import chat.rocket.android.server.domain.model.Account -import chat.rocket.android.server.infraestructure.RocketChatClientFactory +import chat.rocket.android.server.infrastructure.RocketChatClientFactory import chat.rocket.android.util.retryIO import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.model.Message @@ -19,7 +19,7 @@ suspend fun RocketChatClientFactory.registerPushToken( accounts.forEach { account -> try { retryIO(description = "register push token: ${account.serverUrl}") { - create(account.serverUrl).registerPushToken(token) + get(account.serverUrl).registerPushToken(token) } } catch (ex: Exception) { Timber.d(ex, "Error registering Push token for ${account.serverUrl}") diff --git a/app/src/main/java/chat/rocket/android/util/extensions/String.kt b/app/src/main/java/chat/rocket/android/util/extensions/String.kt index 9c950da7f8..78b2d9c846 100644 --- a/app/src/main/java/chat/rocket/android/util/extensions/String.kt +++ b/app/src/main/java/chat/rocket/android/util/extensions/String.kt @@ -21,13 +21,15 @@ fun String.sanitize(): String { fun String.avatarUrl( avatar: String, + userId: String?, + token: String?, isGroupOrChannel: Boolean = false, format: String = "jpeg" ): String { return if (isGroupOrChannel) { - "${removeTrailingSlash()}/avatar/%23${avatar.removeTrailingSlash()}?format=$format" + "${removeTrailingSlash()}/avatar/%23${avatar.removeTrailingSlash()}?format=$format&rc_uid=$userId&rc_token=$token" } else { - "${removeTrailingSlash()}/avatar/${avatar.removeTrailingSlash()}?format=$format" + "${removeTrailingSlash()}/avatar/${avatar.removeTrailingSlash()}?format=$format&rc_uid=$userId&rc_token=$token" } } @@ -75,6 +77,8 @@ fun String.lowercaseUrl(): String? = HttpUrl.parse(this)?.run { fun String?.isNotNullNorEmpty(): Boolean = this != null && this.isNotEmpty() +fun String?.isNotNullNorBlank(): Boolean = this != null && this.isNotBlank() + inline fun String?.ifNotNullNotEmpty(block: (String) -> Unit) { if (this != null && this.isNotEmpty()) { block(this) diff --git a/app/src/main/java/chat/rocket/android/videoconference/presenter/VideoConferencePresenter.kt b/app/src/main/java/chat/rocket/android/videoconference/presenter/VideoConferencePresenter.kt index ce9233f651..b9d8aecfb5 100644 --- a/app/src/main/java/chat/rocket/android/videoconference/presenter/VideoConferencePresenter.kt +++ b/app/src/main/java/chat/rocket/android/videoconference/presenter/VideoConferencePresenter.kt @@ -6,7 +6,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.helper.JitsiHelper import chat.rocket.android.helper.UserHelper import chat.rocket.android.server.domain.* -import chat.rocket.android.server.infraestructure.ConnectionManagerFactory +import chat.rocket.android.server.infrastructure.ConnectionManagerFactory import chat.rocket.android.util.extension.launchUI import chat.rocket.common.model.RoomType import chat.rocket.common.model.roomTypeOf diff --git a/app/src/main/java/chat/rocket/android/videoconference/ui/VideoConferenceActivity.kt b/app/src/main/java/chat/rocket/android/videoconference/ui/VideoConferenceActivity.kt index 6fed291e94..94e9a5704b 100644 --- a/app/src/main/java/chat/rocket/android/videoconference/ui/VideoConferenceActivity.kt +++ b/app/src/main/java/chat/rocket/android/videoconference/ui/VideoConferenceActivity.kt @@ -3,14 +3,15 @@ package chat.rocket.android.videoconference.ui import android.content.Context import android.content.Intent import android.os.Bundle -import androidx.core.os.bundleOf import chat.rocket.android.videoconference.presenter.JitsiVideoConferenceView import chat.rocket.android.videoconference.presenter.VideoConferencePresenter import dagger.android.AndroidInjection import org.jitsi.meet.sdk.JitsiMeetActivity +import org.jitsi.meet.sdk.JitsiMeetConferenceOptions import org.jitsi.meet.sdk.JitsiMeetView import org.jitsi.meet.sdk.JitsiMeetViewListener import timber.log.Timber +import java.net.URL import javax.inject.Inject fun Context.videoConferenceIntent(chatRoomId: String, chatRoomType: String): Intent = @@ -23,8 +24,7 @@ private const val INTENT_CHAT_ROOM_TYPE = "chat_room_type" class VideoConferenceActivity : JitsiMeetActivity(), JitsiVideoConferenceView, JitsiMeetViewListener { - @Inject - lateinit var presenter: VideoConferencePresenter + @Inject lateinit var presenter: VideoConferencePresenter private lateinit var chatRoomId: String private lateinit var chatRoomType: String private var view: JitsiMeetView? = null @@ -34,9 +34,7 @@ class VideoConferenceActivity : JitsiMeetActivity(), JitsiVideoConferenceView, super.onCreate(savedInstanceState) chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) - requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" } chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE) - requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" } view = JitsiMeetView(this) view?.listener = this @@ -52,34 +50,24 @@ class VideoConferenceActivity : JitsiMeetActivity(), JitsiVideoConferenceView, override fun onConferenceJoined(map: MutableMap?) = logJitsiMeetViewState("Joined video conferencing", map) - override fun onConferenceWillLeave(map: MutableMap?) = - logJitsiMeetViewState("Leaving video conferencing", map) - - override fun onConferenceLeft(map: MutableMap?) { - logJitsiMeetViewState("Left video conferencing", map) + override fun onConferenceTerminated(map: MutableMap?) { + map?.let { + if (it.containsKey("error")) { + logJitsiMeetViewState("Terminated video conferencing with error", map) + } else { + logJitsiMeetViewState("Terminated video conferencing", map) + } + } finishJitsiVideoConference() } - override fun onLoadConfigError(map: MutableMap?) = - logJitsiMeetViewState("Error loading video conference config", map) - - override fun onConferenceFailed(map: MutableMap?) = - logJitsiMeetViewState("Video conference failed", map) - override fun startJitsiVideoConference(url: String, name: String?) { - view?.loadURLObject( - bundleOf( - "config" to bundleOf( - "startWithAudioMuted" to true, - "startWithVideoMuted" to true - ), - "context" to bundleOf( - "user" to bundleOf("name" to name), - "iss" to "rocketchat-android" - ), - "url" to url - ) - ) + JitsiMeetConferenceOptions.Builder() + .setAudioMuted(true) + .setVideoMuted(true) + .setServerURL(URL(url)) + .setAudioOnly(false) + .build().let { view?.join(it) } } override fun finishJitsiVideoConference() { diff --git a/app/src/main/java/chat/rocket/android/webview/adminpanel/ui/AdminPanelWebViewFragment.kt b/app/src/main/java/chat/rocket/android/webview/adminpanel/ui/AdminPanelWebViewFragment.kt index 268182fd7f..a94408177b 100644 --- a/app/src/main/java/chat/rocket/android/webview/adminpanel/ui/AdminPanelWebViewFragment.kt +++ b/app/src/main/java/chat/rocket/android/webview/adminpanel/ui/AdminPanelWebViewFragment.kt @@ -16,9 +16,17 @@ import dagger.android.support.DaggerFragment import kotlinx.android.synthetic.main.fragment_admin_panel_web_view.* import javax.inject.Inject +internal const val TAG_ADMIN_PANEL_WEB_VIEW_FRAGMENT = "AdminPanelWebViewFragment" private const val BUNDLE_WEB_PAGE_URL = "web_page_url" private const val BUNDLE_USER_TOKEN = "user_token" +fun newInstance(webPageUrl: String, userToken: String) = AdminPanelWebViewFragment().apply { + arguments = Bundle(2).apply { + putString(BUNDLE_WEB_PAGE_URL, webPageUrl) + putString(BUNDLE_USER_TOKEN, userToken) + } +} + class AdminPanelWebViewFragment : DaggerFragment() { private lateinit var webPageUrl: String private lateinit var userToken: String @@ -30,7 +38,8 @@ class AdminPanelWebViewFragment : DaggerFragment() { arguments?.run { webPageUrl = getString(BUNDLE_WEB_PAGE_URL, "") userToken = getString(BUNDLE_USER_TOKEN, "") - } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } + } + ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } } override fun onCreateView( @@ -49,7 +58,7 @@ class AdminPanelWebViewFragment : DaggerFragment() { private fun setupToolbar() { (activity as AppCompatActivity?)?.supportActionBar?.title = - getString(R.string.title_admin_panel) + getString(R.string.title_admin_panel) } @SuppressLint("SetJavaScriptEnabled") @@ -70,13 +79,4 @@ class AdminPanelWebViewFragment : DaggerFragment() { } web_view.loadUrl(webPageUrl) } - - companion object { - fun newInstance(webPageUrl: String, userToken: String) = AdminPanelWebViewFragment().apply { - arguments = Bundle(2).apply { - putString(BUNDLE_WEB_PAGE_URL, webPageUrl) - putString(BUNDLE_USER_TOKEN, userToken) - } - } - } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_empty_user_avatar.xml b/app/src/main/res/drawable/bg_empty_user_avatar.xml index 280bebb878..722144a73c 100644 --- a/app/src/main/res/drawable/bg_empty_user_avatar.xml +++ b/app/src/main/res/drawable/bg_empty_user_avatar.xml @@ -1,6 +1,15 @@ - - - - + + + + diff --git a/app/src/main/res/drawable/black_gradient.xml b/app/src/main/res/drawable/black_gradient.xml deleted file mode 100644 index 98a5ebacf1..0000000000 --- a/app/src/main/res/drawable/black_gradient.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_action_message_info_outline_24dp.xml b/app/src/main/res/drawable/ic_action_message_info_outline_24dp.xml index 45176fb957..91752eab05 100644 --- a/app/src/main/res/drawable/ic_action_message_info_outline_24dp.xml +++ b/app/src/main/res/drawable/ic_action_message_info_outline_24dp.xml @@ -1,7 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_add_new_server_48dp.xml b/app/src/main/res/drawable/ic_add_new_server_48dp.xml new file mode 100644 index 0000000000..8bf08b2bcb --- /dev/null +++ b/app/src/main/res/drawable/ic_add_new_server_48dp.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_app_name.xml b/app/src/main/res/drawable/ic_app_name.xml index 803fc4452f..8fb8062f8c 100644 --- a/app/src/main/res/drawable/ic_app_name.xml +++ b/app/src/main/res/drawable/ic_app_name.xml @@ -1,14 +1,14 @@ + android:width="832.0dp" + android:height="220.0dp" + android:viewportWidth="832.0" + android:viewportHeight="220.0"> + android:translateY="220" + android:scaleX="0.1" + android:scaleY="-0.1"> diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 0000000000..c55b3be976 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_arrow_expand_20dp.xml b/app/src/main/res/drawable/ic_arrow_expand_20dp.xml new file mode 100644 index 0000000000..b2a9234093 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_expand_20dp.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml deleted file mode 100644 index a37ceaf6ce..0000000000 --- a/app/src/main/res/drawable/ic_camera.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_camera_24dp.xml b/app/src/main/res/drawable/ic_camera_24dp.xml new file mode 100644 index 0000000000..9f80789f6c --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_chat_bubble_black_24dp.xml b/app/src/main/res/drawable/ic_chat_bubble_black_24dp.xml deleted file mode 100644 index 3eeab8286d..0000000000 --- a/app/src/main/res/drawable/ic_chat_bubble_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000000..6046365a41 --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_check_read_24dp.xml b/app/src/main/res/drawable/ic_check_read_24dp.xml index 9855900a9b..76cd1135df 100644 --- a/app/src/main/res/drawable/ic_check_read_24dp.xml +++ b/app/src/main/res/drawable/ic_check_read_24dp.xml @@ -1,9 +1,9 @@ + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_check_unread_24dp.xml b/app/src/main/res/drawable/ic_check_unread_24dp.xml index ccafe1c835..31bb59746c 100644 --- a/app/src/main/res/drawable/ic_check_unread_24dp.xml +++ b/app/src/main/res/drawable/ic_check_unread_24dp.xml @@ -1,7 +1,6 @@ - - diff --git a/app/src/main/res/drawable/ic_directory_48dp.xml b/app/src/main/res/drawable/ic_directory_48dp.xml new file mode 100644 index 0000000000..07649a1dfa --- /dev/null +++ b/app/src/main/res/drawable/ic_directory_48dp.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_discussion_20dp.xml b/app/src/main/res/drawable/ic_discussion_20dp.xml new file mode 100644 index 0000000000..2e41ab6867 --- /dev/null +++ b/app/src/main/res/drawable/ic_discussion_20dp.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_favorites_20dp.xml b/app/src/main/res/drawable/ic_favorites_20dp.xml new file mode 100644 index 0000000000..94c6c62039 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorites_20dp.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_filter_20dp.xml b/app/src/main/res/drawable/ic_filter_20dp.xml new file mode 100644 index 0000000000..f92bb571b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_filter_20dp.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_group_by_type_20dp.xml b/app/src/main/res/drawable/ic_group_by_type_20dp.xml new file mode 100644 index 0000000000..5cc3e55cbd --- /dev/null +++ b/app/src/main/res/drawable/ic_group_by_type_20dp.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_hashtag_16dp.xml b/app/src/main/res/drawable/ic_hashtag_16dp.xml new file mode 100644 index 0000000000..db18661f34 --- /dev/null +++ b/app/src/main/res/drawable/ic_hashtag_16dp.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock_12_dp.xml b/app/src/main/res/drawable/ic_lock_12_dp.xml index 277c4c2206..ddebe566d4 100644 --- a/app/src/main/res/drawable/ic_lock_12_dp.xml +++ b/app/src/main/res/drawable/ic_lock_12_dp.xml @@ -3,14 +3,14 @@ android:height="12dp" android:viewportWidth="12" android:viewportHeight="12"> - - + + diff --git a/app/src/main/res/drawable/ic_logout_black_24dp.xml b/app/src/main/res/drawable/ic_logout_black_24dp.xml deleted file mode 100644 index 30b1d97fa2..0000000000 --- a/app/src/main/res/drawable/ic_logout_black_24dp.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_menu_white_24dp.xml b/app/src/main/res/drawable/ic_menu_white_24dp.xml deleted file mode 100644 index cf37e2a393..0000000000 --- a/app/src/main/res/drawable/ic_menu_white_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_navigation_24dp.xml b/app/src/main/res/drawable/ic_navigation_24dp.xml new file mode 100644 index 0000000000..d8a8df3ac0 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigation_24dp.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_new_channel_24dp.xml b/app/src/main/res/drawable/ic_new_channel_24dp.xml new file mode 100644 index 0000000000..cecbac4add --- /dev/null +++ b/app/src/main/res/drawable/ic_new_channel_24dp.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_send_24dp.xml b/app/src/main/res/drawable/ic_send_24dp.xml index c8f25312b3..ab820d67a6 100644 --- a/app/src/main/res/drawable/ic_send_24dp.xml +++ b/app/src/main/res/drawable/ic_send_24dp.xml @@ -1,12 +1,12 @@ \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_black_24dp.xml b/app/src/main/res/drawable/ic_settings_black_24dp.xml deleted file mode 100644 index ace746c40e..0000000000 --- a/app/src/main/res/drawable/ic_settings_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml deleted file mode 100644 index 8f4129ed2a..0000000000 --- a/app/src/main/res/drawable/ic_sort.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_unread_20dp.xml b/app/src/main/res/drawable/ic_unread_20dp.xml new file mode 100644 index 0000000000..0bd793ec9d --- /dev/null +++ b/app/src/main/res/drawable/ic_unread_20dp.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_user_16dp.xml b/app/src/main/res/drawable/ic_user_16dp.xml new file mode 100644 index 0000000000..e5be22cc64 --- /dev/null +++ b/app/src/main/res/drawable/ic_user_16dp.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/image_dummy.xml b/app/src/main/res/drawable/image_dummy.xml index 6eba190b78..ec0767e390 100644 --- a/app/src/main/res/drawable/image_dummy.xml +++ b/app/src/main/res/drawable/image_dummy.xml @@ -1,16 +1,16 @@ + android:width="80dp" + android:height="45dp" + android:viewportWidth="80.0" + android:viewportHeight="45.0"> + android:pathData="M0,0h80v45h-80z" + android:strokeColor="#00000000" + android:fillColor="#C3D1DA" + android:strokeWidth="1" /> + android:pathData="M43.99,16.75C44.34,16.75 44.64,16.87 44.88,17.12C45.13,17.36 45.25,17.66 45.25,18.01L45.25,26.74C45.25,27.09 45.13,27.39 44.88,27.63C44.64,27.88 44.34,28 43.99,28L35.26,28C34.91,28 34.61,27.88 34.37,27.63C34.12,27.39 34,27.09 34,26.74L34,18.01C34,17.66 34.12,17.36 34.37,17.12C34.61,16.87 34.91,16.75 35.26,16.75L43.99,16.75ZM43.99,26.74L43.99,18.01L35.26,18.01L35.26,26.74L43.99,26.74ZM40.86,22.55L43.05,25.51L36.2,25.51L37.9,23.28L39.13,24.78L40.86,22.55Z" + android:strokeColor="#00000000" + android:fillColor="#5D8298" + android:strokeWidth="1" /> diff --git a/app/src/main/res/drawable/message_reply_button_bg.xml b/app/src/main/res/drawable/message_reply_button_bg.xml index eda4b8f2bc..85a9bdb663 100644 --- a/app/src/main/res/drawable/message_reply_button_bg.xml +++ b/app/src/main/res/drawable/message_reply_button_bg.xml @@ -6,5 +6,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_textview.xml b/app/src/main/res/drawable/round_textview.xml index 2d5ad4a47c..f5f6c954de 100644 --- a/app/src/main/res/drawable/round_textview.xml +++ b/app/src/main/res/drawable/round_textview.xml @@ -1,10 +1,10 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_members.xml b/app/src/main/res/layout/activity_add_members.xml deleted file mode 100644 index 8cbeb10293..0000000000 --- a/app/src/main/res/layout/activity_add_members.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c4dcd5eb30..d308f2af2f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,48 +1,7 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file + tools:context=".main.ui.MainActivity" /> \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar.xml b/app/src/main/res/layout/app_bar.xml index e0fff9af70..87845b60de 100644 --- a/app/src/main/res/layout/app_bar.xml +++ b/app/src/main/res/layout/app_bar.xml @@ -3,13 +3,13 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/colorPrimary"> + android:background="@color/colorPrimary" + android:theme="@style/Theme.AppCompat.Light.NoActionBar"> diff --git a/app/src/main/res/layout/app_bar_chat_rooms.xml b/app/src/main/res/layout/app_bar_chat_rooms.xml new file mode 100644 index 0000000000..cc1317b32e --- /dev/null +++ b/app/src/main/res/layout/app_bar_chat_rooms.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar_password.xml b/app/src/main/res/layout/app_bar_password.xml index 79b4de1cdf..8db44bba59 100644 --- a/app/src/main/res/layout/app_bar_password.xml +++ b/app/src/main/res/layout/app_bar_password.xml @@ -15,9 +15,11 @@ app:navigationIcon="?android:attr/homeAsUpIndicator" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ActionModeStyle"> + + + app:roundedCornerRadius="4dp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_fragment_directory_sorting.xml b/app/src/main/res/layout/bottom_sheet_fragment_directory_sorting.xml new file mode 100644 index 0000000000..215b7cd1b3 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_fragment_directory_sorting.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_fragment_servers.xml b/app/src/main/res/layout/bottom_sheet_fragment_servers.xml new file mode 100644 index 0000000000..e8ecff5af5 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_fragment_servers.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_fragment_sort_by.xml b/app/src/main/res/layout/bottom_sheet_fragment_sort_by.xml new file mode 100644 index 0000000000..409ab42ae1 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_fragment_sort_by.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chatroom_sort_dialog.xml b/app/src/main/res/layout/chatroom_sort_dialog.xml deleted file mode 100644 index fc85aa8b79..0000000000 --- a/app/src/main/res/layout/chatroom_sort_dialog.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_account_delete.xml b/app/src/main/res/layout/dialog_delete_account.xml similarity index 95% rename from app/src/main/res/layout/item_account_delete.xml rename to app/src/main/res/layout/dialog_delete_account.xml index 9dafc0d9e8..2c60f9a6fc 100644 --- a/app/src/main/res/layout/item_account_delete.xml +++ b/app/src/main/res/layout/dialog_delete_account.xml @@ -1,7 +1,6 @@ diff --git a/app/src/main/res/layout/dialog_report.xml b/app/src/main/res/layout/dialog_report.xml deleted file mode 100644 index c0ce689a98..0000000000 --- a/app/src/main/res/layout/dialog_report.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - diff --git a/app/src/main/res/layout/dialog_status.xml b/app/src/main/res/layout/dialog_status.xml new file mode 100644 index 0000000000..03405d7f28 --- /dev/null +++ b/app/src/main/res/layout/dialog_status.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/emoji_image_row_item.xml b/app/src/main/res/layout/emoji_image_row_item.xml index 5aca146ac1..2fffc659a0 100644 --- a/app/src/main/res/layout/emoji_image_row_item.xml +++ b/app/src/main/res/layout/emoji_image_row_item.xml @@ -1,14 +1,9 @@ - - - - + android:layout_gravity="center" + android:padding="8dp" + tools:src="@tools:sample/avatars" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml deleted file mode 100644 index 701020f089..0000000000 --- a/app/src/main/res/layout/fragment_about.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_authentication_log_in.xml b/app/src/main/res/layout/fragment_authentication_log_in.xml index beb090c3ce..1af5755e61 100644 --- a/app/src/main/res/layout/fragment_authentication_log_in.xml +++ b/app/src/main/res/layout/fragment_authentication_log_in.xml @@ -54,7 +54,7 @@ android:id="@+id/button_forgot_your_password" style="@style/Authentication.Button.Borderless" android:layout_marginTop="10dp" - android:text="@string/msg_forgot__your_password" + android:text="@string/msg_forgot_your_password" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/fragment_authentication_on_boarding.xml b/app/src/main/res/layout/fragment_authentication_on_boarding.xml index 5f609450ca..4cf9ab4697 100644 --- a/app/src/main/res/layout/fragment_authentication_on_boarding.xml +++ b/app/src/main/res/layout/fragment_authentication_on_boarding.xml @@ -25,6 +25,7 @@ android:id="@+id/text_on_boarding_title" style="@style/Authentication.TextView.Headline" android:layout_marginTop="32dp" + android:gravity="center" android:text="@string/msg_welcome_to_rocket_chat" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -33,6 +34,7 @@ + android:drawableStart="@drawable/ic_hashtag_black_12dp" + android:drawablePadding="@dimen/text_view_drawable_padding" + tools:text="important" /> - - - - + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/layout_app_bar_chat_room" /> + android:layout_height="42dp" + android:background="#54585E" + android:drawableEnd="@drawable/ic_group_by_type_20dp" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical" + android:paddingStart="@dimen/screen_edge_left_and_right_margins" + android:paddingEnd="@dimen/screen_edge_left_and_right_margins" + android:text="@string/msg_sort_by_placeholder" + android:textColor="#CBCED1" + android:textSize="14sp" + android:textStyle="normal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_directory" /> - + + - + + diff --git a/app/src/main/res/layout/fragment_create_channel.xml b/app/src/main/res/layout/fragment_create_channel.xml index 69097aa000..7e7249e9e8 100644 --- a/app/src/main/res/layout/fragment_create_channel.xml +++ b/app/src/main/res/layout/fragment_create_channel.xml @@ -4,10 +4,13 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_margin="16dp" - android:focusableInTouchMode="true" tools:context="createchannel.ui.CreateChannelFragment"> + + + app:layout_constraintTop_toBottomOf="@+id/layout_app_bar" /> @@ -53,6 +60,8 @@ android:id="@+id/text_read_only" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" android:text="@string/msg_ready_only_channel" android:textColor="@color/colorPrimaryText" @@ -64,6 +73,8 @@ android:id="@+id/text_read_only_description" android:layout_width="wrap_content" android:layout_height="wrap_content" + + android:layout_marginStart="16dp" android:text="@string/msg_ready_only_channel_description" android:textColor="@color/colorSecondaryText" android:textSize="12sp" @@ -74,6 +85,8 @@ android:id="@+id/switch_read_only" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + app:layout_constraintBottom_toBottomOf="@+id/text_read_only_description" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/text_read_only" /> @@ -81,7 +94,9 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_preferences.xml b/app/src/main/res/layout/fragment_preferences.xml deleted file mode 100644 index df859fb237..0000000000 --- a/app/src/main/res/layout/fragment_preferences.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index 9877aec9ec..70ba755078 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -8,9 +8,15 @@ android:focusableInTouchMode="true" tools:context=".profile.ui.ProfileFragment"> + + + android:layout_height="wrap_content" + android:layout_below="@+id/layout_app_bar"> + + + + + + + + + - - + + - + android:layout_height="0dp" + android:layout_below="@+id/layout_app_bar" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@+id/layout_app_bar"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_user_details.xml b/app/src/main/res/layout/fragment_user_details.xml index 8f78b7142e..5234df1d6c 100644 --- a/app/src/main/res/layout/fragment_user_details.xml +++ b/app/src/main/res/layout/fragment_user_details.xml @@ -95,7 +95,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/screen_edge_left_and_right_margins" android:layout_marginTop="20dp" - android:text="@string/status" + android:text="@string/user_detail_status" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_message" /> @@ -143,6 +143,6 @@ android:id="@+id/group_user_details" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:constraint_referenced_ids="image_blur, image_avatar, text_name, text_username, text_message, text_title_status, text_description_status, text_title_timezone, text_description_timezone" /> + app:constraint_referenced_ids="image_blur, image_avatar, text_name, text_username, text_message, text_title_status, text_description_status, text_title_timezone, text_description_timezone, text_video_call" /> diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml deleted file mode 100644 index ed902d21a3..0000000000 --- a/app/src/main/res/layout/item_account.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_actions_attachment.xml b/app/src/main/res/layout/item_actions_attachment.xml index be85d47aad..ecac02e795 100644 --- a/app/src/main/res/layout/item_actions_attachment.xml +++ b/app/src/main/res/layout/item_actions_attachment.xml @@ -24,7 +24,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" /> + tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have made sure that everything is amazing!" /> - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_add_new_server.xml b/app/src/main/res/layout/item_add_new_server.xml new file mode 100644 index 0000000000..de8f1cf10b --- /dev/null +++ b/app/src/main/res/layout/item_add_new_server.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_change_status.xml b/app/src/main/res/layout/item_change_status.xml deleted file mode 100644 index b7bf77079b..0000000000 --- a/app/src/main/res/layout/item_change_status.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml index febbd65fe6..541fcf66c3 100644 --- a/app/src/main/res/layout/item_chat.xml +++ b/app/src/main/res/layout/item_chat.xml @@ -8,21 +8,22 @@ android:paddingStart="@dimen/screen_edge_left_and_right_padding" android:paddingTop="@dimen/chat_item_top_and_bottom_padding" android:paddingEnd="@dimen/screen_edge_left_and_right_padding" - android:paddingBottom="@dimen/chat_item_top_and_bottom_padding"> + android:paddingBottom="@dimen/chat_item_top_and_bottom_padding" + tools:context=".chatrooms.adapter.RoomsAdapter"> - + tools:src="@tools:sample/avatars" /> + tools:text="Filipe de Lima Brito: Type something that is very long and need at least two lines, or maybe even more" /> diff --git a/app/src/main/res/layout/item_chatroom_header.xml b/app/src/main/res/layout/item_chatroom_header.xml index 7cdcc4f53d..3324ad081c 100644 --- a/app/src/main/res/layout/item_chatroom_header.xml +++ b/app/src/main/res/layout/item_chatroom_header.xml @@ -10,8 +10,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" - android:paddingEnd="@dimen/screen_edge_left_and_right_padding" android:paddingStart="@dimen/screen_edge_left_and_right_padding" - android:text="@string/chatroom_header" /> + android:paddingEnd="@dimen/screen_edge_left_and_right_padding" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_directory_channel.xml b/app/src/main/res/layout/item_directory_channel.xml new file mode 100644 index 0000000000..940f482635 --- /dev/null +++ b/app/src/main/res/layout/item_directory_channel.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_directory_user.xml b/app/src/main/res/layout/item_directory_user.xml new file mode 100644 index 0000000000..7f73ec4cbc --- /dev/null +++ b/app/src/main/res/layout/item_directory_user.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml index 9f1e708032..c9632ed562 100644 --- a/app/src/main/res/layout/item_message.xml +++ b/app/src/main/res/layout/item_message.xml @@ -156,7 +156,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/message_header" app:layout_constraintTop_toBottomOf="@+id/message_header" - tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" /> + tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have made sure that everything is amazing!" />