diff --git a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/25.json b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/25.json new file mode 100644 index 00000000000..70a6de88898 --- /dev/null +++ b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/25.json @@ -0,0 +1,811 @@ +{ + "formatVersion": 1, + "database": { + "version": 25, + "identityHash": "196d16e5bbb24d7617b4d3c43a0ed2ad", + "entities": [ + { + "tableName": "User", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT" + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT" + }, + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT" + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT" + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT" + }, + { + "fieldPath": "pushConfigurationState", + "columnName": "pushConfigurationState", + "affinity": "TEXT" + }, + { + "fieldPath": "capabilities", + "columnName": "capabilities", + "affinity": "TEXT" + }, + { + "fieldPath": "serverVersion", + "columnName": "serverVersion", + "affinity": "TEXT", + "defaultValue": "''" + }, + { + "fieldPath": "clientCertificate", + "columnName": "clientCertificate", + "affinity": "TEXT" + }, + { + "fieldPath": "externalSignalingServer", + "columnName": "externalSignalingServer", + "affinity": "TEXT" + }, + { + "fieldPath": "current", + "columnName": "current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scheduledForDeletion", + "columnName": "scheduledForDeletion", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "ArbitraryStorage", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))", + "fields": [ + { + "fieldPath": "accountIdentifier", + "columnName": "accountIdentifier", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "storageObject", + "columnName": "object", + "affinity": "TEXT" + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "accountIdentifier", + "key" + ] + } + }, + { + "tableName": "Conversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `objectId` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, `hasSensitive` INTEGER NOT NULL, `hasImportant` INTEGER NOT NULL, `hiddenPinnedId` INTEGER, `lastPinnedId` INTEGER, `attributes` INTEGER, `messageDraft` TEXT, `hiddenUpcomingEvent` TEXT, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "internalId", + "columnName": "internalId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorId", + "columnName": "actorId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorType", + "columnName": "actorType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarVersion", + "columnName": "avatarVersion", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "callFlag", + "columnName": "callFlag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "callRecording", + "columnName": "callRecording", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "callStartTime", + "columnName": "callStartTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canDeleteConversation", + "columnName": "canDeleteConversation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canLeaveConversation", + "columnName": "canLeaveConversation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canStartCall", + "columnName": "canStartCall", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasCall", + "columnName": "hasCall", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasPassword", + "columnName": "hasPassword", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasCustomAvatar", + "columnName": "isCustomAvatar", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favorite", + "columnName": "isFavorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastActivity", + "columnName": "lastActivity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastCommonReadMessage", + "columnName": "lastCommonReadMessage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastMessage", + "columnName": "lastMessage", + "affinity": "TEXT" + }, + { + "fieldPath": "lastPing", + "columnName": "lastPing", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastReadMessage", + "columnName": "lastReadMessage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lobbyState", + "columnName": "lobbyState", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lobbyTimer", + "columnName": "lobbyTimer", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageExpiration", + "columnName": "messageExpiration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationCalls", + "columnName": "notificationCalls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationLevel", + "columnName": "notificationLevel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "objectType", + "columnName": "objectType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "objectId", + "columnName": "objectId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "participantType", + "columnName": "participantType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "conversationReadOnlyState", + "columnName": "readOnly", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recordingConsentRequired", + "columnName": "recordingConsent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteServer", + "columnName": "remoteServer", + "affinity": "TEXT" + }, + { + "fieldPath": "remoteToken", + "columnName": "remoteToken", + "affinity": "TEXT" + }, + { + "fieldPath": "sessionId", + "columnName": "sessionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT" + }, + { + "fieldPath": "statusClearAt", + "columnName": "statusClearAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "statusIcon", + "columnName": "statusIcon", + "affinity": "TEXT" + }, + { + "fieldPath": "statusMessage", + "columnName": "statusMessage", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unreadMention", + "columnName": "unreadMention", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadMentionDirect", + "columnName": "unreadMentionDirect", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadMessages", + "columnName": "unreadMessages", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasArchived", + "columnName": "hasArchived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasSensitive", + "columnName": "hasSensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasImportant", + "columnName": "hasImportant", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hiddenPinnedId", + "columnName": "hiddenPinnedId", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastPinnedId", + "columnName": "lastPinnedId", + "affinity": "INTEGER" + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "affinity": "INTEGER" + }, + { + "fieldPath": "messageDraft", + "columnName": "messageDraft", + "affinity": "TEXT" + }, + { + "fieldPath": "hiddenUpcomingEvent", + "columnName": "hiddenUpcomingEvent", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "internalId" + ] + }, + "indices": [ + { + "name": "index_Conversations_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "User", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "ChatMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `threadId` INTEGER, `isThread` INTEGER NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendStatus` TEXT, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `threadTitle` TEXT, `threadReplies` INTEGER, `timestamp` INTEGER NOT NULL, `pinnedActorType` TEXT, `pinnedActorId` TEXT, `pinnedActorDisplayName` TEXT, `pinnedAt` INTEGER, `pinnedUntil` INTEGER, `sendAt` INTEGER, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "internalId", + "columnName": "internalId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "internalConversationId", + "columnName": "internalConversationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "threadId", + "columnName": "threadId", + "affinity": "INTEGER" + }, + { + "fieldPath": "isThread", + "columnName": "isThread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "actorDisplayName", + "columnName": "actorDisplayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorId", + "columnName": "actorId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorType", + "columnName": "actorType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expirationTimestamp", + "columnName": "expirationTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "replyable", + "columnName": "isReplyable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isTemporary", + "columnName": "isTemporary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastEditActorDisplayName", + "columnName": "lastEditActorDisplayName", + "affinity": "TEXT" + }, + { + "fieldPath": "lastEditActorId", + "columnName": "lastEditActorId", + "affinity": "TEXT" + }, + { + "fieldPath": "lastEditActorType", + "columnName": "lastEditActorType", + "affinity": "TEXT" + }, + { + "fieldPath": "lastEditTimestamp", + "columnName": "lastEditTimestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "renderMarkdown", + "columnName": "markdown", + "affinity": "INTEGER" + }, + { + "fieldPath": "messageParameters", + "columnName": "messageParameters", + "affinity": "TEXT" + }, + { + "fieldPath": "messageType", + "columnName": "messageType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentMessageId", + "columnName": "parent", + "affinity": "INTEGER" + }, + { + "fieldPath": "reactions", + "columnName": "reactions", + "affinity": "TEXT" + }, + { + "fieldPath": "reactionsSelf", + "columnName": "reactionsSelf", + "affinity": "TEXT" + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "TEXT" + }, + { + "fieldPath": "sendStatus", + "columnName": "sendStatus", + "affinity": "TEXT" + }, + { + "fieldPath": "silent", + "columnName": "silent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "systemMessageType", + "columnName": "systemMessage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "threadTitle", + "columnName": "threadTitle", + "affinity": "TEXT" + }, + { + "fieldPath": "threadReplies", + "columnName": "threadReplies", + "affinity": "INTEGER" + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pinnedActorType", + "columnName": "pinnedActorType", + "affinity": "TEXT" + }, + { + "fieldPath": "pinnedActorId", + "columnName": "pinnedActorId", + "affinity": "TEXT" + }, + { + "fieldPath": "pinnedActorDisplayName", + "columnName": "pinnedActorDisplayName", + "affinity": "TEXT" + }, + { + "fieldPath": "pinnedAt", + "columnName": "pinnedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "pinnedUntil", + "columnName": "pinnedUntil", + "affinity": "INTEGER" + }, + { + "fieldPath": "sendAt", + "columnName": "sendAt", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "internalId" + ] + }, + "indices": [ + { + "name": "index_ChatMessages_internalId", + "unique": true, + "columnNames": [ + "internalId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `${TABLE_NAME}` (`internalId`)" + }, + { + "name": "index_ChatMessages_internalConversationId", + "unique": false, + "columnNames": [ + "internalConversationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)" + } + ], + "foreignKeys": [ + { + "table": "Conversations", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "internalConversationId" + ], + "referencedColumns": [ + "internalId" + ] + } + ] + }, + { + "tableName": "ChatBlocks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `internalConversationId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `threadId` INTEGER, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL, FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "internalConversationId", + "columnName": "internalConversationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER" + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT" + }, + { + "fieldPath": "threadId", + "columnName": "threadId", + "affinity": "INTEGER" + }, + { + "fieldPath": "oldestMessageId", + "columnName": "oldestMessageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "newestMessageId", + "columnName": "newestMessageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasHistory", + "columnName": "hasHistory", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_ChatBlocks_internalConversationId", + "unique": false, + "columnNames": [ + "internalConversationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ChatBlocks_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)" + } + ], + "foreignKeys": [ + { + "table": "Conversations", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "internalConversationId" + ], + "referencedColumns": [ + "internalId" + ] + } + ] + } + ], + "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, '196d16e5bbb24d7617b4d3c43a0ed2ad')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt index 1723ce5ee69..bc4026a087e 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt @@ -41,7 +41,6 @@ import android.view.OrientationEventListener import android.view.View import android.view.View.OnTouchListener import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.app.NotificationManagerCompat import androidx.annotation.DrawableRes import androidx.appcompat.app.AlertDialog import androidx.compose.material3.MaterialTheme @@ -50,6 +49,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.app.NotificationManagerCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.net.toUri import androidx.lifecycle.ViewModelProvider @@ -78,6 +78,7 @@ import com.nextcloud.talk.camera.BackgroundBlurFrameProcessor import com.nextcloud.talk.camera.BlurBackgroundViewModel import com.nextcloud.talk.camera.BlurBackgroundViewModel.BackgroundBlurOn import com.nextcloud.talk.chat.ChatActivity +import com.nextcloud.talk.conversationlist.ConversationsListActivity import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.CallActivityBinding import com.nextcloud.talk.events.ConfigurationChangeEvent @@ -85,6 +86,8 @@ import com.nextcloud.talk.events.NetworkEvent import com.nextcloud.talk.events.ProximitySensorEvent import com.nextcloud.talk.events.WebSocketCommunicationEvent import com.nextcloud.talk.models.ExternalSignalingServer +import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.domain.ConversationModel.Companion.checkIfVoiceRoom import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall @@ -121,10 +124,10 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_TIMESTAMP import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_BREAKOUT_ROOM import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_MODERATOR import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MODIFIED_BASE_URL +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_TIMESTAMP import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_RECORDING_STATE @@ -364,6 +367,7 @@ class CallActivity : CallBaseActivity() { private var reactionAnimator: ReactionAnimator? = null private var othersInCall = false private var isOneToOneConversation = false + private var currentConversation: Conversation? = null private lateinit var micInputAudioRecorder: AudioRecord private var micInputAudioRecordThread: Thread? = null @@ -629,6 +633,7 @@ class CallActivity : CallBaseActivity() { override fun onNext(roomOverall: RoomOverall) { val conversation = roomOverall.ocs!!.data + currentConversation = conversation if (conversation?.recordingConsentRequired == 1) { askForRecordingConsent() } else { @@ -1553,6 +1558,7 @@ class CallActivity : CallBaseActivity() { override fun onNext(roomOverall: RoomOverall) { val conversation = roomOverall.ocs!!.data + currentConversation = conversation callRecordingViewModel!!.setRecordingState(conversation!!.callRecording) callSession = conversation.sessionId Log.d(TAG, " new callSession by joinRoom= $callSession") @@ -1605,6 +1611,7 @@ class CallActivity : CallBaseActivity() { override fun onNext(roomOverall: RoomOverall) { val conversation = roomOverall.ocs!!.data + currentConversation = conversation callRecordingViewModel!!.setRecordingState(conversation!!.callRecording) callSession = conversation.sessionId @@ -2037,7 +2044,15 @@ class CallActivity : CallBaseActivity() { } override fun onNext(genericOverall: GenericOverall) { - if (switchToRoomToken.isNotEmpty()) { + val conversationModel = currentConversation?.let { + ConversationModel.mapToConversationModel(it, conversationUser) + } + + if (conversationModel?.checkIfVoiceRoom() == true) { + val intent = Intent(context, ConversationsListActivity::class.java) + startActivity(intent) + finish() + } else if (switchToRoomToken.isNotEmpty()) { val intent = Intent(context, ChatActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) val bundle = Bundle() diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 43b8aaf7253..91da8254415 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -117,6 +117,7 @@ import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApiCoroutines import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.chat.ui.ShowReactionsModalBottomSheet import com.nextcloud.talk.chat.ui.model.MessageTypeContent import com.nextcloud.talk.chat.viewmodels.ChatViewModel import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel @@ -137,6 +138,7 @@ import com.nextcloud.talk.location.LocationPickerActivity import com.nextcloud.talk.messagesearch.MessageSearchActivity import com.nextcloud.talk.models.ExternalSignalingServer import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.domain.ConversationModel.Companion.checkIfVoiceRoom import com.nextcloud.talk.models.json.capabilities.SpreedCapability import com.nextcloud.talk.models.json.chat.ChatMessageJson import com.nextcloud.talk.models.json.conversations.ConversationEnums @@ -154,8 +156,8 @@ import com.nextcloud.talk.translate.ui.TranslateActivity import com.nextcloud.talk.ui.PinnedMessageView import com.nextcloud.talk.ui.PlaybackSpeed import com.nextcloud.talk.ui.StatusDrawable -import com.nextcloud.talk.ui.chat.ChatView import com.nextcloud.talk.ui.chat.ChatMessageCallbacks +import com.nextcloud.talk.ui.chat.ChatView import com.nextcloud.talk.ui.chat.ChatViewCallbacks import com.nextcloud.talk.ui.chat.ChatViewState import com.nextcloud.talk.ui.dialog.DateTimeCompose @@ -163,7 +165,6 @@ import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment import com.nextcloud.talk.ui.dialog.GetPinnedOptionsDialog import com.nextcloud.talk.ui.dialog.MessageActionsDialog import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment -import com.nextcloud.talk.chat.ui.ShowReactionsModalBottomSheet import com.nextcloud.talk.ui.dialog.TempMessageActionsDialog import com.nextcloud.talk.ui.theme.LocalMessageUtils import com.nextcloud.talk.ui.theme.LocalOpenGraphFetcher @@ -997,6 +998,10 @@ class ChatActivity : setupWebsocket() + if (currentConversation.checkIfVoiceRoom()) { + startACall(false, true) + } + if (startCallFromNotification) { startCallFromNotification = false startACall(voiceOnly, false) @@ -3571,7 +3576,6 @@ class ChatActivity : if (noteToSelfConversation != null) { var shareUri: Uri? = null - val data: HashMap? var metaData = "" var objectId = "" if (message.hasFileAttachment) { diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt index 94f6f04f7d4..667e0d04903 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/ConversationCreationActivity.kt @@ -21,6 +21,7 @@ import androidx.activity.compose.setContent import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -41,6 +42,9 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Chat +import androidx.compose.material.icons.outlined.VolumeUp import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card @@ -69,7 +73,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import com.nextcloud.talk.utils.DisplayUtils import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -94,7 +97,9 @@ import com.nextcloud.talk.conversationcreation.viewmodel.ConversationCreationVie import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser import com.nextcloud.talk.utils.CapabilitiesUtil +import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.PickImage +import com.nextcloud.talk.utils.SpreedFeatures import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.preview.ComposePreviewUtils import javax.inject.Inject @@ -219,6 +224,15 @@ fun ConversationCreationScreen( } ConversationNameAndDescription(conversationCreationViewModel) + + if ( + CapabilitiesUtil.hasSpreedFeatureCapability( + conversationCreationViewModel.currentUser.capabilities?.spreedCapability!!, + SpreedFeatures.CONVERSATION_PRESETS + ) + ) { + ConversationPresets(conversationCreationViewModel) + } AddParticipants(launcher, context, conversationCreationViewModel) RoomCreationOptions(conversationCreationViewModel) CreateConversation(conversationCreationViewModel, context) @@ -362,6 +376,87 @@ fun ConversationNameAndDescription(conversationCreationViewModel: ConversationCr ) } +@Suppress("LongMethod") +@SuppressLint("SuspiciousIndentation") +@Composable +fun ConversationPresets(conversationCreationViewModel: ConversationCreationViewModel) { + val preset by conversationCreationViewModel.conversationPreset + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + SelectableCard( + modifier = Modifier.weight(1f), + title = stringResource(R.string.default_room), + subtitle = stringResource(R.string.default_room_preset), + icon = Icons.Outlined.Chat, + isSelected = preset == "default", + onClick = { conversationCreationViewModel.conversationPreset.value = "default" } + ) + + SelectableCard( + modifier = Modifier.weight(1f), + title = stringResource(R.string.voice_room), + subtitle = stringResource(R.string.voice_room_preset), + icon = Icons.Outlined.VolumeUp, + isSelected = preset == "voiceroom", + onClick = { conversationCreationViewModel.conversationPreset.value = "voiceroom" } + ) + } +} + +@Composable +fun SelectableCard( + modifier: Modifier = Modifier, + title: String, + subtitle: String, + icon: ImageVector, + isSelected: Boolean, + onClick: () -> Unit +) { + val borderColor = if (isSelected) Color.LightGray else Color.Transparent + val borderWidth = 1.dp + + Column( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .clickable { onClick() } + .border( + width = borderWidth, + color = borderColor, + shape = RoundedCornerShape(8.dp) + ) + .padding(16.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Text( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 15.sp + ) + } + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = subtitle, + fontSize = 13.sp, + lineHeight = 18.sp + ) + } +} + @Suppress("LongMethod") @SuppressLint("SuspiciousIndentation") @Composable @@ -469,7 +564,7 @@ fun RoomCreationOptions(conversationCreationViewModel: ConversationCreationViewM color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(top = 24.dp, start = 16.dp, end = 16.dp) ) - ConversationOptions( + ConversationOption( icon = R.drawable.ic_avatar_link, text = R.string.nc_guest_access_allow_title, switch = { @@ -484,7 +579,7 @@ fun RoomCreationOptions(conversationCreationViewModel: ConversationCreationViewM ) if (isGuestsAllowed && !isPasswordSet) { - ConversationOptions( + ConversationOption( icon = R.drawable.baseline_lock_open_24, text = R.string.nc_set_password, conversationCreationViewModel = conversationCreationViewModel @@ -492,14 +587,14 @@ fun RoomCreationOptions(conversationCreationViewModel: ConversationCreationViewM } if (isGuestsAllowed && isPasswordSet) { - ConversationOptions( + ConversationOption( icon = R.drawable.ic_lock_grey600_24px, text = R.string.nc_change_password, conversationCreationViewModel = conversationCreationViewModel ) } - ConversationOptions( + ConversationOption( icon = R.drawable.baseline_format_list_bulleted_24, text = R.string.nc_open_conversation_to_registered_users, switch = { @@ -514,7 +609,7 @@ fun RoomCreationOptions(conversationCreationViewModel: ConversationCreationViewM ) if (isConversationAvailableForRegisteredUsers) { - ConversationOptions( + ConversationOption( text = R.string.nc_open_to_guest_app_users, switch = { Switch( @@ -530,7 +625,7 @@ fun RoomCreationOptions(conversationCreationViewModel: ConversationCreationViewM } @Composable -fun ConversationOptions( +fun ConversationOption( icon: Int? = null, text: Int, switch: @Composable (() -> Unit)? = null, @@ -721,7 +816,8 @@ fun CreateConversation(conversationCreationViewModel: ConversationCreationViewMo conversationCreationViewModel.createRoomAndAddParticipants( roomType = CompanionClass.ROOM_TYPE_GROUP, conversationName = conversationCreationViewModel.roomName.value, - participants = selectedParticipants.toSet() + participants = selectedParticipants.toSet(), + preset = conversationCreationViewModel.conversationPreset.value ) { roomToken -> val bundle = Bundle() bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken) diff --git a/app/src/main/java/com/nextcloud/talk/conversationcreation/viewmodel/ConversationCreationViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationcreation/viewmodel/ConversationCreationViewModel.kt index b1ac6d00a4d..d9f0fc9c5ee 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationcreation/viewmodel/ConversationCreationViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationcreation/viewmodel/ConversationCreationViewModel.kt @@ -66,6 +66,7 @@ class ConversationCreationViewModel @Inject constructor( val conversationDescription: StateFlow = _conversationDescription var isGuestsAllowed = mutableStateOf(false) var isConversationAvailableForRegisteredUsers = mutableStateOf(false) + val conversationPreset = mutableStateOf("default") var openForGuestAppUsers = mutableStateOf(false) private val addParticipantsViewState = MutableStateFlow(AddParticipantsUiState.None) private val allowGuestsResult = MutableStateFlow(AllowGuestsUiState.None) @@ -85,6 +86,7 @@ class ConversationCreationViewModel @Inject constructor( fun createRoomAndAddParticipants( roomType: String, conversationName: String, + preset: String = "default", participants: Set, onRoomCreated: (String) -> Unit ) { @@ -103,6 +105,7 @@ class ConversationCreationViewModel @Inject constructor( version = apiVersion, baseUrl = _currentUser.baseUrl, roomType = roomType, + preset = preset, conversationName = conversationName ) val roomResult = repository.createRoom( diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt index 80663cdd1be..a8f1b20f510 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt @@ -14,6 +14,7 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.provider.Settings +import android.text.TextUtils import android.util.Log import android.widget.Toast import androidx.activity.OnBackPressedCallback @@ -40,11 +41,11 @@ import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.MainActivity import com.nextcloud.talk.api.NcApiCoroutines import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.conversation.RenameConversationDialogFragment import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.contacts.ContactsActivity import com.nextcloud.talk.contacts.ContactsViewModel import com.nextcloud.talk.contextchat.ContextChatViewModel +import com.nextcloud.talk.conversation.RenameConversationDialogFragment import com.nextcloud.talk.conversationlist.ui.ConversationOpsAction import com.nextcloud.talk.conversationlist.ui.ConversationsListScreen import com.nextcloud.talk.conversationlist.ui.ConversationsListScreenCallbacks @@ -61,6 +62,7 @@ import com.nextcloud.talk.jobs.DeleteConversationWorker import com.nextcloud.talk.jobs.LeaveConversationWorker import com.nextcloud.talk.jobs.UploadAndShareFilesWorker import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.domain.ConversationModel.Companion.checkIfVoiceRoom import com.nextcloud.talk.models.domain.SearchMessageEntry import com.nextcloud.talk.models.json.conversations.ConversationEnums import com.nextcloud.talk.settings.SettingsActivity @@ -87,17 +89,21 @@ import com.nextcloud.talk.utils.SpreedFeatures import com.nextcloud.talk.utils.UserIdUtils import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_BREAKOUT_ROOM +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_MODERATOR +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_RECORDING_STATE import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SCROLL_TO_NOTIFICATION_CATEGORY import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.power.PowerManagerUtils import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder -import android.text.TextUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect @@ -683,6 +689,8 @@ class ConversationsListActivity : BaseActivity() { } else { showSnackbar(getString(R.string.send_to_forbidden)) } + } else if (conversation.checkIfVoiceRoom()) { + startACall(false, true) } else { openConversation() } @@ -921,6 +929,79 @@ class ConversationsListActivity : BaseActivity() { ClosedInterfaceImpl().isGooglePlayServicesAvailable } + fun isOneToOneConversation() = + selectedConversation != null && + selectedConversation?.type != null && + selectedConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + + private fun getIntentForCall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean): Intent? { + selectedConversation?.let { + val pp = ParticipantPermissions(currentUser?.capabilities?.spreedCapability, it) + + val bundle = Bundle() + bundle.putString(KEY_ROOM_TOKEN, it.token) + bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, "") + bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, currentUser?.baseUrl!!) + bundle.putString(KEY_CONVERSATION_NAME, it.displayName) + bundle.putInt(KEY_RECORDING_STATE, it.callRecording) + bundle.putBoolean(KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(it)) + bundle.putBoolean( + BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO, + pp.canPublishAudio() + ) + bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, isOneToOneConversation()) + bundle.putBoolean( + BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO, + pp.canPublishVideo() + ) + + if (isVoiceOnlyCall) { + bundle.putBoolean(KEY_CALL_VOICE_ONLY, true) + } + if (callWithoutNotification) { + bundle.putBoolean(BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION, true) + } + + if (it.objectType == ConversationEnums.ObjectType.ROOM) { + bundle.putBoolean(KEY_IS_BREAKOUT_ROOM, true) + } + + val callIntent = Intent(this, CallActivity::class.java) + callIntent.putExtras(bundle) + return callIntent + } + + return null + } + + private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) { + selectedConversation?.let { + val bundle = Bundle() + bundle.putString(KEY_ROOM_TOKEN, selectedConversation!!.token) + bundle.putString(KEY_SHARED_TEXT, textToPaste) + if (selectedMessageId != null) { + bundle.putString(BundleKeys.KEY_MESSAGE_ID, selectedMessageId) + selectedMessageId = null + } + + val chatIntent = Intent(context, ChatActivity::class.java) + chatIntent.putExtras(bundle) + + if (currentUser != null) { + val pp = ParticipantPermissions(currentUser?.capabilities?.spreedCapability, it) + if (!pp.canStartCall() && selectedConversation?.hasCall == false) { + Log.e(TAG, "Error starting call from conversations list: call is forbidden") + } else { + ApplicationWideCurrentRoomHolder.getInstance().isDialing = true + val callIntent = getIntentForCall(isVoiceOnlyCall, callWithoutNotification) + if (callIntent != null) { + startActivities(arrayOf(chatIntent, callIntent)) + } + } + } + } + } + private fun openConversation(textToPaste: String? = "") { if (CallActivity.active && selectedConversation!!.token != ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt index 6bf6f86c531..c4b622bd0cc 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt @@ -67,6 +67,7 @@ fun ConversationModel.asEntity() = messageDraft = messageDraft, hiddenPinnedId = hiddenPinnedId, lastPinnedId = lastPinnedId, + attributes = attributes, hiddenUpcomingEvent = hiddenUpcomingEvent ) @@ -125,6 +126,7 @@ fun ConversationEntity.toDomainModel() = messageDraft = messageDraft, hiddenPinnedId = hiddenPinnedId, lastPinnedId = lastPinnedId, + attributes = attributes, hiddenUpcomingEvent = hiddenUpcomingEvent ) @@ -180,5 +182,6 @@ fun Conversation.asEntity(accountId: Long) = hasSensitive = hasSensitive, hasImportant = hasImportant, hiddenPinnedId = hiddenPinnedId, - lastPinnedId = lastPinnedId + lastPinnedId = lastPinnedId, + attributes = attributes ) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt index 5d047906da5..615975ec130 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt @@ -100,6 +100,8 @@ data class ConversationEntity( @ColumnInfo(name = "hasImportant") var hasImportant: Boolean = false, @ColumnInfo(name = "hiddenPinnedId") var hiddenPinnedId: Long? = null, @ColumnInfo(name = "lastPinnedId") var lastPinnedId: Long? = null, + @ColumnInfo(name = "attributes") var attributes: Int? = null, + // local-only field @ColumnInfo(name = "messageDraft") var messageDraft: MessageDraft? = MessageDraft(), // local-only field diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index 86cc518567d..bb61d0b38f4 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -48,7 +48,7 @@ import java.util.Locale ChatMessageEntity::class, ChatBlockEntity::class ], - version = 24, + version = 25, autoMigrations = [ AutoMigration(from = 9, to = 10), AutoMigration(from = 16, to = 17, spec = AutoMigration16To17::class), @@ -56,7 +56,8 @@ import java.util.Locale AutoMigration(from = 20, to = 21), AutoMigration(from = 21, to = 22), AutoMigration(from = 22, to = 23), - AutoMigration(from = 23, to = 24) + AutoMigration(from = 23, to = 24), + AutoMigration(from = 24, to = 25) ], exportSchema = true ) diff --git a/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt b/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt index b0297f6607b..6b1092aee8a 100644 --- a/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt +++ b/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt @@ -66,6 +66,7 @@ data class ConversationModel( var hasImportant: Boolean = false, var lastPinnedId: Long? = null, var hiddenPinnedId: Long? = null, + var attributes: Int? = null, // attributes that don't come from API. This should be changed?! var password: String? = null, @@ -74,6 +75,11 @@ data class ConversationModel( ) { companion object { + fun ConversationModel?.checkIfVoiceRoom(): Boolean = + this?.attributes?.let { + it and ConversationEnums.Preset.VOICE_ROOM.ordinal != 0 + } ?: false + @Suppress("LongMethod") fun mapToConversationModel(conversation: Conversation, user: User): ConversationModel = ConversationModel( @@ -136,7 +142,8 @@ data class ConversationModel( hasSensitive = conversation.hasSensitive, hasImportant = conversation.hasImportant, lastPinnedId = conversation.lastPinnedId, - hiddenPinnedId = conversation.hiddenPinnedId + hiddenPinnedId = conversation.hiddenPinnedId, + attributes = conversation.attributes ) } } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt index d32230cf76a..019fc7ce8f4 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt @@ -177,5 +177,9 @@ data class Conversation( var lastPinnedId: Long? = null, @JsonField(name = ["hiddenPinnedId"]) - var hiddenPinnedId: Long? = null + var hiddenPinnedId: Long? = null, + + // https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) + @JsonField(name = ["attributes"]) + var attributes: Int? = null ) : Parcelable diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/ConversationEnums.kt b/app/src/main/java/com/nextcloud/talk/models/json/conversations/ConversationEnums.kt index c91c7eaf89f..ce0347ae849 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/conversations/ConversationEnums.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/ConversationEnums.kt @@ -49,4 +49,9 @@ class ConversationEnums { PHONE_PERSIST, INSTANT_MEETING } + + enum class Preset { + DEFAULT, + VOICE_ROOM + } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt index 9a74248387d..1b1370bd804 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt @@ -281,7 +281,8 @@ object ApiUtils { baseUrl: String? = null, source: String? = null, invite: String? = null, - conversationName: String? = null + conversationName: String? = null, + preset: String? = null ): RetrofitBucket { val retrofitBucket = RetrofitBucket() retrofitBucket.url = getUrlForRooms(version, baseUrl) @@ -289,6 +290,7 @@ object ApiUtils { queryMap["roomType"] = roomType invite?.let { queryMap["invite"] = it } source?.let { queryMap["source"] = it } + preset?.let { queryMap["preset"] = it } conversationName?.let { queryMap["roomName"] = it } retrofitBucket.queryMap = queryMap return retrofitBucket diff --git a/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt b/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt index ca614ce9357..ba6a964e16f 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt @@ -64,7 +64,8 @@ enum class SpreedFeatures(val value: String) { THREADS("threads"), PINNED_MESSAGES("pinned-messages"), SCHEDULED_MESSAGES("scheduled-messages"), - REACT_PERMISSION("react-permission") + REACT_PERMISSION("react-permission"), + CONVERSATION_PRESETS("conversation-presets") } @Suppress("TooManyFunctions") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ada7d33978e..856aa8c5b0b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -853,6 +853,10 @@ How to translate with transifex: Switch to main room Switch to breakout room + Default + Voice room + Send messages, create threads, and start voice and video calls + Directly join the call, ideal for catch-ups or spontaneous meetings Invitations