mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-13 07:44:11 +01:00
New chat - baby steps
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
cb2696871d
commit
e305979cdd
@ -155,7 +155,7 @@ android {
|
||||
|
||||
ext {
|
||||
work_version = '2.3.3'
|
||||
koin_version = "2.1.0-alpha-1"
|
||||
koin_version = "2.1.4"
|
||||
lifecycle_version = '2.2.0'
|
||||
coil_version = "0.9.5"
|
||||
room_version = "2.2.4"
|
||||
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters.messages
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.parcel.RawValue
|
||||
|
||||
@Parcelize
|
||||
data class ImageLoaderPayload(
|
||||
val map: @RawValue HashMap<String, Any>?
|
||||
) : Parcelable
|
@ -1,231 +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.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.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.emoji.widget.EmojiTextView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.nextcloud.talk.R
|
||||
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
|
||||
|
||||
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: ImageView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageTime)
|
||||
var messageTimeView: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedChatMessageView)
|
||||
var quotedChatMessageView: RelativeLayout? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageAuthor)
|
||||
var quotedUserName: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageImage)
|
||||
var quotedMessagePreview: ImageView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessage)
|
||||
var quotedMessage: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quoteColoredView)
|
||||
var quoteColoredView: View? = null
|
||||
|
||||
val context: Context by inject()
|
||||
|
||||
val appPreferences: AppPreferences by inject()
|
||||
|
||||
init {
|
||||
ButterKnife.bind(
|
||||
this,
|
||||
itemView
|
||||
)
|
||||
}
|
||||
|
||||
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.actorId == "changelog") {
|
||||
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?.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable))
|
||||
} else if (message.actorType == "bots") {
|
||||
val drawable = TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(
|
||||
">",
|
||||
context.resources.getColor(R.color.black)
|
||||
)
|
||||
messageUserAvatarView!!.visibility = View.VISIBLE
|
||||
messageUserAvatarView?.setImageDrawable(drawable)
|
||||
}
|
||||
} else {
|
||||
if (message.oneToOneConversation) {
|
||||
messageUserAvatarView!!.visibility = View.GONE
|
||||
} else {
|
||||
messageUserAvatarView!!.visibility = View.INVISIBLE
|
||||
}
|
||||
messageAuthor!!.visibility = View.GONE
|
||||
}
|
||||
|
||||
val resources = itemView.resources
|
||||
|
||||
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.isSelected = false
|
||||
messageTimeView!!.setTextColor(context.resources.getColor(R.color.warm_grey_four))
|
||||
|
||||
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()
|
||||
itemView.isSelected = true
|
||||
messageAuthor!!.visibility = View.GONE
|
||||
}
|
||||
|
||||
messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
messageText!!.text = messageString
|
||||
|
||||
// parent message handling
|
||||
|
||||
message.parentMessage?.let { parentChatMessage ->
|
||||
parentChatMessage.activeUser = message.activeUser
|
||||
parentChatMessage.imageUrl?.let {
|
||||
quotedMessagePreview?.visibility = View.VISIBLE
|
||||
imageLoader.loadImage(quotedMessagePreview, it, null)
|
||||
} ?: run {
|
||||
quotedMessagePreview?.visibility = View.GONE
|
||||
}
|
||||
quotedUserName?.text = parentChatMessage.actorDisplayName
|
||||
?: context.getText(R.string.nc_nick_guest)
|
||||
quotedMessage?.text = parentChatMessage.text
|
||||
|
||||
quotedUserName?.setTextColor(context.resources.getColor(R.color.colorPrimary))
|
||||
|
||||
quoteColoredView?.setBackgroundResource(R.color.colorPrimary)
|
||||
quotedChatMessageView?.visibility = View.VISIBLE
|
||||
} ?: run {
|
||||
quotedChatMessageView?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
@ -1,164 +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.net.Uri
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.emoji.widget.EmojiTextView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector
|
||||
import com.nextcloud.talk.utils.DisplayUtils.searchAndReplaceWithMentionSpan
|
||||
import com.nextcloud.talk.utils.TextMatchers
|
||||
import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import java.util.*
|
||||
|
||||
class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewHolder<ChatMessage>(itemView), KoinComponent {
|
||||
@JvmField
|
||||
@BindView(R.id.messageText)
|
||||
var messageText: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageTime)
|
||||
var messageTimeView: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedChatMessageView)
|
||||
var quotedChatMessageView: RelativeLayout? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageAuthor)
|
||||
var quotedUserName: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageImage)
|
||||
var quotedMessagePreview: ImageView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessage)
|
||||
var quotedMessage: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quoteColoredView)
|
||||
var quoteColoredView: View? = null
|
||||
|
||||
val context: Context by inject()
|
||||
private val realView: View
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
val messageParameters: HashMap<String, HashMap<String, String>>? = message.messageParameters
|
||||
var messageString: Spannable = SpannableString(message.text)
|
||||
realView.isSelected = false
|
||||
messageTimeView!!.setTextColor(context.resources.getColor(R.color.white60))
|
||||
val layoutParams = messageTimeView!!.layoutParams as FlexboxLayout.LayoutParams
|
||||
layoutParams.isWrapBefore = false
|
||||
var textSize = context.resources.getDimension(R.dimen.chat_text_size)
|
||||
if (messageParameters != null && messageParameters.size > 0) {
|
||||
for (key in messageParameters.keys) {
|
||||
val individualHashMap: HashMap<String, String>? = message.messageParameters!![key]
|
||||
if (individualHashMap != null) {
|
||||
if (individualHashMap["type"] == "user" || (individualHashMap["type"]
|
||||
== "guest") || individualHashMap["type"] == "call") {
|
||||
messageString = searchAndReplaceWithMentionSpan(messageText!!.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
individualHashMap["type"]!!,
|
||||
message.activeUser!!,
|
||||
R.xml.chip_others)
|
||||
} else if (individualHashMap["type"] == "file") {
|
||||
realView.setOnClickListener(View.OnClickListener { v: View? ->
|
||||
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
|
||||
messageTimeView!!.setTextColor(context.resources.getColor(R.color.warm_grey_four))
|
||||
realView.isSelected = true
|
||||
}
|
||||
val resources = sharedApplication!!.resources
|
||||
if (message.grouped) {
|
||||
val bubbleDrawable = getMessageSelector(
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
resources.getColor(R.color.transparent),
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
R.drawable.shape_grouped_outcoming_message)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
} else {
|
||||
val bubbleDrawable = getMessageSelector(
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
resources.getColor(R.color.transparent),
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble),
|
||||
R.drawable.shape_outcoming_message)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
}
|
||||
messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
messageTimeView!!.layoutParams = layoutParams
|
||||
messageText!!.text = messageString
|
||||
|
||||
// parent message handling
|
||||
|
||||
message.parentMessage?.let { parentChatMessage ->
|
||||
parentChatMessage.activeUser = message.activeUser
|
||||
parentChatMessage.imageUrl?.let {
|
||||
quotedMessagePreview?.visibility = View.VISIBLE
|
||||
imageLoader.loadImage(quotedMessagePreview, it, null)
|
||||
} ?: run {
|
||||
quotedMessagePreview?.visibility = View.GONE
|
||||
}
|
||||
quotedUserName?.text = parentChatMessage.actorDisplayName
|
||||
?: context.getText(R.string.nc_nick_guest)
|
||||
quotedMessage?.text = parentChatMessage.text
|
||||
quotedMessage?.setTextColor(context.resources.getColor(R.color.nc_outcoming_text_default))
|
||||
quotedUserName?.setTextColor(context.resources.getColor(R.color.nc_grey))
|
||||
|
||||
quoteColoredView?.setBackgroundResource(R.color.white)
|
||||
|
||||
quotedChatMessageView?.visibility = View.VISIBLE
|
||||
} ?: run {
|
||||
quotedChatMessageView?.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
this.realView = itemView
|
||||
}
|
||||
}
|
@ -1,216 +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.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import androidx.emoji.widget.EmojiTextView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import coil.api.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.nextcloud.talk.R.*
|
||||
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.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.*
|
||||
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
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
|
||||
import com.stfalcon.chatkit.messages.MessageHolders.IncomingImageMessageViewHolder
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
|
||||
class MagicPreviewMessageViewHolder(itemView: View?) : IncomingImageMessageViewHolder<ChatMessage>(
|
||||
itemView
|
||||
), KoinComponent {
|
||||
@JvmField
|
||||
@BindView(id.messageText)
|
||||
var messageText: EmojiTextView? = null
|
||||
val context: Context by inject()
|
||||
val okHttpClient: OkHttpClient by inject()
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
if (userAvatar != null) {
|
||||
if (message.grouped || message.oneToOneConversation) {
|
||||
if (message.oneToOneConversation) {
|
||||
userAvatar.visibility = View.GONE
|
||||
} else {
|
||||
userAvatar.visibility = View.INVISIBLE
|
||||
}
|
||||
} else {
|
||||
userAvatar.visibility = View.VISIBLE
|
||||
if ("bots" == message.actorType && "changelog" == message.actorId) {
|
||||
val layers =
|
||||
arrayOfNulls<Drawable?>(2)
|
||||
layers[0] = context.getDrawable(drawable.ic_launcher_background)
|
||||
layers[1] = context.getDrawable(drawable.ic_launcher_foreground)
|
||||
val layerDrawable =
|
||||
LayerDrawable(layers)
|
||||
userAvatar.load(layerDrawable) {
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.messageType == SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||
// it's a preview for a Nextcloud share
|
||||
|
||||
messageText!!.text = message.selectedIndividualHashMap!!["name"]
|
||||
setClickableString(
|
||||
message.selectedIndividualHashMap!!["name"]!!,
|
||||
message.selectedIndividualHashMap!!["link"]!!, messageText!!
|
||||
)
|
||||
|
||||
if (message.selectedIndividualHashMap!!.containsKey("mimetype")) {
|
||||
if (message.imageUrl == "no-preview") {
|
||||
image.load(getDrawableResourceIdForMimeType(message.selectedIndividualHashMap!!["mimetype"]))
|
||||
}
|
||||
} else {
|
||||
fetchFileInformation(
|
||||
"/" + message.selectedIndividualHashMap!!["path"],
|
||||
message.activeUser
|
||||
)
|
||||
}
|
||||
|
||||
image.setOnClickListener { v: View? ->
|
||||
val accountString =
|
||||
message.activeUser!!.username + "@" + message.activeUser!!
|
||||
.baseUrl
|
||||
.replace("https://", "")
|
||||
.replace("http://", "")
|
||||
if (canWeOpenFilesApp(context, accountString)) {
|
||||
val filesAppIntent =
|
||||
Intent(Intent.ACTION_VIEW, null)
|
||||
val componentName = ComponentName(
|
||||
context.getString(string.nc_import_accounts_from),
|
||||
"com.owncloud.android.ui.activity.FileDisplayActivity"
|
||||
)
|
||||
filesAppIntent.component = componentName
|
||||
filesAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
filesAppIntent.setPackage(
|
||||
context.getString(string.nc_import_accounts_from)
|
||||
)
|
||||
filesAppIntent.putExtra(
|
||||
KEY_ACCOUNT, accountString
|
||||
)
|
||||
filesAppIntent.putExtra(
|
||||
KEY_FILE_ID,
|
||||
message.selectedIndividualHashMap!!["id"]
|
||||
)
|
||||
context.startActivity(filesAppIntent)
|
||||
} else {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(message.selectedIndividualHashMap!!["link"])
|
||||
)
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
}
|
||||
} else if (message.messageType == SINGLE_LINK_GIPHY_MESSAGE) {
|
||||
messageText!!.text = "GIPHY"
|
||||
setClickableString(
|
||||
"GIPHY", "https://giphy.com", messageText!!
|
||||
)
|
||||
} else if (message.messageType == SINGLE_LINK_TENOR_MESSAGE) {
|
||||
messageText!!.text = "Tenor"
|
||||
setClickableString(
|
||||
"Tenor", "https://tenor.com", messageText!!
|
||||
)
|
||||
} else {
|
||||
if (message.messageType == SINGLE_LINK_IMAGE_MESSAGE) {
|
||||
image.setOnClickListener { v: View? ->
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW, Uri.parse(message.imageUrl)
|
||||
)
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
} else {
|
||||
image.setOnClickListener(null)
|
||||
}
|
||||
messageText!!.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchFileInformation(
|
||||
url: String,
|
||||
activeUser: UserNgEntity?
|
||||
) {
|
||||
Single.fromCallable {
|
||||
ReadFilesystemOperation(
|
||||
okHttpClient, activeUser, url, 0
|
||||
)
|
||||
}
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(object : SingleObserver<ReadFilesystemOperation?> {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
override fun onSuccess(readFilesystemOperation: ReadFilesystemOperation) {
|
||||
val davResponse: DavResponse =
|
||||
readFilesystemOperation.readRemotePath()
|
||||
if (davResponse.data != null) {
|
||||
val browserFileList =
|
||||
davResponse.data as List<BrowserFile>
|
||||
if (browserFileList.isNotEmpty()) {
|
||||
image.load(getDrawableResourceIdForMimeType(browserFileList[0].mimeType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getPayloadForImageLoader(message: ChatMessage): Any {
|
||||
val map = HashMap<String, Any>()
|
||||
// used for setting a placeholder
|
||||
if (message.selectedIndividualHashMap!!.containsKey("mimetype")) {
|
||||
map["mimetype"] = message.selectedIndividualHashMap!!["mimetype"]!!
|
||||
}
|
||||
|
||||
map["hasPreview"] = message.selectedIndividualHashMap!!.getOrDefault("has-preview", false)
|
||||
|
||||
return ImageLoaderPayload(map)
|
||||
}
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, itemView!!)
|
||||
}
|
||||
}
|
@ -1,81 +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.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector
|
||||
import com.nextcloud.talk.utils.DisplayUtils.searchAndColor
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import com.stfalcon.chatkit.messages.MessageHolders.IncomingTextMessageViewHolder
|
||||
import com.stfalcon.chatkit.utils.DateFormatter
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import java.util.*
|
||||
|
||||
class MagicSystemMessageViewHolder(itemView: View) : IncomingTextMessageViewHolder<ChatMessage>(itemView), KoinComponent {
|
||||
val appPreferences: AppPreferences by inject()
|
||||
val context: Context by inject()
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageTime)
|
||||
var messageTime: TextView? = null
|
||||
|
||||
init {
|
||||
ButterKnife.bind(
|
||||
this,
|
||||
itemView
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
val resources = itemView.resources
|
||||
val normalColor = resources.getColor(R.color.bg_message_list_incoming_bubble)
|
||||
val pressedColor: Int
|
||||
val mentionColor: Int
|
||||
pressedColor = normalColor
|
||||
mentionColor = resources.getColor(R.color.nc_author_text)
|
||||
val bubbleDrawable = getMessageSelector(normalColor,
|
||||
resources.getColor(R.color.transparent), pressedColor,
|
||||
R.drawable.shape_grouped_incoming_message)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
var messageString: Spannable = SpannableString(message.text)
|
||||
if (message.messageParameters != null && message.messageParameters!!.size > 0) {
|
||||
for (key in message.messageParameters!!.keys) {
|
||||
val individualHashMap: HashMap<String, String>? = message.messageParameters!![key]
|
||||
if (individualHashMap != null && (individualHashMap["type"] == "user" || individualHashMap["type"] == "guest" || individualHashMap["type"] == "call")) {
|
||||
messageString = searchAndColor(messageString, "@" + individualHashMap["name"],
|
||||
mentionColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
text.text = messageString
|
||||
messageTime?.text = DateFormatter.format(message.createdAt, DateFormatter.Template.TIME)
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters.messages;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||
import com.stfalcon.chatkit.messages.MessageHolders;
|
||||
|
||||
public class MagicUnreadNoticeMessageViewHolder
|
||||
extends MessageHolders.SystemMessageViewHolder<ChatMessage> {
|
||||
|
||||
public MagicUnreadNoticeMessageViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public MagicUnreadNoticeMessageViewHolder(View itemView, Object payload) {
|
||||
super(itemView, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewDetached() {
|
||||
messagesListAdapter.deleteById("-1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewAttached() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewRecycled() {
|
||||
|
||||
}
|
||||
}
|
@ -176,7 +176,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration
|
||||
//endregion
|
||||
|
||||
//region Protected methods
|
||||
protected fun startKoin() {
|
||||
private fun startKoin() {
|
||||
startKoin {
|
||||
androidContext(this@NextcloudTalkApplication)
|
||||
androidLogger()
|
||||
|
@ -27,6 +27,7 @@ import android.widget.EditText;
|
||||
|
||||
import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.models.json.mention.Mention;
|
||||
import com.nextcloud.talk.newarch.local.models.User;
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
|
||||
import com.nextcloud.talk.utils.BetterImageSpan;
|
||||
import com.nextcloud.talk.utils.DisplayUtils;
|
||||
@ -38,10 +39,10 @@ import com.vanniktech.emoji.EmojiUtils;
|
||||
|
||||
public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> {
|
||||
private Context context;
|
||||
private UserNgEntity conversationUser;
|
||||
private User conversationUser;
|
||||
private EditText editText;
|
||||
|
||||
public MentionAutocompleteCallback(Context context, UserNgEntity conversationUser,
|
||||
public MentionAutocompleteCallback(Context context, User conversationUser,
|
||||
EditText editText) {
|
||||
this.context = context;
|
||||
this.conversationUser = conversationUser;
|
||||
|
@ -32,6 +32,7 @@ import coil.api.load
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
|
||||
import com.nextcloud.talk.interfaces.SelectionInterface
|
||||
import com.nextcloud.talk.newarch.local.models.User
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
@ -47,7 +48,7 @@ import org.koin.core.inject
|
||||
|
||||
class BrowserFileItem(
|
||||
val model: BrowserFile,
|
||||
private val activeUser: UserNgEntity,
|
||||
private val activeUser: User,
|
||||
private val selectionInterface: SelectionInterface
|
||||
) : AbstractFlexibleItem<BrowserFileItem.ViewHolder>(), IFilterable<String>, KoinComponent {
|
||||
val context: Context by inject()
|
||||
|
@ -40,6 +40,7 @@ import com.nextcloud.talk.components.filebrowser.operations.ListingAbstractClass
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import com.nextcloud.talk.interfaces.SelectionInterface
|
||||
import com.nextcloud.talk.jobs.ShareOperationWorker
|
||||
import com.nextcloud.talk.newarch.local.models.User
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import eu.davidea.fastscroller.FastScroller
|
||||
@ -83,13 +84,13 @@ class BrowserController(args: Bundle) : BaseController(), ListingInterface, Flex
|
||||
private var listingAbstractClass: ListingAbstractClass? = null
|
||||
private val browserType: BrowserType
|
||||
private var currentPath: String? = null
|
||||
private val activeUser: UserNgEntity
|
||||
private val activeUser: User
|
||||
private val roomToken: String?
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
browserType = Parcels.unwrap(args.getParcelable(BundleKeys.KEY_BROWSER_TYPE))
|
||||
activeUser = Parcels.unwrap(args.getParcelable(BundleKeys.KEY_USER_ENTITY))
|
||||
activeUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY)!!
|
||||
roomToken = args.getString(BundleKeys.KEY_CONVERSATION_TOKEN)
|
||||
|
||||
currentPath = "/"
|
||||
@ -130,7 +131,7 @@ class BrowserController(args: Bundle) : BaseController(), ListingInterface, Flex
|
||||
iterator.remove()
|
||||
if (paths.size == 10 || !iterator.hasNext()) {
|
||||
data = Data.Builder()
|
||||
.putLong(BundleKeys.KEY_INTERNAL_USER_ID, activeUser.id)
|
||||
.putLong(BundleKeys.KEY_INTERNAL_USER_ID, activeUser.id!!)
|
||||
.putString(BundleKeys.KEY_CONVERSATION_TOKEN, roomToken)
|
||||
.putStringArray(BundleKeys.KEY_FILE_PATHS, paths.toTypedArray())
|
||||
.build()
|
||||
|
@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
|
||||
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.newarch.local.models.User;
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@ -43,7 +44,7 @@ public class DavListing extends ListingAbstractClass {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getFiles(String path, UserNgEntity currentUser, @Nullable OkHttpClient okHttpClient) {
|
||||
public void getFiles(String path, User currentUser, @Nullable OkHttpClient okHttpClient) {
|
||||
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
||||
@Override
|
||||
public ReadFilesystemOperation call() {
|
||||
|
@ -25,7 +25,7 @@ import android.os.Handler;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
|
||||
import com.nextcloud.talk.newarch.local.models.User;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@ -38,7 +38,7 @@ public abstract class ListingAbstractClass {
|
||||
this.listingInterface = listingInterface;
|
||||
}
|
||||
|
||||
public abstract void getFiles(String path, UserNgEntity currentUser,
|
||||
public abstract void getFiles(String path, User currentUser,
|
||||
@Nullable OkHttpClient okHttpClient);
|
||||
|
||||
public void cancelAllJobs() {
|
||||
|
@ -22,6 +22,7 @@ package com.nextcloud.talk.components.filebrowser.webdav;
|
||||
|
||||
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
|
||||
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
||||
import com.nextcloud.talk.newarch.local.models.User;
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
|
||||
import com.nextcloud.talk.newarch.utils.NetworkUtils;
|
||||
import com.nextcloud.talk.utils.ApiUtils;
|
||||
@ -44,7 +45,7 @@ public class ReadFilesystemOperation {
|
||||
private final int depth;
|
||||
private final String basePath;
|
||||
|
||||
public ReadFilesystemOperation(OkHttpClient okHttpClient, UserNgEntity currentUser, String path,
|
||||
public ReadFilesystemOperation(OkHttpClient okHttpClient, User currentUser, String path,
|
||||
int depth) {
|
||||
OkHttpClient.Builder okHttpClientBuilder = okHttpClient.newBuilder();
|
||||
okHttpClientBuilder.followRedirects(false);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -69,6 +69,7 @@ import com.nextcloud.talk.models.json.participants.ParticipantsOverall
|
||||
import com.nextcloud.talk.newarch.features.contactsflow.contacts.ContactsView
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||
import com.nextcloud.talk.newarch.local.models.toUser
|
||||
import com.nextcloud.talk.newarch.utils.Images
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.DateUtils
|
||||
@ -268,7 +269,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
||||
if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type
|
||||
== Conversation.ConversationType.GROUP_CONVERSATION || conversation!!.type ==
|
||||
PUBLIC_CONVERSATION) && conversation!!.canModerate(
|
||||
conversationUser
|
||||
conversationUser.toUser()
|
||||
)
|
||||
) {
|
||||
conversationInfoWebinar.visibility = View.VISIBLE
|
||||
@ -652,7 +653,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
||||
|
||||
|
||||
if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
|
||||
if (conversationCopy!!.canModerate(conversationUser)) {
|
||||
if (conversationCopy!!.canModerate(conversationUser.toUser())) {
|
||||
actionTextView.visibility = View.VISIBLE
|
||||
} else {
|
||||
actionTextView.visibility = View.GONE
|
||||
@ -663,13 +664,13 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
||||
setupGeneralSettings()
|
||||
setupWebinaryView()
|
||||
|
||||
if (!conversation!!.canLeave(conversationUser)) {
|
||||
if (!conversation!!.canLeave(conversationUser.toUser())) {
|
||||
leaveConversationAction.visibility = View.GONE
|
||||
} else {
|
||||
leaveConversationAction.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (!conversation!!.canModerate(conversationUser)) {
|
||||
if (!conversation!!.canModerate(conversationUser.toUser())) {
|
||||
deleteConversationAction.visibility = View.GONE
|
||||
} else {
|
||||
deleteConversationAction.visibility = View.VISIBLE
|
||||
@ -709,7 +710,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
||||
if (conversation != null && conversationUser != null) {
|
||||
changeConversationName.value = conversation!!.displayName
|
||||
|
||||
if (conversation!!.isNameEditable(conversationUser)) {
|
||||
if (conversation!!.isNameEditable(conversationUser.toUser())) {
|
||||
changeConversationName.visibility = View.VISIBLE
|
||||
} else {
|
||||
changeConversationName.visibility = View.GONE
|
||||
@ -873,7 +874,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
||||
)
|
||||
)
|
||||
|
||||
if (!conversation!!.canModerate(conversationUser)) {
|
||||
if (!conversation!!.canModerate(conversationUser.toUser())) {
|
||||
items = mutableListOf()
|
||||
} else {
|
||||
if (participant.type == Participant.ParticipantType.MODERATOR || participant.type == Participant.ParticipantType.OWNER) {
|
||||
|
@ -289,7 +289,7 @@ class NotificationWorker(
|
||||
}
|
||||
|
||||
val request = Images().getRequestForUrl(
|
||||
Coil.loader(), applicationContext, avatarUrl!!, signatureVerification.userEntity,
|
||||
Coil.loader(), applicationContext, avatarUrl!!, signatureVerification.userEntity!!.toUser(),
|
||||
target, null, CircleCropTransformation()
|
||||
)
|
||||
|
||||
|
@ -29,7 +29,8 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.models.json.converters.*
|
||||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.local.models.User
|
||||
import com.nextcloud.talk.newarch.local.models.hasSpreedFeatureCapability
|
||||
import kotlinx.serialization.Serializable
|
||||
import lombok.Data
|
||||
import org.parceler.Parcel
|
||||
@ -152,36 +153,35 @@ class Conversation {
|
||||
return resources.getString(R.string.nc_delete_conversation_default)
|
||||
}
|
||||
|
||||
private fun isLockedOneToOne(conversationUser: UserNgEntity): Boolean {
|
||||
private fun isLockedOneToOne(conversationUser: User): Boolean {
|
||||
return type == ConversationType.ONE_TO_ONE_CONVERSATION && conversationUser
|
||||
.hasSpreedFeatureCapability(
|
||||
"locked-one-to-one-rooms"
|
||||
)
|
||||
}
|
||||
|
||||
fun canModerate(conversationUser: UserNgEntity): Boolean {
|
||||
fun canModerate(conversationUser: User): Boolean {
|
||||
return (Participant.ParticipantType.OWNER == participantType || Participant.ParticipantType.MODERATOR == participantType) && !isLockedOneToOne(
|
||||
conversationUser
|
||||
)
|
||||
}
|
||||
|
||||
fun shouldShowLobby(conversationUser: UserNgEntity): Boolean {
|
||||
return LobbyState.LOBBY_STATE_MODERATORS_ONLY == lobbyState && !canModerate(
|
||||
conversationUser
|
||||
fun shouldShowLobby(conversationUser: User): Boolean {
|
||||
return LobbyState.LOBBY_STATE_MODERATORS_ONLY == lobbyState && !canModerate(conversationUser
|
||||
)
|
||||
}
|
||||
|
||||
fun isLobbyViewApplicable(conversationUser: UserNgEntity): Boolean {
|
||||
fun isLobbyViewApplicable(conversationUser: User): Boolean {
|
||||
return !canModerate(
|
||||
conversationUser
|
||||
) && (type == ConversationType.GROUP_CONVERSATION || type == ConversationType.PUBLIC_CONVERSATION)
|
||||
}
|
||||
|
||||
fun isNameEditable(conversationUser: UserNgEntity): Boolean {
|
||||
fun isNameEditable(conversationUser: User): Boolean {
|
||||
return canModerate(conversationUser) && ConversationType.ONE_TO_ONE_CONVERSATION != type
|
||||
}
|
||||
|
||||
fun canLeave(conversationUser: UserNgEntity): Boolean {
|
||||
fun canLeave(conversationUser: User): Boolean {
|
||||
return !canModerate(
|
||||
conversationUser
|
||||
) || type != ConversationType.ONE_TO_ONE_CONVERSATION && participants!!.size > 1
|
||||
|
@ -0,0 +1,56 @@
|
||||
package com.nextcloud.talk.newarch.features.chat
|
||||
|
||||
import android.content.Context
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.otaliastudios.elements.Page
|
||||
import com.otaliastudios.elements.Source
|
||||
import com.otaliastudios.elements.extensions.HeaderSource
|
||||
import com.stfalcon.chatkit.utils.DateFormatter
|
||||
import java.util.*
|
||||
|
||||
class ChatDateHeaderSource(private val context: Context, private val elementType: Int) : HeaderSource<ChatElement, String>() {
|
||||
// Store the last header that was added, even if it belongs to a previous page.
|
||||
private var headersAlreadyAdded = mutableListOf<String>()
|
||||
|
||||
override fun dependsOn(source: Source<*>) = source is ChatViewSource
|
||||
|
||||
override fun getElementType(data: Data<ChatElement, String>): Int {
|
||||
return elementType
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(first: Data<ChatElement, String>, second: Data<ChatElement, String>): Boolean {
|
||||
return first == second
|
||||
}
|
||||
override fun computeHeaders(page: Page, list: List<ChatElement>): List<Data<ChatElement, String>> {
|
||||
val results = arrayListOf<Data<ChatElement, String>>()
|
||||
headersAlreadyAdded = mutableListOf()
|
||||
var dateHeader = ""
|
||||
for (chatElement in list) {
|
||||
if (chatElement.data is ChatMessage) {
|
||||
dateHeader = formatDate(chatElement.data.createdAt)
|
||||
if (!headersAlreadyAdded.contains(dateHeader)) {
|
||||
results.add(Data(chatElement, dateHeader))
|
||||
headersAlreadyAdded.add(dateHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private fun formatDate(date: Date): String {
|
||||
return when {
|
||||
DateFormatter.isToday(date) -> {
|
||||
context.getString(R.string.nc_date_header_today)
|
||||
}
|
||||
DateFormatter.isYesterday(date) -> {
|
||||
context.resources.getString(R.string.nc_date_header_yesterday)
|
||||
}
|
||||
else -> {
|
||||
DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.nextcloud.talk.newarch.features.chat
|
||||
|
||||
data class ChatElement(
|
||||
val data: Any,
|
||||
val elementType: Int
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package com.nextcloud.talk.newarch.features.chat
|
||||
|
||||
enum class ChatElementTypes {
|
||||
INCOMING_TEXT_MESSAGE,
|
||||
OUTGOING_TEXT_MESSAGE,
|
||||
INCOMING_PREVIEW_MESSAGE,
|
||||
OUTGOING_PREVIEW_MESSAGE,
|
||||
SYSTEM_MESSAGE,
|
||||
UNREAD_MESSAGE_NOTICE,
|
||||
DATE_HEADER
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
package com.nextcloud.talk.newarch.features.chat
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import coil.api.loadAny
|
||||
import coil.api.newLoadBuilder
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.newarch.features.chat.interfaces.ImageLoaderInterface
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
|
||||
import com.otaliastudios.elements.Element
|
||||
import com.otaliastudios.elements.Page
|
||||
import com.otaliastudios.elements.Presenter
|
||||
import com.otaliastudios.elements.extensions.HeaderSource
|
||||
import com.stfalcon.chatkit.utils.DateFormatter
|
||||
import kotlinx.android.synthetic.main.item_message_quote.view.*
|
||||
import kotlinx.android.synthetic.main.rv_chat_incoming_preview_item.view.*
|
||||
import kotlinx.android.synthetic.main.rv_chat_incoming_text_item.view.*
|
||||
import kotlinx.android.synthetic.main.rv_chat_incoming_text_item.view.messageUserAvatar
|
||||
import kotlinx.android.synthetic.main.rv_chat_outgoing_preview_item.view.*
|
||||
import kotlinx.android.synthetic.main.rv_chat_outgoing_text_item.view.*
|
||||
import kotlinx.android.synthetic.main.rv_chat_system_item.view.*
|
||||
import kotlinx.android.synthetic.main.rv_date_and_unread_notice_item.view.*
|
||||
import org.koin.core.KoinComponent
|
||||
|
||||
open class ChatPresenter<T : Any>(context: Context, onElementClick: ((Page, Holder, Element<T>) -> Unit)?, private val onElementLongClick: ((Page, Holder, Element<T>) -> Unit)?, private val imageLoader: ImageLoaderInterface) : Presenter<T>(context, onElementClick), KoinComponent {
|
||||
override val elementTypes: Collection<Int>
|
||||
get() = listOf(ChatElementTypes.INCOMING_TEXT_MESSAGE.ordinal, ChatElementTypes.OUTGOING_TEXT_MESSAGE.ordinal, ChatElementTypes.INCOMING_PREVIEW_MESSAGE.ordinal, ChatElementTypes.OUTGOING_PREVIEW_MESSAGE.ordinal, ChatElementTypes.SYSTEM_MESSAGE.ordinal, ChatElementTypes.UNREAD_MESSAGE_NOTICE.ordinal, ChatElementTypes.DATE_HEADER.ordinal)
|
||||
|
||||
override fun onCreate(parent: ViewGroup, elementType: Int): Holder {
|
||||
return when (elementType) {
|
||||
ChatElementTypes.INCOMING_TEXT_MESSAGE.ordinal -> {
|
||||
Holder(getLayoutInflater().inflate(R.layout.rv_chat_incoming_text_item, parent, false))
|
||||
}
|
||||
ChatElementTypes.OUTGOING_TEXT_MESSAGE.ordinal -> {
|
||||
Holder(getLayoutInflater().inflate(R.layout.rv_chat_outgoing_text_item, parent, false))
|
||||
}
|
||||
ChatElementTypes.INCOMING_PREVIEW_MESSAGE.ordinal -> {
|
||||
Holder(getLayoutInflater().inflate(R.layout.rv_date_and_unread_notice_item, parent, false))
|
||||
}
|
||||
ChatElementTypes.OUTGOING_PREVIEW_MESSAGE.ordinal -> {
|
||||
Holder(getLayoutInflater().inflate(R.layout.rv_date_and_unread_notice_item, parent, false))
|
||||
}
|
||||
ChatElementTypes.SYSTEM_MESSAGE.ordinal -> {
|
||||
Holder(getLayoutInflater().inflate(R.layout.rv_chat_system_item, parent, false))
|
||||
}
|
||||
else -> {
|
||||
Holder(getLayoutInflater().inflate(R.layout.rv_date_and_unread_notice_item, parent, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(page: Page, holder: Holder, element: Element<T>, payloads: List<Any>) {
|
||||
super.onBind(page, holder, element, payloads)
|
||||
|
||||
holder.itemView.setOnLongClickListener {
|
||||
onElementLongClick?.invoke(page, holder, element)
|
||||
true
|
||||
}
|
||||
|
||||
var chatElement: ChatElement?
|
||||
var chatMessage: ChatMessage? = null
|
||||
|
||||
if (element.data is ChatElement) {
|
||||
chatElement = element.data as ChatElement
|
||||
chatMessage = chatElement.data as ChatMessage?
|
||||
}
|
||||
|
||||
when {
|
||||
chatMessage != null -> {
|
||||
chatMessage.let {
|
||||
if (element.type == ChatElementTypes.INCOMING_TEXT_MESSAGE.ordinal || element.type == ChatElementTypes.INCOMING_TEXT_MESSAGE.ordinal) {
|
||||
holder.itemView.messageAuthor?.text = it.actorDisplayName
|
||||
holder.itemView.messageUserAvatar?.isVisible = !it.grouped && !it.oneToOneConversation
|
||||
|
||||
if (element.type == ChatElementTypes.INCOMING_TEXT_MESSAGE.ordinal) {
|
||||
holder.itemView.incomingMessageTime.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
|
||||
holder.itemView.incomingMessageText.text = it.text
|
||||
|
||||
if (it.actorType == "bots" && it.actorId == "changelog") {
|
||||
holder.itemView.messageUserAvatar.isVisible = true
|
||||
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)
|
||||
val loadBuilder = imageLoader.getImageLoader().newLoadBuilder(context).target(holder.itemView.messageUserAvatar).data(DisplayUtils.getRoundedDrawable(layerDrawable))
|
||||
imageLoader.getImageLoader().load(loadBuilder.build())
|
||||
} else if (it.actorType == "bots") {
|
||||
holder.itemView.messageUserAvatar.isVisible = true
|
||||
val drawable = TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(
|
||||
">",
|
||||
context.resources.getColor(R.color.black)
|
||||
)
|
||||
val loadBuilder = imageLoader.getImageLoader().newLoadBuilder(context).target(holder.itemView.messageUserAvatar).data(DisplayUtils.getRoundedDrawable(drawable))
|
||||
imageLoader.getImageLoader().load(loadBuilder.build())
|
||||
} else if (!it.grouped && !it.oneToOneConversation) {
|
||||
holder.itemView.messageUserAvatar.isVisible = true
|
||||
imageLoader.loadImage(holder.itemView.messageUserAvatar, it.user.avatar)
|
||||
} else {
|
||||
holder.itemView.messageUserAvatar.isVisible = false
|
||||
}
|
||||
} else {
|
||||
holder.itemView.outgoingMessageTime.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
|
||||
holder.itemView.outgoingMessageText.text = it.text
|
||||
}
|
||||
|
||||
it.parentMessage?.let { parentMessage ->
|
||||
parentMessage.imageUrl?.let { previewMessageUrl ->
|
||||
holder.itemView.quotedMessageImage.visibility = View.VISIBLE
|
||||
imageLoader.loadImage(holder.itemView.quotedMessageImage, previewMessageUrl)
|
||||
} ?: run {
|
||||
holder.itemView.quotedMessageImage.visibility = View.GONE
|
||||
}
|
||||
|
||||
holder.itemView.quotedMessageAuthor.text = parentMessage.actorDisplayName ?: context.getText(R.string.nc_nick_guest)
|
||||
holder.itemView.quotedMessageAuthor.setTextColor(context.resources.getColor(R.color.colorPrimary))
|
||||
holder.itemView.quoteColoredView.setBackgroundResource(R.color.colorPrimary)
|
||||
holder.itemView.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} ?: run {
|
||||
holder.itemView.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
} else if (element.type == ChatElementTypes.INCOMING_PREVIEW_MESSAGE.ordinal || element.type == ChatElementTypes.OUTGOING_PREVIEW_MESSAGE.ordinal) {
|
||||
var previewAvailable = true
|
||||
val mutableMap = mutableMapOf<String, String>()
|
||||
if (it.selectedIndividualHashMap!!.containsKey("mimetype")) {
|
||||
mutableMap.put("mimetype", it.selectedIndividualHashMap!!["mimetype"]!!)
|
||||
if (it.imageUrl == "no-preview") {
|
||||
previewAvailable = false
|
||||
imageLoader.getImageLoader().loadAny(context, getDrawableResourceIdForMimeType(chatMessage.selectedIndividualHashMap!!["mimetype"]))
|
||||
}
|
||||
}
|
||||
|
||||
// Before someone tells me parts of this can be refactored so there is less code:
|
||||
// YES, I KNOW!
|
||||
// But the way it's done now means pretty much anyone can understand it and it's easy
|
||||
// to modify. Prefer simplicity over complexity wherever possible
|
||||
|
||||
if (element.type == ChatElementTypes.INCOMING_PREVIEW_MESSAGE.ordinal) {
|
||||
if (previewAvailable) {
|
||||
imageLoader.loadImage(holder.itemView.incomingPreviewImage, it.imageUrl!!)
|
||||
}
|
||||
if (!it.grouped && !it.oneToOneConversation) {
|
||||
holder.itemView.messageUserAvatar.visibility = View.GONE
|
||||
} else {
|
||||
holder.itemView.messageUserAvatar.visibility = View.VISIBLE
|
||||
imageLoader.loadImage(holder.itemView.messageUserAvatar, chatMessage.user.avatar)
|
||||
}
|
||||
|
||||
when (it.messageType) {
|
||||
ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> {
|
||||
holder.itemView.incomingPreviewMessageText.text = chatMessage.selectedIndividualHashMap!!["name"]
|
||||
}
|
||||
ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE -> {
|
||||
holder.itemView.incomingPreviewMessageText.text = "GIPHY"
|
||||
}
|
||||
ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE -> {
|
||||
holder.itemView.incomingPreviewMessageText.text = "TENOR"
|
||||
}
|
||||
else -> {
|
||||
holder.itemView.incomingPreviewMessageText.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemView.incomingPreviewTime.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
|
||||
} else {
|
||||
if (previewAvailable) {
|
||||
imageLoader.loadImage(holder.itemView.incomingPreviewImage, it.imageUrl!!)
|
||||
}
|
||||
|
||||
when (it.messageType) {
|
||||
ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> {
|
||||
holder.itemView.outgoingPreviewMessageText.text = chatMessage.selectedIndividualHashMap!!["name"]
|
||||
}
|
||||
ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE -> {
|
||||
holder.itemView.outgoingPreviewMessageText.text = "GIPHY"
|
||||
}
|
||||
ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE -> {
|
||||
holder.itemView.outgoingPreviewMessageText.text = "TENOR"
|
||||
}
|
||||
else -> {
|
||||
holder.itemView.outgoingPreviewMessageText.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemView.outgoingPreviewTime.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
// it's ChatElementTypes.SYSTEM_MESSAGE
|
||||
holder.itemView.systemMessageText.text = chatMessage.text
|
||||
holder.itemView.systemItemTime.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
|
||||
}
|
||||
}
|
||||
}
|
||||
element.type == ChatElementTypes.UNREAD_MESSAGE_NOTICE.ordinal -> {
|
||||
holder.itemView.noticeText.text = context.resources.getString(R.string.nc_new_messages)
|
||||
}
|
||||
else -> {
|
||||
// Date header
|
||||
holder.itemView.noticeText.text = (element.data as HeaderSource.Data<*, *>).header.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,19 +22,22 @@
|
||||
|
||||
package com.nextcloud.talk.newarch.features.chat
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.InputFilter
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.*
|
||||
import android.widget.AbsListView
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.observe
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import coil.api.load
|
||||
import coil.target.Target
|
||||
import coil.transform.CircleCropTransformation
|
||||
@ -43,61 +46,61 @@ import com.bluelinelabs.conductor.archlifecycle.ControllerLifecycleOwner
|
||||
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
|
||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.adapters.messages.*
|
||||
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
|
||||
import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
|
||||
import com.nextcloud.talk.controllers.ChatController
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.models.json.mention.Mention
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.features.chat.interfaces.ImageLoaderInterface
|
||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||
import com.nextcloud.talk.newarch.local.models.getMaxMessageLength
|
||||
import com.nextcloud.talk.newarch.mvvm.BaseView
|
||||
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
||||
import com.nextcloud.talk.newarch.utils.Images
|
||||
import com.nextcloud.talk.newarch.utils.NetworkComponents
|
||||
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
|
||||
import com.nextcloud.talk.utils.*
|
||||
import com.nextcloud.talk.utils.AccountUtils.canWeOpenFilesApp
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
|
||||
import com.nextcloud.talk.utils.text.Spans
|
||||
import com.otaliastudios.autocomplete.Autocomplete
|
||||
import com.stfalcon.chatkit.commons.models.IMessage
|
||||
import com.stfalcon.chatkit.messages.MessageHolders
|
||||
import com.otaliastudios.elements.Adapter
|
||||
import com.otaliastudios.elements.Element
|
||||
import com.otaliastudios.elements.Page
|
||||
import com.otaliastudios.elements.Presenter
|
||||
import com.otaliastudios.elements.pagers.PageSizePager
|
||||
import com.stfalcon.chatkit.messages.MessagesListAdapter
|
||||
import com.stfalcon.chatkit.utils.DateFormatter
|
||||
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
|
||||
import kotlinx.android.synthetic.main.controller_chat.view.*
|
||||
import kotlinx.android.synthetic.main.conversations_list_view.view.*
|
||||
import kotlinx.android.synthetic.main.lobby_view.view.*
|
||||
import kotlinx.android.synthetic.main.view_message_input.view.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.parceler.Parcels
|
||||
import java.util.*
|
||||
import coil.ImageLoader as CoilImageLoader
|
||||
import com.stfalcon.chatkit.commons.ImageLoader as ChatKitImageLoader
|
||||
|
||||
class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesListAdapter.OnLoadMoreListener, MessagesListAdapter
|
||||
.OnMessageLongClickListener<IMessage>, MessagesListAdapter.Formatter<Date> {
|
||||
class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
|
||||
override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
||||
override val lifecycleOwner = ControllerLifecycleOwner(this)
|
||||
|
||||
lateinit var viewModel: ChatViewModel
|
||||
private lateinit var viewModel: ChatViewModel
|
||||
val factory: ChatViewModelFactory by inject()
|
||||
val imageLoader: CoilImageLoader by inject()
|
||||
private val networkComponents: NetworkComponents by inject()
|
||||
|
||||
var conversationInfoMenuItem: MenuItem? = null
|
||||
var conversationVoiceCallMenuItem: MenuItem? = null
|
||||
var conversationVideoMenuItem: MenuItem? = null
|
||||
|
||||
private var newMessagesCount = 0
|
||||
|
||||
private lateinit var recyclerViewAdapter: MessagesListAdapter<ChatMessage>
|
||||
private lateinit var mentionAutocomplete: Autocomplete<*>
|
||||
|
||||
private var shouldShowLobby: Boolean = false
|
||||
private var isReadOnlyConversation: Boolean = false
|
||||
|
||||
private lateinit var messagesAdapter: Adapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup
|
||||
@ -105,12 +108,22 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
||||
setHasOptionsMenu(true)
|
||||
actionBar?.show()
|
||||
viewModel = viewModelProvider(factory).get(ChatViewModel::class.java)
|
||||
viewModel.init(args.getParcelable(BundleKeys.KEY_USER_ENTITY)!!, args.getString(BundleKeys.KEY_CONVERSATION_TOKEN)!!, args.getString(KEY_CONVERSATION_PASSWORD))
|
||||
val view = super.onCreateView(inflater, container)
|
||||
|
||||
viewModel.init(bundle.getParcelable(BundleKeys.KEY_USER)!!, bundle.getString(BundleKeys.KEY_CONVERSATION_TOKEN)!!, bundle.getString(KEY_CONVERSATION_PASSWORD))
|
||||
|
||||
messagesAdapter = Adapter.builder(this)
|
||||
.setPager(PageSizePager(80))
|
||||
//.addSource(ChatViewSource(itemsPerPage = 10))
|
||||
.addSource(ChatDateHeaderSource(activity as Context, ChatElementTypes.DATE_HEADER.ordinal))
|
||||
.addPresenter(Presenter.forLoadingIndicator(activity as Context, R.layout.loading_state))
|
||||
.addPresenter(ChatPresenter(activity as Context, ::onElementClick, ::onElementLongClick, this))
|
||||
.setAutoScrollMode(Adapter.AUTOSCROLL_POSITION_0, true)
|
||||
.into(view.messagesRecyclerView)
|
||||
|
||||
viewModel.apply {
|
||||
conversation.observe(this@ChatView) { conversation ->
|
||||
setTitle()
|
||||
setupAdapter()
|
||||
|
||||
if (Conversation.ConversationType.ONE_TO_ONE_CONVERSATION == conversation?.type) {
|
||||
loadAvatar()
|
||||
@ -124,36 +137,100 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
||||
activity?.invalidateOptionsMenu()
|
||||
|
||||
if (shouldShowLobby) {
|
||||
view?.messagesListView?.visibility = View.GONE
|
||||
view?.messageInputView?.visibility = View.GONE
|
||||
view?.lobbyView?.visibility = View.VISIBLE
|
||||
view.messagesListView?.visibility = View.GONE
|
||||
view.messageInputView?.visibility = View.GONE
|
||||
view.lobbyView?.visibility = View.VISIBLE
|
||||
val timer = conversation.lobbyTimer
|
||||
if (timer != null && timer != 0L) {
|
||||
view?.lobbyTextView?.text = String.format(
|
||||
val unit = if (timer != null && timer != 0L) {
|
||||
view.lobbyTextView?.text = String.format(
|
||||
resources!!.getString(R.string.nc_lobby_waiting_with_date),
|
||||
DateUtils.getLocalDateStringFromTimestampForLobby(
|
||||
conversation.lobbyTimer!!
|
||||
))
|
||||
} else {
|
||||
view?.lobbyTextView?.setText(R.string.nc_lobby_waiting)
|
||||
view.lobbyTextView?.setText(R.string.nc_lobby_waiting)
|
||||
}
|
||||
} else {
|
||||
view?.messagesListView?.visibility = View.GONE
|
||||
view?.lobbyView?.visibility = View.GONE
|
||||
view.messagesListView?.visibility = View.GONE
|
||||
view.lobbyView?.visibility = View.GONE
|
||||
|
||||
if (isReadOnlyConversation) {
|
||||
view?.messageInputView?.visibility = View.GONE
|
||||
view.messageInputView?.visibility = View.GONE
|
||||
} else {
|
||||
view?.messageInputView?.visibility = View.VISIBLE
|
||||
|
||||
view.messageInputView?.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return super.onCreateView(inflater, container)
|
||||
return view
|
||||
}
|
||||
|
||||
private fun onElementClick(page: Page, holder: Presenter.Holder, element: Element<ChatElement>) {
|
||||
if (element.type == ChatElementTypes.INCOMING_PREVIEW_MESSAGE.ordinal || element.type == ChatElementTypes.OUTGOING_PREVIEW_MESSAGE.ordinal) {
|
||||
element.data?.let { chatElement ->
|
||||
val chatMessage = chatElement.data as ChatMessage
|
||||
val currentUser = viewModel.user
|
||||
if (chatMessage.messageType == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||
val accountString = currentUser.username + "@" + currentUser.baseUrl
|
||||
.replace("https://", "")
|
||||
.replace("http://", "")
|
||||
if (canWeOpenFilesApp(context, accountString)) {
|
||||
val filesAppIntent = Intent(Intent.ACTION_VIEW, null)
|
||||
val componentName = ComponentName(
|
||||
context.getString(R.string.nc_import_accounts_from),
|
||||
"com.owncloud.android.ui.activity.FileDisplayActivity"
|
||||
)
|
||||
filesAppIntent.component = componentName
|
||||
filesAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
filesAppIntent.setPackage(
|
||||
context.getString(R.string.nc_import_accounts_from)
|
||||
)
|
||||
filesAppIntent.putExtra(
|
||||
KEY_ACCOUNT, accountString
|
||||
)
|
||||
filesAppIntent.putExtra(
|
||||
KEY_FILE_ID,
|
||||
chatMessage.selectedIndividualHashMap!!["id"]
|
||||
)
|
||||
context.startActivity(filesAppIntent)
|
||||
} else {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(chatMessage.selectedIndividualHashMap!!["link"])
|
||||
)
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
} else if (chatMessage.messageType == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("https://giphy.com")
|
||||
)
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(browserIntent)
|
||||
} else if (chatMessage.messageType == ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE) {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("https://tenor.com")
|
||||
)
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(browserIntent)
|
||||
} else if (chatMessage.messageType == ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE) {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(chatMessage.imageUrl)
|
||||
)
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun onElementLongClick(page: Page, holder: Presenter.Holder, element: Element<ChatElement>) {
|
||||
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
@ -185,56 +262,15 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
||||
|
||||
private fun setupViews() {
|
||||
view?.let { view ->
|
||||
view.recyclerView.initRecyclerView(
|
||||
view.messagesRecyclerView.initRecyclerView(
|
||||
LinearLayoutManager(view.context), recyclerViewAdapter, false
|
||||
)
|
||||
|
||||
recyclerViewAdapter.setLoadMoreListener(this)
|
||||
recyclerViewAdapter.setDateHeadersFormatter { format(it) }
|
||||
recyclerViewAdapter.setOnMessageLongClickListener { onMessageLongClick(it) }
|
||||
|
||||
view.popupBubbleView.setRecyclerView(view.messagesListView)
|
||||
|
||||
view.popupBubbleView.setPopupBubbleListener { context ->
|
||||
if (newMessagesCount != 0) {
|
||||
val scrollPosition: Int
|
||||
if (newMessagesCount - 1 < 0) {
|
||||
scrollPosition = 0
|
||||
} else {
|
||||
scrollPosition = newMessagesCount - 1
|
||||
}
|
||||
view.messagesListView.postDelayed({
|
||||
view.messagesListView.smoothScrollToPosition(scrollPosition)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
view.messagesListView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(
|
||||
recyclerView: RecyclerView,
|
||||
newState: Int
|
||||
) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
|
||||
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
|
||||
if (newMessagesCount != 0) {
|
||||
val layoutManager: LinearLayoutManager = view.messagesListView.layoutManager as LinearLayoutManager
|
||||
if (layoutManager.findFirstCompletelyVisibleItemPosition() <
|
||||
newMessagesCount
|
||||
) {
|
||||
newMessagesCount = 0
|
||||
|
||||
view.popupBubbleView?.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val filters = arrayOfNulls<InputFilter>(1)
|
||||
val lengthFilter = viewModel.user.getMaxMessageLength()
|
||||
|
||||
|
||||
filters[0] = InputFilter.LengthFilter(lengthFilter)
|
||||
view.messageInput.filters = filters
|
||||
|
||||
@ -365,9 +401,9 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
||||
viewModel.conversation.value?.let {
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(
|
||||
BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType)
|
||||
BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap(browserType)
|
||||
)
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserNgEntity>(viewModel.user))
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.user)
|
||||
bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, it.token)
|
||||
router.pushController(
|
||||
RouterTransaction.with(BrowserController(bundle))
|
||||
@ -378,65 +414,8 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAdapter() {
|
||||
val messageHolders = MessageHolders()
|
||||
messageHolders.setIncomingTextConfig(
|
||||
MagicIncomingTextMessageViewHolder::class.java, R.layout.item_custom_incoming_text_message
|
||||
)
|
||||
messageHolders.setOutcomingTextConfig(
|
||||
MagicOutcomingTextMessageViewHolder::class.java,
|
||||
R.layout.item_custom_outcoming_text_message
|
||||
)
|
||||
|
||||
messageHolders.setIncomingImageConfig(
|
||||
MagicPreviewMessageViewHolder::class.java, R.layout.item_custom_incoming_preview_message
|
||||
)
|
||||
messageHolders.setOutcomingImageConfig(
|
||||
MagicPreviewMessageViewHolder::class.java, R.layout.item_custom_outcoming_preview_message
|
||||
)
|
||||
|
||||
messageHolders.registerContentType(
|
||||
ChatController.CONTENT_TYPE_SYSTEM_MESSAGE, MagicSystemMessageViewHolder::class.java,
|
||||
R.layout.item_system_message, MagicSystemMessageViewHolder::class.java,
|
||||
R.layout.item_system_message,
|
||||
this
|
||||
)
|
||||
|
||||
messageHolders.registerContentType(
|
||||
ChatController.CONTENT_TYPE_UNREAD_NOTICE_MESSAGE,
|
||||
MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header,
|
||||
MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header, this
|
||||
)
|
||||
|
||||
recyclerViewAdapter = MessagesListAdapter(
|
||||
viewModel.user.userId, messageHolders, ChatKitImageLoader { imageView, url, payload ->
|
||||
imageView.load(url) {
|
||||
if (url!!.contains("/avatar/")) {
|
||||
transformations(CircleCropTransformation())
|
||||
} else {
|
||||
if (payload is ImageLoaderPayload) {
|
||||
payload.map?.let {
|
||||
if (payload.map.containsKey("mimetype")) {
|
||||
placeholder(
|
||||
DrawableUtils.getDrawableResourceIdForMimeType(
|
||||
payload.map.get("mimetype") as String?
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val needsAuthBasedOnUrl = url.contains("index.php/core/preview?fileId=") || url.contains("index.php/avatar/")
|
||||
if (url.startsWith(viewModel.user.baseUrl) && needsAuthBasedOnUrl) {
|
||||
addHeader("Authorization", viewModel.user.getCredentials())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun loadAvatar() {
|
||||
val imageLoader = networkComponents.getImageLoader(viewModel.user)
|
||||
val avatarSize = DisplayUtils.convertDpToPixel(
|
||||
conversationVoiceCallMenuItem?.icon!!
|
||||
.intrinsicWidth.toFloat(), activity!!
|
||||
@ -459,9 +438,7 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
||||
), viewModel.user, target, this,
|
||||
CircleCropTransformation()
|
||||
)
|
||||
|
||||
imageLoader.load(avatarRequest)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -474,35 +451,30 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
||||
return viewModel.conversation.value?.displayName
|
||||
}
|
||||
|
||||
override fun hasContentFor(message: IMessage, type: Byte): Boolean {
|
||||
when (type) {
|
||||
ChatController.CONTENT_TYPE_SYSTEM_MESSAGE -> return !TextUtils.isEmpty(message.systemMessage)
|
||||
ChatController.CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> return message.id == "-1"
|
||||
}
|
||||
|
||||
return false
|
||||
override fun getImageLoader(): ImageLoader {
|
||||
return networkComponents.getImageLoader(viewModel.user)
|
||||
}
|
||||
|
||||
override fun format(date: Date): String {
|
||||
return when {
|
||||
DateFormatter.isToday(date) -> {
|
||||
resources!!.getString(R.string.nc_date_header_today)
|
||||
override fun loadImage(imageView: ImageView, url: String, payload: MutableMap<String, String>?) {
|
||||
val imageLoader = networkComponents.getImageLoader(viewModel.user)
|
||||
|
||||
imageLoader.load(activity as Context, url) {
|
||||
if (url.contains("/avatar/")) {
|
||||
transformations(CircleCropTransformation())
|
||||
} else {
|
||||
payload?.let {
|
||||
if (payload.containsKey("mimetype")) {
|
||||
placeholder(DrawableUtils.getDrawableResourceIdForMimeType(payload["mimetype"])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DateFormatter.isYesterday(date) -> {
|
||||
resources!!.getString(R.string.nc_date_header_yesterday)
|
||||
}
|
||||
else -> {
|
||||
DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR)
|
||||
|
||||
target(imageView)
|
||||
val needsAuthBasedOnUrl = url.contains("index.php/core/preview?fileId=") || url.contains("index.php/avatar/")
|
||||
if (url.startsWith(viewModel.user.baseUrl) && needsAuthBasedOnUrl) {
|
||||
addHeader("Authorization", viewModel.user.getCredentials())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadMore(page: Int, totalItemsCount: Int) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun onMessageLongClick(message: IMessage?) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
}
|
@ -32,6 +32,7 @@ import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsReposit
|
||||
import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
|
||||
import com.nextcloud.talk.newarch.domain.usecases.ExitConversationUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
|
||||
import com.nextcloud.talk.newarch.local.models.User
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.services.GlobalService
|
||||
import com.nextcloud.talk.newarch.services.GlobalServiceInterface
|
||||
@ -43,7 +44,7 @@ class ChatViewModel constructor(application: Application,
|
||||
private val conversationsRepository: ConversationsRepository,
|
||||
private val messagesRepository: MessagesRepository,
|
||||
private val globalService: GlobalService) : BaseViewModel<ChatView>(application), GlobalServiceInterface {
|
||||
lateinit var user: UserNgEntity
|
||||
lateinit var user: User
|
||||
val conversation: MutableLiveData<Conversation?> = MutableLiveData()
|
||||
var initConversation: Conversation? = null
|
||||
val messagesLiveData = Transformations.switchMap(conversation) {
|
||||
@ -54,10 +55,10 @@ class ChatViewModel constructor(application: Application,
|
||||
var conversationPassword: String? = null
|
||||
|
||||
|
||||
fun init(user: UserNgEntity, conversationToken: String, conversationPassword: String?) {
|
||||
fun init(user: User, conversationToken: String, conversationPassword: String?) {
|
||||
viewModelScope.launch {
|
||||
this@ChatViewModel.user = user
|
||||
this@ChatViewModel.initConversation = conversationsRepository.getConversationForUserWithToken(user.id, conversationToken)
|
||||
this@ChatViewModel.initConversation = conversationsRepository.getConversationForUserWithToken(user.id!!, conversationToken)
|
||||
this@ChatViewModel.conversationPassword = conversationPassword
|
||||
globalService.getConversation(conversationToken, this@ChatViewModel)
|
||||
}
|
||||
@ -70,7 +71,7 @@ class ChatViewModel constructor(application: Application,
|
||||
override suspend fun gotConversationInfoForUser(userNgEntity: UserNgEntity, conversation: Conversation?, operationStatus: GlobalServiceInterface.OperationStatus) {
|
||||
if (operationStatus == GlobalServiceInterface.OperationStatus.STATUS_OK) {
|
||||
if (userNgEntity.id == user.id && conversation!!.token == initConversation?.token) {
|
||||
this.conversation.value = conversationsRepository.getConversationForUserWithToken(user.id, conversation.token!!)
|
||||
this.conversation.value = conversationsRepository.getConversationForUserWithToken(user.id!!, conversation.token!!)
|
||||
conversation.token?.let { conversationToken ->
|
||||
globalService.joinConversation(conversationToken, conversationPassword, this)
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.nextcloud.talk.newarch.features.chat
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
|
||||
import com.otaliastudios.elements.extensions.MainSource
|
||||
|
||||
class ChatViewSource<T : ChatElement>(loadingIndicatorsEnabled: Boolean = true, errorIndicatorEnabled: Boolean = false, emptyIndicatorEnabled: Boolean = false) : MainSource<T>(loadingIndicatorsEnabled, errorIndicatorEnabled, emptyIndicatorEnabled) {
|
||||
override fun areItemsTheSame(first: T, second: T): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.nextcloud.talk.newarch.features.chat.interfaces
|
||||
|
||||
import android.widget.ImageView
|
||||
import coil.ImageLoader
|
||||
|
||||
interface ImageLoaderInterface {
|
||||
fun getImageLoader(): ImageLoader
|
||||
fun loadImage(imageView: ImageView, url: String, payload: MutableMap<String, String>? = null)
|
||||
}
|
@ -38,9 +38,9 @@ import com.bluelinelabs.conductor.archlifecycle.ControllerLifecycleOwner
|
||||
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.controllers.ChatController
|
||||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
import com.nextcloud.talk.newarch.data.presenters.AdvancedEmptyPresenter
|
||||
import com.nextcloud.talk.newarch.features.chat.ChatView
|
||||
import com.nextcloud.talk.newarch.features.contactsflow.ContactsViewOperationState
|
||||
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
|
||||
import com.nextcloud.talk.newarch.features.contactsflow.groupconversation.GroupConversationView
|
||||
@ -180,7 +180,7 @@ class ContactsView(private val bundle: Bundle? = null) : BaseView() {
|
||||
val bundle = Bundle()
|
||||
if (!hasToken || isNewGroupConversation) {
|
||||
bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, operationState.conversationToken)
|
||||
router.replaceTopController(RouterTransaction.with(ChatController(bundle))
|
||||
router.replaceTopController(RouterTransaction.with(ChatView(bundle))
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
.pushChangeHandler(HorizontalChangeHandler()))
|
||||
} else {
|
||||
|
@ -49,6 +49,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.newarch.data.presenters.AdvancedEmptyPresenter
|
||||
import com.nextcloud.talk.newarch.features.contactsflow.contacts.ContactsView
|
||||
import com.nextcloud.talk.newarch.features.search.DebouncingTextWatcher
|
||||
import com.nextcloud.talk.newarch.local.models.toUser
|
||||
import com.nextcloud.talk.newarch.mvvm.BaseView
|
||||
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
||||
import com.nextcloud.talk.utils.ConductorRemapping
|
||||
@ -265,7 +266,7 @@ class ConversationsListView : BaseView() {
|
||||
)
|
||||
}
|
||||
|
||||
if (conversation.canLeave(viewModel.globalService.currentUserLiveData.value!!)) {
|
||||
if (conversation.canLeave(viewModel.globalService.currentUserLiveData.value!!.toUser())) {
|
||||
items.add(
|
||||
BasicListItemWithImage(
|
||||
drawable.ic_exit_to_app_black_24dp, context.getString
|
||||
@ -274,7 +275,7 @@ class ConversationsListView : BaseView() {
|
||||
)
|
||||
}
|
||||
|
||||
if (conversation.canModerate(viewModel.globalService.currentUserLiveData.value!!)) {
|
||||
if (conversation.canModerate(viewModel.globalService.currentUserLiveData.value!!.toUser())) {
|
||||
items.add(
|
||||
BasicListItemWithImage(
|
||||
drawable.ic_delete_grey600_24dp, context.getString(
|
||||
|
@ -5,6 +5,7 @@ import com.nextcloud.talk.models.json.capabilities.Capabilities
|
||||
import com.nextcloud.talk.models.json.push.PushConfiguration
|
||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings
|
||||
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@ -24,6 +25,16 @@ data class User(
|
||||
var status: UserStatus? = null
|
||||
) : Parcelable
|
||||
|
||||
fun User.getMaxMessageLength(): Int {
|
||||
return capabilities?.spreedCapability?.config?.get("chat")?.get("max-length")?.toInt() ?: 1000
|
||||
}
|
||||
|
||||
fun User.getCredentials(): String = ApiUtils.getCredentials(username, token)
|
||||
|
||||
fun User.hasSpreedFeatureCapability(capabilityName: String): Boolean {
|
||||
return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false
|
||||
}
|
||||
|
||||
fun User.toUserEntity(): UserNgEntity {
|
||||
var userNgEntity: UserNgEntity? = null
|
||||
this.id?.let {
|
||||
|
@ -91,11 +91,3 @@ fun UserNgEntity.toUser(): User {
|
||||
}
|
||||
|
||||
fun UserNgEntity.getCredentials(): String = ApiUtils.getCredentials(username, token)
|
||||
|
||||
fun UserNgEntity.hasSpreedFeatureCapability(capabilityName: String): Boolean {
|
||||
return capabilities?.spreedCapability?.features?.contains(capabilityName) ?: false
|
||||
}
|
||||
|
||||
fun UserNgEntity.getMaxMessageLength(): Int {
|
||||
return capabilities?.spreedCapability?.config?.get("chat")?.get("max-length")?.toInt() ?: 1000
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ class CallService : Service(), KoinComponent, CoroutineScope {
|
||||
.setContentTitle(EmojiCompat.get().process(decryptedPushMessage.subject.toString()))
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(true)
|
||||
.addAction(R.drawable.ic_call_end_white_24px, resources.getString(R.string.reject_call), rejectCallPendingIntent)
|
||||
.addAction(R.drawable.ic_call_end_white_24px, resources.getString(R.string.nc_reject_call), rejectCallPendingIntent)
|
||||
.setContentIntent(fullScreenPendingIntent)
|
||||
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||
.setSound(NotificationUtils.getCallSoundUri(applicationContext, appPreferences), AudioManager.STREAM_RING)
|
||||
@ -184,7 +184,7 @@ class CallService : Service(), KoinComponent, CoroutineScope {
|
||||
val imageLoader = networkComponents.getImageLoader(signatureVerification.userEntity!!.toUser())
|
||||
|
||||
val request = Images().getRequestForUrl(
|
||||
imageLoader, applicationContext, avatarUrl, signatureVerification.userEntity,
|
||||
imageLoader, applicationContext, avatarUrl, signatureVerification.userEntity!!.toUser(),
|
||||
target, null, CircleCropTransformation())
|
||||
|
||||
imageLoader.load(request)
|
||||
|
@ -36,6 +36,7 @@ import coil.target.Target
|
||||
import coil.transform.Transformation
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.newarch.local.models.User
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
@ -46,7 +47,7 @@ class Images {
|
||||
context: Context,
|
||||
url: String,
|
||||
userEntity:
|
||||
UserNgEntity?,
|
||||
User?,
|
||||
target: Target?,
|
||||
lifecycleOwner: LifecycleOwner?,
|
||||
vararg transformations: Transformation
|
||||
|
@ -24,7 +24,7 @@ import android.os.Bundle
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.nextcloud.talk.controllers.ChatController
|
||||
import com.nextcloud.talk.newarch.features.chat.ChatView
|
||||
|
||||
object ConductorRemapping {
|
||||
fun remapChatController(
|
||||
@ -51,13 +51,13 @@ object ConductorRemapping {
|
||||
} else {
|
||||
if (!replaceTop) {
|
||||
router.pushController(
|
||||
RouterTransaction.with(ChatController(bundle))
|
||||
RouterTransaction.with(ChatView(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||
)
|
||||
} else {
|
||||
router.replaceTopController(
|
||||
RouterTransaction.with(ChatController(bundle))
|
||||
RouterTransaction.with(ChatView(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||
)
|
||||
|
@ -61,6 +61,7 @@ import com.google.android.material.chip.ChipDrawable
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.events.UserMentionClickEvent
|
||||
import com.nextcloud.talk.newarch.local.models.User
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.utils.Images
|
||||
import com.nextcloud.talk.utils.text.Spans
|
||||
@ -186,7 +187,7 @@ object DisplayUtils {
|
||||
context: Context,
|
||||
id: String,
|
||||
label: CharSequence,
|
||||
conversationUser: UserNgEntity,
|
||||
conversationUser: User,
|
||||
type: String,
|
||||
@XmlRes chipResource: Int,
|
||||
emojiEditText: EditText?
|
||||
@ -264,7 +265,7 @@ object DisplayUtils {
|
||||
id: String,
|
||||
label: String,
|
||||
type: String,
|
||||
conversationUser: UserNgEntity,
|
||||
conversationUser: User,
|
||||
@XmlRes chipXmlRes: Int
|
||||
): Spannable {
|
||||
|
||||
|
@ -36,6 +36,7 @@ object BundleKeys {
|
||||
val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD"
|
||||
val KEY_CONVERSATION_TOKEN = "KEY_CONVERSATION_TOKEN"
|
||||
val KEY_USER_ENTITY = "KEY_USER_ENTITY"
|
||||
val KEY_USER = "KEY_USER"
|
||||
val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION"
|
||||
val KEY_NEW_GROUP_CONVERSATION = "KEY_NEW_GROUP_CONVERSATION"
|
||||
val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS"
|
||||
|
@ -72,6 +72,14 @@
|
||||
app:inputTextSize="16sp"
|
||||
app:showAttachmentButton="true" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/messageInputView"
|
||||
app:stackFromEnd="true"
|
||||
app:reverseLayout="true"
|
||||
android:id="@+id/messagesRecyclerView"/>
|
||||
|
||||
<com.stfalcon.chatkit.messages.MessagesList
|
||||
android:id="@+id/messagesListView"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -1,89 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="2dp">
|
||||
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/messageUserAvatar"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="vertical"
|
||||
app:alignContent="stretch"
|
||||
app:alignItems="stretch"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<ImageView
|
||||
android:id="@id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:textColor="@color/warm_grey_four"
|
||||
android:textColorLink="@color/warm_grey_four"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="12sp"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/warm_grey_four"
|
||||
app:layout_alignSelf="center" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@id/messageUserAvatar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
89
app/src/main/res/layout/rv_chat_incoming_preview_item.xml
Normal file
89
app/src/main/res/layout/rv_chat_incoming_preview_item.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="2dp">
|
||||
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/messageUserAvatar"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="vertical"
|
||||
app:alignContent="stretch"
|
||||
app:alignItems="stretch"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/incomingPreviewImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/incomingPreviewMessageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:textColor="@color/warm_grey_four"
|
||||
android:textColorLink="@color/warm_grey_four"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="12sp"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/incomingPreviewTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/warm_grey_four"
|
||||
app:layout_alignSelf="center" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@id/messageUserAvatar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
@ -57,7 +57,7 @@
|
||||
<include layout="@layout/item_message_quote" android:visibility="gone"/>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
android:id="@+id/incomingMessageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
@ -68,10 +68,10 @@
|
||||
app:layout_wrapBefore="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:id="@+id/incomingMessageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_below="@+id/outgoingMessageText"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center" />
|
||||
|
@ -41,7 +41,7 @@
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<ImageView
|
||||
android:id="@id/image"
|
||||
android:id="@+id/outgoingPreviewImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
@ -52,7 +52,7 @@
|
||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
android:id="@+id/outgoingPreviewMessageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
@ -65,7 +65,7 @@
|
||||
app:layout_wrapBefore="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:id="@+id/outgoingPreviewTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
@ -41,7 +41,7 @@
|
||||
<include layout="@layout/item_message_quote" android:visibility="gone"/>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
android:id="@+id/outgoingMessageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
@ -54,10 +54,10 @@
|
||||
android:autoLink="all"/>
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:id="@+id/outgoingMessageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_below="@id/outgoingMessageText"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center" />
|
||||
|
@ -40,7 +40,7 @@
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/messageText"
|
||||
android:id="@+id/systemMessageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
@ -54,7 +54,7 @@
|
||||
app:layout_wrapBefore="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:id="@+id/systemItemTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/warm_grey_four"
|
12
app/src/main/res/layout/rv_date_and_unread_notice_item.xml
Normal file
12
app/src/main/res/layout/rv_date_and_unread_notice_item.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noticeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="16dp"/>
|
||||
|
||||
</RelativeLayout>
|
@ -313,5 +313,5 @@
|
||||
<string name="nc_search_for_more">Vyhledat další účastníky</string>
|
||||
<string name="nc_new_group">Nová skupina</string>
|
||||
<string name="nc_search_empty_contacts">Kam se všichni schovali?</string>
|
||||
<string name="reject_call">Odmítnout</string>
|
||||
<string name="nc_reject_call">Odmítnout</string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@ Meeting ist für %1$s geplant.</string>
|
||||
<string name="nc_search_for_more">Weitere Teilnehmer suchen</string>
|
||||
<string name="nc_new_group">Neue Gruppe</string>
|
||||
<string name="nc_search_empty_contacts">Wo haben sie sich alle versteckt?</string>
|
||||
<string name="reject_call">Ablehnen</string>
|
||||
<string name="nc_reject_call">Ablehnen</string>
|
||||
</resources>
|
||||
|
@ -318,5 +318,5 @@
|
||||
<string name="nc_search_for_more">Αναζήτηση περισσότερων συμμετεχόντων</string>
|
||||
<string name="nc_new_group">Νέα ομάδα</string>
|
||||
<string name="nc_search_empty_contacts">Πού κρύβονταν όλοι;</string>
|
||||
<string name="reject_call">Απόρριψη</string>
|
||||
<string name="nc_reject_call">Απόρριψη</string>
|
||||
</resources>
|
||||
|
@ -316,5 +316,5 @@
|
||||
<string name="nc_search_for_more">Buscar más participantes</string>
|
||||
<string name="nc_new_group">Nuevo grupo</string>
|
||||
<string name="nc_search_empty_contacts">¿Dónde se escondieron todos?</string>
|
||||
<string name="reject_call">Rechazar</string>
|
||||
<string name="nc_reject_call">Rechazar</string>
|
||||
</resources>
|
||||
|
@ -321,5 +321,5 @@
|
||||
<string name="nc_search_for_more">Bilatu parte-hartzaile gehiago</string>
|
||||
<string name="nc_new_group">Talde berria</string>
|
||||
<string name="nc_search_empty_contacts">Non ezkutatu dira denak?</string>
|
||||
<string name="reject_call">Baztertu</string>
|
||||
<string name="nc_reject_call">Baztertu</string>
|
||||
</resources>
|
||||
|
@ -270,5 +270,5 @@
|
||||
<string name="nc_search_for_more">Etsi lisää osallistujia</string>
|
||||
<string name="nc_new_group">Uusi ryhmä</string>
|
||||
<string name="nc_search_empty_contacts">Mihin he kaikki piiloutuivat?</string>
|
||||
<string name="reject_call">Hylkää</string>
|
||||
<string name="nc_reject_call">Hylkää</string>
|
||||
</resources>
|
||||
|
@ -316,5 +316,5 @@ Le démarrage de cette réunion est prévu à %1$s.</string>
|
||||
<string name="nc_search_for_more">Rechercher d\'autres participants</string>
|
||||
<string name="nc_new_group">Nouveau groupe</string>
|
||||
<string name="nc_search_empty_contacts">Où se cachent-ils tous ?</string>
|
||||
<string name="reject_call">Refuser</string>
|
||||
<string name="nc_reject_call">Refuser</string>
|
||||
</resources>
|
||||
|
@ -322,5 +322,5 @@ móbiles. Pode tentar unirse á chamada empregando o navegador web.</string>
|
||||
<string name="nc_search_for_more">Buscar máis participantes</string>
|
||||
<string name="nc_new_group">Grupo novo</string>
|
||||
<string name="nc_search_empty_contacts">Onde se agocharon todos?</string>
|
||||
<string name="reject_call">Rexeitar</string>
|
||||
<string name="nc_reject_call">Rexeitar</string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@
|
||||
<string name="nc_search_for_more">Cerca altri partecipanti</string>
|
||||
<string name="nc_new_group">Nuovo gruppo</string>
|
||||
<string name="nc_search_empty_contacts">Dove si sono nascosti?</string>
|
||||
<string name="reject_call">Rifiuta</string>
|
||||
<string name="nc_reject_call">Rifiuta</string>
|
||||
</resources>
|
||||
|
@ -319,5 +319,5 @@ Je kunt proberen om aan het gesprek deel te nemen via een browser.</string>
|
||||
<string name="nc_search_for_more">Zoek meer deelnemers</string>
|
||||
<string name="nc_new_group">Nieuwe groep</string>
|
||||
<string name="nc_search_empty_contacts">Waar zijn ze allemaal naar toe?</string>
|
||||
<string name="reject_call">Afwijzen</string>
|
||||
<string name="nc_reject_call">Afwijzen</string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@
|
||||
<string name="nc_search_for_more">Wyszukaj więcej uczestników</string>
|
||||
<string name="nc_new_group">Nowa grupa</string>
|
||||
<string name="nc_search_empty_contacts">Gdzie oni wszyscy się schowali?</string>
|
||||
<string name="reject_call">Odrzuć </string>
|
||||
<string name="nc_reject_call">Odrzuć </string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@
|
||||
<string name="nc_search_for_more">Procurar por mais participantes</string>
|
||||
<string name="nc_new_group">Novo grupo</string>
|
||||
<string name="nc_search_empty_contacts">Onde eles todos se esconderam?</string>
|
||||
<string name="reject_call">Rejeitar</string>
|
||||
<string name="nc_reject_call">Rejeitar</string>
|
||||
</resources>
|
||||
|
@ -315,5 +315,5 @@
|
||||
<string name="nc_search_for_more">Искать дополнительных участников</string>
|
||||
<string name="nc_new_group">Новая группа</string>
|
||||
<string name="nc_search_empty_contacts">Где все?</string>
|
||||
<string name="reject_call">Отклонить</string>
|
||||
<string name="nc_reject_call">Отклонить</string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@ v zozname rozhovorov</string>
|
||||
<string name="nc_search_for_more">Vyhľadanie ďalších účastníkov</string>
|
||||
<string name="nc_new_group">Nová skupina</string>
|
||||
<string name="nc_search_empty_contacts">Kde sa všetci schovali?</string>
|
||||
<string name="reject_call">Odmietnuť </string>
|
||||
<string name="nc_reject_call">Odmietnuť </string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@ Pozdravite prijatelje in znance.</string>
|
||||
<string name="nc_search_for_more">Poišči več udeležencev</string>
|
||||
<string name="nc_new_group">Nova skupina</string>
|
||||
<string name="nc_search_empty_contacts">Kam so se vsi skrili?</string>
|
||||
<string name="reject_call">Zavrni</string>
|
||||
<string name="nc_reject_call">Zavrni</string>
|
||||
</resources>
|
||||
|
@ -319,5 +319,5 @@
|
||||
<string name="nc_search_for_more">Тражи још учесника</string>
|
||||
<string name="nc_new_group">Нова група</string>
|
||||
<string name="nc_search_empty_contacts">Где су сви нестали?</string>
|
||||
<string name="reject_call">Одбиј</string>
|
||||
<string name="nc_reject_call">Одбиј</string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@
|
||||
<string name="nc_search_for_more">Sök efter fler deltagare</string>
|
||||
<string name="nc_new_group">Ny grupp</string>
|
||||
<string name="nc_search_empty_contacts">Var gömmer sig alla?</string>
|
||||
<string name="reject_call">Avvisa</string>
|
||||
<string name="nc_reject_call">Avvisa</string>
|
||||
</resources>
|
||||
|
@ -320,5 +320,5 @@
|
||||
<string name="nc_search_for_more">Başka katılımcılar arayın</string>
|
||||
<string name="nc_new_group">Yeni grup</string>
|
||||
<string name="nc_search_empty_contacts">Hepsi nereye gizlendiler?</string>
|
||||
<string name="reject_call">Reddet</string>
|
||||
<string name="nc_reject_call">Reddet</string>
|
||||
</resources>
|
||||
|
@ -316,5 +316,5 @@
|
||||
<string name="nc_search_for_more">搜索更多参与者</string>
|
||||
<string name="nc_new_group">新建群组</string>
|
||||
<string name="nc_search_empty_contacts">他们都藏在哪里?</string>
|
||||
<string name="reject_call">拒绝</string>
|
||||
<string name="nc_reject_call">拒绝</string>
|
||||
</resources>
|
||||
|
@ -344,5 +344,5 @@
|
||||
<string name="nc_search_for_more">Search for more participants</string>
|
||||
<string name="nc_new_group">New group</string>
|
||||
<string name="nc_search_empty_contacts">Where did they all hide?</string>
|
||||
<string name="reject_call">Reject </string>
|
||||
<string name="nc_reject_call">Reject </string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user