More progress

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-22 12:49:29 +02:00
parent b0c821462d
commit 2681e6ef24
71 changed files with 1388 additions and 613 deletions

View File

@ -70,15 +70,18 @@ android {
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments = [ arguments = ["room.schemaLocation":
parcelerStacktrace: "true" "$projectDir/schemas".toString()]
]
} }
} }
dataBinding { dataBinding {
enabled = true enabled = true
} }
androidExtensions {
experimental = true
}
} }
dexOptions { dexOptions {
@ -109,6 +112,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = "1.8"
}
lintOptions { lintOptions {
abortOnError false abortOnError false
htmlReport true htmlReport true

View File

@ -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')"
]
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<CallItem.RoomItemViewHolder>
implements IFilterable<String> {
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);
}
}
}

View File

@ -104,7 +104,7 @@ public class ConversationItem
holder.dialogAvatar.setController(null); holder.dialogAvatar.setController(null);
if (conversation.isUpdating()) { if (conversation.getChanging()) {
holder.progressBar.setVisibility(View.VISIBLE); holder.progressBar.setVisibility(View.VISIBLE);
} else { } else {
holder.progressBar.setVisibility(View.GONE); holder.progressBar.setVisibility(View.GONE);
@ -127,7 +127,7 @@ public class ConversationItem
holder.dialogUnreadBubble.setText("99+"); holder.dialogUnreadBubble.setText("99+");
} }
if (conversation.isUnreadMention()) { if (conversation.getUnreadMention()) {
holder.dialogUnreadBubble.setBackground( holder.dialogUnreadBubble.setBackground(
context.getDrawable(R.drawable.bubble_circle_unread_mention)); context.getDrawable(R.drawable.bubble_circle_unread_mention));
} else { } else {
@ -138,13 +138,13 @@ public class ConversationItem
holder.dialogUnreadBubble.setVisibility(View.GONE); holder.dialogUnreadBubble.setVisibility(View.GONE);
} }
if (conversation.isHasPassword()) { if (conversation.getHasPassword()) {
holder.passwordProtectedRoomImageView.setVisibility(View.VISIBLE); holder.passwordProtectedRoomImageView.setVisibility(View.VISIBLE);
} else { } else {
holder.passwordProtectedRoomImageView.setVisibility(View.GONE); holder.passwordProtectedRoomImageView.setVisibility(View.GONE);
} }
if (conversation.isFavorite()) { if (conversation.getFavorite()) {
holder.pinnedConversationImageView.setVisibility(View.VISIBLE); holder.pinnedConversationImageView.setVisibility(View.VISIBLE);
} else { } else {
holder.pinnedConversationImageView.setVisibility(View.GONE); holder.pinnedConversationImageView.setVisibility(View.GONE);
@ -157,7 +157,7 @@ public class ConversationItem
System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE)); System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
if (!TextUtils.isEmpty(conversation.getLastMessage().getSystemMessage()) 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()); holder.dialogLastMessage.setText(conversation.getLastMessage().getText());
} else { } else {
String authorDisplayName = ""; 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]; Drawable[] layers = new Drawable[2];
layers[0] = context.getDrawable(R.drawable.ic_launcher_background); layers[0] = context.getDrawable(R.drawable.ic_launcher_background);
layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground); layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground);
@ -227,7 +227,7 @@ public class ConversationItem
if (shouldLoadAvatar) { if (shouldLoadAvatar) {
switch (conversation.getType()) { switch (conversation.getType()) {
case ROOM_TYPE_ONE_TO_ONE_CALL: case ONE_TO_ONE_CONVERSATION:
if (!TextUtils.isEmpty(conversation.getName())) { if (!TextUtils.isEmpty(conversation.getName())) {
DraweeController draweeController = Fresco.newDraweeControllerBuilder() DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setOldController(holder.dialogAvatar.getController()) .setOldController(holder.dialogAvatar.getController())
@ -241,13 +241,13 @@ public class ConversationItem
holder.dialogAvatar.setVisibility(View.GONE); holder.dialogAvatar.setVisibility(View.GONE);
} }
break; break;
case ROOM_GROUP_CALL: case GROUP_CONVERSATION:
holder.dialogAvatar.getHierarchy() holder.dialogAvatar.getHierarchy()
.setImage(new BitmapDrawable( .setImage(new BitmapDrawable(
DisplayUtils.getRoundedBitmapFromVectorDrawableResource(context.getResources(), DisplayUtils.getRoundedBitmapFromVectorDrawableResource(context.getResources(),
R.drawable.ic_people_group_white_24px)), 100, true); R.drawable.ic_people_group_white_24px)), 100, true);
break; break;
case ROOM_PUBLIC_CALL: case PUBLIC_CONVERSATION:
holder.dialogAvatar.getHierarchy().setImage(new BitmapDrawable(DisplayUtils holder.dialogAvatar.getHierarchy().setImage(new BitmapDrawable(DisplayUtils
.getRoundedBitmapFromVectorDrawableResource(context.getResources(), .getRoundedBitmapFromVectorDrawableResource(context.getResources(),
R.drawable.ic_link_white_24px)), 100, true); R.drawable.ic_link_white_24px)), 100, true);

View File

@ -411,7 +411,7 @@ public class CallNotificationController extends BaseController {
private void loadAvatar() { private void loadAvatar() {
switch (currentConversation.getType()) { switch (currentConversation.getType()) {
case ROOM_TYPE_ONE_TO_ONE_CALL: case ONE_TO_ONE_CONVERSATION:
avatarImageView.setVisibility(View.VISIBLE); avatarImageView.setVisibility(View.VISIBLE);
ImageRequest imageRequest = ImageRequest imageRequest =
@ -463,12 +463,12 @@ public class CallNotificationController extends BaseController {
}, UiThreadImmediateExecutorService.getInstance()); }, UiThreadImmediateExecutorService.getInstance());
break; break;
case ROOM_GROUP_CALL: case GROUP_CONVERSATION:
avatarImageView.getHierarchy() avatarImageView.getHierarchy()
.setImage(DisplayUtils.getRoundedDrawable( .setImage(DisplayUtils.getRoundedDrawable(
context.getDrawable(R.drawable.ic_people_group_white_24px)) context.getDrawable(R.drawable.ic_people_group_white_24px))
, 100, true); , 100, true);
case ROOM_PUBLIC_CALL: case PUBLIC_CONVERSATION:
avatarImageView.getHierarchy() avatarImageView.getHierarchy()
.setImage(DisplayUtils.getRoundedDrawable( .setImage(DisplayUtils.getRoundedDrawable(
context.getDrawable(R.drawable.ic_people_group_white_24px)) context.getDrawable(R.drawable.ic_people_group_white_24px))

View File

@ -312,7 +312,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
private fun loadAvatarForStatusBar() { private fun loadAvatarForStatusBar() {
if (currentConversation != null && currentConversation?.type != null && if (currentConversation != null && currentConversation?.type != null &&
currentConversation?.type == Conversation.ConversationType 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( val avatarSize = DisplayUtils.convertDpToPixel(
conversationVoiceCallMenuItem?.icon!! conversationVoiceCallMenuItem?.icon!!
@ -536,9 +536,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
} }
private fun checkReadOnlyState() { private fun checkReadOnlyState() {
if (currentConversation != null) { if (currentConversation != null && conversationUser != null) {
if (currentConversation?.shouldShowLobby( if (currentConversation?.shouldShowLobby(conversationUser
conversationUser
) == true || currentConversation?.conversationReadOnlyState != null && currentConversation?.conversationReadOnlyState == Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY ) == 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 conversationVideoMenuItem?.icon?.alpha = 255
} }
if (currentConversation != null && currentConversation!!.shouldShowLobby if (conversationUser != null && currentConversation != null && currentConversation!!
(conversationUser) .shouldShowLobby(conversationUser)
) { ) {
messageInputView?.visibility = View.GONE messageInputView?.visibility = View.GONE
} else { } else {
@ -567,8 +566,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
} }
private fun checkLobbyState() { private fun checkLobbyState() {
if (currentConversation != null && currentConversation?.isLobbyViewApplicable( if (currentConversation != null && conversationUser != null && currentConversation?.isLobbyViewApplicable(conversationUser
conversationUser
) == true ) == true
) { ) {
@ -754,9 +752,11 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
override fun getTitle(): String? { override fun getTitle(): String? {
if (currentConversation != null && currentConversation?.displayName != null) { if (currentConversation != null && currentConversation?.displayName != null) {
return EmojiCompat.get() return currentConversation!!.displayName?.let {
.process(currentConversation!!.displayName) EmojiCompat.get()
.process(it)
.toString() .toString()
}
} else { } else {
return "" return ""
} }
@ -1032,7 +1032,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
return return
} }
if (currentConversation != null && currentConversation!!.shouldShowLobby(conversationUser)) { if (currentConversation != null && conversationUser != null && currentConversation!!
.shouldShowLobby(conversationUser)) {
return return
} }
@ -1050,8 +1051,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
lookingIntoFuture = true lookingIntoFuture = true
} else if (isFirstMessagesProcessing) { } else if (isFirstMessagesProcessing) {
if (currentConversation != null) { if (currentConversation != null) {
globalLastKnownFutureMessageId = currentConversation!!.lastReadMessage globalLastKnownFutureMessageId = currentConversation!!.lastReadMessageId
globalLastKnownPastMessageId = currentConversation!!.lastReadMessage globalLastKnownPastMessageId = currentConversation!!.lastReadMessageId
fieldMap["includeLastKnown"] = 1 fieldMap["includeLastKnown"] = 1
} }
} }
@ -1188,7 +1189,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
val chatMessage = chatMessageList[i] val chatMessage = chatMessageList[i]
chatMessage.isOneToOneConversation = chatMessage.isOneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL currentConversation?.type == Conversation.ConversationType.ONE_TO_ONE_CONVERSATION
chatMessage.isLinkPreviewAllowed = isLinkPreviewAllowed chatMessage.isLinkPreviewAllowed = isLinkPreviewAllowed
chatMessage.activeUser = conversationUser chatMessage.activeUser = conversationUser
@ -1258,7 +1259,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
.actorId, -1 .actorId, -1
) && adapter!!.getSameAuthorLastMessagesCount(chatMessage.actorId) % 5 > 0) ) && adapter!!.getSameAuthorLastMessagesCount(chatMessage.actorId) % 5 > 0)
chatMessage.isOneToOneConversation = chatMessage.isOneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) (currentConversation?.type == Conversation.ConversationType.ONE_TO_ONE_CONVERSATION)
adapter?.addToStart(chatMessage, shouldScroll) adapter?.addToStart(chatMessage, shouldScroll)
} }
@ -1447,7 +1448,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
@Subscribe(threadMode = ThreadMode.BACKGROUND) @Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onMessageEvent(userMentionClickEvent: UserMentionClickEvent) { fun onMessageEvent(userMentionClickEvent: UserMentionClickEvent) {
if (currentConversation?.type != Conversation.ConversationType if (currentConversation?.type != Conversation.ConversationType
.ROOM_TYPE_ONE_TO_ONE_CALL || currentConversation?.name != .ONE_TO_ONE_CONVERSATION || currentConversation?.name !=
userMentionClickEvent.userId userMentionClickEvent.userId
) { ) {
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
@ -1482,11 +1483,14 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
) )
conversationIntent.putExtras(bundle) conversationIntent.putExtras(bundle)
if (roomOverall != null && roomOverall.ocs != null && roomOverall.ocs.data !=
null && roomOverall.ocs.data.token != null) {
ConductorRemapping.remapChatController( ConductorRemapping.remapChatController(
router, conversationUser.id, router, conversationUser.id,
roomOverall.ocs.data.token, bundle, false roomOverall.ocs.data.token!!, bundle, false
) )
} }
}
} else { } else {
conversationIntent.putExtras(bundle) conversationIntent.putExtras(bundle)

View File

@ -372,9 +372,9 @@ public class ContactsController extends BaseController implements SearchView.OnQ
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
Conversation.ConversationType roomType; Conversation.ConversationType roomType;
if (isPublicCall) { if (isPublicCall) {
roomType = Conversation.ConversationType.ROOM_PUBLIC_CALL; roomType = Conversation.ConversationType.PUBLIC_CONVERSATION;
} else { } else {
roomType = Conversation.ConversationType.ROOM_GROUP_CALL; roomType = Conversation.ConversationType.GROUP_CONVERSATION;
} }
ArrayList<String> userIdsArray = new ArrayList<>(selectedUserIds); ArrayList<String> userIdsArray = new ArrayList<>(selectedUserIds);

View File

@ -63,7 +63,7 @@ import com.nextcloud.talk.jobs.LeaveConversationWorker
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation 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
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.conversations.RoomOverall
import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
@ -253,8 +253,8 @@ class ConversationInfoController(args: Bundle) : BaseController(),
private fun setupWebinaryView() { private fun setupWebinaryView() {
if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type
== Conversation.ConversationType.ROOM_GROUP_CALL || conversation!!.type == == Conversation.ConversationType.GROUP_CONVERSATION || conversation!!.type ==
Conversation.ConversationType.ROOM_PUBLIC_CALL) && conversation!!.canModerate( Conversation.ConversationType.PUBLIC_CONVERSATION) && conversation!!.canModerate(
conversationUser conversationUser
) )
) { ) {
@ -270,8 +270,8 @@ class ConversationInfoController(args: Bundle) : BaseController(),
startTimeView.setOnClickListener { startTimeView.setOnClickListener {
MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show { MaterialDialog(activity!!, BottomSheet(WRAP_CONTENT)).show {
val currentTimeCalendar = Calendar.getInstance() val currentTimeCalendar = Calendar.getInstance()
if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) { if (conversation != null && conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != 0L) {
currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer * 1000 currentTimeCalendar.timeInMillis = conversation!!.lobbyTimer!! * 1000
} }
dateTimePicker(minDateTime = Calendar.getInstance(), requireFutureDateTime = 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) { if (conversation!!.lobbyTimer != null && conversation!!.lobbyTimer != java.lang.Long.MIN_VALUE && conversation!!.lobbyTimer != 0L) {
startTimeView.setSummary( startTimeView.setSummary(
DateUtils.getLocalDateStringFromTimestampForLobby(conversation!!.lobbyTimer) conversation!!.lobbyTimer?.let { DateUtils.getLocalDateStringFromTimestampForLobby(it) }
) )
} else { } else {
startTimeView.setSummary(R.string.nc_manual) startTimeView.setSummary(R.string.nc_manual)
@ -670,7 +670,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
deleteConversationAction.visibility = View.VISIBLE deleteConversationAction.visibility = View.VISIBLE
} }
if (Conversation.ConversationType.ROOM_SYSTEM == conversation!!.type) { if (Conversation.ConversationType.SYSTEM_CONVERSATION == conversation!!.type) {
muteCalls.visibility = View.GONE muteCalls.visibility = View.GONE
} }
@ -701,7 +701,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
} }
private fun setupGeneralSettings() { private fun setupGeneralSettings() {
if (conversation != null) { if (conversation != null && conversationUser != null) {
changeConversationName.value = conversation!!.displayName changeConversationName.value = conversation!!.displayName
if (conversation!!.isNameEditable(conversationUser)) { if (conversation!!.isNameEditable(conversationUser)) {
@ -710,12 +710,12 @@ class ConversationInfoController(args: Bundle) : BaseController(),
changeConversationName.visibility = View.GONE changeConversationName.visibility = View.GONE
} }
favoriteConversationAction.value = conversation!!.isFavorite favoriteConversationAction.value = conversation!!.favorite
if (conversation!!.type.equals(ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) || conversation!! if (conversation!!.type!!.equals(ConversationType.ONE_TO_ONE_CONVERSATION) || conversation!!
.type.equals(ConversationType.ROOM_SYSTEM)) { .type!!.equals(ConversationType.SYSTEM_CONVERSATION)) {
allowGuestsAction.visibility = View.GONE allowGuestsAction.visibility = View.GONE
} else { } else {
allowGuestsAction.value = conversation!!.type == ROOM_PUBLIC_CALL allowGuestsAction.value = conversation!!.type == PUBLIC_CONVERSATION
} }
(allowGuestsAction.findViewById<View>(R.id.mp_checkable) as SwitchCompat) (allowGuestsAction.findViewById<View>(R.id.mp_checkable) as SwitchCompat)
@ -813,7 +813,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
} }
private fun setProperNotificationValue(conversation: Conversation?) { 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 // hack to see if we get mentioned always or just on mention
if (conversationUser!!.hasSpreedFeatureCapability("mention-flag")) { if (conversationUser!!.hasSpreedFeatureCapability("mention-flag")) {
messageNotificationLevel.value = "always" messageNotificationLevel.value = "always"
@ -827,7 +827,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
private fun loadConversationAvatar() { private fun loadConversationAvatar() {
when (conversation!!.type) { 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) (conversation!!.name)
) { ) {
val draweeController = Fresco.newDraweeControllerBuilder() val draweeController = Fresco.newDraweeControllerBuilder()
@ -844,21 +844,21 @@ class ConversationInfoController(args: Bundle) : BaseController(),
.build() .build()
conversationAvatarImageView.controller = draweeController conversationAvatarImageView.controller = draweeController
} }
Conversation.ConversationType.ROOM_GROUP_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage( Conversation.ConversationType.GROUP_CONVERSATION -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
DisplayUtils DisplayUtils
.getRoundedBitmapDrawableFromVectorDrawableResource( .getRoundedBitmapDrawableFromVectorDrawableResource(
resources, resources,
R.drawable.ic_people_group_white_24px R.drawable.ic_people_group_white_24px
) )
) )
Conversation.ConversationType.ROOM_PUBLIC_CALL -> conversationAvatarImageView.hierarchy.setPlaceholderImage( Conversation.ConversationType.PUBLIC_CONVERSATION -> conversationAvatarImageView.hierarchy.setPlaceholderImage(
DisplayUtils DisplayUtils
.getRoundedBitmapDrawableFromVectorDrawableResource( .getRoundedBitmapDrawableFromVectorDrawableResource(
resources, resources,
R.drawable.ic_link_white_24px R.drawable.ic_link_white_24px
) )
) )
Conversation.ConversationType.ROOM_SYSTEM -> { Conversation.ConversationType.SYSTEM_CONVERSATION -> {
val layers = arrayOfNulls<Drawable>(2) val layers = arrayOfNulls<Drawable>(2)
layers[0] = context.getDrawable(R.drawable.ic_launcher_background) layers[0] = context.getDrawable(R.drawable.ic_launcher_background)
layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground) layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground)

View File

@ -123,6 +123,9 @@ public class WebViewLoginController extends BaseController {
private WebViewFidoBridge webViewFidoBridge; private WebViewFidoBridge webViewFidoBridge;
public WebViewLoginController() {
}
public WebViewLoginController(String baseUrl, boolean isPasswordUpdate) { public WebViewLoginController(String baseUrl, boolean isPasswordUpdate) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.isPasswordUpdate = isPasswordUpdate; this.isPasswordUpdate = isPasswordUpdate;

View File

@ -270,7 +270,7 @@ public class OperationsMenuController extends BaseController {
invite = invitedGroups.get(0); invite = invitedGroups.get(0);
} }
if (conversationType.equals(Conversation.ConversationType.ROOM_PUBLIC_CALL) || if (conversationType.equals(Conversation.ConversationType.PUBLIC_CONVERSATION) ||
!currentUser.hasSpreedFeatureCapability("empty-group-room")) { !currentUser.hasSpreedFeatureCapability("empty-group-room")) {
retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(currentUser.getBaseUrl(), retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(currentUser.getBaseUrl(),
"3", invite, conversationName); "3", invite, conversationName);
@ -314,7 +314,7 @@ public class OperationsMenuController extends BaseController {
public void onNext(RoomOverall roomOverall) { public void onNext(RoomOverall roomOverall) {
conversation = roomOverall.getOcs().getData(); conversation = roomOverall.getOcs().getData();
if (conversationType.equals( if (conversationType.equals(
Conversation.ConversationType.ROOM_PUBLIC_CALL) Conversation.ConversationType.PUBLIC_CONVERSATION)
&& isGroupCallWorkaroundFinal) { && isGroupCallWorkaroundFinal) {
performGroupCallWorkaround(credentials); performGroupCallWorkaround(credentials);
} else { } else {
@ -490,7 +490,7 @@ public class OperationsMenuController extends BaseController {
.getFeatures() != null && capabilitiesOverall.getOcs().getData() .getFeatures() != null && capabilitiesOverall.getOcs().getData()
.getCapabilities().getSpreedCapability() .getCapabilities().getSpreedCapability()
.getFeatures().contains("chat-v2")) { .getFeatures().contains("chat-v2")) {
if (conversation.isHasPassword() && conversation.isGuest()) { if (conversation.getHasPassword() && conversation.isGuest()) {
eventBus.post(new BottomSheetLockEvent(true, 0, eventBus.post(new BottomSheetLockEvent(true, 0,
true, false)); true, false));
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();

View File

@ -27,7 +27,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.Uri; import android.net.Uri;
@ -171,12 +170,12 @@ public class NotificationWorker extends Worker {
intent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation)); intent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM(), Parcels.wrap(conversation));
if (conversation.getType() 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 (!TextUtils.isEmpty(conversation.getObjectType()) && "share:password".equals
(conversation.getObjectType()))) { (conversation.getObjectType()))) {
context.startActivity(intent); context.startActivity(intent);
} else { } else {
if (conversation.getType().equals(Conversation.ConversationType.ROOM_GROUP_CALL)) { if (conversation.getType().equals(Conversation.ConversationType.GROUP_CONVERSATION)) {
conversationType = "group"; conversationType = "group";
} else { } else {
conversationType = "public"; conversationType = "public";

View File

@ -43,7 +43,7 @@ import org.parceler.Parcel;
@Parcel @Parcel
@Data @Data
@JsonObject @JsonObject(serializeNullCollectionElements = true, serializeNullObjects = true)
public class ChatMessage implements IMessage, MessageContentType, MessageContentType.Image { public class ChatMessage implements IMessage, MessageContentType, MessageContentType.Image {
@JsonIgnore @JsonIgnore
public boolean isGrouped; public boolean isGrouped;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, HashMap<String, Object>> guestList;
@JsonField(name = "participants")
public HashMap<String, HashMap<String, Object>> 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
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, HashMap<String, Any>>? = null*/
@JsonField(name = ["participants"])
var participants: HashMap<String, HashMap<String, Any>>? = 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)
}
}

View File

@ -28,33 +28,31 @@ public class EnumRoomTypeConverter extends IntBasedTypeConverter<Conversation.Co
public Conversation.ConversationType getFromInt(int i) { public Conversation.ConversationType getFromInt(int i) {
switch (i) { switch (i) {
case 1: case 1:
return Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL; return Conversation.ConversationType.ONE_TO_ONE_CONVERSATION;
case 2: case 2:
return Conversation.ConversationType.ROOM_GROUP_CALL; return Conversation.ConversationType.GROUP_CONVERSATION;
case 3: case 3:
return Conversation.ConversationType.ROOM_PUBLIC_CALL; return Conversation.ConversationType.PUBLIC_CONVERSATION;
case 4: case 4:
return Conversation.ConversationType.ROOM_SYSTEM; return Conversation.ConversationType.SYSTEM_CONVERSATION;
default: default:
return Conversation.ConversationType.DUMMY; return Conversation.ConversationType.ONE_TO_ONE_CONVERSATION;
} }
} }
@Override @Override
public int convertToInt(Conversation.ConversationType object) { public int convertToInt(Conversation.ConversationType object) {
switch (object) { switch (object) {
case DUMMY: case ONE_TO_ONE_CONVERSATION:
return 0;
case ROOM_TYPE_ONE_TO_ONE_CALL:
return 1; return 1;
case ROOM_GROUP_CALL: case GROUP_CONVERSATION:
return 2; return 2;
case ROOM_PUBLIC_CALL: case PUBLIC_CONVERSATION:
return 3; return 3;
case ROOM_SYSTEM: case SYSTEM_CONVERSATION:
return 4; return 4;
default: default:
return 0; return 1;
} }
} }
} }

View File

@ -0,0 +1,99 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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<List<Conversation>> {
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<Conversation>
) {
nextcloudTalkDatabase.conversationsDao()
.updateConversationsForUser(
user.id,
conversations.map {
it.toConversationEntity()
}.toTypedArray()
)
}
override suspend fun deleteConversationForUserWithTimestamp(
user: UserEntity,
timestamp: Long
) {
nextcloudTalkDatabase.conversationsDao()
.deleteConversationsForUserWithTimestamp(user.id, timestamp)
}
}

View File

@ -33,15 +33,21 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
user: UserEntity, user: UserEntity,
conversation: Conversation conversation: Conversation
): GenericOverall { ): 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( override suspend fun leaveConversationForUser(
user: UserEntity, user: UserEntity,
conversation: Conversation conversation: Conversation
): GenericOverall { ): GenericOverall {
return apiService.leaveConversation(user.getCredentials(), ApiUtils.getUrlForRemoveSelfFromRoom(user return apiService.leaveConversation(
.baseUrl, conversation.token)) user.getCredentials(), ApiUtils.getUrlForRemoveSelfFromRoom(
user
.baseUrl, conversation.token
)
)
} }
override suspend fun setFavoriteValueForConversation( override suspend fun setFavoriteValueForConversation(
@ -66,6 +72,7 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
return apiService.getConversations( return apiService.getConversations(
user.getCredentials(), user.getCredentials(),
ApiUtils.getUrlForGetRooms(user.baseUrl) ApiUtils.getUrlForGetRooms(user.baseUrl)
).ocs.data )
.ocs.data
} }
} }

View File

@ -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.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import io.reactivex.Observable
import retrofit2.http.DELETE import retrofit2.http.DELETE
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header

View File

@ -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.ApiErrorHandler
import com.nextcloud.talk.newarch.data.source.remote.ApiService import com.nextcloud.talk.newarch.data.source.remote.ApiService
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository 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
import com.nextcloud.talk.newarch.utils.NetworkUtils.GetProxyRunnable import com.nextcloud.talk.newarch.utils.NetworkUtils.GetProxyRunnable
import com.nextcloud.talk.newarch.utils.NetworkUtils.MagicAuthenticator 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()) } single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
factory { createApiErrorHandler() } factory { createApiErrorHandler() }
single { createNextcloudTalkRepository(get()) } single { createNextcloudTalkRepository(get()) }
} }
fun createCookieManager(): CookieManager { fun createCookieManager(): CookieManager {
@ -253,10 +253,3 @@ fun createService(retrofit: Retrofit): ApiService {
fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkRepository { fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkRepository {
return NextcloudTalkRepositoryImpl(apiService) return NextcloudTalkRepositoryImpl(apiService)
} }
fun createGetConversationsUseCase(
nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler
): GetConversationsUseCase {
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
}

View File

@ -21,8 +21,12 @@
package com.nextcloud.talk.newarch.di.module package com.nextcloud.talk.newarch.di.module
import android.content.Context import android.content.Context
import androidx.room.Room
import com.nextcloud.talk.R.string import com.nextcloud.talk.R.string
import com.nextcloud.talk.models.database.Models 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.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import io.requery.Persistable import io.requery.Persistable
@ -31,6 +35,7 @@ import io.requery.reactivex.ReactiveEntityStore
import io.requery.reactivex.ReactiveSupport import io.requery.reactivex.ReactiveSupport
import io.requery.sql.EntityDataStore import io.requery.sql.EntityDataStore
import net.orange_box.storebox.StoreBox import net.orange_box.storebox.StoreBox
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
@ -39,6 +44,21 @@ val StorageModule = module {
single { createSqlCipherDatabaseSource(androidContext()) } single { createSqlCipherDatabaseSource(androidContext()) }
single { createDataStore(get()) } single { createDataStore(get()) }
single { createUserUtils(get()) } single { createUserUtils(get()) }
single { createNextcloudTalkOfflineRepository(get()) }
single { TalkDatabase.getInstance(androidApplication()) }
single { get<TalkDatabase>().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 { fun createPreferences(context: Context): AppPreferences {

View File

@ -0,0 +1,56 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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<List<Conversation>>
suspend fun clearConversationsForUser(user: UserEntity)
suspend fun saveConversationsForUser(
user: UserEntity,
conversations: List<Conversation>
)
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
)
}

View File

@ -30,7 +30,15 @@ interface NextcloudTalkRepository {
user: UserEntity, user: UserEntity,
conversation: Conversation, conversation: Conversation,
favorite: Boolean favorite: Boolean
) : GenericOverall ): GenericOverall
suspend fun deleteConversationForUser(user: UserEntity, conversation: Conversation) : GenericOverall
suspend fun leaveConversationForUser(userEntity: UserEntity, conversation: Conversation) : GenericOverall suspend fun deleteConversationForUser(
user: UserEntity,
conversation: Conversation
): GenericOverall
suspend fun leaveConversationForUser(
userEntity: UserEntity,
conversation: Conversation
): GenericOverall
} }

View File

@ -23,7 +23,7 @@ package com.nextcloud.talk.newarch.domain.usecases.base
import com.nextcloud.talk.newarch.data.model.ErrorModel import com.nextcloud.talk.newarch.data.model.ErrorModel
interface UseCaseResponse<Type> { interface UseCaseResponse<Type> {
fun onSuccess(result: Type) suspend fun onSuccess(result: Type)
fun onError(errorModel: ErrorModel?) fun onError(errorModel: ErrorModel?)
} }

View File

@ -23,6 +23,7 @@ package com.nextcloud.talk.newarch.features.conversationsList
import android.app.Application import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider 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.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
@ -35,12 +36,15 @@ class ConversationListViewModelFactory constructor(
private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase, private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
private val leaveConversationUseCase: LeaveConversationUseCase, private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase, private val deleteConversationUseCase: DeleteConversationUseCase,
private val userUtils: UserUtils private val userUtils: UserUtils,
private val offlineRepository: NextcloudTalkOfflineRepository
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T { override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ConversationsListViewModel(application, conversationsUseCase, return ConversationsListViewModel(
application, conversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase, setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
userUtils) as T userUtils, offlineRepository
) as T
} }
} }

View File

@ -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.errorStateTextView
import kotlinx.android.synthetic.main.view_states.view.loadingStateView import kotlinx.android.synthetic.main.view_states.view.loadingStateView
import kotlinx.android.synthetic.main.view_states.view.stateWithMessageView 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.koin.android.ext.android.inject
import org.parceler.Parcels import org.parceler.Parcels
import java.util.ArrayList import java.util.ArrayList
@ -180,6 +181,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
container: ViewGroup container: ViewGroup
): View { ): View {
setHasOptionsMenu(true) setHasOptionsMenu(true)
actionBar?.show()
viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java) viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java)
viewModel.apply { 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<ConversationItem>() val newConversations = mutableListOf<ConversationItem>()
for (conversation in it) { for (conversation in sortedConversationsList) {
newConversations.add(ConversationItem(conversation, viewModel.currentUser, activity)) newConversations.add(
ConversationItem(
conversation, viewModel.currentUserLiveData.value,
activity
)
)
} }
recyclerViewAdapter.updateDataSet(newConversations as List<IFlexible<ViewHolder>>?) recyclerViewAdapter.updateDataSet(newConversations as List<IFlexible<ViewHolder>>?)
@ -313,7 +334,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
var displayName = var displayName =
(recyclerViewAdapter.getItem(position) as ConversationItem).model.displayName (recyclerViewAdapter.getItem(position) as ConversationItem).model.displayName
if (displayName.length > 8) { if (displayName!!.length > 8) {
displayName = displayName.substring(0, 4) + "..." displayName = displayName.substring(0, 4) + "..."
} }
@ -394,12 +415,12 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
val conversation = (clickedItem as ConversationItem).model val conversation = (clickedItem as ConversationItem).model
val bundle = Bundle() 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_TOKEN, conversation.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId) bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId)
bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation)) bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
ConductorRemapping.remapChatController( ConductorRemapping.remapChatController(
router, viewModel.currentUser.id, conversation.token, router, viewModel.currentUserLiveData.value!!.id, conversation!!.token!!,
bundle, false bundle, false
) )
} }

View File

@ -26,6 +26,7 @@ import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.facebook.common.executors.UiThreadImmediateExecutorService import com.facebook.common.executors.UiThreadImmediateExecutorService
import com.facebook.common.references.CloseableReference 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.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
import com.nextcloud.talk.newarch.data.model.ErrorModel 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.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
import com.nextcloud.talk.newarch.utils.ViewState 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.newarch.utils.ViewState.LOADING
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.ShareUtils import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.utils.database.user.UserUtils 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 import org.koin.core.parameter.parametersOf
class ConversationsListViewModel constructor( class ConversationsListViewModel constructor(
@ -66,15 +64,18 @@ class ConversationsListViewModel constructor(
private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase, private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
private val leaveConversationUseCase: LeaveConversationUseCase, private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase, private val deleteConversationUseCase: DeleteConversationUseCase,
private val userUtils: UserUtils private val userUtils: UserUtils,
private val offlineRepository: NextcloudTalkOfflineRepository
) : BaseViewModel<ConversationsListView>(application) { ) : BaseViewModel<ConversationsListView>(application) {
private var conversations: MutableList<Conversation> = mutableListOf() val viewState = MutableLiveData<ViewState>(LOADING)
val conversationsLiveListData = MutableLiveData<List<Conversation>>()
val viewState = MutableLiveData<ViewState>(INITIAL_LOAD)
var messageData: String? = null var messageData: String? = null
val searchQuery = MutableLiveData<String>() val searchQuery = MutableLiveData<String>()
var currentUser: UserEntity = userUtils.currentUser val currentUserLiveData: MutableLiveData<UserEntity> = MutableLiveData()
val conversationsLiveData = Transformations.switchMap(currentUserLiveData) {
offlineRepository.getConversationsForUser(it)
}
var currentUserAvatar: MutableLiveData<Drawable> = MutableLiveData() var currentUserAvatar: MutableLiveData<Drawable> = MutableLiveData()
get() { get() {
if (field.value == null) { if (field.value == null) {
@ -85,48 +86,53 @@ class ConversationsListViewModel constructor(
} }
fun leaveConversation(conversation: Conversation) { fun leaveConversation(conversation: Conversation) {
viewModelScope.launch {
setConversationUpdateStatus(conversation, true) setConversationUpdateStatus(conversation, true)
}
leaveConversationUseCase.invoke(viewModelScope, parametersOf(currentUser, conversation), leaveConversationUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value,
conversation
),
object : UseCaseResponse<GenericOverall> { object : UseCaseResponse<GenericOverall> {
override fun onSuccess(result: GenericOverall) { override suspend fun onSuccess(result: GenericOverall) {
// TODO: Use binary search to find the right room offlineRepository.deleteConversation(
conversations.find { it.conversationId == conversation.conversationId } currentUserLiveData.value!!.id, conversation
?.let { .conversationId
conversations.remove(it) )
conversationsLiveListData.value = conversations
if (conversations.isEmpty()) {
viewState.value = LOADED_EMPTY
}
}
} }
override fun onError(errorModel: ErrorModel?) { override fun onError(errorModel: ErrorModel?) {
messageData = errorModel?.getErrorMessage()
viewModelScope.launch {
setConversationUpdateStatus(conversation, false) setConversationUpdateStatus(conversation, false)
} }
}
}) })
} }
fun deleteConversation(conversation: Conversation) { fun deleteConversation(conversation: Conversation) {
viewModelScope.launch {
setConversationUpdateStatus(conversation, true) setConversationUpdateStatus(conversation, true)
}
deleteConversationUseCase.invoke(viewModelScope, parametersOf(currentUser, conversation), deleteConversationUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value,
conversation
),
object : UseCaseResponse<GenericOverall> { object : UseCaseResponse<GenericOverall> {
override fun onSuccess(result: GenericOverall) { override suspend fun onSuccess(result: GenericOverall) {
// TODO: Use binary search to find the right room offlineRepository.deleteConversation(
conversations.find { it.conversationId == conversation.conversationId } currentUserLiveData.value!!.id, conversation
?.let { .conversationId
conversations.remove(it) )
conversationsLiveListData.value = conversations
if (conversations.isEmpty()) {
viewState.value = LOADED_EMPTY
}
}
} }
override fun onError(errorModel: ErrorModel?) { override fun onError(errorModel: ErrorModel?) {
messageData = errorModel?.getErrorMessage()
viewModelScope.launch {
setConversationUpdateStatus(conversation, false) setConversationUpdateStatus(conversation, false)
} }
}
}) })
} }
@ -135,74 +141,63 @@ class ConversationsListViewModel constructor(
conversation: Conversation, conversation: Conversation,
favorite: Boolean favorite: Boolean
) { ) {
viewModelScope.launch {
setConversationUpdateStatus(conversation, true) setConversationUpdateStatus(conversation, true)
}
setConversationFavoriteValueUseCase.invoke(viewModelScope, parametersOf( setConversationFavoriteValueUseCase.invoke(viewModelScope, parametersOf(
currentUser, currentUserLiveData.value,
conversation, conversation,
favorite favorite
), ),
object : UseCaseResponse<GenericOverall> { object : UseCaseResponse<GenericOverall> {
override fun onSuccess(result: GenericOverall) { override suspend fun onSuccess(result: GenericOverall) {
// TODO: Use binary search to find the right room offlineRepository.setFavoriteValueForConversation(
conversations.find { it.conversationId == conversation.conversationId } currentUserLiveData.value!!.id,
?.apply { conversation.conversationId, favorite
updating = false )
isFavorite = favorite
conversationsLiveListData.value = conversations
}
} }
override fun onError(errorModel: ErrorModel?) { override fun onError(errorModel: ErrorModel?) {
messageData = errorModel?.getErrorMessage()
viewModelScope.launch {
setConversationUpdateStatus(conversation, false) setConversationUpdateStatus(conversation, false)
} }
}
}) })
} }
fun loadConversations() { fun loadConversations() {
val userChanged = !currentUser.equals(userUtils.currentUser) val userChanged = !(currentUserLiveData.value?.equals(userUtils.currentUser) ?: false)
if (userChanged) { if (userChanged) {
currentUser = userUtils.currentUser currentUserLiveData.value = userUtils.currentUser
}
if ((FAILED).equals(viewState.value) || (LOADED_EMPTY).equals(viewState.value) ||
(INITIAL_LOAD).equals(viewState.value) || !currentUser.equals(userUtils.currentUser) || userChanged
) {
viewState.value = LOADING viewState.value = LOADING
} }
getConversationsUseCase.invoke( getConversationsUseCase.invoke(viewModelScope, parametersOf(currentUserLiveData.value), object :
viewModelScope, parametersOf(currentUser), object : UseCaseResponse<List<Conversation>> { UseCaseResponse<List<Conversation>> {
override fun onSuccess(result: List<Conversation>) { override suspend fun onSuccess(result: List<Conversation>) {
val newConversations = result.toMutableList() val mutableList = result.toMutableList()
mutableList.forEach {
it.user = currentUserLiveData.value!!.id
}
newConversations.sortWith(Comparator { conversation1, conversation2 -> offlineRepository.saveConversationsForUser(currentUserLiveData.value!!, mutableList)
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
messageData = "" messageData = ""
} }
override fun onError(errorModel: ErrorModel?) { override fun onError(errorModel: ErrorModel?) {
messageData = errorModel?.getErrorMessage() messageData = errorModel?.getErrorMessage()
viewState.value = FAILED
} }
}) })
} }
fun loadAvatar(avatarSize: Int) { fun loadAvatar(avatarSize: Int) {
val imageRequest = DisplayUtils.getImageRequestForUrl( val imageRequest = DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatarWithNameAndPixels( ApiUtils.getUrlForAvatarWithNameAndPixels(
currentUser.baseUrl, currentUserLiveData.value!!.baseUrl,
currentUser.userId, avatarSize currentUserLiveData.value!!.userId, avatarSize
), null ), null
) )
@ -257,7 +252,7 @@ class ConversationsListViewModel constructor(
fun getConversationMenuItemsForConversation(conversation: Conversation): MutableList<BasicListItemWithImage> { fun getConversationMenuItemsForConversation(conversation: Conversation): MutableList<BasicListItemWithImage> {
val items = mutableListOf<BasicListItemWithImage>() val items = mutableListOf<BasicListItemWithImage>()
if (conversation.isFavorite) { if (conversation.favorite) {
items.add( items.add(
BasicListItemWithImage( BasicListItemWithImage(
drawable.ic_star_border_black_24dp, drawable.ic_star_border_black_24dp,
@ -282,7 +277,7 @@ class ConversationsListViewModel constructor(
) )
} }
if (conversation.canLeave(currentUser)) { if (conversation.canLeave(currentUserLiveData.value!!)) {
items.add( items.add(
BasicListItemWithImage( BasicListItemWithImage(
drawable.ic_exit_to_app_black_24dp, context.getString 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( items.add(
BasicListItemWithImage( BasicListItemWithImage(
drawable.ic_delete_grey600_24dp, context.getString( drawable.ic_delete_grey600_24dp, context.getString(
@ -304,14 +299,13 @@ class ConversationsListViewModel constructor(
return items return items
} }
private fun setConversationUpdateStatus( private suspend fun setConversationUpdateStatus(
conversation: Conversation, conversation: Conversation,
value: Boolean value: Boolean
) { ) {
conversations.find { it.conversationId == conversation.conversationId } offlineRepository.setChangingValueForConversation(
?.apply { currentUserLiveData.value!!.id, conversation
updating = value .conversationId, value
conversationsLiveListData.value = conversations )
}
} }
} }

View File

@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.features.conversationsList.di.module
import android.app.Application import android.app.Application
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler 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.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
@ -41,7 +42,7 @@ val ConversationsListModule = module {
factory { factory {
createConversationListViewModelFactory( createConversationListViewModelFactory(
androidApplication(), get(), get(), get(), get androidApplication(), get(), get(), get(), get
(), get() (), get(), get()
) )
} }
} }
@ -81,11 +82,12 @@ fun createConversationListViewModelFactory(
setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase, setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
leaveConversationUseCase: LeaveConversationUseCase, leaveConversationUseCase: LeaveConversationUseCase,
deleteConversationUseCase: DeleteConversationUseCase, deleteConversationUseCase: DeleteConversationUseCase,
userUtils: UserUtils userUtils: UserUtils,
offlineRepository: NextcloudTalkOfflineRepository
): ConversationListViewModelFactory { ): ConversationListViewModelFactory {
return ConversationListViewModelFactory( return ConversationListViewModelFactory(
application, getConversationsUseCase, application, getConversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase, setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
userUtils userUtils, offlineRepository
) )
} }

View File

@ -0,0 +1,41 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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)
}
}

View File

@ -0,0 +1,42 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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<List<ConversationEntity>>
@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<ConversationEntity>
) {
val timestamp = System.currentTimeMillis()
val conversationsWithTimestampApplied = newConversations.map {
it.modifiedAt = System.currentTimeMillis()
it
}
.toTypedArray()
saveConversations(*conversationsWithTimestampApplied)
deleteConversationsForUserWithTimestamp(userId, timestamp)
}
}

View File

@ -0,0 +1,63 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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()
}
}

View File

@ -0,0 +1,130 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>.
*/
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<String, HashMap<String, Any>>? = null,
@ColumnInfo(name = "participants") var participants: HashMap<String, HashMap<String, Any>>? =
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
}

View File

@ -83,7 +83,8 @@ class NetworkUtils {
return null return null
} }
} }
return response.request().newBuilder() return response.request()
.newBuilder()
.header(authenticatorType, credentials) .header(authenticatorType, credentials)
.build() .build()
} }

View File

@ -21,7 +21,6 @@
package com.nextcloud.talk.newarch.utils package com.nextcloud.talk.newarch.utils
enum class ViewState { enum class ViewState {
INITIAL_LOAD,
LOADING, LOADING,
LOADED_EMPTY, LOADED_EMPTY,
LOADED, LOADED,

View File

@ -24,28 +24,36 @@
android:id="@+id/generic_rv_layout" android:id="@+id/generic_rv_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true"> android:animateLayoutChanges="true"
>
<include layout="@layout/view_states"/> <include
layout="@layout/view_states"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayoutView" android:id="@+id/swipeRefreshLayoutView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="visible" android:visibility="visible"
app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior"> app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior"
>
<FrameLayout <FrameLayout
android:id="@+id/dataStateView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/dataStateView" android:visibility="invisible"
android:visibility="invisible"> >
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:listitem="@layout/rv_item_conversation" /> tools:listitem="@layout/rv_item_conversation"
/>
</FrameLayout> </FrameLayout>
@ -58,7 +66,8 @@
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_margin="16dp" android:layout_margin="16dp"
android:visibility="gone" android:visibility="gone"
app:srcCompat="@drawable/ic_add_white_24px" /> app:srcCompat="@drawable/ic_add_white_24px"
/>
<include layout="@layout/fast_scroller" /> <include layout="@layout/fast_scroller" />

View File

@ -88,7 +88,7 @@
<string name="nc_select_an_account">Select an account</string> <string name="nc_select_an_account">Select an account</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Start a conversation</string> <string name="nc_start_conversation">Start a conversation</string>
<string name="nc_configure_room">Configure conversation</string> <string name="nc_configure_room">Configure conversation</string>
<string name="nc_leave">Leave conversation</string> <string name="nc_leave">Leave conversation</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">Selecciona un compte</string> <string name="nc_select_an_account">Selecciona un compte</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Enceta una conversa</string> <string name="nc_start_conversation">Enceta una conversa</string>
<string name="nc_configure_room">Configura la conversa</string> <string name="nc_configure_room">Configura la conversa</string>
<string name="nc_leave">Surt de la conversa</string> <string name="nc_leave">Surt de la conversa</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">Vyberte účet</string> <string name="nc_select_an_account">Vyberte účet</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Začněte konverzaci</string> <string name="nc_start_conversation">Začněte konverzaci</string>
<string name="nc_configure_room">Nastavení konverzace</string> <string name="nc_configure_room">Nastavení konverzace</string>
<string name="nc_leave">Opustit konverzaci</string> <string name="nc_leave">Opustit konverzaci</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">Vælg konto</string> <string name="nc_select_an_account">Vælg konto</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Start en samtale</string> <string name="nc_start_conversation">Start en samtale</string>
<string name="nc_configure_room">Samtaleopsætning</string> <string name="nc_configure_room">Samtaleopsætning</string>
<string name="nc_leave">Forlad samtale</string> <string name="nc_leave">Forlad samtale</string>

View File

@ -125,7 +125,7 @@
<string name="nc_select_an_account">Konto auswählen</string> <string name="nc_select_an_account">Konto auswählen</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Starte eine Unterhaltung</string> <string name="nc_start_conversation">Starte eine Unterhaltung</string>
<string name="nc_configure_room">Unterhaltung einrichten</string> <string name="nc_configure_room">Unterhaltung einrichten</string>
<string name="nc_leave">Unterhaltung verlassen</string> <string name="nc_leave">Unterhaltung verlassen</string>

View File

@ -93,7 +93,7 @@
<string name="nc_license_title">Άδεια χρήσης</string> <string name="nc_license_title">Άδεια χρήσης</string>
<string name="nc_select_an_account">Επιλογή λογαριασμού</string> <string name="nc_select_an_account">Επιλογή λογαριασμού</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Έναρξη συνομιλίας</string> <string name="nc_start_conversation">Έναρξη συνομιλίας</string>
<string name="nc_configure_room">Ρύθμιση συνομιλίας</string> <string name="nc_configure_room">Ρύθμιση συνομιλίας</string>
<string name="nc_leave">Εγκατάλειψη συνομιλίας</string> <string name="nc_leave">Εγκατάλειψη συνομιλίας</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">Selecciona una cuenta</string> <string name="nc_select_an_account">Selecciona una cuenta</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Comienza una conversación</string> <string name="nc_start_conversation">Comienza una conversación</string>
<string name="nc_configure_room">Configura la conversación</string> <string name="nc_configure_room">Configura la conversación</string>
<string name="nc_leave">Abandonar conversación</string> <string name="nc_leave">Abandonar conversación</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">Choisissez un compte</string> <string name="nc_select_an_account">Choisissez un compte</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Commencer une conversation</string> <string name="nc_start_conversation">Commencer une conversation</string>
<string name="nc_configure_room">Configurer la conversation</string> <string name="nc_configure_room">Configurer la conversation</string>
<string name="nc_leave">Quitter la conversation</string> <string name="nc_leave">Quitter la conversation</string>

View File

@ -127,7 +127,7 @@ Produciuse un fallo aorecuperar os axustes da sinalización</string>
<string name="nc_select_an_account">Seleccione unha conta</string> <string name="nc_select_an_account">Seleccione unha conta</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Iniciar unha conversa</string> <string name="nc_start_conversation">Iniciar unha conversa</string>
<string name="nc_configure_room">Configurar a conversa</string> <string name="nc_configure_room">Configurar a conversa</string>
<string name="nc_leave">Abandonar a conversa</string> <string name="nc_leave">Abandonar a conversa</string>

View File

@ -123,7 +123,7 @@
<string name="nc_select_an_account">Odaberi račun</string> <string name="nc_select_an_account">Odaberi račun</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Započni razgovor</string> <string name="nc_start_conversation">Započni razgovor</string>
<string name="nc_configure_room">Konfiguriraj razgovor</string> <string name="nc_configure_room">Konfiguriraj razgovor</string>
<string name="nc_leave">Napusti razgovor</string> <string name="nc_leave">Napusti razgovor</string>

View File

@ -123,7 +123,7 @@
<string name="nc_select_an_account">Válasszon fiókot</string> <string name="nc_select_an_account">Válasszon fiókot</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Beszélgetés indítása</string> <string name="nc_start_conversation">Beszélgetés indítása</string>
<string name="nc_configure_room">Beszélgetés beállításai</string> <string name="nc_configure_room">Beszélgetés beállításai</string>
<string name="nc_leave">Beszélgetés elhagyása</string> <string name="nc_leave">Beszélgetés elhagyása</string>

View File

@ -110,7 +110,7 @@
<string name="nc_select_an_account">Veldu aðgang</string> <string name="nc_select_an_account">Veldu aðgang</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Hefja samtal</string> <string name="nc_start_conversation">Hefja samtal</string>
<string name="nc_configure_room">Stilla samtal</string> <string name="nc_configure_room">Stilla samtal</string>
<string name="nc_leave">Hætta í samtali</string> <string name="nc_leave">Hætta í samtali</string>

View File

@ -125,7 +125,7 @@
<string name="nc_select_an_account">Seleziona account</string> <string name="nc_select_an_account">Seleziona account</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Inizia una conversazione</string> <string name="nc_start_conversation">Inizia una conversazione</string>
<string name="nc_configure_room">Configura conversazione</string> <string name="nc_configure_room">Configura conversazione</string>
<string name="nc_leave">Lascia la conversazione</string> <string name="nc_leave">Lascia la conversazione</string>

View File

@ -103,7 +103,7 @@
<string name="nc_select_an_account">בחירת חשבון</string> <string name="nc_select_an_account">בחירת חשבון</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">התחלת דיון</string> <string name="nc_start_conversation">התחלת דיון</string>
<string name="nc_configure_room">הגדרת דיון</string> <string name="nc_configure_room">הגדרת דיון</string>
<string name="nc_leave">יציאה מהדיון</string> <string name="nc_leave">יציאה מהדיון</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">アカウントを選択</string> <string name="nc_select_an_account">アカウントを選択</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">会話を開始する</string> <string name="nc_start_conversation">会話を開始する</string>
<string name="nc_configure_room">会話を構成する</string> <string name="nc_configure_room">会話を構成する</string>
<string name="nc_leave">会話を離れる</string> <string name="nc_leave">会話を離れる</string>

View File

@ -104,7 +104,7 @@
<string name="nc_select_an_account">계정 선택</string> <string name="nc_select_an_account">계정 선택</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">대화 시작</string> <string name="nc_start_conversation">대화 시작</string>
<string name="nc_configure_room">대화 설정</string> <string name="nc_configure_room">대화 설정</string>
<string name="nc_leave">대화 나가기</string> <string name="nc_leave">대화 나가기</string>

View File

@ -93,7 +93,7 @@
<string name="nc_select_an_account">Velg en konto</string> <string name="nc_select_an_account">Velg en konto</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Start en samtale</string> <string name="nc_start_conversation">Start en samtale</string>
<string name="nc_configure_room">Konfigurer samtale</string> <string name="nc_configure_room">Konfigurer samtale</string>
<string name="nc_leave">Forlat samtale</string> <string name="nc_leave">Forlat samtale</string>

View File

@ -126,7 +126,7 @@ Kies er eentje van een provider.</string>
<string name="nc_select_an_account">Selecteer een account</string> <string name="nc_select_an_account">Selecteer een account</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Begin een gesprek</string> <string name="nc_start_conversation">Begin een gesprek</string>
<string name="nc_configure_room">Configureren gesprek</string> <string name="nc_configure_room">Configureren gesprek</string>
<string name="nc_leave">Verlaat gesprek</string> <string name="nc_leave">Verlaat gesprek</string>

View File

@ -125,7 +125,7 @@
<string name="nc_select_an_account">Wybierz konto</string> <string name="nc_select_an_account">Wybierz konto</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Rozpocznij rozmowę</string> <string name="nc_start_conversation">Rozpocznij rozmowę</string>
<string name="nc_configure_room">Konfiguruj rozmowę</string> <string name="nc_configure_room">Konfiguruj rozmowę</string>
<string name="nc_leave">Opuść rozmowę</string> <string name="nc_leave">Opuść rozmowę</string>

View File

@ -125,7 +125,7 @@
<string name="nc_select_an_account">Selecionar uma conta</string> <string name="nc_select_an_account">Selecionar uma conta</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Iniciar uma conversa</string> <string name="nc_start_conversation">Iniciar uma conversa</string>
<string name="nc_configure_room">Configurar uma conversa</string> <string name="nc_configure_room">Configurar uma conversa</string>
<string name="nc_leave">Sair da conversa</string> <string name="nc_leave">Sair da conversa</string>

View File

@ -95,7 +95,7 @@
<string name="nc_select_an_account">Selecionar uma conta</string> <string name="nc_select_an_account">Selecionar uma conta</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Iniciar uma conversação</string> <string name="nc_start_conversation">Iniciar uma conversação</string>
<string name="nc_configure_room">Configurar conversação</string> <string name="nc_configure_room">Configurar conversação</string>
<string name="nc_leave">Sair da conversação</string> <string name="nc_leave">Sair da conversação</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">Выберите учётную запись</string> <string name="nc_select_an_account">Выберите учётную запись</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Начать беседу</string> <string name="nc_start_conversation">Начать беседу</string>
<string name="nc_configure_room">Настроить беседу</string> <string name="nc_configure_room">Настроить беседу</string>
<string name="nc_leave">Покинуть беседу</string> <string name="nc_leave">Покинуть беседу</string>

View File

@ -123,7 +123,7 @@
<string name="nc_select_an_account">Zvoľte si účet</string> <string name="nc_select_an_account">Zvoľte si účet</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Začať rozhovor</string> <string name="nc_start_conversation">Začať rozhovor</string>
<string name="nc_configure_room">Nastavenia rozhovoru</string> <string name="nc_configure_room">Nastavenia rozhovoru</string>
<string name="nc_leave">Odísť z rozhovoru</string> <string name="nc_leave">Odísť z rozhovoru</string>

View File

@ -115,7 +115,7 @@
<string name="nc_select_an_account">Izbor računa</string> <string name="nc_select_an_account">Izbor računa</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Začni s pogovorom</string> <string name="nc_start_conversation">Začni s pogovorom</string>
<string name="nc_configure_room">Nastavitev pogovora</string> <string name="nc_configure_room">Nastavitev pogovora</string>
<string name="nc_leave">Zapusti pogovor</string> <string name="nc_leave">Zapusti pogovor</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">Изаберите налог</string> <string name="nc_select_an_account">Изаберите налог</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Започни разговор</string> <string name="nc_start_conversation">Започни разговор</string>
<string name="nc_configure_room">Подеси разговор</string> <string name="nc_configure_room">Подеси разговор</string>
<string name="nc_leave">Напусти разговор</string> <string name="nc_leave">Напусти разговор</string>

View File

@ -125,7 +125,7 @@
<string name="nc_select_an_account">Välj ett konto</string> <string name="nc_select_an_account">Välj ett konto</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Starta en konversation</string> <string name="nc_start_conversation">Starta en konversation</string>
<string name="nc_configure_room">Anpassa konversation</string> <string name="nc_configure_room">Anpassa konversation</string>
<string name="nc_leave">Lämna konversationen</string> <string name="nc_leave">Lämna konversationen</string>

View File

@ -125,7 +125,7 @@
<string name="nc_select_an_account">Bir hesap seçin</string> <string name="nc_select_an_account">Bir hesap seçin</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Yeni bir görüşme başlat</string> <string name="nc_start_conversation">Yeni bir görüşme başlat</string>
<string name="nc_configure_room">Görüşmeyi yapılandır</string> <string name="nc_configure_room">Görüşmeyi yapılandır</string>
<string name="nc_leave">Görüşmeden ayrıl</string> <string name="nc_leave">Görüşmeden ayrıl</string>

View File

@ -83,7 +83,7 @@
<string name="nc_select_an_account">Chọn một tài khoản</string> <string name="nc_select_an_account">Chọn một tài khoản</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Bắt đầu một cuộc Đàm thoại</string> <string name="nc_start_conversation">Bắt đầu một cuộc Đàm thoại</string>
<string name="nc_configure_room">Cấu hình đàm thoại</string> <string name="nc_configure_room">Cấu hình đàm thoại</string>
<string name="nc_leave">Rời khỏi cuộc đàm thoại</string> <string name="nc_leave">Rời khỏi cuộc đàm thoại</string>

View File

@ -124,7 +124,7 @@
<string name="nc_select_an_account">选择一个账户</string> <string name="nc_select_an_account">选择一个账户</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">发起会话</string> <string name="nc_start_conversation">发起会话</string>
<string name="nc_configure_room">配置会话</string> <string name="nc_configure_room">配置会话</string>
<string name="nc_leave">离开会话</string> <string name="nc_leave">离开会话</string>

View File

@ -99,7 +99,7 @@
<string name="nc_select_an_account">選擇一個帳號</string> <string name="nc_select_an_account">選擇一個帳號</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">新對話</string> <string name="nc_start_conversation">新對話</string>
<string name="nc_configure_room">設定對話</string> <string name="nc_configure_room">設定對話</string>
<string name="nc_leave">結束對話</string> <string name="nc_leave">結束對話</string>

View File

@ -144,7 +144,7 @@
<string name="nc_select_an_account">Select an account</string> <string name="nc_select_an_account">Select an account</string>
<!-- Conversation menu --> <!-- ConversationEntity menu -->
<string name="nc_start_conversation">Start a conversation</string> <string name="nc_start_conversation">Start a conversation</string>
<string name="nc_configure_room">Configure conversation</string> <string name="nc_configure_room">Configure conversation</string>
<string name="nc_leave">Leave conversation</string> <string name="nc_leave">Leave conversation</string>