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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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) {
switch (i) {
case 1:
return Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL;
return Conversation.ConversationType.ONE_TO_ONE_CONVERSATION;
case 2:
return Conversation.ConversationType.ROOM_GROUP_CALL;
return Conversation.ConversationType.GROUP_CONVERSATION;
case 3:
return Conversation.ConversationType.ROOM_PUBLIC_CALL;
return Conversation.ConversationType.PUBLIC_CONVERSATION;
case 4:
return Conversation.ConversationType.ROOM_SYSTEM;
return Conversation.ConversationType.SYSTEM_CONVERSATION;
default:
return Conversation.ConversationType.DUMMY;
return Conversation.ConversationType.ONE_TO_ONE_CONVERSATION;
}
}
@Override
public int convertToInt(Conversation.ConversationType object) {
switch (object) {
case DUMMY:
return 0;
case ROOM_TYPE_ONE_TO_ONE_CALL:
case ONE_TO_ONE_CONVERSATION:
return 1;
case ROOM_GROUP_CALL:
case GROUP_CONVERSATION:
return 2;
case ROOM_PUBLIC_CALL:
case PUBLIC_CONVERSATION:
return 3;
case ROOM_SYSTEM:
case SYSTEM_CONVERSATION:
return 4;
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,
conversation: Conversation
): GenericOverall {
return apiService.deleteConversation(user.getCredentials(), ApiUtils.getRoom(user.baseUrl, conversation.token))
return apiService.deleteConversation(
user.getCredentials(), ApiUtils.getRoom(user.baseUrl, conversation.token)
)
}
override suspend fun leaveConversationForUser(
user: UserEntity,
conversation: Conversation
): GenericOverall {
return apiService.leaveConversation(user.getCredentials(), ApiUtils.getUrlForRemoveSelfFromRoom(user
.baseUrl, conversation.token))
return apiService.leaveConversation(
user.getCredentials(), ApiUtils.getUrlForRemoveSelfFromRoom(
user
.baseUrl, conversation.token
)
)
}
override suspend fun setFavoriteValueForConversation(
@ -66,6 +72,7 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
return apiService.getConversations(
user.getCredentials(),
ApiUtils.getUrlForGetRooms(user.baseUrl)
).ocs.data
)
.ocs.data
}
}

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.generic.GenericOverall
import io.reactivex.Observable
import retrofit2.http.DELETE
import retrofit2.http.GET
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.ApiService
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.utils.NetworkUtils
import com.nextcloud.talk.newarch.utils.NetworkUtils.GetProxyRunnable
import com.nextcloud.talk.newarch.utils.NetworkUtils.MagicAuthenticator
@ -81,6 +80,7 @@ val NetworkModule = module {
single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
factory { createApiErrorHandler() }
single { createNextcloudTalkRepository(get()) }
}
fun createCookieManager(): CookieManager {
@ -253,10 +253,3 @@ fun createService(retrofit: Retrofit): ApiService {
fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkRepository {
return NextcloudTalkRepositoryImpl(apiService)
}
fun createGetConversationsUseCase(
nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler
): GetConversationsUseCase {
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
}

View File

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

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,
conversation: Conversation,
favorite: Boolean
) : GenericOverall
suspend fun deleteConversationForUser(user: UserEntity, conversation: Conversation) : GenericOverall
suspend fun leaveConversationForUser(userEntity: UserEntity, conversation: Conversation) : GenericOverall
): GenericOverall
suspend fun deleteConversationForUser(
user: UserEntity,
conversation: Conversation
): GenericOverall
suspend fun leaveConversationForUser(
userEntity: UserEntity,
conversation: Conversation
): GenericOverall
}

View File

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

View File

@ -23,6 +23,7 @@ package com.nextcloud.talk.newarch.features.conversationsList
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkOfflineRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
@ -32,15 +33,18 @@ import com.nextcloud.talk.utils.database.user.UserUtils
class ConversationListViewModelFactory constructor(
private val application: Application,
private val conversationsUseCase: GetConversationsUseCase,
private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
private val setConversationFavoriteValueUseCase: SetConversationFavoriteValueUseCase,
private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase,
private val userUtils: UserUtils
private val userUtils: UserUtils,
private val offlineRepository: NextcloudTalkOfflineRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ConversationsListViewModel(application, conversationsUseCase,
return ConversationsListViewModel(
application, conversationsUseCase,
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.loadingStateView
import kotlinx.android.synthetic.main.view_states.view.stateWithMessageView
import org.apache.commons.lang3.builder.CompareToBuilder
import org.koin.android.ext.android.inject
import org.parceler.Parcels
import java.util.ArrayList
@ -180,6 +181,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
container: ViewGroup
): View {
setHasOptionsMenu(true)
actionBar?.show()
viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java)
viewModel.apply {
@ -243,10 +245,29 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
}
})
conversationsLiveListData.observe(this@ConversationsListView, Observer {
conversationsLiveData.observe(this@ConversationsListView, Observer {
if (it.size == 0) {
viewState.value = LOADED_EMPTY
} else {
viewState.value = LOADED
}
val sortedConversationsList = it.toMutableList()
sortedConversationsList.sortWith(Comparator { conversation1, conversation2 ->
CompareToBuilder()
.append(conversation2.favorite, conversation1.favorite)
.append(conversation2.lastActivity, conversation1.lastActivity)
.toComparison()
})
val newConversations = mutableListOf<ConversationItem>()
for (conversation in it) {
newConversations.add(ConversationItem(conversation, viewModel.currentUser, activity))
for (conversation in sortedConversationsList) {
newConversations.add(
ConversationItem(
conversation, viewModel.currentUserLiveData.value,
activity
)
)
}
recyclerViewAdapter.updateDataSet(newConversations as List<IFlexible<ViewHolder>>?)
@ -313,7 +334,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
var displayName =
(recyclerViewAdapter.getItem(position) as ConversationItem).model.displayName
if (displayName.length > 8) {
if (displayName!!.length > 8) {
displayName = displayName.substring(0, 4) + "..."
}
@ -394,12 +415,12 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
val conversation = (clickedItem as ConversationItem).model
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.currentUser)
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.currentUserLiveData.value)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId)
bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
ConductorRemapping.remapChatController(
router, viewModel.currentUser.id, conversation.token,
router, viewModel.currentUserLiveData.value!!.id, conversation!!.token!!,
bundle, false
)
}

View File

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

View File

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

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

@ -23,4 +23,4 @@ package com.nextcloud.talk.newarch.utils
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.utils.ApiUtils
fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token)
fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token)

View File

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

View File

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

View File

@ -24,42 +24,51 @@
android:id="@+id/generic_rv_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
android:animateLayoutChanges="true"
>
<include layout="@layout/view_states"/>
<include
layout="@layout/view_states"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayoutView"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayoutView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior"
>
<FrameLayout
android:id="@+id/dataStateView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:layout_behavior="com.nextcloud.talk.utils.FABAwareScrollingViewBehavior">
android:visibility="invisible"
>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/dataStateView"
android:visibility="invisible">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/rv_item_conversation"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/rv_item_conversation" />
</FrameLayout>
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:visibility="gone"
app:srcCompat="@drawable/ic_add_white_24px"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:visibility="gone"
app:srcCompat="@drawable/ic_add_white_24px" />
<include layout="@layout/fast_scroller" />
<include layout="@layout/fast_scroller" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -88,7 +88,7 @@
<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_configure_room">Configure 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Enceta una conversa</string>
<string name="nc_configure_room">Configura 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Začněte konverzaci</string>
<string name="nc_configure_room">Nastavení konverzace</string>
<string name="nc_leave">Opustit konverzaci</string>

View File

@ -124,7 +124,7 @@
<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_configure_room">Samtaleopsætning</string>
<string name="nc_leave">Forlad samtale</string>

View File

@ -125,7 +125,7 @@
<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_configure_room">Unterhaltung einrichten</string>
<string name="nc_leave">Unterhaltung verlassen</string>

View File

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

View File

@ -124,7 +124,7 @@
<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_configure_room">Configura la 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Commencer une conversation</string>
<string name="nc_configure_room">Configurer 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Iniciar unha conversa</string>
<string name="nc_configure_room">Configurar 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Započni razgovor</string>
<string name="nc_configure_room">Konfiguriraj 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<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_leave">Beszélgetés elhagyása</string>

View File

@ -110,7 +110,7 @@
<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_configure_room">Stilla samtal</string>
<string name="nc_leave">Hætta í samtali</string>

View File

@ -125,7 +125,7 @@
<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_configure_room">Configura conversazione</string>
<string name="nc_leave">Lascia la conversazione</string>

View File

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

View File

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

View File

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

View File

@ -93,7 +93,7 @@
<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_configure_room">Konfigurer 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Begin een gesprek</string>
<string name="nc_configure_room">Configureren gesprek</string>
<string name="nc_leave">Verlaat gesprek</string>

View File

@ -125,7 +125,7 @@
<string name="nc_select_an_account">Wybierz konto</string>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Rozpocznij rozmowę</string>
<string name="nc_configure_room">Konfiguruj 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Iniciar uma conversa</string>
<string name="nc_configure_room">Configurar uma 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Iniciar uma conversação</string>
<string name="nc_configure_room">Configurar 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Начать беседу</string>
<string name="nc_configure_room">Настроить беседу</string>
<string name="nc_leave">Покинуть беседу</string>

View File

@ -123,7 +123,7 @@
<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_configure_room">Nastavenia 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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<string name="nc_start_conversation">Začni s pogovorom</string>
<string name="nc_configure_room">Nastavitev pogovora</string>
<string name="nc_leave">Zapusti pogovor</string>

View File

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

View File

@ -125,7 +125,7 @@
<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_configure_room">Anpassa konversation</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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<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_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>
<!-- Conversation menu -->
<!-- ConversationEntity menu -->
<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_leave">Rời khỏi cuộc đàm thoại</string>

View File

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

View File

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

View File

@ -144,7 +144,7 @@
<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_configure_room">Configure conversation</string>
<string name="nc_leave">Leave conversation</string>