Compiles again

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-31 02:05:25 +01:00
parent 4b38eb1d19
commit ca4998614c
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
82 changed files with 4966 additions and 4915 deletions

View File

@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "8b9e5dddd027e51eb17ffd53d365e6d4",
"identityHash": "c81b4edb8abdd29b77836d16a7d991c2",
"entities": [
{
"tableName": "conversations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user` INTEGER, `conversation_id` TEXT, `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, FOREIGN KEY(`user`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user` INTEGER, `conversation_id` TEXT, `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` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, FOREIGN KEY(`user`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "id",
@ -148,7 +148,7 @@
},
{
"fieldPath": "lastReadMessageId",
"columnName": "last_read_message_id",
"columnName": "last_read_message",
"affinity": "INTEGER",
"notNull": true
},
@ -173,19 +173,20 @@
},
"indices": [
{
"name": "index_conversations_user",
"unique": false,
"name": "index_conversations_user_conversation_id",
"unique": true,
"columnNames": [
"user"
"user",
"conversation_id"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_conversations_user` ON `${TABLE_NAME}` (`user`)"
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_conversations_user_conversation_id` ON `${TABLE_NAME}` (`user`, `conversation_id`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"onUpdate": "CASCADE",
"columns": [
"user"
],
@ -197,7 +198,7 @@
},
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user` INTEGER, `conversation` INTEGER, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, FOREIGN KEY(`conversation`) REFERENCES `conversations`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `conversation` INTEGER, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, FOREIGN KEY(`conversation`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "id",
@ -205,12 +206,6 @@
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "user",
"columnName": "user",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "conversation",
"columnName": "conversation",
@ -274,22 +269,13 @@
"conversation"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation` ON `${TABLE_NAME}` (`conversation`)"
},
{
"name": "index_messages_user_conversation",
"unique": false,
"columnNames": [
"user",
"conversation"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_messages_user_conversation` ON `${TABLE_NAME}` (`user`, `conversation`)"
}
],
"foreignKeys": [
{
"table": "conversations",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"onUpdate": "CASCADE",
"columns": [
"conversation"
],
@ -301,25 +287,31 @@
},
{
"tableName": "users",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user_id` TEXT, `username` TEXT, `token` TEXT, `display_name` TEXT, `push_configuration` TEXT, `capabilities` TEXT, `client_auth_cert` TEXT, `external_signaling` TEXT, `status` INTEGER)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_id` TEXT NOT NULL, `username` TEXT NOT NULL, `base_url` TEXT NOT NULL, `token` TEXT, `display_name` TEXT, `push_configuration` TEXT, `capabilities` TEXT, `client_auth_cert` TEXT, `external_signaling` TEXT, `status` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": false
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
"notNull": true
},
{
"fieldPath": "baseUrl",
"columnName": "base_url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "token",
@ -377,7 +369,7 @@
"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, '8b9e5dddd027e51eb17ffd53d365e6d4')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c81b4edb8abdd29b77836d16a7d991c2')"
]
}
}

View File

@ -76,7 +76,7 @@ class MagicCallActivity : BaseActivity() {
)
} else {
router!!.setRoot(
RouterTransaction.with(CallController(intent.extras))
RouterTransaction.with(CallController(intent.extras!!))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)

View File

@ -34,6 +34,7 @@ import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.ONE_TO_ONE_CONVERSATION
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.ApiUtils
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -54,7 +55,7 @@ import java.util.regex.Pattern
class ConversationItem(
val model: Conversation,
private val userEntity: UserEntity,
private val user: UserNgEntity,
private val context: Context
) : AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder>(), IFilterable<String> {
@ -74,7 +75,7 @@ class ConversationItem(
&& model.unreadMention == comparedConversation.unreadMention
&& model.objectType == comparedConversation.objectType
&& model.changing == comparedConversation.changing
&& userEntity.id == inItem.userEntity.id)
&& user.id == inItem.user.id)
}
return false
}
@ -82,7 +83,7 @@ class ConversationItem(
override fun hashCode(): Int {
return Objects.hash(
model.conversationId, model.token,
userEntity.id
user.id
)
}
@ -168,13 +169,13 @@ class ConversationItem(
holder.itemView.dialogLastMessage!!.text = model.lastMessage!!.text
} else {
var authorDisplayName = ""
model.lastMessage!!.activeUser = userEntity
model.lastMessage!!.activeUser = user
val text: String
if (model.lastMessage!!
.messageType == ChatMessage.MessageType.REGULAR_TEXT_MESSAGE && (!(ONE_TO_ONE_CONVERSATION).equals(
model.type) || model.lastMessage!!.actorId == userEntity.userId)
model.type) || model.lastMessage!!.actorId == user.userId)
) {
if (model.lastMessage!!.actorId == userEntity.userId) {
if (model.lastMessage!!.actorId == user.userId) {
text = String.format(
appContext.getString(R.string.nc_formatted_message_you),
model.lastMessage!!.lastMessageDisplayText
@ -248,7 +249,7 @@ class ConversationItem(
) {
holder.itemView.dialogAvatar.load(
ApiUtils.getUrlForAvatarWithName(
userEntity.baseUrl,
user.baseUrl,
model.name, R.dimen.avatar_size
)
) {

View File

@ -35,6 +35,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.ApiUtils
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -51,7 +52,7 @@ class UserItem(
*/
val model: Participant,
val entity: UserEntity,
val entity: UserNgEntity,
private var header: GenericTextHeaderItem?,
private val activityContext: Context
) : AbstractFlexibleItem<UserItem.UserItemViewHolder>(),

View File

@ -1,207 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 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.messages;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
import androidx.core.view.ViewCompat;
import androidx.emoji.widget.EmojiTextView;
import autodagger.AutoInjector;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.amulyakhare.textdrawable.TextDrawable;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.android.flexbox.FlexboxLayout;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.TextMatchers;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.stfalcon.chatkit.messages.MessageHolders;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class MagicIncomingTextMessageViewHolder
extends MessageHolders.IncomingTextMessageViewHolder<ChatMessage> {
@BindView(R.id.messageAuthor)
EmojiTextView messageAuthor;
@BindView(R.id.messageText)
EmojiTextView messageText;
@BindView(R.id.messageUserAvatar)
SimpleDraweeView messageUserAvatarView;
@BindView(R.id.messageTime)
TextView messageTimeView;
@Inject
UserUtils userUtils;
@Inject
Context context;
@Inject
AppPreferences appPreferences;
private View itemView;
public MagicIncomingTextMessageViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
this.itemView = itemView;
}
@Override
public void onBind(ChatMessage message) {
super.onBind(message);
String author;
if (!TextUtils.isEmpty(author = message.getActorDisplayName())) {
messageAuthor.setText(author);
} else {
messageAuthor.setText(R.string.nc_nick_guest);
}
if (!message.isGrouped() && !message.isOneToOneConversation()) {
messageUserAvatarView.setVisibility(View.VISIBLE);
if (message.getActorType().equals("guests")) {
// do nothing, avatar is set
} else if (message.getActorType().equals("bots") && message.getActorId()
.equals("changelog")) {
messageUserAvatarView.setController(null);
Drawable[] layers = new Drawable[2];
layers[0] = context.getDrawable(R.drawable.ic_launcher_background);
layers[1] = context.getDrawable(R.drawable.ic_launcher_foreground);
LayerDrawable layerDrawable = new LayerDrawable(layers);
messageUserAvatarView.getHierarchy()
.setPlaceholderImage(DisplayUtils.INSTANCE.getRoundedDrawable(layerDrawable));
} else if (message.getActorType().equals("bots")) {
messageUserAvatarView.setController(null);
TextDrawable drawable =
TextDrawable.builder().beginConfig().bold().endConfig().buildRound(">",
context.getResources().getColor(R.color.black));
messageUserAvatarView.setVisibility(View.VISIBLE);
messageUserAvatarView.getHierarchy().setPlaceholderImage(drawable);
}
} else {
if (message.isOneToOneConversation()) {
messageUserAvatarView.setVisibility(View.GONE);
} else {
messageUserAvatarView.setVisibility(View.INVISIBLE);
}
messageAuthor.setVisibility(View.GONE);
}
Resources resources = itemView.getResources();
int bg_bubble_color = resources.getColor(R.color.bg_message_list_incoming_bubble);
int bubbleResource = R.drawable.shape_incoming_message;
if (message.isGrouped) {
bubbleResource = R.drawable.shape_grouped_incoming_message;
}
Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector(bg_bubble_color,
resources.getColor(R.color.transparent),
bg_bubble_color, bubbleResource);
ViewCompat.setBackground(bubble, bubbleDrawable);
HashMap<String, HashMap<String, String>> messageParameters = message.getMessageParameters();
itemView.setSelected(false);
messageTimeView.setTextColor(context.getResources().getColor(R.color.warm_grey_four));
FlexboxLayout.LayoutParams layoutParams =
(FlexboxLayout.LayoutParams) messageTimeView.getLayoutParams();
layoutParams.setWrapBefore(false);
Spannable messageString = new SpannableString(message.getText());
float textSize = context.getResources().getDimension(R.dimen.chat_text_size);
if (messageParameters != null && messageParameters.size() > 0) {
for (String key : messageParameters.keySet()) {
Map<String, String> individualHashMap = message.getMessageParameters().get(key);
if (individualHashMap != null) {
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type")
.equals("guest") || individualHashMap.get("type").equals("call")) {
if (individualHashMap.get("id").equals(message.getActiveUser().getUserId())) {
messageString =
DisplayUtils.INSTANCE.searchAndReplaceWithMentionSpan(messageText.getContext(),
messageString,
individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
userUtils.getUserById(message.getActiveUser().getUserId()),
R.xml.chip_you);
} else {
messageString =
DisplayUtils.INSTANCE.searchAndReplaceWithMentionSpan(messageText.getContext(),
messageString,
individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
userUtils.getUserById(message.getActiveUser().getUserId()),
R.xml.chip_others);
}
} else if (individualHashMap.get("type").equals("file")) {
itemView.setOnClickListener(v -> {
Intent browserIntent =
new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link")));
context.startActivity(browserIntent);
});
}
}
}
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.getText())) {
textSize = (float) (textSize * 2.5);
layoutParams.setWrapBefore(true);
itemView.setSelected(true);
messageAuthor.setVisibility(View.GONE);
}
messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
messageTimeView.setLayoutParams(layoutParams);
messageText.setText(messageString);
}
}

View File

@ -0,0 +1,207 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 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.messages
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.text.Spannable
import android.text.SpannableString
import android.text.TextUtils
import android.util.TypedValue
import android.view.View
import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.emoji.widget.EmojiTextView
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.ButterKnife
import com.amulyakhare.textdrawable.TextDrawable
import com.facebook.drawee.view.SimpleDraweeView
import com.google.android.flexbox.FlexboxLayout
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
import org.koin.core.KoinComponent
import org.koin.core.inject
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView), KoinComponent {
@JvmField
@BindView(R.id.messageAuthor)
var messageAuthor: EmojiTextView? = null
@JvmField
@BindView(R.id.messageText)
var messageText: EmojiTextView? = null
@JvmField
@BindView(R.id.messageUserAvatar)
var messageUserAvatarView: SimpleDraweeView? = null
@JvmField
@BindView(R.id.messageTime)
var messageTimeView: TextView? = null
@JvmField
@Inject
var context: Context? = null
val appPreferences: AppPreferences by inject()
init {
ButterKnife.bind(
this,
itemView
)
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
}
override fun onBind(message: ChatMessage) {
super.onBind(message)
val author: String = message.actorDisplayName
if (!TextUtils.isEmpty(author)) {
messageAuthor!!.text = author
} else {
messageAuthor!!.setText(R.string.nc_nick_guest)
}
if (!message.grouped && !message.oneToOneConversation) {
messageUserAvatarView!!.visibility = View.VISIBLE
if (message.actorType == "guests") {
// do nothing, avatar is set
} else if (message.actorType == "bots" && message.actorType == "changelog") {
messageUserAvatarView!!.controller = null
val layers = arrayOfNulls<Drawable>(2)
layers[0] = context!!.getDrawable(R.drawable.ic_launcher_background)
layers[1] = context!!.getDrawable(R.drawable.ic_launcher_foreground)
val layerDrawable = LayerDrawable(layers)
messageUserAvatarView!!.hierarchy
.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable))
} else if (message.actorType == "bots") {
messageUserAvatarView!!.controller = null
val drawable = TextDrawable.builder()
.beginConfig()
.bold()
.endConfig()
.buildRound(
">",
context!!.resources.getColor(R.color.black)
)
messageUserAvatarView!!.visibility = View.VISIBLE
messageUserAvatarView!!.hierarchy.setPlaceholderImage(drawable)
}
} else {
if (message.oneToOneConversation) {
messageUserAvatarView!!.visibility = View.GONE
} else {
messageUserAvatarView!!.visibility = View.INVISIBLE
}
messageAuthor!!.visibility = View.GONE
}
val resources = itemView.getResources()
val bg_bubble_color = resources.getColor(R.color.bg_message_list_incoming_bubble)
var bubbleResource = R.drawable.shape_incoming_message
if (message.grouped) {
bubbleResource = R.drawable.shape_grouped_incoming_message
}
val bubbleDrawable = DisplayUtils.getMessageSelector(
bg_bubble_color,
resources.getColor(R.color.transparent),
bg_bubble_color, bubbleResource
)
ViewCompat.setBackground(bubble, bubbleDrawable)
val messageParameters = message.messageParameters
itemView.setSelected(false)
messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.warm_grey_four))
val layoutParams = messageTimeView!!.layoutParams as FlexboxLayout.LayoutParams
layoutParams.isWrapBefore = false
var messageString: Spannable = SpannableString(message.text)
var textSize = context!!.resources.getDimension(R.dimen.chat_text_size)
if (messageParameters != null && messageParameters.size > 0) {
for (key in messageParameters.keys) {
val individualHashMap = message.messageParameters[key]
if (individualHashMap != null) {
if (individualHashMap["type"] == "user" || individualHashMap["type"] == "guest" || individualHashMap["type"] == "call") {
if (individualHashMap["id"] == message.activeUser.userId) {
messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
messageText!!.context,
messageString,
individualHashMap["id"]!!,
individualHashMap["name"]!!,
individualHashMap["type"]!!,
message.activeUser,
R.xml.chip_you
)
} else {
messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
messageText!!.context,
messageString,
individualHashMap["id"]!!,
individualHashMap["name"]!!,
individualHashMap["type"]!!,
message.activeUser,
R.xml.chip_others
)
}
} else if (individualHashMap["type"] == "file") {
itemView.setOnClickListener({ v ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap["link"]))
context!!.startActivity(browserIntent)
})
}
}
}
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
textSize = (textSize * 2.5).toFloat()
layoutParams.isWrapBefore = true
itemView.setSelected(true)
messageAuthor!!.visibility = View.GONE
}
messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
messageTimeView!!.layoutParams = layoutParams
messageText!!.text = messageString
}
}

View File

@ -103,7 +103,7 @@ public class MagicOutcomingTextMessageViewHolder
individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
userUtils.getUserById(message.getActiveUser().getUserId()),
message.activeUser,
R.xml.chip_others);
} else if (individualHashMap.get("type").equals("file")) {
itemView.setOnClickListener(v -> {
@ -122,7 +122,7 @@ public class MagicOutcomingTextMessageViewHolder
}
Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources();
if (message.isGrouped) {
if (message.grouped) {
Drawable bubbleDrawable =
DisplayUtils.INSTANCE.getMessageSelector(
resources.getColor(R.color.bg_message_list_outcoming_bubble),

View File

@ -43,12 +43,12 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
import com.nextcloud.talk.components.filebrowser.models.DavResponse
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.AccountUtils.canWeOpenFilesApp
import com.nextcloud.talk.utils.DisplayUtils.setClickableString
import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
@ -80,8 +80,8 @@ class MagicPreviewMessageViewHolder(itemView: View?) : IncomingImageMessageViewH
override fun onBind(message: ChatMessage) {
super.onBind(message)
if (userAvatar != null) {
if (message.isGrouped || message.isOneToOneConversation) {
if (message.isOneToOneConversation) {
if (message.grouped || message.oneToOneConversation) {
if (message.oneToOneConversation) {
userAvatar.visibility = View.GONE
} else {
userAvatar.visibility = View.INVISIBLE
@ -183,7 +183,7 @@ class MagicPreviewMessageViewHolder(itemView: View?) : IncomingImageMessageViewH
private fun fetchFileInformation(
url: String,
activeUser: UserEntity?
activeUser: UserNgEntity?
) {
Single.fromCallable {
ReadFilesystemOperation(

View File

@ -246,15 +246,25 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
var userNg: UserNgEntity
val newUsers = mutableListOf<UserNgEntity>()
for (user in users) {
userNg = UserNgEntity()
userNg.userId = user.userId
userNg.username = user.username
userNg = UserNgEntity(user.id, user.userId, user.username, user.baseUrl)
userNg.token = user.token
userNg.displayName = user.displayName
userNg.pushConfiguration = LoganSquare.parse(user.pushConfigurationState, PushConfigurationState::class.java)
userNg.capabilities = LoganSquare.parse(user.capabilities, Capabilities::class.java)
try {
userNg.pushConfiguration =
LoganSquare.parse(user.pushConfigurationState, PushConfigurationState::class.java)
} catch (e: Exception) {
// no push
}
if (user.capabilities != null) {
userNg.capabilities = LoganSquare.parse(user.capabilities, Capabilities::class.java)
}
userNg.clientCertificate = user.clientCertificate
userNg.externalSignaling = LoganSquare.parse(user.externalSignalingServer, ExternalSignalingServer::class.java)
try {
userNg.externalSignaling =
LoganSquare.parse(user.externalSignalingServer, ExternalSignalingServer::class.java)
} catch (e: Exception) {
// no external signaling
}
if (user.current) {
userNg.status = ACTIVE
} else {

View File

@ -28,6 +28,7 @@ import com.facebook.widget.text.span.BetterImageSpan;
import com.nextcloud.talk.R;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.mention.Mention;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.MagicCharPolicy;
import com.nextcloud.talk.utils.text.Spans;
@ -37,10 +38,10 @@ import com.vanniktech.emoji.EmojiUtils;
public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> {
private Context context;
private UserEntity conversationUser;
private UserNgEntity conversationUser;
private EditText editText;
public MentionAutocompleteCallback(Context context, UserEntity conversationUser,
public MentionAutocompleteCallback(Context context, UserNgEntity conversationUser,
EditText editText) {
this.context = context;
this.conversationUser = conversationUser;

View File

@ -35,6 +35,8 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
import com.nextcloud.talk.interfaces.SelectionInterface
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.utils.getCredentials
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
@ -49,7 +51,7 @@ import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class BrowserFileItem(
val model: BrowserFile,
private val activeUser: UserEntity,
private val activeUser: UserNgEntity,
private val selectionInterface: SelectionInterface
) : AbstractFlexibleItem<BrowserFileItem.ViewHolder>(), IFilterable<String> {
@JvmField
@ -63,9 +65,9 @@ class BrowserFileItem(
.inject(this)
}
override fun equals(o: Any?): Boolean {
if (o is BrowserFileItem) {
val inItem = o as BrowserFileItem?
override fun equals(other: Any?): Boolean {
if (other is BrowserFileItem) {
val inItem = other as BrowserFileItem?
return model.path == inItem!!.model.path
}

View File

@ -50,6 +50,7 @@ import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.interfaces.SelectionInterface;
import com.nextcloud.talk.jobs.ShareOperationWorker;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import eu.davidea.fastscroller.FastScroller;
@ -73,8 +74,6 @@ import org.parceler.Parcels;
public class BrowserController extends BaseController implements ListingInterface,
FlexibleAdapter.OnItemClickListener, SelectionInterface {
private final Set<String> selectedPaths;
@Inject
UserUtils userUtils;
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@BindView(R.id.fast_scroller)
@ -97,7 +96,7 @@ public class BrowserController extends BaseController implements ListingInterfac
private ListingAbstractClass listingAbstractClass;
private BrowserType browserType;
private String currentPath;
private UserEntity activeUser;
private UserNgEntity activeUser;
private String roomToken;
public BrowserController(Bundle args) {

View File

@ -25,6 +25,7 @@ import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.disposables.Disposable;
@ -40,7 +41,7 @@ public class DavListing extends ListingAbstractClass {
}
@Override
public void getFiles(String path, UserEntity currentUser, @Nullable OkHttpClient okHttpClient) {
public void getFiles(String path, UserNgEntity currentUser, @Nullable OkHttpClient okHttpClient) {
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
@Override
public ReadFilesystemOperation call() {

View File

@ -24,6 +24,7 @@ import android.os.Handler;
import androidx.annotation.Nullable;
import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import okhttp3.OkHttpClient;
public abstract class ListingAbstractClass {
@ -35,7 +36,7 @@ public abstract class ListingAbstractClass {
this.listingInterface = listingInterface;
}
public abstract void getFiles(String path, UserEntity currentUser,
public abstract void getFiles(String path, UserNgEntity currentUser,
@Nullable OkHttpClient okHttpClient);
public void cancelAllJobs() {

View File

@ -27,6 +27,7 @@ import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
import com.nextcloud.talk.dagger.modules.RestModule;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.ApiUtils;
import java.io.IOException;
import java.util.ArrayList;
@ -42,7 +43,7 @@ public class ReadFilesystemOperation {
private final int depth;
private final String basePath;
public ReadFilesystemOperation(OkHttpClient okHttpClient, UserEntity currentUser, String path,
public ReadFilesystemOperation(OkHttpClient okHttpClient, UserNgEntity currentUser, String path,
int depth) {
OkHttpClient.Builder okHttpClientBuilder = okHttpClient.newBuilder();
okHttpClientBuilder.followRedirects(false);

File diff suppressed because it is too large Load Diff

View File

@ -73,7 +73,6 @@ import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.events.UserMentionClickEvent
import com.nextcloud.talk.events.WebSocketCommunicationEvent
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.conversations.Conversation
@ -81,8 +80,10 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.local.models.maxMessageLength
import com.nextcloud.talk.newarch.utils.Images
import com.nextcloud.talk.newarch.utils.getCredentials
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConductorRemapping
@ -94,7 +95,6 @@ import com.nextcloud.talk.utils.MagicCharPolicy
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
import com.nextcloud.talk.utils.text.Spans
import com.nextcloud.talk.webrtc.MagicWebSocketInstance
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper
@ -161,7 +161,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
@JvmField
var conversationLobbyText: TextView? = null
var roomToken: String? = null
val conversationUser: UserEntity?
val conversationUser: UserNgEntity?
val roomPassword: String
var credentials: String? = null
var currentConversation: Conversation? = null
@ -459,7 +459,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
})
val filters = arrayOfNulls<InputFilter>(1)
val lengthFilter = conversationUser?.messageMaxLength ?: 1000
val lengthFilter = conversationUser?.maxMessageLength() ?: 1000
filters[0] = InputFilter.LengthFilter(lengthFilter)
@ -627,7 +627,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
bundle.putParcelable(
BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType)
)
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser))
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserNgEntity>(conversationUser))
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
router.pushController(
RouterTransaction.with(BrowserController(bundle))
@ -682,14 +682,6 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
}
isLeavingForConversation = false
ApplicationWideCurrentRoomHolder.getInstance()
.currentRoomId = roomId
ApplicationWideCurrentRoomHolder.getInstance()
.currentRoomToken = roomId
ApplicationWideCurrentRoomHolder.getInstance()
.isInCall = false
ApplicationWideCurrentRoomHolder.getInstance()
.userInRoom = conversationUser
isLinkPreviewAllowed = appPreferences.areLinkPreviewsAllowed
@ -745,8 +737,6 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
override fun onDetach(view: View) {
eventBus.unregister(this)
ApplicationWideCurrentRoomHolder.getInstance()
.clear()
if (activity != null) {
activity?.findViewById<View>(R.id.toolbar)
@ -850,10 +840,6 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
override fun onNext(roomOverall: RoomOverall) {
inConversation = true
currentConversation?.sessionId = roomOverall.ocs.data.sessionId
ApplicationWideCurrentRoomHolder.getInstance()
.session =
currentConversation?.sessionId
startPing()
setupWebsocket()
@ -886,8 +872,6 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
})
} else {
inConversation = true
ApplicationWideCurrentRoomHolder.getInstance()
.session = currentConversation?.sessionId
if (magicWebSocketInstance != null) {
magicWebSocketInstance?.joinRoomWithRoomTokenAndSession(
roomToken,
@ -1198,7 +1182,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
chatMessageList[i + 1].createdAt
)
) {
chatMessageList[i].isGrouped = true
chatMessageList[i].grouped = true
countGroupedMessages++
} else {
countGroupedMessages = 0
@ -1206,7 +1190,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
}
val chatMessage = chatMessageList[i]
chatMessage.isOneToOneConversation =
chatMessage.oneToOneConversation =
currentConversation?.type == Conversation.ConversationType.ONE_TO_ONE_CONVERSATION
chatMessage.isLinkPreviewAllowed = isLinkPreviewAllowed
chatMessage.activeUser = conversationUser
@ -1272,11 +1256,11 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
}
if (adapter != null) {
chatMessage.isGrouped = (adapter!!.isPreviousSameAuthor(
chatMessage.grouped = (adapter!!.isPreviousSameAuthor(
chatMessage
.actorId, -1
) && adapter!!.getSameAuthorLastMessagesCount(chatMessage.actorId) % 5 > 0)
chatMessage.isOneToOneConversation =
chatMessage.oneToOneConversation =
(currentConversation?.type == Conversation.ConversationType.ONE_TO_ONE_CONVERSATION)
adapter?.addToStart(chatMessage, shouldScroll)
}

File diff suppressed because it is too large Load Diff

View File

@ -73,6 +73,9 @@ import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.ParticipantsOverall
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.local.models.hasSpreedFeatureCapability
import com.nextcloud.talk.newarch.utils.getCredentials
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
@ -174,7 +177,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
lateinit var userUtils: UserUtils
private val conversationToken: String?
private val conversationUser: UserEntity?
private val conversationUser: UserNgEntity?
private val credentials: String?
private var roomDisposable: Disposable? = null
private var participantsDisposable: Disposable? = null
@ -245,7 +248,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
if (databaseStorageModule == null) {
databaseStorageModule = DatabaseStorageModule(
conversationUser, conversationToken, this)
conversationUser!!, conversationToken!!, this)
}
notificationsPreferenceScreen.setStorageModule(databaseStorageModule)

View File

@ -24,11 +24,11 @@ import lombok.Data;
@Data
public class BottomSheetLockEvent {
private final boolean cancelable;
private final int delay;
private final boolean shouldRefreshData;
private final boolean cancel;
private boolean dismissView;
public final boolean cancelable;
public final int delay;
public final boolean shouldRefreshData;
public final boolean cancel;
public boolean dismissView;
public BottomSheetLockEvent(boolean cancelable, int delay, boolean shouldRefreshData,
boolean cancel) {

View File

@ -26,9 +26,9 @@ import org.webrtc.MediaStream;
@Data
public class MediaStreamEvent {
private final MediaStream mediaStream;
private final String session;
private final String videoStreamType;
public final MediaStream mediaStream;
public final String session;
public final String videoStreamType;
public MediaStreamEvent(@Nullable MediaStream mediaStream, String session,
String videoStreamType) {

View File

@ -24,7 +24,7 @@ import lombok.Data;
@Data
public class NetworkEvent {
private final NetworkConnectionEvent networkConnectionEvent;
public final NetworkConnectionEvent networkConnectionEvent;
public NetworkEvent(NetworkConnectionEvent networkConnectionEvent) {
this.networkConnectionEvent = networkConnectionEvent;

View File

@ -25,11 +25,11 @@ import lombok.Data;
@Data
public class PeerConnectionEvent {
private final PeerConnectionEventType peerConnectionEventType;
private final String sessionId;
private final String nick;
private final Boolean changeValue;
private final String videoStreamType;
public final PeerConnectionEventType peerConnectionEventType;
public final String sessionId;
public final String nick;
public final Boolean changeValue;
public final String videoStreamType;
public PeerConnectionEvent(PeerConnectionEventType peerConnectionEventType,
@Nullable String sessionId,

View File

@ -28,12 +28,12 @@ import org.webrtc.SessionDescription;
@Data
public class SessionDescriptionSendEvent {
@Nullable
private final SessionDescription sessionDescription;
private final String peerId;
private final String type;
public final SessionDescription sessionDescription;
public final String peerId;
public final String type;
@Nullable
private final NCIceCandidate ncIceCandidate;
private final String videoStreamType;
public final NCIceCandidate ncIceCandidate;
public final String videoStreamType;
public SessionDescriptionSendEvent(@Nullable SessionDescription sessionDescription, String peerId,
String type,

View File

@ -76,6 +76,10 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.notifications.NotificationOverall
import com.nextcloud.talk.models.json.push.DecryptedPushMessage
import com.nextcloud.talk.models.json.push.NotificationUser
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.local.models.hasSpreedFeatureCapability
import com.nextcloud.talk.newarch.utils.Images
import com.nextcloud.talk.newarch.utils.getCredentials
import com.nextcloud.talk.utils.ApiUtils
@ -101,11 +105,12 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import okhttp3.JavaNetCookieJar
import okhttp3.OkHttpClient
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.parceler.Parcels
import retrofit2.Retrofit
import java.io.IOException
@ -124,19 +129,16 @@ import javax.inject.Inject
class NotificationWorker(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
@JvmField
@Inject
var appPreferences: AppPreferences? = null
) : Worker(context, workerParams), KoinComponent {
val appPreferences: AppPreferences by inject()
val retrofit: Retrofit by inject()
val okHttpClient: OkHttpClient by inject()
val usersRepository: UsersRepository by inject()
@JvmField
@Inject
var arbitraryStorageUtils: ArbitraryStorageUtils? = null
@JvmField
@Inject
var retrofit: Retrofit? = null
@JvmField
@Inject
var okHttpClient: OkHttpClient? = null
private var ncApi: NcApi? = null
private var decryptedPushMessage: DecryptedPushMessage? = null
private var context: Context? = null
@ -148,7 +150,7 @@ class NotificationWorker(
private fun showNotificationForCallWithNoPing(intent: Intent) {
val userEntity: UserEntity =
val userEntity: UserNgEntity =
signatureVerification!!.userEntity
var arbitraryStorageEntity: ArbitraryStorageEntity?
@ -224,7 +226,7 @@ class NotificationWorker(
}
private fun showNotificationWithObjectData(intent: Intent) {
val userEntity: UserEntity =
val userEntity: UserNgEntity =
signatureVerification!!.userEntity
ncApi!!.getNotification(
credentials, ApiUtils.getUrlForNotificationWithId(
@ -575,8 +577,7 @@ class NotificationWorker(
}
}
if (soundUri != null && !ApplicationWideCurrentRoomHolder.getInstance().isInCall &&
(shouldPlaySound(importantConversation))
if (soundUri != null && (shouldPlaySound(importantConversation))
) {
val audioAttributesBuilder: AudioAttributes.Builder =
AudioAttributes.Builder()
@ -630,7 +631,7 @@ class NotificationWorker(
data.getString(KEY_NOTIFICATION_SIGNATURE)
val base64DecodedSubject: ByteArray = Base64.decode(subject, Base64.DEFAULT)
val base64DecodedSignature: ByteArray = Base64.decode(signature, Base64.DEFAULT)
val pushUtils = PushUtils()
val pushUtils = PushUtils(usersRepository)
val privateKey = pushUtils.readKeyFromFile(false) as PrivateKey
try {
signatureVerification = pushUtils.verifySignature(

View File

@ -1,45 +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.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.nextcloud.talk.utils.PushUtils;
public class PushRegistrationWorker extends Worker {
public static final String TAG = "PushRegistrationWorker";
public PushRegistrationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
PushUtils pushUtils = new PushUtils();
pushUtils.generateRsa2048KeyPair();
pushUtils.pushRegistrationToServer();
return Result.success();
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.jobs
import android.content.Context
import androidx.work.ListenableWorker.Result
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.utils.PushUtils
import org.koin.core.KoinComponent
import org.koin.core.inject
class PushRegistrationWorker(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams), KoinComponent {
val usersRepository: UsersRepository by inject()
override fun doWork(): Result {
val pushUtils = PushUtils(usersRepository)
pushUtils.generateRsa2048KeyPair()
pushUtils.pushRegistrationToServer()
return Result.success()
}
companion object {
const val TAG = "PushRegistrationWorker"
}
}

View File

@ -1,87 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 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.jobs;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import autodagger.AutoInjector;
import com.bluelinelabs.logansquare.LoganSquare;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.ExternalSignalingServer;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper;
import java.io.IOException;
import java.util.List;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class WebsocketConnectionsWorker extends Worker {
private static final String TAG = "WebsocketConnectionsWorker";
@Inject
UserUtils userUtils;
public WebsocketConnectionsWorker(@NonNull Context context,
@NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@SuppressLint("LongLogTag")
@NonNull
@Override
public Result doWork() {
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
List<UserEntity> userEntityList = userUtils.getUsers();
UserEntity userEntity;
ExternalSignalingServer externalSignalingServer;
WebSocketConnectionHelper webSocketConnectionHelper = new WebSocketConnectionHelper();
for (int i = 0; i < userEntityList.size(); i++) {
userEntity = userEntityList.get(i);
if (!TextUtils.isEmpty(userEntity.getExternalSignalingServer())) {
try {
externalSignalingServer = LoganSquare.parse(userEntity.getExternalSignalingServer(),
ExternalSignalingServer.class);
if (!TextUtils.isEmpty(externalSignalingServer.getExternalSignalingServer()) &&
!TextUtils.isEmpty(externalSignalingServer.getExternalSignalingTicket())) {
WebSocketConnectionHelper.getExternalSignalingInstanceForServer(
externalSignalingServer.getExternalSignalingServer(),
userEntity, externalSignalingServer.getExternalSignalingTicket(),
false);
}
} catch (IOException e) {
Log.e(TAG, "Failed to parse external signaling server");
}
}
}
return Result.success();
}
}

View File

@ -0,0 +1,80 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 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.jobs
import android.annotation.SuppressLint
import android.content.Context
import android.text.TextUtils
import android.util.Log
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters
import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.ExternalSignalingServer
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.io.IOException
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class WebsocketConnectionsWorker(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams), KoinComponent {
val usersRepository: UsersRepository by inject()
override fun doWork(): Result {
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
val userEntityList = usersRepository.getUsers()
var userEntity: UserNgEntity
for (i in userEntityList.indices) {
userEntity = userEntityList[i]
if (userEntity.externalSignaling != null) {
if (!userEntity.externalSignaling!!.externalSignalingServer.isNullOrEmpty() &&
!userEntity.externalSignaling!!.externalSignalingTicket.isNullOrEmpty()) {
WebSocketConnectionHelper.getExternalSignalingInstanceForServer(
userEntity.externalSignaling!!.externalSignalingServer,
userEntity, userEntity.externalSignaling!!.externalSignalingTicket,
false
)
}
}
}
return Result.success()
}
companion object {
private val TAG = "WebsocketConnectionsWorker"
}
}

View File

@ -18,19 +18,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models;
package com.nextcloud.talk.models
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import lombok.Data;
import org.parceler.Parcel;
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
import lombok.Data
import org.parceler.Parcel
@Data
@Parcel
@JsonObject
public class ExternalSignalingServer {
@JsonField(name = "externalSignalingServer")
public String externalSignalingServer;
@JsonField(name = "externalSignalingTicket")
public String externalSignalingTicket;
}
@Parcelize
data class ExternalSignalingServer(
@JsonField(name = ["externalSignalingServer"])
var externalSignalingServer: String? = null,
@JsonField(name = ["externalSignalingTicket"])
var externalSignalingTicket: String? = null
): Parcelable

View File

@ -21,6 +21,7 @@
package com.nextcloud.talk.models;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import lombok.Data;
import org.parceler.Parcel;
@ -28,5 +29,5 @@ import org.parceler.Parcel;
@Parcel
public class SignatureVerification {
public boolean signatureValid;
public UserEntity userEntity;
public UserNgEntity userEntity;
}

View File

@ -32,5 +32,5 @@ import org.parceler.Parcel;
@JsonObject
public class AutocompleteOCS extends GenericOCS {
@JsonField(name = "data")
List<AutocompleteUser> data;
public List<AutocompleteUser> data;
}

View File

@ -30,5 +30,5 @@ import org.parceler.Parcel;
@JsonObject
public class AutocompleteOverall {
@JsonField(name = "ocs")
AutocompleteOCS ocs;
public AutocompleteOCS ocs;
}

View File

@ -30,11 +30,11 @@ import org.parceler.Parcel;
@JsonObject
public class AutocompleteUser {
@JsonField(name = "id")
String id;
public String id;
@JsonField(name = "label")
String label;
public String label;
@JsonField(name = "source")
String source;
public String source;
}

View File

@ -30,5 +30,5 @@ import org.parceler.Parcel;
@JsonObject
public class CapabilitiesList {
@JsonField(name = "capabilities")
Capabilities capabilities;
public Capabilities capabilities;
}

View File

@ -30,5 +30,5 @@ import org.parceler.Parcel;
@JsonObject
public class CapabilitiesOCS extends GenericOCS {
@JsonField(name = "data")
CapabilitiesList data;
public CapabilitiesList data;
}

View File

@ -29,5 +29,5 @@ import org.parceler.Parcel;
@JsonObject
public class CapabilitiesOverall {
@JsonField(name = "ocs")
CapabilitiesOCS ocs;
public CapabilitiesOCS ocs;
}

View File

@ -32,8 +32,8 @@ import org.parceler.Parcel;
@JsonObject
public class SpreedCapability {
@JsonField(name = "features")
List<String> features;
public List<String> features;
@JsonField(name = "config")
HashMap<String, HashMap<String, String>> config;
public HashMap<String, HashMap<String, String>> config;
}

View File

@ -29,6 +29,7 @@ import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.TextMatchers;
import com.stfalcon.chatkit.commons.models.IMessage;
@ -48,13 +49,13 @@ import org.parceler.Parcel;
public class ChatMessage implements IMessage, MessageContentType, MessageContentType.Image {
@JsonIgnore
@Ignore
public boolean isGrouped;
public boolean grouped;
@JsonIgnore
@Ignore
public boolean isOneToOneConversation;
public boolean oneToOneConversation;
@JsonIgnore
@Ignore
public UserEntity activeUser;
public UserNgEntity activeUser;
@JsonIgnore
@Ignore
public Map<String, String> selectedIndividualHashMap;

View File

@ -34,6 +34,8 @@ 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 com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.hasSpreedFeatureCapability
import lombok.Data
import org.parceler.Parcel
import org.parceler.ParcelConstructor
@ -120,36 +122,36 @@ class Conversation {
return resources.getString(R.string.nc_delete_conversation_default)
}
private fun isLockedOneToOne(conversationUser: UserEntity): Boolean {
private fun isLockedOneToOne(conversationUser: UserNgEntity): Boolean {
return type == ConversationType.ONE_TO_ONE_CONVERSATION && conversationUser
.hasSpreedFeatureCapability(
"locked-one-to-one-rooms"
)
}
fun canModerate(conversationUser: UserEntity): Boolean {
fun canModerate(conversationUser: UserNgEntity): Boolean {
return (Participant.ParticipantType.OWNER == participantType || Participant.ParticipantType.MODERATOR == participantType) && !isLockedOneToOne(
conversationUser
)
}
fun shouldShowLobby(conversationUser: UserEntity): Boolean {
fun shouldShowLobby(conversationUser: UserNgEntity): Boolean {
return LobbyState.LOBBY_STATE_MODERATORS_ONLY == lobbyState && !canModerate(
conversationUser
)
}
fun isLobbyViewApplicable(conversationUser: UserEntity): Boolean {
fun isLobbyViewApplicable(conversationUser: UserNgEntity): Boolean {
return !canModerate(
conversationUser
) && (type == ConversationType.GROUP_CONVERSATION || type == ConversationType.PUBLIC_CONVERSATION)
}
fun isNameEditable(conversationUser: UserEntity): Boolean {
fun isNameEditable(conversationUser: UserNgEntity): Boolean {
return canModerate(conversationUser) && ConversationType.ONE_TO_ONE_CONVERSATION != type
}
fun canLeave(conversationUser: UserEntity): Boolean {
fun canLeave(conversationUser: UserNgEntity): Boolean {
return !canModerate(
conversationUser
) || type != ConversationType.ONE_TO_ONE_CONVERSATION && participants!!.size > 1

View File

@ -18,29 +18,28 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.push;
package com.nextcloud.talk.models.json.push
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import lombok.Data;
import org.parceler.Parcel;
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
import lombok.Data
import org.parceler.Parcel
@Parcel
@Data
@JsonObject
public class PushConfigurationState {
@JsonField(name = "pushToken")
public String pushToken;
@JsonField(name = "deviceIdentifier")
public String deviceIdentifier;
@JsonField(name = "deviceIdentifierSignature")
public String deviceIdentifierSignature;
@JsonField(name = "userPublicKey")
public String userPublicKey;
@JsonField(name = "usesRegularPass")
public boolean usesRegularPass;
}
@Parcelize
class PushConfigurationState(
@JsonField(name = ["pushToken"])
var pushToken: String? = null,
@JsonField(name = ["deviceIdentifier"])
var deviceIdentifier: String? = null,
@JsonField(name = ["deviceIdentifierSignature"])
var deviceIdentifierSignature: String? = null,
@JsonField(name = ["userPublicKey"])
var userPublicKey: String? = null,
@JsonField(name = ["usesRegularPass"])
var usesRegularPass: Boolean = false
): Parcelable

View File

@ -30,12 +30,12 @@ import org.parceler.Parcel;
@JsonObject
public class PushRegistration {
@JsonField(name = "publicKey")
String publicKey;
public String publicKey;
@JsonField(name = "deviceIdentifier")
String deviceIdentifier;
public String deviceIdentifier;
@JsonField(name = "signature")
String signature;
public String signature;
}

View File

@ -31,5 +31,5 @@ import org.parceler.Parcel;
@JsonObject
public class PushRegistrationOCS extends GenericOCS {
@JsonField(name = "data")
PushRegistration data;
public PushRegistration data;
}

View File

@ -30,5 +30,5 @@ import org.parceler.Parcel;
@JsonObject
public class PushRegistrationOverall {
@JsonField(name = "ocs")
PushRegistrationOCS ocs;
public PushRegistrationOCS ocs;
}

View File

@ -31,11 +31,11 @@ import org.parceler.ParcelPropertyConverter;
@JsonObject
public class DataChannelMessageNick {
@JsonField(name = "type")
String type;
public String type;
@ParcelPropertyConverter(ObjectParcelConverter.class)
@JsonField(name = "payload")
HashMap<String, String> payload;
public HashMap<String, String> payload;
public DataChannelMessageNick(String type) {
this.type = type;

View File

@ -30,11 +30,11 @@ import org.parceler.Parcel;
@Parcel
public class NCIceCandidate {
@JsonField(name = "sdpMLineIndex")
int sdpMLineIndex;
public int sdpMLineIndex;
@JsonField(name = "sdpMid")
String sdpMid;
public String sdpMid;
@JsonField(name = "candidate")
String candidate;
public String candidate;
}

View File

@ -30,17 +30,17 @@ import org.parceler.Parcel;
@Parcel
public class NCMessagePayload {
@JsonField(name = "type")
String type;
public String type;
@JsonField(name = "sdp")
String sdp;
public String sdp;
@JsonField(name = "nick")
String nick;
public String nick;
@JsonField(name = "candidate")
NCIceCandidate iceCandidate;
public NCIceCandidate iceCandidate;
@JsonField(name = "name")
String name;
public String name;
}

View File

@ -30,12 +30,12 @@ import org.parceler.Parcel;
@Parcel
public class NCMessageWrapper {
@JsonField(name = "fn")
NCSignalingMessage signalingMessage;
public NCSignalingMessage signalingMessage;
// always a "message"
@JsonField(name = "ev")
String ev;
public String ev;
@JsonField(name = "sessionId")
String sessionId;
public String sessionId;
}

View File

@ -30,17 +30,17 @@ import org.parceler.Parcel;
@Parcel
public class NCSignalingMessage {
@JsonField(name = "from")
String from;
public String from;
@JsonField(name = "to")
String to;
public String to;
@JsonField(name = "type")
String type;
public String type;
@JsonField(name = "payload")
NCMessagePayload payload;
public NCMessagePayload payload;
@JsonField(name = "roomType")
String roomType;
public String roomType;
@JsonField(name = "sid")
String sid;
public String sid;
@JsonField(name = "prefix")
String prefix;
public String prefix;
}

View File

@ -32,8 +32,8 @@ import lombok.Data;
@JsonObject
public class Signaling {
@JsonField(name = "type")
String type;
public String type;
//can be NCMessageWrapper or List<HashMap<String,String>>
@JsonField(name = "data")
Object messageWrapper;
public Object messageWrapper;
}

View File

@ -30,5 +30,5 @@ import lombok.Data;
@JsonObject
public class SignalingOCS extends GenericOCS {
@JsonField(name = "data")
List<Signaling> signalings;
public List<Signaling> signalings;
}

View File

@ -28,5 +28,5 @@ import lombok.Data;
@Data
public class SignalingOverall {
@JsonField(name = "ocs")
SignalingOCS ocs;
public SignalingOCS ocs;
}

View File

@ -29,14 +29,14 @@ import lombok.Data;
@JsonObject
public class IceServer {
@JsonField(name = "url")
String url;
public String url;
@JsonField(name = "urls")
List<String> urls;
public List<String> urls;
@JsonField(name = "username")
String username;
public String username;
@JsonField(name = "credential")
String credential;
public String credential;
}

View File

@ -29,14 +29,14 @@ import lombok.Data;
@JsonObject
public class Settings {
@JsonField(name = "stunservers")
List<IceServer> stunServers;
public List<IceServer> stunServers;
@JsonField(name = "turnservers")
List<IceServer> turnServers;
public List<IceServer> turnServers;
@JsonField(name = "server")
String externalSignalingServer;
public String externalSignalingServer;
@JsonField(name = "ticket")
String externalSignalingTicket;
public String externalSignalingTicket;
}

View File

@ -29,5 +29,5 @@ import lombok.Data;
@JsonObject
public class SignalingSettingsOcs extends GenericOCS {
@JsonField(name = "data")
Settings settings;
public Settings settings;
}

View File

@ -28,5 +28,5 @@ import lombok.Data;
@JsonObject
public class SignalingSettingsOverall {
@JsonField(name = "ocs")
SignalingSettingsOcs ocs;
public SignalingSettingsOcs ocs;
}

View File

@ -20,9 +20,21 @@
package com.nextcloud.talk.newarch.data.repository.offline
import androidx.lifecycle.LiveData
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.dao.UsersDao
import com.nextcloud.talk.newarch.local.models.UserNgEntity
class UsersRepositoryImpl(val usersDao: UsersDao): UsersRepository {
override fun getActiveUserLiveData(): LiveData<UserNgEntity> {
return usersDao.getActiveUserLiveData()
}
override fun getActiveUser(): UserNgEntity {
return usersDao.getActiveUser()
}
override fun getUsers(): List<UserNgEntity> {
return usersDao.getUsers()
}
}

View File

@ -25,12 +25,14 @@ import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.data.source.remote.ApiService
import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.utils.getCredentials
import com.nextcloud.talk.utils.ApiUtils
class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : NextcloudTalkRepository {
override suspend fun deleteConversationForUser(
user: UserEntity,
user: UserNgEntity,
conversation: Conversation
): GenericOverall {
return apiService.deleteConversation(
@ -39,7 +41,7 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
}
override suspend fun leaveConversationForUser(
user: UserEntity,
user: UserNgEntity,
conversation: Conversation
): GenericOverall {
return apiService.leaveConversation(
@ -51,7 +53,7 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
}
override suspend fun setFavoriteValueForConversation(
user: UserEntity,
user: UserNgEntity,
conversation: Conversation,
favorite: Boolean
): GenericOverall {
@ -68,7 +70,7 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
}
}
override suspend fun getConversationsForUser(user: UserEntity): List<Conversation> {
override suspend fun getConversationsForUser(user: UserNgEntity): List<Conversation> {
return apiService.getConversations(
user.getCredentials(),
ApiUtils.getUrlForGetRooms(user.baseUrl)

View File

@ -20,6 +20,11 @@
package com.nextcloud.talk.newarch.domain.repository.offline
interface UsersRepository {
import androidx.lifecycle.LiveData
import com.nextcloud.talk.newarch.local.models.UserNgEntity
interface UsersRepository {
fun getActiveUserLiveData(): LiveData<UserNgEntity>
fun getActiveUser(): UserNgEntity
fun getUsers(): List<UserNgEntity>
}

View File

@ -23,22 +23,23 @@ package com.nextcloud.talk.newarch.domain.repository.online
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.local.models.UserNgEntity
interface NextcloudTalkRepository {
suspend fun getConversationsForUser(user: UserEntity): List<Conversation>
suspend fun getConversationsForUser(user: UserNgEntity): List<Conversation>
suspend fun setFavoriteValueForConversation(
user: UserEntity,
user: UserNgEntity,
conversation: Conversation,
favorite: Boolean
): GenericOverall
suspend fun deleteConversationForUser(
user: UserEntity,
user: UserNgEntity,
conversation: Conversation
): GenericOverall
suspend fun leaveConversationForUser(
userEntity: UserEntity,
userEntity: UserNgEntity,
conversation: Conversation
): GenericOverall
}

View File

@ -24,6 +24,7 @@ import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
@ -37,14 +38,15 @@ class ConversationListViewModelFactory constructor(
private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase,
private val userUtils: UserUtils,
private val offlineRepository: ConversationsRepository
private val conversationsRepository: ConversationsRepository,
private val usersRepository: UsersRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ConversationsListViewModel(
application, conversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
userUtils, offlineRepository
userUtils, conversationsRepository, usersRepository
) as T
}
}

View File

@ -310,11 +310,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
recyclerViewAdapter.setFilter(it)
recyclerViewAdapter.filterItems(500)
})
currentUserAvatar.observe(this@ConversationsListView, Observer {
settingsItem?.icon = it
})
}
return super.onCreateView(inflater, container)
@ -452,12 +447,12 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
val conversation = (clickedItem as ConversationItem).model
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.currentUserLiveData.value)
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap(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.currentUserLiveData.value!!.id, conversation!!.token!!,
router, viewModel.currentUserLiveData.value!!.id!!, conversation.token!!,
bundle, false
)
}

View File

@ -22,7 +22,7 @@ package com.nextcloud.talk.newarch.features.conversationsList
import android.app.Application
import android.content.Intent
import android.graphics.drawable.Drawable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope
@ -30,17 +30,18 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.R.drawable
import com.nextcloud.talk.R.string
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.models.database.UserEntity
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.offline.ConversationsRepository
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
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.local.models.UserNgEntity
import com.nextcloud.talk.newarch.utils.ViewState.LOADING
import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.utils.database.user.UserUtils
@ -54,26 +55,18 @@ class ConversationsListViewModel constructor(
private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase,
private val userUtils: UserUtils,
private val offlineRepository: ConversationsRepository
private val conversationsRepository: ConversationsRepository,
usersRepository: UsersRepository
) : BaseViewModel<ConversationsListView>(application) {
val viewState = MutableLiveData(LOADING)
var messageData: String? = null
val searchQuery = MutableLiveData<String>()
val currentUserLiveData: MutableLiveData<UserEntity> = MutableLiveData()
val currentUserLiveData = usersRepository.getActiveUserLiveData()
val conversationsLiveData = Transformations.switchMap(currentUserLiveData) {
offlineRepository.getConversationsForUser(it.id)
conversationsRepository.getConversationsForUser(it.id)
}
var currentUserAvatar: MutableLiveData<Drawable> = MutableLiveData()
get() {
if (field.value == null) {
field.value = context.resources.getDrawable(drawable.ic_settings_white_24dp)
}
return field
}
fun leaveConversation(conversation: Conversation) {
viewModelScope.launch {
setConversationUpdateStatus(conversation, true)
@ -85,7 +78,7 @@ class ConversationsListViewModel constructor(
),
object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) {
offlineRepository.deleteConversation(
conversationsRepository.deleteConversation(
currentUserLiveData.value!!.id, conversation
.conversationId!!
)
@ -114,7 +107,7 @@ class ConversationsListViewModel constructor(
),
object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) {
offlineRepository.deleteConversation(
conversationsRepository.deleteConversation(
currentUserLiveData.value!!.id, conversation
.conversationId!!
)
@ -145,7 +138,7 @@ class ConversationsListViewModel constructor(
),
object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) {
offlineRepository.setFavoriteValueForConversation(
conversationsRepository.setFavoriteValueForConversation(
currentUserLiveData.value!!.id,
conversation.conversationId!!, favorite
)
@ -161,22 +154,18 @@ class ConversationsListViewModel constructor(
}
fun loadConversations() {
val userChanged = !(currentUserLiveData.value?.equals(userUtils.currentUser) ?: false)
if (userChanged) {
currentUserLiveData.value = userUtils.currentUser
viewState.value = LOADING
}
getConversationsUseCase.invoke(viewModelScope, parametersOf(currentUserLiveData.value), object :
UseCaseResponse<List<Conversation>> {
override suspend fun onSuccess(result: List<Conversation>) {
val mutableList = result.toMutableList()
val internalUserId = currentUserLiveData.value!!.id
mutableList.forEach {
it.internalUserId = currentUserLiveData.value!!.id
it.internalUserId = internalUserId
}
offlineRepository.saveConversationsForUser(currentUserLiveData.value!!.id, mutableList)
conversationsRepository.saveConversationsForUser(
internalUserId,
mutableList)
messageData = ""
}
@ -266,7 +255,7 @@ class ConversationsListViewModel constructor(
conversation: Conversation,
value: Boolean
) {
offlineRepository.setChangingValueForConversation(
conversationsRepository.setChangingValueForConversation(
currentUserLiveData.value!!.id, conversation
.conversationId!!, value
)

View File

@ -23,6 +23,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.offline.ConversationsRepository
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
@ -42,7 +43,7 @@ val ConversationsListModule = module {
factory {
createConversationListViewModelFactory(
androidApplication(), get(), get(), get(), get
(), get(), get()
(), get(), get(), get()
)
}
}
@ -83,11 +84,12 @@ fun createConversationListViewModelFactory(
leaveConversationUseCase: LeaveConversationUseCase,
deleteConversationUseCase: DeleteConversationUseCase,
userUtils: UserUtils,
offlineRepository: ConversationsRepository
conversationsRepository: ConversationsRepository,
usersRepository: UsersRepository
): ConversationListViewModelFactory {
return ConversationListViewModelFactory(
application, getConversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
userUtils, offlineRepository
userUtils, conversationsRepository, usersRepository
)
}

View File

@ -20,14 +20,24 @@
package com.nextcloud.talk.newarch.local.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.ConversationEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
@Dao
abstract class UsersDao {
// get active user
@Query("SELECT * FROM users where status = 1")
abstract fun getActiveUser(): UserNgEntity
@Query("SELECT * FROM users WHERE status = 1")
abstract fun getActiveUserLiveData(): LiveData<UserNgEntity>
@Query("DELETE FROM users WHERE id = :userId")
abstract fun deleteUserForId(userId: Long)
@ -41,6 +51,7 @@ abstract class UsersDao {
@Query("SELECT * FROM users where status != 2")
abstract fun getUsers(): List<UserNgEntity>
@Query("SELECT * FROM users where status = 2")
abstract fun getUsersScheduledForDeletion(): List<UserNgEntity>

View File

@ -37,17 +37,18 @@ import java.util.HashMap
@Entity(
tableName = "conversations",
indices = [Index(value = ["user"])],
indices = [Index(value = ["user", "conversation_id"], unique = true)],
foreignKeys = [ForeignKey(
entity = UserNgEntity::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("user"),
onDelete = CASCADE,
onUpdate = CASCADE,
deferred = true
)]
)
data class ConversationEntity(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long? = null,
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long? = 0,
@ColumnInfo(name = "user") var user: Long?,
@ColumnInfo(name = "conversation_id") var conversationId: String?,
@ColumnInfo(name = "token") var token: String? = null,
@ -77,7 +78,7 @@ data class ConversationEntity(
) 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: Long = 0,
@ColumnInfo(name = "last_read_message") var lastReadMessageId: Long = 0,
@ColumnInfo(name = "modified_at") var modifiedAt: Long? = null,
@ColumnInfo(name = "changing") var changing: Boolean = false
)
@ -116,8 +117,7 @@ fun ConversationEntity.toConversation(): Conversation {
}
fun Conversation.toConversationEntity(): ConversationEntity {
val conversationEntity =
ConversationEntity(this.internalId, this.internalUserId, this.conversationId)
val conversationEntity = ConversationEntity(null, this.internalUserId, this.conversationId)
conversationEntity.token = this.token
conversationEntity.name = this.name
conversationEntity.displayName = this.displayName

View File

@ -20,9 +20,13 @@
package com.nextcloud.talk.newarch.local.models
import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.ForeignKey.CASCADE
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.RoomWarnings
@ -31,18 +35,18 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType
@Entity(
tableName = "messages",
indices = [Index(value = ["conversation"]), Index(value = ["user", "conversation"])],
indices = [Index(value = ["conversation"])],
foreignKeys = [ForeignKey(
entity = ConversationEntity::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("conversation"),
onDelete = ForeignKey.CASCADE,
onDelete = CASCADE,
onUpdate = CASCADE,
deferred = true
)]
)
data class MessageEntity(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long? = null,
@ColumnInfo(name = "user") var user: Long? = 0,
@ColumnInfo(name = "conversation") var conversation: Long? = null,
@ColumnInfo(name = "message_id") var messageId: Long = 0,
@ColumnInfo(name = "actor_id") var actorId: String? = null,
@ -59,7 +63,6 @@ data class MessageEntity(
fun MessageEntity.toChatMessage(): ChatMessage {
val chatMessage = ChatMessage()
chatMessage.internalMessageId = this.id
chatMessage.internalUserId = this.user
chatMessage.internalConversationId = this.conversation
chatMessage.jsonMessageId = this.messageId
chatMessage.actorType = this.actorType
@ -74,9 +77,7 @@ fun MessageEntity.toChatMessage(): ChatMessage {
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
fun ChatMessage.toMessageEntity(): MessageEntity {
val messageEntity = MessageEntity()
messageEntity.id = this.internalMessageId
messageEntity.user = this.internalUserId
val messageEntity = MessageEntity(this.internalMessageId)
messageEntity.conversation = this.internalConversationId
messageEntity.messageId = this.jsonMessageId
messageEntity.actorType = this.actorType
@ -88,4 +89,4 @@ fun ChatMessage.toMessageEntity(): MessageEntity {
//messageEntity.messageParameters = this.messageParameters
return messageEntity
}
}

View File

@ -20,6 +20,7 @@
package com.nextcloud.talk.newarch.local.models
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@ -27,17 +28,68 @@ import com.nextcloud.talk.models.ExternalSignalingServer
import com.nextcloud.talk.models.json.capabilities.Capabilities
import com.nextcloud.talk.models.json.push.PushConfigurationState
import com.nextcloud.talk.newarch.local.models.other.UserStatus
import com.nextcloud.talk.utils.ApiUtils
import kotlinx.android.parcel.Parcelize
import kotlinx.android.parcel.RawValue
import kotlinx.android.parcel.WriteWith
@Parcelize
@Entity(tableName = "users")
data class UserNgEntity(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long? = null,
@ColumnInfo(name = "user_id") var userId: String? = null,
@ColumnInfo(name = "username") var username: String? = null,
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "user_id") var userId: String,
@ColumnInfo(name = "username") var username: String,
@ColumnInfo(name = "base_url") var baseUrl: String,
@ColumnInfo(name = "token") var token: String? = null,
@ColumnInfo(name = "display_name") var displayName: String? = null,
@ColumnInfo(name = "push_configuration") var pushConfiguration: PushConfigurationState? = null,
@ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null,
@ColumnInfo(
name = "push_configuration"
) var pushConfiguration: PushConfigurationState? = null,
@ColumnInfo(name = "capabilities") var capabilities: @RawValue Capabilities? = null,
@ColumnInfo(name = "client_auth_cert") var clientCertificate: String? = null,
@ColumnInfo(name = "external_signaling") var externalSignaling: ExternalSignalingServer? = null,
@ColumnInfo(
name = "external_signaling"
) var externalSignaling: ExternalSignalingServer? = null,
@ColumnInfo(name = "status") var status: UserStatus? = null
)
) : Parcelable {
fun hasSpreedFeatureCapability(capabilityName: String): Boolean {
val capabilityExists = capabilities?.spreedCapability?.features?.contains(capabilityName)
if (capabilityExists != null) {
return capabilityExists
} else {
return false
}
}
}
fun UserNgEntity.getCredentials() = ApiUtils.getCredentials(username, token)
fun UserNgEntity.hasExternalCapability(capabilityName: String): Boolean {
val capabilityExists = capabilities?.externalCapability?.get("v1")
?.contains(capabilityName)
if (capabilityExists != null) {
return capabilityExists
} else {
return false
}
}
fun UserNgEntity.hasSpreedFeatureCapability(capabilityName: String): Boolean {
val capabilityExists = capabilities?.spreedCapability?.features?.contains(capabilityName)
if (capabilityExists != null) {
return capabilityExists
} else {
return false
}
}
fun UserNgEntity.maxMessageLength(): Int {
val maxLength = capabilities?.spreedCapability?.config?.get("chat")
?.get("max-length")
if (maxLength != null) {
return maxLength.toInt()
} else {
return 1000
}
}

View File

@ -21,6 +21,7 @@
package com.nextcloud.talk.newarch.utils
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.ApiUtils
fun UserEntity.getCredentials() = ApiUtils.getCredentials(username, token)

View File

@ -27,6 +27,8 @@ import coil.request.LoadRequest
import coil.target.Target
import coil.transform.Transformation
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
class Images {
fun getRequestForUrl(
@ -34,7 +36,7 @@ class Images {
context: Context,
url: String,
userEntity:
UserEntity?,
UserNgEntity?,
target: Target?,
lifecycleOwner: LifecycleOwner?,
vararg transformations: Transformation

View File

@ -72,6 +72,7 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.events.UserMentionClickEvent
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.utils.Images
import com.nextcloud.talk.utils.text.Spans
import org.greenrobot.eventbus.EventBus
@ -200,7 +201,7 @@ object DisplayUtils {
context: Context,
id: String,
label: CharSequence,
conversationUser: UserEntity,
conversationUser: UserNgEntity,
type: String,
@XmlRes chipResource: Int,
emojiEditText: EditText?
@ -278,7 +279,7 @@ object DisplayUtils {
id: String,
label: String,
type: String,
conversationUser: UserEntity,
conversationUser: UserNgEntity,
@XmlRes chipXmlRes: Int
): Spannable {

View File

@ -30,6 +30,7 @@ import android.os.Build
import android.service.notification.StatusBarNotification
import com.nextcloud.talk.R
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.bundle.BundleKeys
object NotificationUtils {
@ -91,7 +92,7 @@ object NotificationUtils {
fun cancelAllNotificationsForAccount(
context: Context?,
conversationUser: UserEntity
conversationUser: UserNgEntity
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L && context != null) {
@ -115,7 +116,7 @@ object NotificationUtils {
fun cancelExistingNotificationWithId(
context: Context?,
conversationUser: UserEntity,
conversationUser: UserNgEntity,
notificationId: Long
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L &&
@ -144,7 +145,7 @@ object NotificationUtils {
fun findNotificationForRoom(
context: Context?,
conversationUser: UserEntity,
conversationUser: UserNgEntity,
roomTokenOrId: String
): StatusBarNotification? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L &&
@ -177,7 +178,7 @@ object NotificationUtils {
fun cancelExistingNotificationsForRoom(
context: Context?,
conversationUser: UserEntity,
conversationUser: UserNgEntity,
roomTokenOrId: String
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L &&

View File

@ -1,445 +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.utils;
import android.content.Context;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import autodagger.AutoInjector;
import com.bluelinelabs.logansquare.LoganSquare;
import com.nextcloud.talk.R;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.events.EventStatus;
import com.nextcloud.talk.models.SignatureVerification;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.push.PushConfigurationState;
import com.nextcloud.talk.models.json.push.PushRegistrationOverall;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.greenrobot.eventbus.EventBus;
@AutoInjector(NextcloudTalkApplication.class)
public class PushUtils {
private static final String TAG = "PushUtils";
@Inject
UserUtils userUtils;
@Inject
AppPreferences appPreferences;
@Inject
EventBus eventBus;
@Inject
NcApi ncApi;
private File keysFile;
private File publicKeyFile;
private File privateKeyFile;
private String proxyServer;
public PushUtils() {
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
keysFile = NextcloudTalkApplication.Companion.getSharedApplication()
.getDir("PushKeyStore", Context.MODE_PRIVATE);
publicKeyFile =
new File(NextcloudTalkApplication.Companion.getSharedApplication().getDir("PushKeystore",
Context.MODE_PRIVATE), "push_key.pub");
privateKeyFile =
new File(NextcloudTalkApplication.Companion.getSharedApplication().getDir("PushKeystore",
Context.MODE_PRIVATE), "push_key.priv");
proxyServer = NextcloudTalkApplication.Companion.getSharedApplication().getResources().
getString(R.string.nc_push_server_url);
}
public SignatureVerification verifySignature(byte[] signatureBytes, byte[] subjectBytes) {
Signature signature = null;
PushConfigurationState pushConfigurationState;
PublicKey publicKey;
SignatureVerification signatureVerification = new SignatureVerification();
signatureVerification.setSignatureValid(false);
List<UserEntity> userEntities = userUtils.getUsers();
try {
signature = Signature.getInstance("SHA512withRSA");
if (userEntities != null && userEntities.size() > 0) {
for (UserEntity userEntity : userEntities) {
if (!TextUtils.isEmpty(userEntity.getPushConfigurationState())) {
pushConfigurationState = LoganSquare.parse(userEntity.getPushConfigurationState(),
PushConfigurationState.class);
publicKey = (PublicKey) readKeyFromString(true,
pushConfigurationState.getUserPublicKey());
signature.initVerify(publicKey);
signature.update(subjectBytes);
if (signature.verify(signatureBytes)) {
signatureVerification.setSignatureValid(true);
signatureVerification.setUserEntity(userEntity);
return signatureVerification;
}
}
}
}
} catch (NoSuchAlgorithmException e) {
Log.d(TAG, "No such algorithm");
} catch (IOException e) {
Log.d(TAG, "Error while trying to parse push configuration viewState");
} catch (InvalidKeyException e) {
Log.d(TAG, "Invalid key while trying to verify");
} catch (SignatureException e) {
Log.d(TAG, "Signature exception while trying to verify");
}
return signatureVerification;
}
private int saveKeyToFile(Key key, String path) {
byte[] encoded = key.getEncoded();
try {
if (!new File(path).exists()) {
if (!new File(path).createNewFile()) {
return -1;
}
}
try (FileOutputStream keyFileOutputStream = new FileOutputStream(path)) {
keyFileOutputStream.write(encoded);
return 0;
}
} catch (FileNotFoundException e) {
Log.d(TAG, "Failed to save key to file");
} catch (IOException e) {
Log.d(TAG, "Failed to save key to file via IOException");
}
return -1;
}
private String generateSHA512Hash(String pushToken) {
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA-512");
messageDigest.update(pushToken.getBytes());
return bytesToHex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
Log.d(TAG, "SHA-512 algorithm not supported");
}
return "";
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte individualByte : bytes) {
result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
.substring(1));
}
return result.toString();
}
public int generateRsa2048KeyPair() {
if (!publicKeyFile.exists() && !privateKeyFile.exists()) {
if (!keysFile.exists()) {
keysFile.mkdirs();
}
KeyPairGenerator keyGen = null;
try {
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
int statusPrivate = saveKeyToFile(pair.getPrivate(), privateKeyFile.getAbsolutePath());
int statusPublic = saveKeyToFile(pair.getPublic(), publicKeyFile.getAbsolutePath());
if (statusPrivate == 0 && statusPublic == 0) {
// all went well
return 0;
} else {
return -2;
}
} catch (NoSuchAlgorithmException e) {
Log.d(TAG, "RSA algorithm not supported");
}
} else {
// We already have the key
return -1;
}
// we failed to generate the key
return -2;
}
public void pushRegistrationToServer() {
String token = appPreferences.getPushToken();
if (!TextUtils.isEmpty(token)) {
String credentials;
String pushTokenHash = generateSHA512Hash(token).toLowerCase();
PublicKey devicePublicKey = (PublicKey) readKeyFromFile(true);
if (devicePublicKey != null) {
byte[] publicKeyBytes = Base64.encode(devicePublicKey.getEncoded(), Base64.NO_WRAP);
String publicKey = new String(publicKeyBytes);
publicKey = publicKey.replaceAll("(.{64})", "$1\n");
publicKey = "-----BEGIN PUBLIC KEY-----\n" + publicKey + "\n-----END PUBLIC KEY-----\n";
if (userUtils.anyUserExists()) {
String providerValue;
PushConfigurationState accountPushData = null;
for (Object userEntityObject : userUtils.getUsers()) {
UserEntity userEntity = (UserEntity) userEntityObject;
providerValue = userEntity.getPushConfigurationState();
if (!TextUtils.isEmpty(providerValue)) {
try {
accountPushData = LoganSquare.parse(providerValue, PushConfigurationState.class);
} catch (IOException e) {
Log.d(TAG, "Failed to parse account push data");
accountPushData = null;
}
} else {
accountPushData = null;
}
if (((TextUtils.isEmpty(providerValue) || accountPushData == null)
&& !userEntity.getScheduledForDeletion()) ||
(accountPushData != null
&& !accountPushData.getPushToken().equals(token)
&& !userEntity.getScheduledForDeletion())) {
Map<String, String> queryMap = new HashMap<>();
queryMap.put("format", "json");
queryMap.put("pushTokenHash", pushTokenHash);
queryMap.put("devicePublicKey", publicKey);
queryMap.put("proxyServer", proxyServer);
credentials =
ApiUtils.getCredentials(userEntity.getUsername(), userEntity.getToken());
String finalCredentials = credentials;
ncApi.registerDeviceForNotificationsWithNextcloud(
credentials,
ApiUtils.getUrlNextcloudPush(userEntity.getBaseUrl()), queryMap)
.subscribe(new Observer<PushRegistrationOverall>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(PushRegistrationOverall pushRegistrationOverall) {
Map<String, String> proxyMap = new HashMap<>();
proxyMap.put("pushToken", token);
proxyMap.put("deviceIdentifier", pushRegistrationOverall.getOcs().getData().
getDeviceIdentifier());
proxyMap.put("deviceIdentifierSignature", pushRegistrationOverall.getOcs()
.getData().getSignature());
proxyMap.put("userPublicKey", pushRegistrationOverall.getOcs()
.getData().getPublicKey());
ncApi.registerDeviceForNotificationsWithProxy(
ApiUtils.getUrlPushProxy(), proxyMap)
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Void>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Void aVoid) {
PushConfigurationState pushConfigurationState =
new PushConfigurationState();
pushConfigurationState.setPushToken(token);
pushConfigurationState.setDeviceIdentifier(
pushRegistrationOverall.getOcs()
.getData().getDeviceIdentifier());
pushConfigurationState.setDeviceIdentifierSignature(
pushRegistrationOverall
.getOcs().getData().getSignature());
pushConfigurationState.setUserPublicKey(
pushRegistrationOverall.getOcs()
.getData().getPublicKey());
pushConfigurationState.setUsesRegularPass(false);
try {
userUtils.createOrUpdateUser(null,
null, null,
userEntity.getDisplayName(),
LoganSquare.serialize(pushConfigurationState), null,
null, userEntity.getId(), null, null, null)
.subscribe(new Observer<UserEntity>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(UserEntity userEntity) {
eventBus.post(new EventStatus(userEntity.getId(),
EventStatus.EventType.PUSH_REGISTRATION, true));
}
@Override
public void onError(Throwable e) {
eventBus.post(new EventStatus
(userEntity.getId(),
EventStatus.EventType
.PUSH_REGISTRATION, false));
}
@Override
public void onComplete() {
}
});
} catch (IOException e) {
Log.e(TAG, "IOException while updating user");
}
}
@Override
public void onError(Throwable e) {
eventBus.post(new EventStatus(userEntity.getId(),
EventStatus.EventType.PUSH_REGISTRATION, false));
}
@Override
public void onComplete() {
}
});
}
@Override
public void onError(Throwable e) {
eventBus.post(new EventStatus(userEntity.getId(),
EventStatus.EventType.PUSH_REGISTRATION, false));
}
@Override
public void onComplete() {
}
});
}
}
}
}
}
}
private Key readKeyFromString(boolean readPublicKey, String keyString) {
if (readPublicKey) {
keyString = keyString.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----",
"").replace("-----END PUBLIC KEY-----", "");
} else {
keyString = keyString.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----",
"").replace("-----END PRIVATE KEY-----", "");
}
KeyFactory keyFactory = null;
try {
keyFactory = KeyFactory.getInstance("RSA");
if (readPublicKey) {
X509EncodedKeySpec keySpec =
new X509EncodedKeySpec(Base64.decode(keyString, Base64.DEFAULT));
return keyFactory.generatePublic(keySpec);
} else {
PKCS8EncodedKeySpec keySpec =
new PKCS8EncodedKeySpec(Base64.decode(keyString, Base64.DEFAULT));
return keyFactory.generatePrivate(keySpec);
}
} catch (NoSuchAlgorithmException e) {
Log.d("TAG", "No such algorithm while reading key from string");
} catch (InvalidKeySpecException e) {
Log.d("TAG", "Invalid key spec while reading key from string");
}
return null;
}
public Key readKeyFromFile(boolean readPublicKey) {
String path;
if (readPublicKey) {
path = publicKeyFile.getAbsolutePath();
} else {
path = privateKeyFile.getAbsolutePath();
}
try (FileInputStream fileInputStream = new FileInputStream(path)) {
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
if (readPublicKey) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
return keyFactory.generatePublic(keySpec);
} else {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
return keyFactory.generatePrivate(keySpec);
}
} catch (FileNotFoundException e) {
Log.d(TAG, "Failed to find path while reading the Key");
} catch (IOException e) {
Log.d(TAG, "IOException while reading the key");
} catch (InvalidKeySpecException e) {
Log.d(TAG, "InvalidKeySpecException while reading the key");
} catch (NoSuchAlgorithmException e) {
Log.d(TAG, "RSA algorithm not supported");
}
return null;
}
}

View File

@ -0,0 +1,450 @@
/*
* 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.utils
import android.content.Context
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.R.string
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.events.EventStatus.EventType.PUSH_REGISTRATION
import com.nextcloud.talk.models.SignatureVerification
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.push.PushConfigurationState
import com.nextcloud.talk.models.json.push.PushRegistrationOverall
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.security.InvalidKeyException
import java.security.Key
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.PublicKey
import java.security.Signature
import java.security.SignatureException
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.HashMap
import javax.inject.Inject
import kotlin.experimental.and
@AutoInjector(NextcloudTalkApplication::class)
class PushUtils(val usersRepository: UsersRepository) {
@JvmField
@Inject
var userUtils: UserUtils? = null
@JvmField
@Inject
var appPreferences: AppPreferences? = null
@JvmField
@Inject
var eventBus: EventBus? = null
@JvmField
@Inject
var ncApi: NcApi? = null
private val keysFile: File
private val publicKeyFile: File
private val privateKeyFile: File
private val proxyServer: String
fun verifySignature(
signatureBytes: ByteArray?,
subjectBytes: ByteArray?
): SignatureVerification {
val signature: Signature?
var pushConfigurationState: PushConfigurationState?
var publicKey: PublicKey?
val signatureVerification =
SignatureVerification()
signatureVerification.signatureValid = false
val userEntities: List<UserNgEntity> = usersRepository.getUsers()
try {
signature = Signature.getInstance("SHA512withRSA")
if (userEntities.size > 0) {
for (userEntity in userEntities) {
pushConfigurationState = userEntity.pushConfiguration
if (pushConfigurationState?.userPublicKey != null) {
publicKey = readKeyFromString(
true, pushConfigurationState.userPublicKey!!
) as PublicKey?
signature.initVerify(publicKey)
signature.update(subjectBytes)
if (signature.verify(signatureBytes)) {
signatureVerification.signatureValid = true
signatureVerification.userEntity = userEntity
return signatureVerification
}
}
}
}
} catch (e: NoSuchAlgorithmException) {
Log.d(TAG, "No such algorithm")
} catch (e: IOException) {
Log.d(TAG, "Error while trying to parse push configuration viewState")
} catch (e: InvalidKeyException) {
Log.d(TAG, "Invalid key while trying to verify")
} catch (e: SignatureException) {
Log.d(TAG, "Signature exception while trying to verify")
}
return signatureVerification
}
private fun saveKeyToFile(
key: Key,
path: String?
): Int {
val encoded: ByteArray? = key.encoded
try {
if (!File(path).exists()) {
if (!File(path).createNewFile()) {
return -1
}
}
FileOutputStream(path)
.use { keyFileOutputStream ->
keyFileOutputStream.write(encoded)
return 0
}
} catch (e: FileNotFoundException) {
Log.d(TAG, "Failed to save key to file")
} catch (e: IOException) {
Log.d(TAG, "Failed to save key to file via IOException")
}
return -1
}
private fun generateSHA512Hash(pushToken: String): String {
var messageDigest: MessageDigest? = null
try {
messageDigest = MessageDigest.getInstance("SHA-512")
messageDigest.update(pushToken.toByteArray())
return bytesToHex(messageDigest.digest())
} catch (e: NoSuchAlgorithmException) {
Log.d(TAG, "SHA-512 algorithm not supported")
}
return ""
}
private fun bytesToHex(bytes: ByteArray): String {
val result = StringBuilder()
for (individualByte in bytes) {
result.append(
((individualByte and 0xff.toByte()) + 0x100).toString(16)
.substring(1)
)
}
return result.toString()
}
fun generateRsa2048KeyPair(): Int {
if (!publicKeyFile.exists() && !privateKeyFile.exists()) {
if (!keysFile.exists()) {
keysFile.mkdirs()
}
var keyGen: KeyPairGenerator? = null
try {
keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(2048)
val pair: KeyPair = keyGen.generateKeyPair()
val statusPrivate =
saveKeyToFile(pair.private, privateKeyFile.absolutePath)
val statusPublic =
saveKeyToFile(pair.public, publicKeyFile.absolutePath)
return if (statusPrivate == 0 && statusPublic == 0) {
// all went well
0
} else {
-2
}
} catch (e: NoSuchAlgorithmException) {
Log.d(TAG, "RSA algorithm not supported")
}
}
// we failed to generate the key
else {
// We already have the key
return -1
}
return -2
}
fun pushRegistrationToServer() {
val token: String = appPreferences!!.pushToken
if (!TextUtils.isEmpty(token)) {
var credentials: String
val pushTokenHash = generateSHA512Hash(token).toLowerCase()
val devicePublicKey =
readKeyFromFile(true) as PublicKey?
if (devicePublicKey != null) {
val publicKeyBytes: ByteArray? =
Base64.encode(devicePublicKey.encoded, Base64.NO_WRAP)
var publicKey = String(publicKeyBytes!!)
publicKey = publicKey.replace("(.{64})".toRegex(), "$1\n")
publicKey = "-----BEGIN PUBLIC KEY-----\n$publicKey\n-----END PUBLIC KEY-----\n"
if (userUtils!!.anyUserExists()) {
var accountPushData: PushConfigurationState? = null
for (userEntityObject in usersRepository.getUsers()) {
val userEntity = userEntityObject
accountPushData = userEntity.pushConfiguration
if (accountPushData == null || accountPushData.pushToken != token) {
val queryMap: MutableMap<String, String> =
HashMap()
queryMap["format"] = "json"
queryMap["pushTokenHash"] = pushTokenHash
queryMap["devicePublicKey"] = publicKey
queryMap["proxyServer"] = proxyServer
credentials = ApiUtils.getCredentials(
userEntity.username, userEntity.token
)
val finalCredentials = credentials
ncApi!!.registerDeviceForNotificationsWithNextcloud(
credentials,
ApiUtils.getUrlNextcloudPush(userEntity.baseUrl),
queryMap
)
.subscribe(object : Observer<PushRegistrationOverall> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(pushRegistrationOverall: PushRegistrationOverall) {
val proxyMap: MutableMap<String, String> =
HashMap()
proxyMap["pushToken"] = token
proxyMap["deviceIdentifier"] =
pushRegistrationOverall.ocs.data.deviceIdentifier
proxyMap["deviceIdentifierSignature"] = pushRegistrationOverall.ocs
.data.signature
proxyMap["userPublicKey"] = pushRegistrationOverall.ocs
.data.publicKey
ncApi!!.registerDeviceForNotificationsWithProxy(
ApiUtils.getUrlPushProxy(), proxyMap
)
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<Void> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(aVoid: Void) {
val pushConfigurationState =
PushConfigurationState()
pushConfigurationState.pushToken = token
pushConfigurationState.deviceIdentifier = pushRegistrationOverall
.ocs.data.deviceIdentifier
pushConfigurationState.deviceIdentifierSignature =
pushRegistrationOverall.ocs.data.signature
pushConfigurationState.userPublicKey = pushRegistrationOverall.ocs
.data.publicKey
pushConfigurationState.usesRegularPass = false
try {
userUtils!!.createOrUpdateUser(
null,
null, null,
userEntity.displayName,
LoganSquare.serialize(
pushConfigurationState
), null,
null, userEntity.id, null, null, null
)
.subscribe(object : Observer<UserEntity> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(userEntity: UserEntity) {
eventBus!!.post(
EventStatus(
userEntity.id,
PUSH_REGISTRATION,
true
)
)
}
override fun onError(e: Throwable) {
eventBus!!.post(
EventStatus(
userEntity.id,
PUSH_REGISTRATION, false
)
)
}
override fun onComplete() {}
})
} catch (e: IOException) {
Log.e(TAG, "IOException while updating user")
}
}
override fun onError(e: Throwable) {
eventBus!!.post(
EventStatus(
userEntity.id,
PUSH_REGISTRATION,
false
)
)
}
override fun onComplete() {}
})
}
override fun onError(e: Throwable) {
eventBus!!.post(
EventStatus(
userEntity.id,
PUSH_REGISTRATION,
false
)
)
}
override fun onComplete() {}
})
}
}
}
}
}
}
private fun readKeyFromString(
readPublicKey: Boolean,
keyString: String
): Key? {
var keyString = keyString
keyString = if (readPublicKey) {
keyString.replace("\\n".toRegex(), "")
.replace(
"-----BEGIN PUBLIC KEY-----",
""
)
.replace("-----END PUBLIC KEY-----", "")
} else {
keyString.replace("\\n".toRegex(), "")
.replace(
"-----BEGIN PRIVATE KEY-----",
""
)
.replace("-----END PRIVATE KEY-----", "")
}
var keyFactory: KeyFactory? = null
try {
keyFactory = KeyFactory.getInstance("RSA")
return if (readPublicKey) {
val keySpec = X509EncodedKeySpec(
Base64.decode(keyString, Base64.DEFAULT)
)
keyFactory.generatePublic(keySpec)
} else {
val keySpec =
PKCS8EncodedKeySpec(
Base64.decode(keyString, Base64.DEFAULT)
)
keyFactory.generatePrivate(keySpec)
}
} catch (e: NoSuchAlgorithmException) {
Log.d("TAG", "No such algorithm while reading key from string")
} catch (e: InvalidKeySpecException) {
Log.d("TAG", "Invalid key spec while reading key from string")
}
return null
}
fun readKeyFromFile(readPublicKey: Boolean): Key? {
val path: String?
path = if (readPublicKey) {
publicKeyFile.absolutePath
} else {
privateKeyFile.absolutePath
}
try {
FileInputStream(path)
.use { fileInputStream ->
val bytes = ByteArray(fileInputStream.available())
fileInputStream.read(bytes)
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
return if (readPublicKey) {
val keySpec =
X509EncodedKeySpec(bytes)
keyFactory.generatePublic(keySpec)
} else {
val keySpec =
PKCS8EncodedKeySpec(bytes)
keyFactory.generatePrivate(keySpec)
}
}
} catch (e: FileNotFoundException) {
Log.d(TAG, "Failed to find path while reading the Key")
} catch (e: IOException) {
Log.d(TAG, "IOException while reading the key")
} catch (e: InvalidKeySpecException) {
Log.d(TAG, "InvalidKeySpecException while reading the key")
} catch (e: NoSuchAlgorithmException) {
Log.d(TAG, "RSA algorithm not supported")
}
return null
}
companion object {
private const val TAG = "PushUtils"
}
init {
sharedApplication!!
.componentApplication
.inject(this)
keysFile = sharedApplication!!
.getDir("PushKeyStore", Context.MODE_PRIVATE)
publicKeyFile = File(
sharedApplication!!.getDir(
"PushKeystore",
Context.MODE_PRIVATE
), "push_key.pub"
)
privateKeyFile = File(
sharedApplication!!.getDir(
"PushKeystore",
Context.MODE_PRIVATE
), "push_key.priv"
)
proxyServer =
sharedApplication!!.resources
.getString(string.nc_push_server_url)
}
}

View File

@ -23,14 +23,15 @@ package com.nextcloud.talk.utils.preferences.preferencestorage;
import android.content.Context;
import com.nextcloud.talk.interfaces.ConversationInfoInterface;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.yarolegovich.mp.io.StorageModule;
public class DatabaseStorageFactory implements StorageModule.Factory {
private UserEntity conversationUser;
private UserNgEntity conversationUser;
private String conversationToken;
private ConversationInfoInterface conversationInfoInterface;
public DatabaseStorageFactory(UserEntity conversationUser, String conversationToken,
public DatabaseStorageFactory(UserNgEntity conversationUser, String conversationToken,
ConversationInfoInterface conversationInfoInterface) {
this.conversationUser = conversationUser;
this.conversationToken = conversationToken;

View File

@ -1,297 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 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.utils.preferences.preferencestorage;
import android.os.Bundle;
import android.text.TextUtils;
import autodagger.AutoInjector;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.interfaces.ConversationInfoInterface;
import com.nextcloud.talk.models.database.ArbitraryStorageEntity;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.generic.GenericOverall;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils;
import com.yarolegovich.mp.io.StorageModule;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.Set;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class DatabaseStorageModule implements StorageModule {
@Inject
ArbitraryStorageUtils arbitraryStorageUtils;
@Inject
NcApi ncApi;
private UserEntity conversationUser;
private String conversationToken;
private long accountIdentifier;
private boolean lobbyValue;
private boolean favoriteConversationValue;
private boolean allowGuestsValue;
private Boolean hasPassword;
private String conversationNameValue;
private String messageNotificationLevel;
private ConversationInfoInterface conversationInfoInterface;
public DatabaseStorageModule(UserEntity conversationUser, String conversationToken,
ConversationInfoInterface conversationInfoInterface) {
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
this.conversationUser = conversationUser;
this.accountIdentifier = conversationUser.getId();
this.conversationToken = conversationToken;
this.conversationInfoInterface = conversationInfoInterface;
}
@Override
public void saveBoolean(String key, boolean value) {
if (!key.equals("conversation_lobby") && !key.equals("allow_guests") && !key.equals(
"favorite_conversation")) {
arbitraryStorageUtils.storeStorageSetting(accountIdentifier, key, Boolean.toString(value),
conversationToken);
} else {
switch (key) {
case "conversation_lobby":
lobbyValue = value;
break;
case "allow_guests":
allowGuestsValue = value;
break;
case "favorite_conversation":
favoriteConversationValue = value;
break;
default:
}
}
}
@Override
public void saveString(String key, String value) {
if (!key.equals("message_notification_level")
&& !key.equals("conversation_name")
&& !key.equals("conversation_password")) {
arbitraryStorageUtils.storeStorageSetting(accountIdentifier, key, value, conversationToken);
} else {
if (key.equals("message_notification_level")) {
if (conversationUser.hasSpreedFeatureCapability("notification-levels")) {
if (!TextUtils.isEmpty(messageNotificationLevel) && !messageNotificationLevel.equals(
value)) {
int intValue;
switch (value) {
case "never":
intValue = 3;
break;
case "mention":
intValue = 2;
break;
case "always":
intValue = 1;
break;
default:
intValue = 0;
}
ncApi.setNotificationLevel(
ApiUtils.getCredentials(conversationUser.getUsername(),
conversationUser.getToken()),
ApiUtils.getUrlForSettingNotificationlevel(conversationUser.getBaseUrl(),
conversationToken),
intValue)
.subscribeOn(Schedulers.io())
.subscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(GenericOverall genericOverall) {
messageNotificationLevel = value;
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
} else {
messageNotificationLevel = value;
}
}
} else if (key.equals("conversation_password")) {
if (hasPassword != null) {
ncApi.setPassword(ApiUtils.getCredentials(conversationUser.getUsername(),
conversationUser.getToken()),
ApiUtils.getUrlForPassword(conversationUser.getBaseUrl(),
conversationToken), value)
.subscribeOn(Schedulers.io())
.subscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(GenericOverall genericOverall) {
hasPassword = !TextUtils.isEmpty(value);
conversationInfoInterface.passwordSet(TextUtils.isEmpty(value));
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
} else {
hasPassword = Boolean.parseBoolean(value);
}
} else if (key.equals("conversation_name")) {
if (!TextUtils.isEmpty(conversationNameValue) && !conversationNameValue.equals(value)) {
ncApi.renameRoom(ApiUtils.getCredentials(conversationUser.getUsername(),
conversationUser.getToken()), ApiUtils.getRoom(conversationUser.getBaseUrl(),
conversationToken), value)
.subscribeOn(Schedulers.io())
.subscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(GenericOverall genericOverall) {
conversationNameValue = value;
conversationInfoInterface.conversationNameSet(value);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
} else {
conversationNameValue = value;
}
}
}
}
@Override
public void saveInt(String key, int value) {
arbitraryStorageUtils.storeStorageSetting(accountIdentifier, key, Integer.toString(value),
conversationToken);
}
@Override
public void saveStringSet(String key, Set<String> value) {
}
@Override
public boolean getBoolean(String key, boolean defaultVal) {
if (key.equals("conversation_lobby")) {
return lobbyValue;
} else if (key.equals("allow_guests")) {
return allowGuestsValue;
} else if (key.equals("favorite_conversation")) {
return favoriteConversationValue;
} else {
ArbitraryStorageEntity valueFromDb =
arbitraryStorageUtils.getStorageSetting(accountIdentifier, key, conversationToken);
if (valueFromDb == null) {
return defaultVal;
} else {
return Boolean.parseBoolean(valueFromDb.getValue());
}
}
}
@Override
public String getString(String key, String defaultVal) {
if (!key.equals("message_notification_level")
&& !key.equals("conversation_name")
&& !key.equals("conversation_password")) {
ArbitraryStorageEntity valueFromDb =
arbitraryStorageUtils.getStorageSetting(accountIdentifier, key, conversationToken);
if (valueFromDb == null) {
return defaultVal;
} else {
return valueFromDb.getValue();
}
} else if (key.equals("message_notification_level")) {
return messageNotificationLevel;
} else if (key.equals("conversation_name")) {
return conversationNameValue;
} else if (key.equals("conversation_password")) {
return "";
}
return "";
}
@Override
public int getInt(String key, int defaultVal) {
ArbitraryStorageEntity valueFromDb =
arbitraryStorageUtils.getStorageSetting(accountIdentifier, key, conversationToken);
if (valueFromDb == null) {
return defaultVal;
} else {
return Integer.parseInt(valueFromDb.getValue());
}
}
@Override
public Set<String> getStringSet(String key, Set<String> defaultVal) {
return null;
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
@Override
public void onRestoreInstanceState(Bundle savedState) {
}
}

View File

@ -0,0 +1,280 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 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.utils.preferences.preferencestorage
import android.os.Bundle
import android.text.TextUtils
import autodagger.AutoInjector
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.interfaces.ConversationInfoInterface
import com.nextcloud.talk.models.database.ArbitraryStorageEntity
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils
import com.yarolegovich.mp.io.StorageModule
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class DatabaseStorageModule(
private val conversationUser: UserNgEntity,
private val conversationToken: String,
private val conversationInfoInterface: ConversationInfoInterface
) : StorageModule {
@JvmField
@Inject
var arbitraryStorageUtils: ArbitraryStorageUtils? =
null
@JvmField
@Inject
var ncApi: NcApi? = null
private val accountIdentifier: Long
private var lobbyValue = false
private var favoriteConversationValue = false
private var allowGuestsValue = false
private var hasPassword: Boolean? = null
private var conversationNameValue: String? = null
private var messageNotificationLevel: String? = null
override fun saveBoolean(
key: String,
value: Boolean
) {
if (key != "conversation_lobby" && key != "allow_guests" && key != "favorite_conversation"
) {
arbitraryStorageUtils!!.storeStorageSetting(
accountIdentifier, key, value.toString(),
conversationToken
)
} else {
when (key) {
"conversation_lobby" -> lobbyValue = value
"allow_guests" -> allowGuestsValue = value
"favorite_conversation" -> favoriteConversationValue = value
else -> {
}
}
}
}
override fun saveString(
key: String,
value: String
) {
if (key != "message_notification_level"
&& key != "conversation_name"
&& key != "conversation_password"
) {
arbitraryStorageUtils!!.storeStorageSetting(accountIdentifier, key, value, conversationToken)
} else {
if (key == "message_notification_level") {
if (conversationUser.hasSpreedFeatureCapability("notification-levels")) {
if (!TextUtils.isEmpty(
messageNotificationLevel
) && messageNotificationLevel != value
) {
val intValue: Int
intValue = when (value) {
"never" -> 3
"mention" -> 2
"always" -> 1
else -> 0
}
ncApi!!.setNotificationLevel(
ApiUtils.getCredentials(
conversationUser.username,
conversationUser.token
),
ApiUtils.getUrlForSettingNotificationlevel(
conversationUser.baseUrl,
conversationToken
),
intValue
)
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(genericOverall: GenericOverall) {
messageNotificationLevel = value
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
} else {
messageNotificationLevel = value
}
}
} else if (key == "conversation_password") {
if (hasPassword != null) {
ncApi!!.setPassword(
ApiUtils.getCredentials(
conversationUser.username,
conversationUser.token
),
ApiUtils.getUrlForPassword(
conversationUser.baseUrl,
conversationToken
), value
)
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(genericOverall: GenericOverall) {
hasPassword = !TextUtils.isEmpty(value)
conversationInfoInterface.passwordSet(TextUtils.isEmpty(value))
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
} else {
hasPassword = value.toBoolean()
}
} else if (key == "conversation_name") {
if (!TextUtils.isEmpty(
conversationNameValue
) && conversationNameValue != value
) {
ncApi!!.renameRoom(
ApiUtils.getCredentials(
conversationUser.username,
conversationUser.token
), ApiUtils.getRoom(
conversationUser.baseUrl,
conversationToken
), value
)
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(genericOverall: GenericOverall) {
conversationNameValue = value
conversationInfoInterface.conversationNameSet(value)
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
} else {
conversationNameValue = value
}
}
}
}
override fun saveInt(
key: String,
value: Int
) {
arbitraryStorageUtils!!.storeStorageSetting(
accountIdentifier, key, Integer.toString(value),
conversationToken
)
}
override fun saveStringSet(
key: String,
value: Set<String>
) {
}
override fun getBoolean(
key: String,
defaultVal: Boolean
): Boolean {
return if (key == "conversation_lobby") {
lobbyValue
} else if (key == "allow_guests") {
allowGuestsValue
} else if (key == "favorite_conversation") {
favoriteConversationValue
} else {
val valueFromDb: ArbitraryStorageEntity? =
arbitraryStorageUtils!!.getStorageSetting(accountIdentifier, key, conversationToken)
if (valueFromDb == null) {
defaultVal
} else {
valueFromDb.value!!.toBoolean()
}
}
}
override fun getString(
key: String,
defaultVal: String
): String {
if (key != "message_notification_level"
&& key != "conversation_name"
&& key != "conversation_password"
) {
val valueFromDb: ArbitraryStorageEntity? =
arbitraryStorageUtils!!.getStorageSetting(accountIdentifier, key, conversationToken)
return if (valueFromDb == null) {
defaultVal
} else {
valueFromDb.value
}
} else if (key == "message_notification_level") {
return messageNotificationLevel!!
} else if (key == "conversation_name") {
return conversationNameValue!!
} else if (key == "conversation_password") {
return ""
}
return ""
}
override fun getInt(
key: String,
defaultVal: Int
): Int {
val valueFromDb: ArbitraryStorageEntity? =
arbitraryStorageUtils!!.getStorageSetting(accountIdentifier, key, conversationToken)
return if (valueFromDb == null) {
defaultVal
} else {
Integer.parseInt(valueFromDb.value)
}
}
override fun getStringSet(
key: String,
defaultVal: Set<String>
): Set<String>? {
return null
}
override fun onSaveInstanceState(outState: Bundle) {}
override fun onRestoreInstanceState(savedState: Bundle) {}
init {
sharedApplication!!
.componentApplication
.inject(this)
accountIdentifier = conversationUser.id
}
}

View File

@ -1,85 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 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.utils.singletons;
import com.nextcloud.talk.models.database.UserEntity;
public class ApplicationWideCurrentRoomHolder {
private static final ApplicationWideCurrentRoomHolder holder =
new ApplicationWideCurrentRoomHolder();
private String currentRoomId = "";
private String currentRoomToken = "";
private UserEntity userInRoom = new UserEntity();
private boolean inCall = false;
private String session = "";
public static ApplicationWideCurrentRoomHolder getInstance() {
return holder;
}
public void clear() {
currentRoomId = "";
userInRoom = new UserEntity();
inCall = false;
currentRoomToken = "";
session = "";
}
public String getCurrentRoomToken() {
return currentRoomToken;
}
public void setCurrentRoomToken(String currentRoomToken) {
this.currentRoomToken = currentRoomToken;
}
public String getCurrentRoomId() {
return currentRoomId;
}
public void setCurrentRoomId(String currentRoomId) {
this.currentRoomId = currentRoomId;
}
public UserEntity getUserInRoom() {
return userInRoom;
}
public void setUserInRoom(UserEntity userInRoom) {
this.userInRoom = userInRoom;
}
public boolean isInCall() {
return inCall;
}
public void setInCall(boolean inCall) {
this.inCall = inCall;
}
public String getSession() {
return session;
}
public void setSession(String session) {
this.session = session;
}
}

View File

@ -40,6 +40,7 @@ import com.nextcloud.talk.models.json.websocket.ErrorOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.EventOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.LoggingUtils;
import com.nextcloud.talk.utils.MagicMap;
import com.nextcloud.talk.utils.bundle.BundleKeys;
@ -72,7 +73,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
@Inject
Context context;
private UserEntity conversationUser;
private UserNgEntity conversationUser;
private String webSocketTicket;
private String resumeId;
private String sessionId;
@ -91,7 +92,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
private List<String> messagesQueue = new ArrayList<>();
MagicWebSocketInstance(UserEntity conversationUser, String connectionUrl,
MagicWebSocketInstance(UserNgEntity conversationUser, String connectionUrl,
String webSocketTicket) {
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()

View File

@ -36,6 +36,7 @@ import com.nextcloud.talk.models.json.websocket.RequestOfferSignalingMessage;
import com.nextcloud.talk.models.json.websocket.RoomOverallWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.RoomWebSocketMessage;
import com.nextcloud.talk.models.json.websocket.SignalingDataWebSocketMessageForOffer;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.ApiUtils;
import java.util.HashMap;
import java.util.Map;
@ -65,7 +66,7 @@ public class WebSocketConnectionHelper {
}
public static synchronized MagicWebSocketInstance getExternalSignalingInstanceForServer(
String url, UserEntity userEntity, String webSocketTicket, boolean isGuest) {
String url, UserNgEntity userEntity, String webSocketTicket, boolean isGuest) {
String generatedURL = url.replace("https://", "wss://").replace("http://", "ws://");
if (generatedURL.endsWith("/")) {
@ -102,7 +103,7 @@ public class WebSocketConnectionHelper {
}
}
HelloOverallWebSocketMessage getAssembledHelloModel(UserEntity userEntity, String ticket) {
HelloOverallWebSocketMessage getAssembledHelloModel(UserNgEntity userEntity, String ticket) {
HelloOverallWebSocketMessage helloOverallWebSocketMessage = new HelloOverallWebSocketMessage();
helloOverallWebSocketMessage.setType("hello");
HelloWebSocketMessage helloWebSocketMessage = new HelloWebSocketMessage();