From 2681e6ef24d65489cea2db2f34403e82abe28a24 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Tue, 22 Oct 2019 12:49:29 +0200 Subject: [PATCH] More progress Signed-off-by: Mario Danic --- app/build.gradle | 13 +- .../1.json | 179 ++++++++++++++++ .../talk/adapters/items/CallItem.java | 192 ------------------ .../talk/adapters/items/ConversationItem.java | 18 +- .../CallNotificationController.java | 6 +- .../talk/controllers/ChatController.kt | 46 +++-- .../talk/controllers/ContactsController.java | 4 +- .../controllers/ConversationInfoController.kt | 34 ++-- .../controllers/WebViewLoginController.java | 3 + .../bottomsheet/OperationsMenuController.java | 6 +- .../talk/jobs/NotificationWorker.java | 5 +- .../talk/models/json/chat/ChatMessage.java | 2 +- .../json/conversations/Conversation.java | 173 ---------------- .../models/json/conversations/Conversation.kt | 180 ++++++++++++++++ .../converters/EnumRoomTypeConverter.java | 22 +- .../NextcloudTalkOfflineRepositoryImpl.kt | 99 +++++++++ .../repository/NextcloudTalkRepositoryImpl.kt | 15 +- .../newarch/data/source/remote/ApiService.kt | 1 - .../talk/newarch/di/module/NetworkModule.kt | 9 +- .../talk/newarch/di/module/StorageModule.kt | 20 ++ .../NextcloudTalkOfflineRepository.kt | 56 +++++ .../repository/NextcloudTalkRepository.kt | 14 +- .../domain/usecases/base/UseCaseResponse.kt | 2 +- .../ConversationListViewModelFactory.kt | 12 +- .../ConversationsListView.kt | 33 ++- .../ConversationsListViewModel.kt | 158 +++++++------- .../di/module/ConversationsListModule.kt | 8 +- .../local/converters/ChatMessageConverter.kt | 41 ++++ .../ConversationReadOnlyStateConverter.kt | 42 ++++ .../converters/ConversationTypeConverter.kt | 45 ++++ .../local/converters/LobbyStateConverter.kt | 39 ++++ .../converters/NotificationLevelConverter.kt | 45 ++++ .../converters/ParticipantTypeConverter.kt | 50 +++++ .../newarch/local/dao/ConversationsDao.kt | 103 ++++++++++ .../talk/newarch/local/db/TalkDatabase.kt | 63 ++++++ .../local/models/ConversationEntity.kt | 130 ++++++++++++ .../talk/newarch/utils/Extensions.kt | 2 +- .../talk/newarch/utils/NetworkUtils.kt | 3 +- .../nextcloud/talk/newarch/utils/ViewState.kt | 1 - .../layout/controller_conversations_rv.xml | 65 +++--- app/src/main/res/values-b+en+001/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs-rCZ/strings.xml | 2 +- app/src/main/res/values-da/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-gl/strings.xml | 2 +- app/src/main/res/values-hr/strings.xml | 2 +- app/src/main/res/values-hu-rHU/strings.xml | 2 +- app/src/main/res/values-is/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-iw/strings.xml | 2 +- app/src/main/res/values-ja-rJP/strings.xml | 2 +- app/src/main/res/values-ko/strings.xml | 2 +- app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt-rPT/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk-rSK/strings.xml | 2 +- app/src/main/res/values-sl/strings.xml | 2 +- app/src/main/res/values-sr/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 71 files changed, 1388 insertions(+), 613 deletions(-) create mode 100644 app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json delete mode 100644 app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java delete mode 100644 app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/data/repository/NextcloudTalkOfflineRepositoryImpl.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkOfflineRepository.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationReadOnlyStateConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationTypeConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/converters/LobbyStateConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/converters/NotificationLevelConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/converters/ParticipantTypeConverter.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt diff --git a/app/build.gradle b/app/build.gradle index 3cbd78b9d..fbf41f376 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -70,15 +70,18 @@ android { javaCompileOptions { annotationProcessorOptions { - arguments = [ - parcelerStacktrace: "true" - ] + arguments = ["room.schemaLocation": + "$projectDir/schemas".toString()] } } dataBinding { enabled = true } + + androidExtensions { + experimental = true + } } dexOptions { @@ -109,6 +112,10 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = "1.8" + } + lintOptions { abortOnError false htmlReport true diff --git a/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json new file mode 100644 index 000000000..239bb5f9d --- /dev/null +++ b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json @@ -0,0 +1,179 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "a547a767687b46e3e5768a3d77d5d212", + "entities": [ + { + "tableName": "conversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user` INTEGER NOT NULL, `conversation_id` TEXT NOT NULL, `token` TEXT, `name` TEXT, `display_name` TEXT, `type` INTEGER, `count` INTEGER NOT NULL, `number_of_guests` INTEGER NOT NULL, `participants_count` INTEGER NOT NULL, `participant_type` INTEGER, `has_password` INTEGER NOT NULL, `session_id` TEXT, `favorite` INTEGER NOT NULL, `last_activity` INTEGER NOT NULL, `unread_messages` INTEGER NOT NULL, `unread_mention` INTEGER NOT NULL, `last_message` TEXT, `object_type` TEXT, `notification_level` INTEGER, `read_only_state` INTEGER, `lobby_state` INTEGER, `lobby_timer` INTEGER, `last_read_message_id` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, PRIMARY KEY(`user`, `conversation_id`))", + "fields": [ + { + "fieldPath": "userId", + "columnName": "user", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayName", + "columnName": "display_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "numberOfGuests", + "columnName": "number_of_guests", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "participantsCount", + "columnName": "participants_count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "participantType", + "columnName": "participant_type", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasPassword", + "columnName": "has_password", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sessionId", + "columnName": "session_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "favorite", + "columnName": "favorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastActivity", + "columnName": "last_activity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadMessages", + "columnName": "unread_messages", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadMention", + "columnName": "unread_mention", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastMessage", + "columnName": "last_message", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "objectType", + "columnName": "object_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "notificationLevel", + "columnName": "notification_level", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "conversationReadOnlyState", + "columnName": "read_only_state", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lobbyState", + "columnName": "lobby_state", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lobbyTimer", + "columnName": "lobby_timer", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastReadMessageId", + "columnName": "last_read_message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "modifiedAt", + "columnName": "modified_at", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "changing", + "columnName": "changing", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "user", + "conversation_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, 'a547a767687b46e3e5768a3d77d5d212')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java deleted file mode 100644 index ce6adfd67..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/CallItem.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.nextcloud.talk.adapters.items; - -import android.content.res.Resources; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.ImageButton; -import android.widget.ImageView; -import androidx.emoji.widget.EmojiTextView; -import butterknife.BindView; -import butterknife.ButterKnife; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.drawee.view.SimpleDraweeView; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.events.MoreMenuClickEvent; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.models.json.conversations.Conversation; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.DisplayUtils; -import eu.davidea.flexibleadapter.FlexibleAdapter; -import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; -import eu.davidea.flexibleadapter.items.IFilterable; -import eu.davidea.flexibleadapter.utils.FlexibleUtils; -import eu.davidea.viewholders.FlexibleViewHolder; -import java.util.List; -import java.util.regex.Pattern; -import org.greenrobot.eventbus.EventBus; - -public class CallItem extends AbstractFlexibleItem - implements IFilterable { - - private Conversation conversation; - private UserEntity userEntity; - - public CallItem(Conversation conversation, UserEntity userEntity) { - this.conversation = conversation; - this.userEntity = userEntity; - } - - @Override - public boolean equals(Object o) { - if (o instanceof CallItem) { - CallItem inItem = (CallItem) o; - return conversation.equals(inItem.getModel()); - } - return false; - } - - @Override - public int hashCode() { - return conversation.hashCode(); - } - - /** - * @return the model object - */ - - public Conversation getModel() { - return conversation; - } - - /** - * Filter is applied to the model fields. - */ - - @Override - public int getLayoutRes() { - return R.layout.rv_item_conversation; - } - - @Override - public RoomItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { - return new RoomItemViewHolder(view, adapter); - } - - @Override - public void bindViewHolder(final FlexibleAdapter adapter, RoomItemViewHolder holder, int position, - List payloads) { - if (adapter.hasFilter()) { - FlexibleUtils.highlightText(holder.roomDisplayName, conversation.getDisplayName(), - String.valueOf(adapter.getFilter(String.class)), - NextcloudTalkApplication.Companion.getSharedApplication() - .getResources().getColor(R.color.colorPrimary)); - } else { - holder.roomDisplayName.setText(conversation.getDisplayName()); - } - - if (conversation.getLastPing() == 0) { - holder.roomLastPing.setText(R.string.nc_never); - } else { - holder.roomLastPing.setText( - DateUtils.getRelativeTimeSpanString(conversation.getLastPing() * 1000L, - System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE)); - } - - if (conversation.hasPassword) { - holder.passwordProtectedImageView.setVisibility(View.VISIBLE); - } else { - holder.passwordProtectedImageView.setVisibility(View.GONE); - } - - Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources(); - switch (conversation.getType()) { - case ROOM_TYPE_ONE_TO_ONE_CALL: - holder.avatarImageView.setVisibility(View.VISIBLE); - - holder.moreMenuButton.setContentDescription(String.format(resources.getString(R.string - .nc_description_more_menu_one_to_one), conversation.getDisplayName())); - - if (!TextUtils.isEmpty(conversation.getName())) { - DraweeController draweeController = Fresco.newDraweeControllerBuilder() - .setOldController(holder.avatarImageView.getController()) - .setAutoPlayAnimations(true) - .setImageRequest(DisplayUtils.getImageRequestForUrl( - ApiUtils.getUrlForAvatarWithName(userEntity.getBaseUrl(), - conversation.getName(), - R.dimen.avatar_size), null)) - .build(); - holder.avatarImageView.setController(draweeController); - } else { - holder.avatarImageView.setVisibility(View.GONE); - } - break; - case ROOM_GROUP_CALL: - holder.moreMenuButton.setContentDescription(String.format(resources.getString(R.string - .nc_description_more_menu_group), conversation.getDisplayName())); - holder.avatarImageView.setActualImageResource(R.drawable.ic_people_group_white_24px); - holder.avatarImageView.setVisibility(View.VISIBLE); - break; - case ROOM_PUBLIC_CALL: - holder.moreMenuButton.setContentDescription(String.format(resources.getString(R.string - .nc_description_more_menu_public), conversation.getDisplayName())); - holder.avatarImageView.setActualImageResource(R.drawable.ic_link_white_24px); - holder.avatarImageView.setVisibility(View.VISIBLE); - break; - default: - holder.avatarImageView.setVisibility(View.GONE); - } - - holder.moreMenuButton.setOnClickListener( - view -> EventBus.getDefault().post(new MoreMenuClickEvent(conversation))); - } - - @Override - public boolean filter(String constraint) { - return conversation.getDisplayName() != null && - Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL) - .matcher(conversation.getDisplayName().trim()) - .find(); - } - - static class RoomItemViewHolder extends FlexibleViewHolder { - - @BindView(R.id.name_text) - public EmojiTextView roomDisplayName; - @BindView(R.id.secondary_text) - public EmojiTextView roomLastPing; - @BindView(R.id.avatar_image) - public SimpleDraweeView avatarImageView; - @BindView(R.id.more_menu) - public ImageButton moreMenuButton; - @BindView(R.id.password_protected_image_view) - ImageView passwordProtectedImageView; - - RoomItemViewHolder(View view, FlexibleAdapter adapter) { - super(view, adapter); - ButterKnife.bind(this, view); - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java index 0941860dd..a6ec18743 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.java @@ -104,7 +104,7 @@ public class ConversationItem holder.dialogAvatar.setController(null); - if (conversation.isUpdating()) { + if (conversation.getChanging()) { holder.progressBar.setVisibility(View.VISIBLE); } else { holder.progressBar.setVisibility(View.GONE); @@ -127,7 +127,7 @@ public class ConversationItem holder.dialogUnreadBubble.setText("99+"); } - if (conversation.isUnreadMention()) { + if (conversation.getUnreadMention()) { holder.dialogUnreadBubble.setBackground( context.getDrawable(R.drawable.bubble_circle_unread_mention)); } else { @@ -138,13 +138,13 @@ public class ConversationItem holder.dialogUnreadBubble.setVisibility(View.GONE); } - if (conversation.isHasPassword()) { + if (conversation.getHasPassword()) { holder.passwordProtectedRoomImageView.setVisibility(View.VISIBLE); } else { holder.passwordProtectedRoomImageView.setVisibility(View.GONE); } - if (conversation.isFavorite()) { + if (conversation.getFavorite()) { holder.pinnedConversationImageView.setVisibility(View.VISIBLE); } else { holder.pinnedConversationImageView.setVisibility(View.GONE); @@ -157,7 +157,7 @@ public class ConversationItem System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE)); if (!TextUtils.isEmpty(conversation.getLastMessage().getSystemMessage()) - || Conversation.ConversationType.ROOM_SYSTEM.equals(conversation.getType())) { + || Conversation.ConversationType.SYSTEM_CONVERSATION.equals(conversation.getType())) { holder.dialogLastMessage.setText(conversation.getLastMessage().getText()); } else { String authorDisplayName = ""; @@ -213,7 +213,7 @@ public class ConversationItem } } - if (Conversation.ConversationType.ROOM_SYSTEM.equals(conversation.getType())) { + if (Conversation.ConversationType.SYSTEM_CONVERSATION.equals(conversation.getType())) { Drawable[] layers = new Drawable[2]; layers[0] = context.getDrawable(R.drawable.ic_launcher_background); layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground); @@ -227,7 +227,7 @@ public class ConversationItem if (shouldLoadAvatar) { switch (conversation.getType()) { - case ROOM_TYPE_ONE_TO_ONE_CALL: + case ONE_TO_ONE_CONVERSATION: if (!TextUtils.isEmpty(conversation.getName())) { DraweeController draweeController = Fresco.newDraweeControllerBuilder() .setOldController(holder.dialogAvatar.getController()) @@ -241,13 +241,13 @@ public class ConversationItem holder.dialogAvatar.setVisibility(View.GONE); } break; - case ROOM_GROUP_CALL: + case GROUP_CONVERSATION: holder.dialogAvatar.getHierarchy() .setImage(new BitmapDrawable( DisplayUtils.getRoundedBitmapFromVectorDrawableResource(context.getResources(), R.drawable.ic_people_group_white_24px)), 100, true); break; - case ROOM_PUBLIC_CALL: + case PUBLIC_CONVERSATION: holder.dialogAvatar.getHierarchy().setImage(new BitmapDrawable(DisplayUtils .getRoundedBitmapFromVectorDrawableResource(context.getResources(), R.drawable.ic_link_white_24px)), 100, true); diff --git a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java index 26b75aa40..1edfbe180 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java @@ -411,7 +411,7 @@ public class CallNotificationController extends BaseController { private void loadAvatar() { switch (currentConversation.getType()) { - case ROOM_TYPE_ONE_TO_ONE_CALL: + case ONE_TO_ONE_CONVERSATION: avatarImageView.setVisibility(View.VISIBLE); ImageRequest imageRequest = @@ -463,12 +463,12 @@ public class CallNotificationController extends BaseController { }, UiThreadImmediateExecutorService.getInstance()); break; - case ROOM_GROUP_CALL: + case GROUP_CONVERSATION: avatarImageView.getHierarchy() .setImage(DisplayUtils.getRoundedDrawable( context.getDrawable(R.drawable.ic_people_group_white_24px)) , 100, true); - case ROOM_PUBLIC_CALL: + case PUBLIC_CONVERSATION: avatarImageView.getHierarchy() .setImage(DisplayUtils.getRoundedDrawable( context.getDrawable(R.drawable.ic_people_group_white_24px)) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 53ee3e71b..81ef833f4 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -312,7 +312,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter private fun loadAvatarForStatusBar() { if (currentConversation != null && currentConversation?.type != null && currentConversation?.type == Conversation.ConversationType - .ROOM_TYPE_ONE_TO_ONE_CALL && activity != null && conversationVoiceCallMenuItem != null + .ONE_TO_ONE_CONVERSATION && activity != null && conversationVoiceCallMenuItem != null ) { val avatarSize = DisplayUtils.convertDpToPixel( conversationVoiceCallMenuItem?.icon!! @@ -536,9 +536,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter } private fun checkReadOnlyState() { - if (currentConversation != null) { - if (currentConversation?.shouldShowLobby( - conversationUser + if (currentConversation != null && conversationUser != null) { + if (currentConversation?.shouldShowLobby(conversationUser ) == true || currentConversation?.conversationReadOnlyState != null && currentConversation?.conversationReadOnlyState == Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY ) { @@ -555,8 +554,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter conversationVideoMenuItem?.icon?.alpha = 255 } - if (currentConversation != null && currentConversation!!.shouldShowLobby - (conversationUser) + if (conversationUser != null && currentConversation != null && currentConversation!! + .shouldShowLobby(conversationUser) ) { messageInputView?.visibility = View.GONE } else { @@ -567,8 +566,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter } private fun checkLobbyState() { - if (currentConversation != null && currentConversation?.isLobbyViewApplicable( - conversationUser + if (currentConversation != null && conversationUser != null && currentConversation?.isLobbyViewApplicable(conversationUser ) == true ) { @@ -754,9 +752,11 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter override fun getTitle(): String? { if (currentConversation != null && currentConversation?.displayName != null) { - return EmojiCompat.get() - .process(currentConversation!!.displayName) - .toString() + return currentConversation!!.displayName?.let { + EmojiCompat.get() + .process(it) + .toString() + } } else { return "" } @@ -1032,7 +1032,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter return } - if (currentConversation != null && currentConversation!!.shouldShowLobby(conversationUser)) { + if (currentConversation != null && conversationUser != null && currentConversation!! + .shouldShowLobby(conversationUser)) { return } @@ -1050,8 +1051,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter lookingIntoFuture = true } else if (isFirstMessagesProcessing) { if (currentConversation != null) { - globalLastKnownFutureMessageId = currentConversation!!.lastReadMessage - globalLastKnownPastMessageId = currentConversation!!.lastReadMessage + globalLastKnownFutureMessageId = currentConversation!!.lastReadMessageId + globalLastKnownPastMessageId = currentConversation!!.lastReadMessageId fieldMap["includeLastKnown"] = 1 } } @@ -1188,7 +1189,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter val chatMessage = chatMessageList[i] chatMessage.isOneToOneConversation = - currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + currentConversation?.type == Conversation.ConversationType.ONE_TO_ONE_CONVERSATION chatMessage.isLinkPreviewAllowed = isLinkPreviewAllowed chatMessage.activeUser = conversationUser @@ -1258,7 +1259,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter .actorId, -1 ) && adapter!!.getSameAuthorLastMessagesCount(chatMessage.actorId) % 5 > 0) chatMessage.isOneToOneConversation = - (currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) + (currentConversation?.type == Conversation.ConversationType.ONE_TO_ONE_CONVERSATION) adapter?.addToStart(chatMessage, shouldScroll) } @@ -1447,7 +1448,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter @Subscribe(threadMode = ThreadMode.BACKGROUND) fun onMessageEvent(userMentionClickEvent: UserMentionClickEvent) { if (currentConversation?.type != Conversation.ConversationType - .ROOM_TYPE_ONE_TO_ONE_CALL || currentConversation?.name != + .ONE_TO_ONE_CONVERSATION || currentConversation?.name != userMentionClickEvent.userId ) { val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( @@ -1482,10 +1483,13 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter ) conversationIntent.putExtras(bundle) - ConductorRemapping.remapChatController( - router, conversationUser.id, - roomOverall.ocs.data.token, bundle, false - ) + if (roomOverall != null && roomOverall.ocs != null && roomOverall.ocs.data != + null && roomOverall.ocs.data.token != null) { + ConductorRemapping.remapChatController( + router, conversationUser.id, + roomOverall.ocs.data.token!!, bundle, false + ) + } } } else { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java index 9c42d7dfa..a9f935a58 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java @@ -372,9 +372,9 @@ public class ContactsController extends BaseController implements SearchView.OnQ Bundle bundle = new Bundle(); Conversation.ConversationType roomType; if (isPublicCall) { - roomType = Conversation.ConversationType.ROOM_PUBLIC_CALL; + roomType = Conversation.ConversationType.PUBLIC_CONVERSATION; } else { - roomType = Conversation.ConversationType.ROOM_GROUP_CALL; + roomType = Conversation.ConversationType.GROUP_CONVERSATION; } ArrayList userIdsArray = new ArrayList<>(selectedUserIds); diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt index 9cb3798b2..9160f013b 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt @@ -63,7 +63,7 @@ import com.nextcloud.talk.jobs.LeaveConversationWorker import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType -import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.ROOM_PUBLIC_CALL +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.PUBLIC_CONVERSATION import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter import com.nextcloud.talk.models.json.generic.GenericOverall @@ -253,8 +253,8 @@ class ConversationInfoController(args: Bundle) : BaseController(), private fun setupWebinaryView() { if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type - == Conversation.ConversationType.ROOM_GROUP_CALL || conversation!!.type == - Conversation.ConversationType.ROOM_PUBLIC_CALL) && conversation!!.canModerate( + == Conversation.ConversationType.GROUP_CONVERSATION || conversation!!.type == + Conversation.ConversationType.PUBLIC_CONVERSATION) && conversation!!.canModerate( conversationUser ) ) { @@ -270,8 +270,8 @@ class ConversationInfoController(args: Bundle) : BaseController(), startTimeView.setOnClickListener { MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show { val currentTimeCalendar = Calendar.getInstance() - if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) { - currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer * 1000 + if (conversation != null && conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) { + currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer!! * 1000 } dateTimePicker(minDateTime = Calendar.getInstance(), requireFutureDateTime = @@ -309,7 +309,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != java.lang.Long.MIN_VALUE && conversation!!.lobbyTimer != 0L) { startTimeView.setSummary( - DateUtils.getLocalDateStringFromTimestampForLobby(conversation!!.lobbyTimer) + conversation!!.lobbyTimer?.let { DateUtils.getLocalDateStringFromTimestampForLobby(it) } ) } else { startTimeView.setSummary(R.string.nc_manual) @@ -670,7 +670,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), deleteConversationAction.visibility = View.VISIBLE } - if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) { + if (Conversation.ConversationType.SYSTEM_CONVERSATION == conversation!!.type) { muteCalls.visibility = View.GONE } @@ -701,7 +701,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), } private fun setupGeneralSettings() { - if (conversation != null) { + if (conversation != null && conversationUser != null) { changeConversationName.value = conversation!!.displayName if (conversation!!.isNameEditable(conversationUser)) { @@ -710,12 +710,12 @@ class ConversationInfoController(args: Bundle) : BaseController(), changeConversationName.visibility = View.GONE } - favoriteConversationAction.value = conversation!!.isFavorite - if (conversation!!.type.equals(ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) || conversation!! - .type.equals(ConversationType.ROOM_SYSTEM)) { + favoriteConversationAction.value = conversation!!.favorite + if (conversation!!.type!!.equals(ConversationType.ONE_TO_ONE_CONVERSATION) || conversation!! + .type!!.equals(ConversationType.SYSTEM_CONVERSATION)) { allowGuestsAction.visibility = View.GONE } else { - allowGuestsAction.value = conversation!!.type == ROOM_PUBLIC_CALL + allowGuestsAction.value = conversation!!.type == PUBLIC_CONVERSATION } (allowGuestsAction.findViewById(R.id.mp_checkable) as SwitchCompat) @@ -813,7 +813,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), } private fun setProperNotificationValue(conversation: Conversation?) { - if (conversation!!.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { + if (conversation!!.type == Conversation.ConversationType.ONE_TO_ONE_CONVERSATION) { // hack to see if we get mentioned always or just on mention if (conversationUser!!.hasSpreedFeatureCapability("mention-flag")) { messageNotificationLevel.value = "always" @@ -827,7 +827,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), private fun loadConversationAvatar() { when (conversation!!.type) { - Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty + Conversation.ConversationType.ONE_TO_ONE_CONVERSATION -> if (!TextUtils.isEmpty (conversation!!.name) ) { val draweeController = Fresco.newDraweeControllerBuilder() @@ -844,21 +844,21 @@ class ConversationInfoController(args: Bundle) : BaseController(), .build() conversationAvatarImageView.controller = draweeController } - Conversation.ConversationType.ROOM_GROUP_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage( + Conversation.ConversationType.GROUP_CONVERSATION -> conversationAvatarImageView.hierarchy.setPlaceholderImage( DisplayUtils .getRoundedBitmapDrawableFromVectorDrawableResource( resources, R.drawable.ic_people_group_white_24px ) ) - Conversation.ConversationType.ROOM_PUBLIC_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage( + Conversation.ConversationType.PUBLIC_CONVERSATION -> conversationAvatarImageView.hierarchy.setPlaceholderImage( DisplayUtils .getRoundedBitmapDrawableFromVectorDrawableResource( resources, R.drawable.ic_link_white_24px ) ) - Conversation.ConversationType.ROOM_SYSTEM -> { + Conversation.ConversationType.SYSTEM_CONVERSATION -> { val layers = arrayOfNulls(2) layers[0] = context.getDrawable(R.drawable.ic_launcher_background) layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java index 05b18c449..b5f4a0184 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.java @@ -123,6 +123,9 @@ public class WebViewLoginController extends BaseController { private WebViewFidoBridge webViewFidoBridge; + public WebViewLoginController() { + } + public WebViewLoginController(String baseUrl, boolean isPasswordUpdate) { this.baseUrl = baseUrl; this.isPasswordUpdate = isPasswordUpdate; diff --git a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java index aed36a97c..0a23d3c04 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/OperationsMenuController.java @@ -270,7 +270,7 @@ public class OperationsMenuController extends BaseController { invite = invitedGroups.get(0); } - if (conversationType.equals(Conversation.ConversationType.ROOM_PUBLIC_CALL) || + if (conversationType.equals(Conversation.ConversationType.PUBLIC_CONVERSATION) || !currentUser.hasSpreedFeatureCapability("empty-group-room")) { retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(currentUser.getBaseUrl(), "3", invite, conversationName); @@ -314,7 +314,7 @@ public class OperationsMenuController extends BaseController { public void onNext(RoomOverall roomOverall) { conversation = roomOverall.getOcs().getData(); if (conversationType.equals( - Conversation.ConversationType.ROOM_PUBLIC_CALL) + Conversation.ConversationType.PUBLIC_CONVERSATION) && isGroupCallWorkaroundFinal) { performGroupCallWorkaround(credentials); } else { @@ -490,7 +490,7 @@ public class OperationsMenuController extends BaseController { .getFeatures() != null && capabilitiesOverall.getOcs().getData() .getCapabilities().getSpreedCapability() .getFeatures().contains("chat-v2")) { - if (conversation.isHasPassword() && conversation.isGuest()) { + if (conversation.getHasPassword() && conversation.isGuest()) { eventBus.post(new BottomSheetLockEvent(true, 0, true, false)); Bundle bundle = new Bundle(); diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index aed265a9c..3eeee34e8 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -27,7 +27,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; import android.media.AudioAttributes; import android.media.MediaPlayer; import android.net.Uri; @@ -171,12 +170,12 @@ public class NotificationWorker extends Worker { intent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation)); if (conversation.getType() - .equals(Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) || + .equals(Conversation.ConversationType.ONE_TO_ONE_CONVERSATION) || (!TextUtils.isEmpty(conversation.getObjectType()) && "share:password".equals (conversation.getObjectType()))) { context.startActivity(intent); } else { - if (conversation.getType().equals(Conversation.ConversationType.ROOM_GROUP_CALL)) { + if (conversation.getType().equals(Conversation.ConversationType.GROUP_CONVERSATION)) { conversationType = "group"; } else { conversationType = "public"; diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java index 6e3d31af3..1b272771c 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java @@ -43,7 +43,7 @@ import org.parceler.Parcel; @Parcel @Data -@JsonObject +@JsonObject(serializeNullCollectionElements = true, serializeNullObjects = true) public class ChatMessage implements IMessage, MessageContentType, MessageContentType.Image { @JsonIgnore public boolean isGrouped; diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java deleted file mode 100644 index 7c0a3fc6a..000000000 --- a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.models.json.conversations; - -import android.content.res.Resources; -import com.bluelinelabs.logansquare.annotation.JsonField; -import com.bluelinelabs.logansquare.annotation.JsonIgnore; -import com.bluelinelabs.logansquare.annotation.JsonObject; -import com.nextcloud.talk.R; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.models.database.UserEntity; -import com.nextcloud.talk.models.json.chat.ChatMessage; -import com.nextcloud.talk.models.json.converters.EnumLobbyStateConverter; -import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter; -import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter; -import com.nextcloud.talk.models.json.converters.EnumReadOnlyConversationConverter; -import com.nextcloud.talk.models.json.converters.EnumRoomTypeConverter; -import com.nextcloud.talk.models.json.participants.Participant; -import java.util.HashMap; -import lombok.Data; -import org.parceler.Parcel; - -@Parcel -@Data -@JsonObject -public class Conversation { - @JsonField(name = "id") - public String conversationId; - @JsonField(name = "token") - public String token; - @JsonField(name = "name") - public String name; - @JsonField(name = "displayName") - public String displayName; - @JsonField(name = "type", typeConverter = EnumRoomTypeConverter.class) - public ConversationType type; - @JsonField(name = "count") - public long count; - @JsonField(name = "lastPing") - public long lastPing; - @JsonField(name = "numGuests") - public long numberOfGuests; - @JsonField(name = "guestList") - public HashMap> guestList; - @JsonField(name = "participants") - public HashMap> participants; - @JsonField(name = "participantType", typeConverter = EnumParticipantTypeConverter.class) - public Participant.ParticipantType participantType; - @JsonField(name = "hasPassword") - public boolean hasPassword; - @JsonField(name = "sessionId") - public String sessionId; - public String password; - @JsonField(name = "isFavorite") - public boolean isFavorite; - @JsonField(name = "lastActivity") - public long lastActivity; - @JsonField(name = "unreadMessages") - public int unreadMessages; - @JsonField(name = "unreadMention") - public boolean unreadMention; - @JsonField(name = "lastMessage") - public ChatMessage lastMessage; - @JsonField(name = "objectType") - public String objectType; - @JsonField(name = "notificationLevel", typeConverter = EnumNotificationLevelConverter.class) - public NotificationLevel notificationLevel; - @JsonField(name = "readOnly", typeConverter = EnumReadOnlyConversationConverter.class) - public ConversationReadOnlyState conversationReadOnlyState; - @JsonField(name = "lobbyState", typeConverter = EnumLobbyStateConverter.class) - public LobbyState lobbyState; - @JsonField(name = "lobbyTimer") - public Long lobbyTimer; - @JsonField(name = "lastReadMessage") - public int lastReadMessage; - public boolean updating; - - public boolean isPublic() { - return (ConversationType.ROOM_PUBLIC_CALL.equals(type)); - } - - public boolean isGuest() { - return (Participant.ParticipantType.GUEST.equals(participantType) || - Participant.ParticipantType.USER_FOLLOWING_LINK.equals(participantType)); - } - - private boolean isLockedOneToOne(UserEntity conversationUser) { - return (getType() == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL - && conversationUser.hasSpreedFeatureCapability("locked-one-to-one-rooms")); - } - - public boolean canModerate(UserEntity conversationUser) { - return ((Participant.ParticipantType.OWNER.equals(participantType) - || Participant.ParticipantType.MODERATOR.equals(participantType)) && !isLockedOneToOne( - conversationUser)); - } - - public boolean shouldShowLobby(UserEntity conversationUser) { - return LobbyState.LOBBY_STATE_MODERATORS_ONLY.equals(getLobbyState()) && !canModerate( - conversationUser); - } - - public boolean isLobbyViewApplicable(UserEntity conversationUser) { - return !canModerate(conversationUser) && (getType() == ConversationType.ROOM_GROUP_CALL - || getType() == ConversationType.ROOM_PUBLIC_CALL); - } - - public boolean isNameEditable(UserEntity conversationUser) { - return (canModerate(conversationUser) && !ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL.equals( - type)); - } - - public boolean canLeave(UserEntity conversationUser) { - return !canModerate(conversationUser) || (getType() - != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && getParticipants().size() > 1); - } - - public String getDeleteWarningMessage() { - Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources(); - if (getType() == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { - return String.format(resources.getString(R.string.nc_delete_conversation_one2one), - getDisplayName()); - } else if (getParticipants().size() > 1) { - return resources.getString(R.string.nc_delete_conversation_more); - } - - return resources.getString(R.string.nc_delete_conversation_default); - } - - public enum NotificationLevel { - DEFAULT, - ALWAYS, - MENTION, - NEVER - } - - public enum LobbyState { - LOBBY_STATE_ALL_PARTICIPANTS, - LOBBY_STATE_MODERATORS_ONLY - } - - public enum ConversationReadOnlyState { - CONVERSATION_READ_WRITE, - CONVERSATION_READ_ONLY - } - - @Parcel - public enum ConversationType { - DUMMY, - ROOM_TYPE_ONE_TO_ONE_CALL, - ROOM_GROUP_CALL, - ROOM_PUBLIC_CALL, - ROOM_SYSTEM - } -} 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 new file mode 100644 index 000000000..f73eda166 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt @@ -0,0 +1,180 @@ +/* + * + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.models.json.conversations + +import androidx.annotation.NonNull +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonIgnore +import com.bluelinelabs.logansquare.annotation.JsonObject +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.models.database.UserEntity +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.converters.EnumLobbyStateConverter +import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter +import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter +import com.nextcloud.talk.models.json.converters.EnumReadOnlyConversationConverter +import com.nextcloud.talk.models.json.converters.EnumRoomTypeConverter +import com.nextcloud.talk.models.json.participants.Participant +import lombok.Data +import org.parceler.Parcel +import org.parceler.ParcelConstructor +import java.util.HashMap + +@Parcel +@Data +@JsonObject(serializeNullCollectionElements = true, serializeNullObjects = true) +class Conversation { + @JsonIgnore + @NonNull + var user: Long = 0L + @JsonField(name = ["id"]) + var conversationId: String = "" + @JsonField(name = ["token"]) + var token: String? = null + @JsonField(name = ["name"]) + var name: String? = null + @JsonField(name = ["displayName"]) + var displayName: String? = null + @JsonField(name = ["type"], typeConverter = EnumRoomTypeConverter::class) + var type: ConversationType? = null + @JsonField(name = ["count"]) + var count: Long = 0 + /*@JsonField(name = ["lastPing"]) + var lastPing: Long = 0*/ + @JsonField(name = ["numGuests"]) + var numberOfGuests: Long = 0 + /*@JsonField(name = ["guestList"]) + var guestList: HashMap>? = null*/ + @JsonField(name = ["participants"]) + var participants: HashMap>? = null + @JsonField(name = ["participantType"], typeConverter = EnumParticipantTypeConverter::class) + var participantType: Participant.ParticipantType? = null + @JsonField(name = ["hasPassword"]) + var hasPassword: Boolean = false + @JsonField(name = ["sessionId"]) + var sessionId: String? = null + @JsonIgnore var password: String? = null + @JsonField(name = ["isFavorite"]) + var favorite: Boolean = false + @JsonField(name = ["lastActivity"]) + var lastActivity: Long = 0 + @JsonField(name = ["unreadMessages"]) + var unreadMessages: Int = 0 + @JsonField(name = ["unreadMention"]) + var unreadMention: Boolean = false + @JsonField(name = ["lastMessage"]) + var lastMessage: ChatMessage? = null + @JsonField(name = ["objectType"]) + var objectType: String? = null + @JsonField(name = ["notificationLevel"], typeConverter = EnumNotificationLevelConverter::class) + var notificationLevel: NotificationLevel? = null + @JsonField(name = ["readOnly"], typeConverter = EnumReadOnlyConversationConverter::class) + var conversationReadOnlyState: + ConversationReadOnlyState? = null + @JsonField(name = ["lobbyState"], typeConverter = EnumLobbyStateConverter::class) + var lobbyState: LobbyState? = null + @JsonField(name = ["lobbyTimer"]) + var lobbyTimer: Long? = 0 + @JsonField(name = ["lastReadMessageId"]) + var lastReadMessageId: Int = 0 + var changing: Boolean = false + + val isPublic: Boolean = ConversationType.PUBLIC_CONVERSATION == type + val isGuest: Boolean = + Participant.ParticipantType.GUEST == participantType || + Participant.ParticipantType.USER_FOLLOWING_LINK == participantType + + val deleteWarningMessage: String + get() { + val resources = NextcloudTalkApplication.sharedApplication!!.resources + if (type == ConversationType.ONE_TO_ONE_CONVERSATION) { + return String.format( + resources.getString(R.string.nc_delete_conversation_one2one), + displayName!! + ) + } else if (participants!!.size > 1) { + return resources.getString(R.string.nc_delete_conversation_more) + } + + return resources.getString(R.string.nc_delete_conversation_default) + } + + private fun isLockedOneToOne(conversationUser: UserEntity): Boolean { + return type == ConversationType.ONE_TO_ONE_CONVERSATION && conversationUser + .hasSpreedFeatureCapability( + "locked-one-to-one-rooms" + ) + } + + fun canModerate(conversationUser: UserEntity): Boolean { + return (Participant.ParticipantType.OWNER == participantType || Participant.ParticipantType.MODERATOR == participantType) && !isLockedOneToOne( + conversationUser + ) + } + + fun shouldShowLobby(conversationUser: UserEntity): Boolean { + return LobbyState.LOBBY_STATE_MODERATORS_ONLY == lobbyState && !canModerate( + conversationUser + ) + } + + fun isLobbyViewApplicable(conversationUser: UserEntity): Boolean { + return !canModerate( + conversationUser + ) && (type == ConversationType.GROUP_CONVERSATION || type == ConversationType.PUBLIC_CONVERSATION) + } + + fun isNameEditable(conversationUser: UserEntity): Boolean { + return canModerate(conversationUser) && ConversationType.ONE_TO_ONE_CONVERSATION != type + } + + fun canLeave(conversationUser: UserEntity): Boolean { + return !canModerate( + conversationUser + ) || type != ConversationType.ONE_TO_ONE_CONVERSATION && participants!!.size > 1 + } + + enum class NotificationLevel { + DEFAULT, + ALWAYS, + MENTION, + NEVER + } + + enum class LobbyState { + LOBBY_STATE_ALL_PARTICIPANTS, + LOBBY_STATE_MODERATORS_ONLY + } + + enum class ConversationReadOnlyState { + CONVERSATION_READ_WRITE, + CONVERSATION_READ_ONLY + } + + @Parcel + enum class ConversationType @ParcelConstructor constructor(val value: Int = 1) { + ONE_TO_ONE_CONVERSATION(1), + GROUP_CONVERSATION(2), + PUBLIC_CONVERSATION(3), + SYSTEM_CONVERSATION(4) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java index 7c1744a88..cd7b9cda6 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java +++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java @@ -28,33 +28,31 @@ public class EnumRoomTypeConverter extends IntBasedTypeConverter + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.data.repository + +import androidx.lifecycle.LiveData +import androidx.lifecycle.map +import com.nextcloud.talk.models.database.UserEntity +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkOfflineRepository +import com.nextcloud.talk.newarch.local.db.TalkDatabase +import com.nextcloud.talk.newarch.local.models.toConversation +import com.nextcloud.talk.newarch.local.models.toConversationEntity + +class NextcloudTalkOfflineRepositoryImpl(val nextcloudTalkDatabase: TalkDatabase) : + NextcloudTalkOfflineRepository { + override suspend fun setChangingValueForConversation( + userId: Long, + conversationId: String, + changing: Boolean + ) { + nextcloudTalkDatabase.conversationsDao() + .updateChangingValueForConversation(userId, conversationId, changing) + } + + override suspend fun setFavoriteValueForConversation( + userId: Long, + conversationId: String, + favorite: Boolean + ) { + nextcloudTalkDatabase.conversationsDao() + .updateFavoriteValueForConversation(userId, conversationId, favorite) + } + + override suspend fun deleteConversation( + userId: Long, + conversationId: String + ) { + nextcloudTalkDatabase.conversationsDao() + .deleteConversation(userId, conversationId) + } + + override fun getConversationsForUser(user: UserEntity): LiveData> { + return nextcloudTalkDatabase.conversationsDao() + .getConversationsForUser(user.id) + .map { data -> + data.map { + it.toConversation() + } + } + } + + internal fun getDatabase(): TalkDatabase { + return nextcloudTalkDatabase + } + + override suspend fun clearConversationsForUser(user: UserEntity) { + nextcloudTalkDatabase.conversationsDao() + .clearConversationsForUser(user.id) + } + + override suspend fun saveConversationsForUser( + user: UserEntity, + conversations: List + ) { + nextcloudTalkDatabase.conversationsDao() + .updateConversationsForUser( + user.id, + conversations.map { + it.toConversationEntity() + }.toTypedArray() + ) + } + + override suspend fun deleteConversationForUserWithTimestamp( + user: UserEntity, + timestamp: Long + ) { + nextcloudTalkDatabase.conversationsDao() + .deleteConversationsForUserWithTimestamp(user.id, timestamp) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/NextcloudTalkRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/NextcloudTalkRepositoryImpl.kt index d4b7fedac..a5e41df68 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/NextcloudTalkRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/NextcloudTalkRepositoryImpl.kt @@ -33,15 +33,21 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou user: UserEntity, conversation: Conversation ): GenericOverall { - return apiService.deleteConversation(user.getCredentials(), ApiUtils.getRoom(user.baseUrl, conversation.token)) + return apiService.deleteConversation( + user.getCredentials(), ApiUtils.getRoom(user.baseUrl, conversation.token) + ) } override suspend fun leaveConversationForUser( user: UserEntity, conversation: Conversation ): GenericOverall { - return apiService.leaveConversation(user.getCredentials(), ApiUtils.getUrlForRemoveSelfFromRoom(user - .baseUrl, conversation.token)) + return apiService.leaveConversation( + user.getCredentials(), ApiUtils.getUrlForRemoveSelfFromRoom( + user + .baseUrl, conversation.token + ) + ) } override suspend fun setFavoriteValueForConversation( @@ -66,6 +72,7 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou return apiService.getConversations( user.getCredentials(), ApiUtils.getUrlForGetRooms(user.baseUrl) - ).ocs.data + ) + .ocs.data } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt index 5807d9fad..fd731b9c2 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt @@ -22,7 +22,6 @@ package com.nextcloud.talk.newarch.data.source.remote import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.generic.GenericOverall -import io.reactivex.Observable import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.Header diff --git a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt index cfd3548c2..2a7882fbc 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt @@ -31,7 +31,6 @@ import com.nextcloud.talk.newarch.data.repository.NextcloudTalkRepositoryImpl import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler import com.nextcloud.talk.newarch.data.source.remote.ApiService import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository -import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.utils.NetworkUtils import com.nextcloud.talk.newarch.utils.NetworkUtils.GetProxyRunnable import com.nextcloud.talk.newarch.utils.NetworkUtils.MagicAuthenticator @@ -81,6 +80,7 @@ val NetworkModule = module { single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) } factory { createApiErrorHandler() } single { createNextcloudTalkRepository(get()) } + } fun createCookieManager(): CookieManager { @@ -253,10 +253,3 @@ fun createService(retrofit: Retrofit): ApiService { fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkRepository { return NextcloudTalkRepositoryImpl(apiService) } - -fun createGetConversationsUseCase( - nextcloudTalkRepository: NextcloudTalkRepository, - apiErrorHandler: ApiErrorHandler -): GetConversationsUseCase { - return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler) -} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt index e3bf510bf..cbd013175 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/di/module/StorageModule.kt @@ -21,8 +21,12 @@ package com.nextcloud.talk.newarch.di.module import android.content.Context +import androidx.room.Room import com.nextcloud.talk.R.string import com.nextcloud.talk.models.database.Models +import com.nextcloud.talk.newarch.data.repository.NextcloudTalkOfflineRepositoryImpl +import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkOfflineRepository +import com.nextcloud.talk.newarch.local.db.TalkDatabase import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.preferences.AppPreferences import io.requery.Persistable @@ -31,6 +35,7 @@ import io.requery.reactivex.ReactiveEntityStore import io.requery.reactivex.ReactiveSupport import io.requery.sql.EntityDataStore import net.orange_box.storebox.StoreBox +import org.koin.android.ext.koin.androidApplication import org.koin.android.ext.koin.androidContext import org.koin.dsl.module @@ -39,6 +44,21 @@ val StorageModule = module { single { createSqlCipherDatabaseSource(androidContext()) } single { createDataStore(get()) } single { createUserUtils(get()) } + single { createNextcloudTalkOfflineRepository(get()) } + single { TalkDatabase.getInstance(androidApplication()) } + single { get().conversationsDao() } +} + +fun createNextcloudTalkOfflineRepository(database: TalkDatabase): NextcloudTalkOfflineRepository { + return NextcloudTalkOfflineRepositoryImpl(database) +} + +fun createDatabase(context: Context): TalkDatabase { + return Room.databaseBuilder( + context, + TalkDatabase::class.java, "talk.db" + ) + .build() } fun createPreferences(context: Context): AppPreferences { diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkOfflineRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkOfflineRepository.kt new file mode 100644 index 000000000..e71d24df0 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkOfflineRepository.kt @@ -0,0 +1,56 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.domain.repository + +import androidx.lifecycle.LiveData +import com.nextcloud.talk.models.database.UserEntity +import com.nextcloud.talk.models.json.conversations.Conversation + +interface NextcloudTalkOfflineRepository { + fun getConversationsForUser(user: UserEntity): LiveData> + suspend fun clearConversationsForUser(user: UserEntity) + suspend fun saveConversationsForUser( + user: UserEntity, + conversations: List + ) + + suspend fun setChangingValueForConversation( + userId: Long, + conversationId: String, + changing: Boolean + ) + + suspend fun setFavoriteValueForConversation( + userId: Long, + conversationId: String, + favorite: Boolean + ) + + suspend fun deleteConversation( + userId: Long, + conversationId: String + ) + + suspend fun deleteConversationForUserWithTimestamp( + user: UserEntity, + timestamp: Long + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkRepository.kt index 7987d1b56..7aed61f6f 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/NextcloudTalkRepository.kt @@ -30,7 +30,15 @@ interface NextcloudTalkRepository { user: UserEntity, conversation: Conversation, favorite: Boolean - ) : GenericOverall - suspend fun deleteConversationForUser(user: UserEntity, conversation: Conversation) : GenericOverall - suspend fun leaveConversationForUser(userEntity: UserEntity, conversation: Conversation) : GenericOverall + ): GenericOverall + + suspend fun deleteConversationForUser( + user: UserEntity, + conversation: Conversation + ): GenericOverall + + suspend fun leaveConversationForUser( + userEntity: UserEntity, + conversation: Conversation + ): GenericOverall } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/UseCaseResponse.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/UseCaseResponse.kt index 2c33f963f..73cb26898 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/UseCaseResponse.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/base/UseCaseResponse.kt @@ -23,7 +23,7 @@ package com.nextcloud.talk.newarch.domain.usecases.base import com.nextcloud.talk.newarch.data.model.ErrorModel interface UseCaseResponse { - fun onSuccess(result: Type) + suspend fun onSuccess(result: Type) fun onError(errorModel: ErrorModel?) } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationListViewModelFactory.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationListViewModelFactory.kt index 66b35e6a6..d2ccf64e8 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationListViewModelFactory.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationListViewModelFactory.kt @@ -23,6 +23,7 @@ package com.nextcloud.talk.newarch.features.conversationsList import android.app.Application import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkOfflineRepository import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase @@ -32,15 +33,18 @@ import com.nextcloud.talk.utils.database.user.UserUtils class ConversationListViewModelFactory constructor( private val application: Application, private val conversationsUseCase: GetConversationsUseCase, - private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase, + private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase, private val leaveConversationUseCase: LeaveConversationUseCase, private val deleteConversationUseCase: DeleteConversationUseCase, - private val userUtils: UserUtils + private val userUtils: UserUtils, + private val offlineRepository: NextcloudTalkOfflineRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return ConversationsListViewModel(application, conversationsUseCase, + return ConversationsListViewModel( + application, conversationsUseCase, setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase, - userUtils) as T + userUtils, offlineRepository + ) as T } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt index 6d9547ab9..f378cbbb0 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListView.kt @@ -75,6 +75,7 @@ import kotlinx.android.synthetic.main.view_states.view.errorStateImageView import kotlinx.android.synthetic.main.view_states.view.errorStateTextView import kotlinx.android.synthetic.main.view_states.view.loadingStateView import kotlinx.android.synthetic.main.view_states.view.stateWithMessageView +import org.apache.commons.lang3.builder.CompareToBuilder import org.koin.android.ext.android.inject import org.parceler.Parcels import java.util.ArrayList @@ -180,6 +181,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener, container: ViewGroup ): View { setHasOptionsMenu(true) + actionBar?.show() viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java) viewModel.apply { @@ -243,10 +245,29 @@ class ConversationsListView : BaseView(), OnQueryTextListener, } }) - conversationsLiveListData.observe(this@ConversationsListView, Observer { + conversationsLiveData.observe(this@ConversationsListView, Observer { + if (it.size == 0) { + viewState.value = LOADED_EMPTY + } else { + viewState.value = LOADED + } + val sortedConversationsList = it.toMutableList() + + sortedConversationsList.sortWith(Comparator { conversation1, conversation2 -> + CompareToBuilder() + .append(conversation2.favorite, conversation1.favorite) + .append(conversation2.lastActivity, conversation1.lastActivity) + .toComparison() + }) + val newConversations = mutableListOf() - for (conversation in it) { - newConversations.add(ConversationItem(conversation, viewModel.currentUser, activity)) + for (conversation in sortedConversationsList) { + newConversations.add( + ConversationItem( + conversation, viewModel.currentUserLiveData.value, + activity + ) + ) } recyclerViewAdapter.updateDataSet(newConversations as List>?) @@ -313,7 +334,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener, var displayName = (recyclerViewAdapter.getItem(position) as ConversationItem).model.displayName - if (displayName.length > 8) { + if (displayName!!.length > 8) { displayName = displayName.substring(0, 4) + "..." } @@ -394,12 +415,12 @@ class ConversationsListView : BaseView(), OnQueryTextListener, val conversation = (clickedItem as ConversationItem).model val bundle = Bundle() - bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.currentUser) + bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.currentUserLiveData.value) bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.token) bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId) bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation)) ConductorRemapping.remapChatController( - router, viewModel.currentUser.id, conversation.token, + router, viewModel.currentUserLiveData.value!!.id, conversation!!.token!!, bundle, false ) } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt index c2f3550b8..0968d52ec 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt @@ -26,6 +26,7 @@ import android.graphics.Bitmap import android.graphics.drawable.Drawable import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations import androidx.lifecycle.viewModelScope import com.facebook.common.executors.UiThreadImmediateExecutorService import com.facebook.common.references.CloseableReference @@ -42,22 +43,19 @@ import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel import com.nextcloud.talk.newarch.data.model.ErrorModel +import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkOfflineRepository import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse import com.nextcloud.talk.newarch.utils.ViewState -import com.nextcloud.talk.newarch.utils.ViewState.FAILED -import com.nextcloud.talk.newarch.utils.ViewState.INITIAL_LOAD -import com.nextcloud.talk.newarch.utils.ViewState.LOADED -import com.nextcloud.talk.newarch.utils.ViewState.LOADED_EMPTY import com.nextcloud.talk.newarch.utils.ViewState.LOADING import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.ShareUtils import com.nextcloud.talk.utils.database.user.UserUtils -import org.apache.commons.lang3.builder.CompareToBuilder +import kotlinx.coroutines.launch import org.koin.core.parameter.parametersOf class ConversationsListViewModel constructor( @@ -66,15 +64,18 @@ class ConversationsListViewModel constructor( private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase, private val leaveConversationUseCase: LeaveConversationUseCase, private val deleteConversationUseCase: DeleteConversationUseCase, - private val userUtils: UserUtils + private val userUtils: UserUtils, + private val offlineRepository: NextcloudTalkOfflineRepository ) : BaseViewModel(application) { - private var conversations: MutableList = mutableListOf() - val conversationsLiveListData = MutableLiveData>() - val viewState = MutableLiveData(INITIAL_LOAD) + val viewState = MutableLiveData(LOADING) var messageData: String? = null val searchQuery = MutableLiveData() - var currentUser: UserEntity = userUtils.currentUser + val currentUserLiveData: MutableLiveData = MutableLiveData() + val conversationsLiveData = Transformations.switchMap(currentUserLiveData) { + offlineRepository.getConversationsForUser(it) + } + var currentUserAvatar: MutableLiveData = MutableLiveData() get() { if (field.value == null) { @@ -85,47 +86,52 @@ class ConversationsListViewModel constructor( } fun leaveConversation(conversation: Conversation) { - setConversationUpdateStatus(conversation, true) - - leaveConversationUseCase.invoke(viewModelScope, parametersOf(currentUser, conversation), + viewModelScope.launch { + setConversationUpdateStatus(conversation, true) + } + leaveConversationUseCase.invoke(viewModelScope, parametersOf( + currentUserLiveData.value, + conversation + ), object : UseCaseResponse { - override fun onSuccess(result: GenericOverall) { - // TODO: Use binary search to find the right room - conversations.find { it.conversationId == conversation.conversationId } - ?.let { - conversations.remove(it) - conversationsLiveListData.value = conversations - if (conversations.isEmpty()) { - viewState.value = LOADED_EMPTY - } - } + override suspend fun onSuccess(result: GenericOverall) { + offlineRepository.deleteConversation( + currentUserLiveData.value!!.id, conversation + .conversationId + ) } override fun onError(errorModel: ErrorModel?) { - setConversationUpdateStatus(conversation, false) + messageData = errorModel?.getErrorMessage() + viewModelScope.launch { + setConversationUpdateStatus(conversation, false) + } } }) } fun deleteConversation(conversation: Conversation) { - setConversationUpdateStatus(conversation, true) + viewModelScope.launch { + setConversationUpdateStatus(conversation, true) + } - deleteConversationUseCase.invoke(viewModelScope, parametersOf(currentUser, conversation), + deleteConversationUseCase.invoke(viewModelScope, parametersOf( + currentUserLiveData.value, + conversation + ), object : UseCaseResponse { - override fun onSuccess(result: GenericOverall) { - // TODO: Use binary search to find the right room - conversations.find { it.conversationId == conversation.conversationId } - ?.let { - conversations.remove(it) - conversationsLiveListData.value = conversations - if (conversations.isEmpty()) { - viewState.value = LOADED_EMPTY - } - } + override suspend fun onSuccess(result: GenericOverall) { + offlineRepository.deleteConversation( + currentUserLiveData.value!!.id, conversation + .conversationId + ) } override fun onError(errorModel: ErrorModel?) { - setConversationUpdateStatus(conversation, false) + messageData = errorModel?.getErrorMessage() + viewModelScope.launch { + setConversationUpdateStatus(conversation, false) + } } }) @@ -135,74 +141,63 @@ class ConversationsListViewModel constructor( conversation: Conversation, favorite: Boolean ) { - setConversationUpdateStatus(conversation, true) + viewModelScope.launch { + setConversationUpdateStatus(conversation, true) + } setConversationFavoriteValueUseCase.invoke(viewModelScope, parametersOf( - currentUser, + currentUserLiveData.value, conversation, favorite ), object : UseCaseResponse { - override fun onSuccess(result: GenericOverall) { - // TODO: Use binary search to find the right room - conversations.find { it.conversationId == conversation.conversationId } - ?.apply { - updating = false - isFavorite = favorite - conversationsLiveListData.value = conversations - } + override suspend fun onSuccess(result: GenericOverall) { + offlineRepository.setFavoriteValueForConversation( + currentUserLiveData.value!!.id, + conversation.conversationId, favorite + ) } override fun onError(errorModel: ErrorModel?) { - setConversationUpdateStatus(conversation, false) + messageData = errorModel?.getErrorMessage() + viewModelScope.launch { + setConversationUpdateStatus(conversation, false) + } } }) } fun loadConversations() { - val userChanged = !currentUser.equals(userUtils.currentUser) + val userChanged = !(currentUserLiveData.value?.equals(userUtils.currentUser) ?: false) if (userChanged) { - currentUser = userUtils.currentUser - } - - if ((FAILED).equals(viewState.value) || (LOADED_EMPTY).equals(viewState.value) || - (INITIAL_LOAD).equals(viewState.value) || !currentUser.equals(userUtils.currentUser) || userChanged - ) { + currentUserLiveData.value = userUtils.currentUser viewState.value = LOADING } - getConversationsUseCase.invoke( - viewModelScope, parametersOf(currentUser), object : UseCaseResponse> { - override fun onSuccess(result: List) { - val newConversations = result.toMutableList() + getConversationsUseCase.invoke(viewModelScope, parametersOf(currentUserLiveData.value), object : + UseCaseResponse> { + override suspend fun onSuccess(result: List) { + val mutableList = result.toMutableList() + mutableList.forEach { + it.user = currentUserLiveData.value!!.id + } - newConversations.sortWith(Comparator { conversation1, conversation2 -> - CompareToBuilder() - .append(conversation2.isFavorite, conversation1.isFavorite) - .append(conversation2.lastActivity, conversation1.lastActivity) - .toComparison() - }) - - conversations = newConversations - conversationsLiveListData.value = conversations - viewState.value = if (newConversations.isNotEmpty()) LOADED else LOADED_EMPTY + offlineRepository.saveConversationsForUser(currentUserLiveData.value!!, mutableList) messageData = "" } override fun onError(errorModel: ErrorModel?) { messageData = errorModel?.getErrorMessage() - viewState.value = FAILED } - }) } fun loadAvatar(avatarSize: Int) { val imageRequest = DisplayUtils.getImageRequestForUrl( ApiUtils.getUrlForAvatarWithNameAndPixels( - currentUser.baseUrl, - currentUser.userId, avatarSize + currentUserLiveData.value!!.baseUrl, + currentUserLiveData.value!!.userId, avatarSize ), null ) @@ -257,7 +252,7 @@ class ConversationsListViewModel constructor( fun getConversationMenuItemsForConversation(conversation: Conversation): MutableList { val items = mutableListOf() - if (conversation.isFavorite) { + if (conversation.favorite) { items.add( BasicListItemWithImage( drawable.ic_star_border_black_24dp, @@ -282,7 +277,7 @@ class ConversationsListViewModel constructor( ) } - if (conversation.canLeave(currentUser)) { + if (conversation.canLeave(currentUserLiveData.value!!)) { items.add( BasicListItemWithImage( drawable.ic_exit_to_app_black_24dp, context.getString @@ -291,7 +286,7 @@ class ConversationsListViewModel constructor( ) } - if (conversation.canModerate(currentUser)) { + if (conversation.canModerate(currentUserLiveData.value!!)) { items.add( BasicListItemWithImage( drawable.ic_delete_grey600_24dp, context.getString( @@ -304,14 +299,13 @@ class ConversationsListViewModel constructor( return items } - private fun setConversationUpdateStatus( + private suspend fun setConversationUpdateStatus( conversation: Conversation, value: Boolean ) { - conversations.find { it.conversationId == conversation.conversationId } - ?.apply { - updating = value - conversationsLiveListData.value = conversations - } + offlineRepository.setChangingValueForConversation( + currentUserLiveData.value!!.id, conversation + .conversationId, value + ) } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/di/module/ConversationsListModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/di/module/ConversationsListModule.kt index bf5c2e96b..a92292f3e 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/di/module/ConversationsListModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/di/module/ConversationsListModule.kt @@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.features.conversationsList.di.module import android.app.Application import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkOfflineRepository import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase @@ -41,7 +42,7 @@ val ConversationsListModule = module { factory { createConversationListViewModelFactory( androidApplication(), get(), get(), get(), get - (), get() + (), get(), get() ) } } @@ -81,11 +82,12 @@ fun createConversationListViewModelFactory( setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase, leaveConversationUseCase: LeaveConversationUseCase, deleteConversationUseCase: DeleteConversationUseCase, - userUtils: UserUtils + userUtils: UserUtils, + offlineRepository: NextcloudTalkOfflineRepository ): ConversationListViewModelFactory { return ConversationListViewModelFactory( application, getConversationsUseCase, setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase, - userUtils + userUtils, offlineRepository ) } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageConverter.kt new file mode 100644 index 000000000..02809b946 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageConverter.kt @@ -0,0 +1,41 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.converters + +import androidx.room.TypeConverter +import com.bluelinelabs.logansquare.LoganSquare +import com.nextcloud.talk.models.json.chat.ChatMessage + +class ChatMessageConverter { + @TypeConverter + fun fromChatMessageToString(chatMessage: ChatMessage?): String { + if (chatMessage == null) { + return "" + } else { + return LoganSquare.serialize(chatMessage) + } + } + + @TypeConverter + fun fromStringToChatMessage(value: String): ChatMessage? { + return LoganSquare.parse(value, ChatMessage::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationReadOnlyStateConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationReadOnlyStateConverter.kt new file mode 100644 index 000000000..4a0572113 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationReadOnlyStateConverter.kt @@ -0,0 +1,42 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.converters + +import androidx.room.TypeConverter +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationReadOnlyState +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationReadOnlyState.CONVERSATION_READ_WRITE + +class ConversationReadOnlyStateConverter { + @TypeConverter + fun fromConversationReadOnlyStateToInt(conversationReadOnlyState: ConversationReadOnlyState): + Int { + return conversationReadOnlyState.ordinal + } + + @TypeConverter + fun fromIntToConversationType(value: Int): ConversationReadOnlyState { + when (value) { + 0 -> return CONVERSATION_READ_WRITE + else -> return CONVERSATION_READ_ONLY + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationTypeConverter.kt new file mode 100644 index 000000000..c5de80f54 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ConversationTypeConverter.kt @@ -0,0 +1,45 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.converters + +import androidx.room.TypeConverter +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.GROUP_CONVERSATION +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.ONE_TO_ONE_CONVERSATION +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.PUBLIC_CONVERSATION +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.SYSTEM_CONVERSATION + +class ConversationTypeConverter { + @TypeConverter + fun fromConversationTypeToInt(conversationType: ConversationType): Int { + return conversationType.value + } + + @TypeConverter + fun fromIntToConversationType(value: Int): ConversationType { + when (value) { + 1 -> return ONE_TO_ONE_CONVERSATION + 2 -> return GROUP_CONVERSATION + 3 -> return PUBLIC_CONVERSATION + else -> return SYSTEM_CONVERSATION + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/LobbyStateConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/LobbyStateConverter.kt new file mode 100644 index 000000000..d59523701 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/LobbyStateConverter.kt @@ -0,0 +1,39 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.converters + +import androidx.room.TypeConverter +import com.nextcloud.talk.models.json.conversations.Conversation.LobbyState + +class LobbyStateConverter { + @TypeConverter + fun fromLobbyStateToInt(lobbyState: LobbyState): Int { + return lobbyState.ordinal + } + + @TypeConverter + fun fromIntToLobbyState(value: Int): LobbyState { + when (value) { + 0 -> return LobbyState.LOBBY_STATE_ALL_PARTICIPANTS + else -> return LobbyState.LOBBY_STATE_ALL_PARTICIPANTS + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/NotificationLevelConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/NotificationLevelConverter.kt new file mode 100644 index 000000000..303f34264 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/NotificationLevelConverter.kt @@ -0,0 +1,45 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.converters + +import androidx.room.TypeConverter +import com.nextcloud.talk.models.json.conversations.Conversation.NotificationLevel +import com.nextcloud.talk.models.json.conversations.Conversation.NotificationLevel.ALWAYS +import com.nextcloud.talk.models.json.conversations.Conversation.NotificationLevel.DEFAULT +import com.nextcloud.talk.models.json.conversations.Conversation.NotificationLevel.MENTION +import com.nextcloud.talk.models.json.conversations.Conversation.NotificationLevel.NEVER + +class NotificationLevelConverter { + @TypeConverter + fun fromNotificationLevelToInt(notificationLevel: NotificationLevel): Int { + return notificationLevel.ordinal + } + + @TypeConverter + fun fromIntToNotificationLevel(value: Int): NotificationLevel { + when (value) { + 0 -> return DEFAULT + 1 -> return ALWAYS + 2 -> return MENTION + else -> return NEVER + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ParticipantTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ParticipantTypeConverter.kt new file mode 100644 index 000000000..f051fc2b1 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ParticipantTypeConverter.kt @@ -0,0 +1,50 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.converters + +import androidx.room.TypeConverter +import com.nextcloud.talk.models.json.participants.Participant.ParticipantType +import com.nextcloud.talk.models.json.participants.Participant.ParticipantType.DUMMY +import com.nextcloud.talk.models.json.participants.Participant.ParticipantType.GUEST +import com.nextcloud.talk.models.json.participants.Participant.ParticipantType.MODERATOR +import com.nextcloud.talk.models.json.participants.Participant.ParticipantType.OWNER +import com.nextcloud.talk.models.json.participants.Participant.ParticipantType.USER +import com.nextcloud.talk.models.json.participants.Participant.ParticipantType.USER_FOLLOWING_LINK + +class ParticipantTypeConverter { + @TypeConverter + fun fromParticipantType(participantType: ParticipantType): Int { + return participantType.ordinal + } + + @TypeConverter + fun fromIntToParticipantType(value: Int): ParticipantType { + when (value) { + 0 -> return DUMMY + 1 -> return OWNER + 2 -> return MODERATOR + 3 -> return USER + 4 -> return GUEST + else -> return USER_FOLLOWING_LINK + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt new file mode 100644 index 000000000..11a5747b4 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt @@ -0,0 +1,103 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import com.nextcloud.talk.newarch.local.models.ConversationEntity + +@Dao +abstract class ConversationsDao { + @Query("SELECT * FROM conversations WHERE user = :userId") + abstract fun getConversationsForUser(userId: Long): LiveData> + + @Query("DELETE FROM conversations WHERE user = :userId") + abstract suspend fun clearConversationsForUser(userId: Long) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract suspend fun saveConversation(conversation: ConversationEntity) + + suspend fun saveConversationWithTimestamp(conversation: ConversationEntity) { + saveConversation(conversation.apply { + modifiedAt = System.currentTimeMillis() + }) + } + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract suspend fun saveConversations(vararg conversations: ConversationEntity) + + @Query( + "UPDATE conversations SET changing = :changing WHERE user = :userId AND " + + "'conversation_id' = :conversationId" + ) + abstract suspend fun updateChangingValueForConversation( + userId: Long, + conversationId: String, + changing: Boolean + ) + + @Query( + "UPDATE conversations SET favorite = :favorite, changing = 0 WHERE user = :userId AND 'conversation_id' = :conversationId" + ) + abstract suspend fun updateFavoriteValueForConversation( + userId: Long, + conversationId: String, + favorite: Boolean + ) + + @Query("DELETE FROM conversations WHERE user = :userId AND 'conversation_id' = :conversationId") + abstract suspend fun deleteConversation( + userId: Long, + conversationId: String + ) + + @Delete + abstract suspend fun deleteConversations(vararg conversation: ConversationEntity) + + @Query("DELETE FROM conversations WHERE user = :userId AND 'modified_at' < :timestamp") + abstract suspend fun deleteConversationsForUserWithTimestamp( + userId: Long, + timestamp: Long + ) + + @Transaction + open suspend fun updateConversationsForUser( + userId: Long, + newConversations: + Array + ) { + val timestamp = System.currentTimeMillis() + + val conversationsWithTimestampApplied = newConversations.map { + it.modifiedAt = System.currentTimeMillis() + it + } + .toTypedArray() + + saveConversations(*conversationsWithTimestampApplied) + deleteConversationsForUserWithTimestamp(userId, timestamp) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt new file mode 100644 index 000000000..b84afa16c --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt @@ -0,0 +1,63 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.nextcloud.talk.newarch.local.converters.ChatMessageConverter +import com.nextcloud.talk.newarch.local.converters.ConversationReadOnlyStateConverter +import com.nextcloud.talk.newarch.local.converters.ConversationTypeConverter +import com.nextcloud.talk.newarch.local.converters.LobbyStateConverter +import com.nextcloud.talk.newarch.local.converters.NotificationLevelConverter +import com.nextcloud.talk.newarch.local.converters.ParticipantTypeConverter +import com.nextcloud.talk.newarch.local.dao.ConversationsDao +import com.nextcloud.talk.newarch.local.models.ConversationEntity + +@Database(entities = arrayOf(ConversationEntity::class), version = 1) +@TypeConverters( + ChatMessageConverter::class, LobbyStateConverter::class, + ConversationReadOnlyStateConverter::class, NotificationLevelConverter::class, + ConversationTypeConverter::class, ParticipantTypeConverter::class +) + +abstract class TalkDatabase : RoomDatabase() { + + abstract fun conversationsDao(): ConversationsDao + + companion object { + private const val DB_NAME = "talk.db" + @Volatile + private var INSTANCE: TalkDatabase? = null + + fun getInstance(context: Context): TalkDatabase = + INSTANCE ?: synchronized(this) { + INSTANCE ?: build(context).also { INSTANCE = it } + } + + private fun build(context: Context) = + Room.databaseBuilder(context.applicationContext, TalkDatabase::class.java, DB_NAME) + .build() + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt new file mode 100644 index 000000000..9b8baa341 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/ConversationEntity.kt @@ -0,0 +1,130 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.newarch.local.models + +import androidx.annotation.NonNull +import androidx.room.ColumnInfo +import androidx.room.Entity +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationReadOnlyState +import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType +import com.nextcloud.talk.models.json.conversations.Conversation.LobbyState +import com.nextcloud.talk.models.json.conversations.Conversation.NotificationLevel +import com.nextcloud.talk.models.json.participants.Participant +import java.util.HashMap + +@Entity(tableName = "conversations", primaryKeys = ["user", "conversation_id"]) +data class ConversationEntity( + @NonNull @ColumnInfo(name = "user") var userId: Long, + @NonNull @ColumnInfo(name = "conversation_id") var conversationId: String, + @ColumnInfo(name = "token") var token: String? = null, + @ColumnInfo(name = "name") var name: String? = null, + @ColumnInfo(name = "display_name") var displayName: String? = null, + @ColumnInfo(name = "type") var type: ConversationType? = null, + @ColumnInfo(name = "count") var count: Long = 0, + @ColumnInfo(name = "number_of_guests") var numberOfGuests: Long = 0, + /*@ColumnInfo(name = "guest_list") var guestList: HashMap>? = null, + @ColumnInfo(name = "participants") var participants: HashMap>? = + null, + */ + // hack for participants list + @ColumnInfo(name = "participants_count") var participantsCount: Int = 0, + @ColumnInfo(name = "participant_type") var participantType: Participant.ParticipantType? = null, + @ColumnInfo(name = "has_password") var hasPassword: Boolean = false, + @ColumnInfo(name = "session_id") var sessionId: String? = null, + @ColumnInfo(name = "favorite") var favorite: Boolean = false, + @ColumnInfo(name = "last_activity") var lastActivity: Long = 0, + @ColumnInfo(name = "unread_messages") var unreadMessages: Int = 0, + @ColumnInfo(name = "unread_mention") var unreadMention: Boolean = false, + @ColumnInfo(name = "last_message") var lastMessage: ChatMessage? = null, + @ColumnInfo(name = "object_type") var objectType: String? = null, + @ColumnInfo(name = "notification_level") var notificationLevel: NotificationLevel? = null, + @ColumnInfo( + name = "read_only_state" + ) var conversationReadOnlyState: ConversationReadOnlyState? = null, + @ColumnInfo(name = "lobby_state") var lobbyState: LobbyState? = null, + @ColumnInfo(name = "lobby_timer") var lobbyTimer: Long? = null, + @ColumnInfo(name = "last_read_message_id") var lastReadMessageId: Int = 0, + @ColumnInfo(name = "modified_at") var modifiedAt: Long? = null, + @ColumnInfo(name = "changing") var changing: Boolean = false +) + +fun ConversationEntity.toConversation(): Conversation { + val conversation = Conversation() + conversation.user = this.userId + conversation.conversationId = this.conversationId + conversation.type = this.type + conversation.token = this.token + conversation.name = this.name + conversation.displayName = this.displayName + conversation.count = this.count + conversation.numberOfGuests = this.numberOfGuests + conversation.participants = HashMap() + for (i in 0 until participantsCount) { + conversation.participants?.put(i.toString(), HashMap()) + } + conversation.participantType = this.participantType + conversation.hasPassword = this.hasPassword + conversation.sessionId = this.sessionId + conversation.favorite = this.favorite + conversation.lastActivity = this.lastActivity + conversation.unreadMessages = this.unreadMessages + conversation.unreadMention = this.unreadMention + conversation.lastMessage = this.lastMessage + conversation.objectType = this.objectType + conversation.notificationLevel = this.notificationLevel + conversation.conversationReadOnlyState = this.conversationReadOnlyState + conversation.lobbyState = this.lobbyState + conversation.lobbyTimer = this.lobbyTimer + conversation.lastReadMessageId = this.lastReadMessageId + conversation.changing = this.changing + + return conversation +} + +fun Conversation.toConversationEntity(): ConversationEntity { + val conversationEntity = ConversationEntity(this.user, this.conversationId) + conversationEntity.token = this.token + conversationEntity.name = this.name + conversationEntity.displayName = this.displayName + conversationEntity.count = this.count + conversationEntity.numberOfGuests = this.numberOfGuests + conversationEntity.participantsCount = this.participants?.size ?: 0 + conversationEntity.participantType = this.participantType + conversationEntity.hasPassword = this.hasPassword + conversationEntity.sessionId = this.sessionId + conversationEntity.favorite = this.favorite + conversationEntity.lastActivity = this.lastActivity + conversationEntity.unreadMessages = this.unreadMessages + conversationEntity.unreadMention = this.unreadMention + conversationEntity.lastMessage = this.lastMessage + conversationEntity.objectType = this.objectType + conversationEntity.notificationLevel = this.notificationLevel + conversationEntity.conversationReadOnlyState = this.conversationReadOnlyState + conversationEntity.lobbyState = this.lobbyState + conversationEntity.lobbyTimer = this.lobbyTimer + conversationEntity.lastReadMessageId = this.lastReadMessageId + conversationEntity.type = this.type + conversationEntity.changing = this.changing + + return conversationEntity +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/utils/Extensions.kt b/app/src/main/java/com/nextcloud/talk/newarch/utils/Extensions.kt index eeca61937..474e583c2 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/utils/Extensions.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/utils/Extensions.kt @@ -23,4 +23,4 @@ package com.nextcloud.talk.newarch.utils import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.utils.ApiUtils -fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token) \ No newline at end of file +fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token) diff --git a/app/src/main/java/com/nextcloud/talk/newarch/utils/NetworkUtils.kt b/app/src/main/java/com/nextcloud/talk/newarch/utils/NetworkUtils.kt index 95b5a4373..7c658071e 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/utils/NetworkUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/utils/NetworkUtils.kt @@ -83,7 +83,8 @@ class NetworkUtils { return null } } - return response.request().newBuilder() + return response.request() + .newBuilder() .header(authenticatorType, credentials) .build() } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/utils/ViewState.kt b/app/src/main/java/com/nextcloud/talk/newarch/utils/ViewState.kt index cf20d3080..23f996292 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/utils/ViewState.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/utils/ViewState.kt @@ -21,7 +21,6 @@ package com.nextcloud.talk.newarch.utils enum class ViewState { - INITIAL_LOAD, LOADING, LOADED_EMPTY, LOADED, diff --git a/app/src/main/res/layout/controller_conversations_rv.xml b/app/src/main/res/layout/controller_conversations_rv.xml index 0038e77c9..102ee1793 100644 --- a/app/src/main/res/layout/controller_conversations_rv.xml +++ b/app/src/main/res/layout/controller_conversations_rv.xml @@ -24,42 +24,51 @@ android:id="@+id/generic_rv_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:animateLayoutChanges="true"> + android:animateLayoutChanges="true" + > - + - + + + android:visibility="invisible" + > - + - + - + - + - - - + diff --git a/app/src/main/res/values-b+en+001/strings.xml b/app/src/main/res/values-b+en+001/strings.xml index 457571a5f..b35445d77 100644 --- a/app/src/main/res/values-b+en+001/strings.xml +++ b/app/src/main/res/values-b+en+001/strings.xml @@ -88,7 +88,7 @@ Select an account - + Start a conversation Configure conversation Leave conversation diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 579236190..293b51bb3 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -124,7 +124,7 @@ Selecciona un compte - + Enceta una conversa Configura la conversa Surt de la conversa diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index df02f32dc..bd3e34ee6 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -124,7 +124,7 @@ Vyberte účet - + Začněte konverzaci Nastavení konverzace Opustit konverzaci diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 49d32b72d..f530004b6 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -124,7 +124,7 @@ Vælg konto - + Start en samtale Samtaleopsætning Forlad samtale diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9b75de8b0..576f68a02 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -125,7 +125,7 @@ Konto auswählen - + Starte eine Unterhaltung Unterhaltung einrichten Unterhaltung verlassen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 680a30972..4728d569a 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -93,7 +93,7 @@ Άδεια χρήσης Επιλογή λογαριασμού - + Έναρξη συνομιλίας Ρύθμιση συνομιλίας Εγκατάλειψη συνομιλίας diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0a571604a..584574d72 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -124,7 +124,7 @@ Selecciona una cuenta - + Comienza una conversación Configura la conversación Abandonar conversación diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 26dd1a81e..d0cfcf11e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -124,7 +124,7 @@ Choisissez un compte - + Commencer une conversation Configurer la conversation Quitter la conversation diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 3ae4ec987..f22d56306 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -127,7 +127,7 @@ Produciuse un fallo aorecuperar os axustes da sinalización Seleccione unha conta - + Iniciar unha conversa Configurar a conversa Abandonar a conversa diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 6e1bd0aa4..c322e2ff5 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -123,7 +123,7 @@ Odaberi račun - + Započni razgovor Konfiguriraj razgovor Napusti razgovor diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index 7db8f59d3..f6a323661 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -123,7 +123,7 @@ Válasszon fiókot - + Beszélgetés indítása Beszélgetés beállításai Beszélgetés elhagyása diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 06f8b4b6a..9cbfd2ad9 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -110,7 +110,7 @@ Veldu aðgang - + Hefja samtal Stilla samtal Hætta í samtali diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 506d1014f..85d0bd0c8 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -125,7 +125,7 @@ Seleziona account - + Inizia una conversazione Configura conversazione Lascia la conversazione diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 37a1c8d9e..461112f0a 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -103,7 +103,7 @@ בחירת חשבון - + התחלת דיון הגדרת דיון יציאה מהדיון diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 8707399c9..07b8f049c 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -124,7 +124,7 @@ アカウントを選択 - + 会話を開始する 会話を構成する 会話を離れる diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4fb349415..8263cd6c8 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -104,7 +104,7 @@ 계정 선택 - + 대화 시작 대화 설정 대화 나가기 diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index affe95111..99a1673d9 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -93,7 +93,7 @@ Velg en konto - + Start en samtale Konfigurer samtale Forlat samtale diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6fd10647f..9dd98b13e 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -126,7 +126,7 @@ Kies er eentje van een provider. Selecteer een account - + Begin een gesprek Configureren gesprek Verlaat gesprek diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 822bfc36b..3ab69643e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -125,7 +125,7 @@ Wybierz konto - + Rozpocznij rozmowę Konfiguruj rozmowę Opuść rozmowę diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6c0bc6d68..bd0ed66ee 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -125,7 +125,7 @@ Selecionar uma conta - + Iniciar uma conversa Configurar uma conversa Sair da conversa diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 05bdfb24c..b150033b3 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -95,7 +95,7 @@ Selecionar uma conta - + Iniciar uma conversação Configurar conversação Sair da conversação diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0209cea4b..874684e25 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -124,7 +124,7 @@ Выберите учётную запись - + Начать беседу Настроить беседу Покинуть беседу diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index 8cb5b3c84..f0c8f77c3 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -123,7 +123,7 @@ Zvoľte si účet - + Začať rozhovor Nastavenia rozhovoru Odísť z rozhovoru diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 141d81437..c1e376f4d 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -115,7 +115,7 @@ Izbor računa - + Začni s pogovorom Nastavitev pogovora Zapusti pogovor diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 9baa52443..6c4afd20d 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -124,7 +124,7 @@ Изаберите налог - + Започни разговор Подеси разговор Напусти разговор diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index ffc83ca4e..283fa509f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -125,7 +125,7 @@ Välj ett konto - + Starta en konversation Anpassa konversation Lämna konversationen diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 640f1101a..d349e251b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -125,7 +125,7 @@ Bir hesap seçin - + Yeni bir görüşme başlat Görüşmeyi yapılandır Görüşmeden ayrıl diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 6e0e66757..dcf27e1ee 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -83,7 +83,7 @@ Chọn một tài khoản - + Bắt đầu một cuộc Đàm thoại Cấu hình đàm thoại Rời khỏi cuộc đàm thoại diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0ca36e35f..ae102d93a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -124,7 +124,7 @@ 选择一个账户 - + 发起会话 配置会话 离开会话 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 54e0ac190..1dc8e4a4e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -99,7 +99,7 @@ 選擇一個帳號 - + 新對話 設定對話 結束對話 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e29e73483..98a01a533 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,7 +144,7 @@ Select an account - + Start a conversation Configure conversation Leave conversation