diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..4016f207be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a bug report to help us improve + +--- + +## Describe the bug + + +## To Reproduce + + +## Expected behavior + + +## Logs + + +## Screenshots + + +## Devices and Versions + + +Your Rocket.Chat.Android version: (e.g. 2.1.0) +Your Rocket.Chat Server version: (e.g. 0.63.1-develop) + + +Mobile device model and OS version: (e.g. "Nexus 7 - Android 6.0.1") + +## Additional context + + diff --git a/.github/ISSUE_TEMPLATE/chore.md b/.github/ISSUE_TEMPLATE/chore.md new file mode 100644 index 0000000000..88aedc0846 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/chore.md @@ -0,0 +1,17 @@ +--- +name: Chore +about: Issues related to docs, workflow, dependency and others + +--- + +## Describe the chore + + +## Devices and Versions + + +Your Rocket.Chat.Android version: (e.g. 2.1.0) +Your Rocket.Chat Server version: (e.g. 0.63.1-develop) + + +Mobile device model and OS version: (e.g. "Nexus 7 - Android 6.0.1") diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..a96576ef46 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +## Describe the feature you'd like + + +## Devices and Versions + + +Your Rocket.Chat.Android version: (e.g. 2.1.0) +Your Rocket.Chat Server version: (e.g. 0.63.1-develop) + + +Mobile device model and OS version: (e.g. "Nexus 7 - Android 6.0.1") + +## Screenshots + + +## Additional context + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e831a3127f..c5f9176a72 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,4 +4,6 @@ Closes #ISSUE_NUMBER - \ No newline at end of file +#### Changes: [Add here what changes were made in this issue and if possible provide links.] + +#### Screenshots or GIF for the change: diff --git a/README.md b/README.md index 8e50209474..025e11007c 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,33 @@ # Rocket.Chat Android native application -[![CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop.svg?style=shield)](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop) [![Build Status](https://travis-ci.org/RocketChat/Rocket.Chat.Android.svg?branch=develop)](https://travis-ci.org/RocketChat/Rocket.Chat.Android) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a81156a8682e4649994270d3670c3c83)](https://www.codacy.com/app/matheusjardimb/Rocket.Chat.Android) +[![CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop.svg?style=shield)](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a81156a8682e4649994270d3670c3c83)](https://www.codacy.com/app/matheusjardimb/Rocket.Chat.Android) + +## Get it from the stores + +[![](https://user-images.githubusercontent.com/551004/48210434-74c07100-e35e-11e8-8eee-3ba84ffa74d7.png)](https://play.google.com/store/apps/details?id=chat.rocket.android) [![](https://user-images.githubusercontent.com/551004/48210349-50649480-e35e-11e8-97d9-74a4331faf3a.png)](https://f-droid.org/en/packages/chat.rocket.android/) ## Description This repository contains all the code related to the Android native application of [Rocket.Chat](https://github.com/RocketChat/Rocket.Chat/#about-rocketchat). To send new pull-requests, always use the branch `develop` as base and open an issue with the description of what you want/need to accomplish, if the issue wasn't created yet. - ## How to build -- You need to download the latest [Android Studio Preview](https://developer.android.com/studio/preview/) version since the stable IDE version does not support the [JetPack](https://developer.android.com/jetpack/) that is being used on this application. -- Make sure that you have the latest **gradle** and the **android plugin** versions installed. Go to `File > Project Structure > Project` and make sure that you have the latest versions installed. Refer [this](https://developer.android.com/studio/releases/gradle-plugin.html#updating-gradle) to see the compatible versions. +- Make sure that you have the latest **Gradle** and the **Android plugin** versions installed. Go to `File > Project Structure > Project` and make sure that you have the latest versions installed. Refer [this](https://developer.android.com/studio/releases/gradle-plugin.html#updating-gradle) to see the compatible versions. - Kotlin is already configured in the project. To check, go to `Tools > Kotlin > Configure Kotlin in project`. A message saying kotlin is already configured in the project pops up. You can update kotlin to the latest version by going to `Tools > Kotlin > Configure Kotlin updates` and download the latest version of kotlin. ### SDK Instructions - This version requires the [Kotlin SDK](https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK) for Rocket.Chat. Clone the Kotlin SDK in by running `git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git`. -- First, a build is required for the SDK, so that required jar files are generated. Make sure that the android repository and the kotlin sdk have the same immediate parent directory. Change the current directory to `Rocket.Chat.Android/app` and run the `build-sdk.sh` which will result in creating of the required jar file `core*.jar` and `common*.jar` in `Rocket.Chat.Android/app/libs`,by the following steps in your terminal window: +- First, a build is required for the SDK, so that required jar files are generated. Make sure that the Android repository and the Kotlin SDK have the same immediate parent directory. Change the current directory to `Rocket.Chat.Android/app` and run the `build-sdk.sh` which will result in creating of the required jar file `core*.jar` and `common*.jar` in `Rocket.Chat.Android/app/libs`, by the following steps in your terminal window: ``` cd Rocket.Chat.Android/app ./build-sdk.sh ``` +**Note:** *You need to have Java 8 as default Java for the system (project won't build when using a Java 9+ version).* + ## How to run ### Command Line @@ -35,7 +39,7 @@ cd Rocket.Chat.Android/app ### Android Studio -- After importing the project in android studio, go to `Run > Run app` and then select your device, or create a new virtual device by following the wizard. +- After importing the project in Android Studio, go to `Run > Run app` and then select your device, or create a new virtual device by following the wizard. ## Bug report & Feature request @@ -43,4 +47,4 @@ Are you having a technical issue trying to compile the app, or setting up Push N ## Coding Style -Please follow our [coding style](https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md) when contributing. +Please follow the official [Kotlin coding convections](https://kotlinlang.org/docs/reference/coding-conventions.html) when contributing. diff --git a/app/build.gradle b/app/build.gradle index 090c3dc927..43d3db6475 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId "chat.rocket.android" minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk - versionCode 2050 - versionName "3.1.1" + versionCode 2057 + versionName "3.2.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true @@ -140,6 +140,7 @@ dependencies { implementation libraries.frescoAnimatedWebP implementation libraries.glide + implementation libraries.glideTransformations kapt libraries.kotshiCompiler implementation libraries.kotshiApi diff --git a/app/schemas/chat.rocket.android.db.RCDatabase/11.json b/app/schemas/chat.rocket.android.db.RCDatabase/11.json new file mode 100644 index 0000000000..1c2b7c60a1 --- /dev/null +++ b/app/schemas/chat.rocket.android.db.RCDatabase/11.json @@ -0,0 +1,1105 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "fb9f1c815809b0217d326452aeb34e92", + "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, 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, \"fb9f1c815809b0217d326452aeb34e92\")" + ] + } +} \ No newline at end of file diff --git a/app/src/debug/res/xml/file_paths.xml b/app/src/debug/res/xml/file_paths.xml new file mode 100644 index 0000000000..d9328528d0 --- /dev/null +++ b/app/src/debug/res/xml/file_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/foss/java/chat/rocket/android/helper/CrashlyticsTree.kt b/app/src/foss/java/chat/rocket/android/helper/CrashlyticsTree.kt index a7d2a2c9a9..d79b935311 100644 --- a/app/src/foss/java/chat/rocket/android/helper/CrashlyticsTree.kt +++ b/app/src/foss/java/chat/rocket/android/helper/CrashlyticsTree.kt @@ -1,15 +1,11 @@ package chat.rocket.android.helper import timber.log.Timber -import android.util.Log +// Production logger... Just ignore it class CrashlyticsTree : Timber.Tree() { override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) { - Log.println(priority, tag, message) - - if (throwable != null) { - Log.e(tag,throwable.toString()) - } + throwable?.printStackTrace() } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a0f96bc45f..f4c022101a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -105,6 +105,15 @@ - + + + + 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 d03ab2b738..dfbbd17679 100644 --- a/app/src/main/java/chat/rocket/android/analytics/Analytics.kt +++ b/app/src/main/java/chat/rocket/android/analytics/Analytics.kt @@ -61,7 +61,14 @@ interface Analytics { fun logServerSwitch(serverUrl: String, serverCount: Int) {} /** - * Logs the admin opening. + * Logs the admin opening event. */ fun logOpenAdmin() {} + + /** + * Logs the reset password event. + * + * @param resetPasswordSucceeded True if successful reset password, false otherwise. + */ + fun logResetPassword(resetPasswordSucceeded: Boolean) {} } diff --git a/app/src/main/java/chat/rocket/android/analytics/AnalyticsManager.kt b/app/src/main/java/chat/rocket/android/analytics/AnalyticsManager.kt index 92c21c449f..084561bbfe 100644 --- a/app/src/main/java/chat/rocket/android/analytics/AnalyticsManager.kt +++ b/app/src/main/java/chat/rocket/android/analytics/AnalyticsManager.kt @@ -70,4 +70,10 @@ class AnalyticsManager @Inject constructor( analytics.forEach { it.logOpenAdmin() } } } + + fun logResetPassword(resetPasswordSucceeded: Boolean) { + if (analyticsTrackingInteractor.get()) { + analytics.forEach { it.logResetPassword(resetPasswordSucceeded) } + } + } } 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 b2fe84d64f..b96de69855 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 @@ -16,9 +16,9 @@ sealed class ScreenViewEvent(val screenName: String) { object ChatRoom : ScreenViewEvent("ChatRoomFragment") object ChatRooms : ScreenViewEvent("ChatRoomsFragment") object CreateChannel : ScreenViewEvent("CreateChannelFragment") + object UserDetails : ScreenViewEvent("UserDetailsFragment") object FavoriteMessages : ScreenViewEvent("FavoriteMessagesFragment") object Files : ScreenViewEvent("FilesFragment") - object MemberBottomSheet : ScreenViewEvent("MemberBottomSheetFragment") object Members : ScreenViewEvent("MembersFragment") object Mentions : ScreenViewEvent("MentionsFragment") object MessageInfo : ScreenViewEvent("MessageInfoFragment") 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 5057a3e3ab..23f9f7df02 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 @@ -23,10 +23,17 @@ class OnBoardingPresenter @Inject constructor( private val getAccountsInteractor: GetAccountsInteractor, val settingsInteractor: GetSettingsInteractor, val factory: RocketChatClientFactory -) : CheckServerPresenter(strategy, factory, settingsInteractor) { +) : CheckServerPresenter( + strategy = strategy, + factory = factory, + settingsInteractor = settingsInteractor, + refreshSettingsInteractor = refreshSettingsInteractor +) { fun toSignInToYourServer() = navigator.toSignInToYourServer() + fun toCreateANewServer(createServerUrl: String) = navigator.toWebPage(createServerUrl) + fun connectToCommunityServer(communityServerUrl: String) { connectToServer(communityServerUrl) { if (totalSocialAccountsEnabled == 0 && !isNewAccountCreationEnabled) { @@ -63,8 +70,6 @@ class OnBoardingPresenter @Inject constructor( } } - fun toCreateANewServer(createServerUrl: String) = navigator.toWebPage(createServerUrl) - private fun connectToServer(serverUrl: String, block: () -> Unit) { launchUI(strategy) { // Check if we already have an account for this server... @@ -77,9 +82,9 @@ class OnBoardingPresenter @Inject constructor( try { withContext(DefaultDispatcher) { setupConnectionInfo(serverUrl) - refreshSettingsInteractor.refresh(serverUrl) // preparing next fragment before showing it + refreshServerAccounts() checkEnabledAccounts(serverUrl) checkIfLoginFormIsEnabled() checkIfCreateNewAccountIsEnabled() 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 1ca5909b6d..d294506ff4 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 @@ -25,7 +25,13 @@ class ServerPresenter @Inject constructor( private val getAccountsInteractor: GetAccountsInteractor, val settingsInteractor: GetSettingsInteractor, val factory: RocketChatClientFactory -) : CheckServerPresenter(strategy, factory, settingsInteractor, view) { +) : CheckServerPresenter( + strategy = strategy, + factory = factory, + settingsInteractor = settingsInteractor, + versionCheckView = view, + refreshSettingsInteractor = refreshSettingsInteractor +) { fun checkServer(server: String) { if (!server.isValidUrl()) { @@ -93,9 +99,8 @@ class ServerPresenter @Inject constructor( view.showLoading() try { withContext(DefaultDispatcher) { - refreshSettingsInteractor.refresh(serverUrl) - // preparing next fragment before showing it + refreshServerAccounts() checkEnabledAccounts(serverUrl) checkIfLoginFormIsEnabled() checkIfCreateNewAccountIsEnabled() @@ -112,5 +117,4 @@ class ServerPresenter @Inject constructor( } } } - } \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt b/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt index 97107154de..adeebc8d20 100644 --- a/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt +++ b/app/src/main/java/chat/rocket/android/authentication/server/ui/ServerFragment.kt @@ -1,6 +1,7 @@ package chat.rocket.android.authentication.server.ui import android.os.Bundle +import android.text.SpannableStringBuilder import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,6 +12,7 @@ import android.widget.ScrollView import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.net.toUri +import androidx.core.text.color import androidx.core.view.ViewCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -51,6 +53,8 @@ class ServerFragment : Fragment(), ServerView { lateinit var analyticsManager: AnalyticsManager private var deepLinkInfo: LoginDeepLinkInfo? = null private var protocol = "https://" + private var isDomainAppended = false + private var appendedText = "" private lateinit var serverUrlDisposable: Disposable private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { if (KeyboardHelper.isSoftKeyboardShown(scroll_view.rootView)) { @@ -131,7 +135,7 @@ class ServerFragment : Fragment(), ServerView { } private fun setupOnClickListener() = - ui { _ -> + ui { button_connect.setOnClickListener { presenter.checkServer("$protocol${text_server_url.textContent.sanitize()}") } @@ -244,17 +248,42 @@ class ServerFragment : Fragment(), ServerView { private fun subscribeEditText() { serverUrlDisposable = text_server_url.asObservable() .filter { it.isNotBlank() } - .subscribe { - if ("$protocol${it.toString()}".isValidUrl()) { - enableButtonConnect() - } else { - disableButtonConnect() - } - } + .subscribe { processUserInput(it.toString()) } } private fun unsubscribeEditText() = serverUrlDisposable.dispose() + private fun processUserInput(text: String) { + if (text.last().toString() == "." && !isDomainAppended) { + addDomain() + } else if (isDomainAppended && text != appendedText) { + removeDomain() + } + + if ("$protocol$text".isValidUrl()) { + enableButtonConnect() + } else { + disableButtonConnect() + } + } + + private fun addDomain() { + val cursorPosition = text_server_url.length() + text_server_url.append(SpannableStringBuilder() + .color(R.color.colorAuthenticationSecondaryText) { append("rocket.chat") }) + text_server_url.setSelection(cursorPosition) + appendedText = text_server_url.text.toString() + isDomainAppended = true + } + + private fun removeDomain() { + text_server_url.setText( + text_server_url.text.toString().substring(0, text_server_url.selectionEnd) + ) + text_server_url.setSelection(text_server_url.length()) + isDomainAppended = false + } + private fun enableUserInput() { enableButtonConnect() text_server_url.isEnabled = true diff --git a/app/src/main/java/chat/rocket/android/authentication/twofactor/di/TwoFAFragmentProvider.kt b/app/src/main/java/chat/rocket/android/authentication/twofactor/di/TwoFAFragmentProvider.kt index 2a7e097455..dd98abfb0e 100644 --- a/app/src/main/java/chat/rocket/android/authentication/twofactor/di/TwoFAFragmentProvider.kt +++ b/app/src/main/java/chat/rocket/android/authentication/twofactor/di/TwoFAFragmentProvider.kt @@ -5,7 +5,8 @@ import chat.rocket.android.dagger.scope.PerFragment import dagger.Module import dagger.android.ContributesAndroidInjector -@Module abstract class TwoFAFragmentProvider { +@Module +abstract class TwoFAFragmentProvider { @ContributesAndroidInjector(modules = [TwoFAFragmentModule::class]) @PerFragment diff --git a/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt b/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt index 7e23411eea..7500d442f1 100644 --- a/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt +++ b/app/src/main/java/chat/rocket/android/authentication/ui/AuthenticationActivity.kt @@ -19,7 +19,6 @@ import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.support.HasSupportFragmentInjector import kotlinx.android.synthetic.main.app_bar.* -import kotlinx.coroutines.experimental.Job import javax.inject.Inject class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { @@ -27,7 +26,6 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector { lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector @Inject lateinit var presenter: AuthenticationPresenter - val job = Job() override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) diff --git a/app/src/main/java/chat/rocket/android/chatdetails/adapter/OptionItemHolder.kt b/app/src/main/java/chat/rocket/android/chatdetails/adapter/OptionItemHolder.kt new file mode 100644 index 0000000000..29a9689af9 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/chatdetails/adapter/OptionItemHolder.kt @@ -0,0 +1,6 @@ +package chat.rocket.android.chatdetails.adapter + +import chat.rocket.android.chatdetails.domain.Option +import chat.rocket.android.chatrooms.adapter.ItemHolder + +data class OptionItemHolder(override val data: Option): ItemHolder