mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-13 15:54:59 +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 {
|
ext {
|
||||||
work_version = '2.3.3'
|
work_version = '2.3.3'
|
||||||
koin_version = "2.1.0-alpha-1"
|
koin_version = "2.1.4"
|
||||||
lifecycle_version = '2.2.0'
|
lifecycle_version = '2.2.0'
|
||||||
coil_version = "0.9.5"
|
coil_version = "0.9.5"
|
||||||
room_version = "2.2.4"
|
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
|
//endregion
|
||||||
|
|
||||||
//region Protected methods
|
//region Protected methods
|
||||||
protected fun startKoin() {
|
private fun startKoin() {
|
||||||
startKoin {
|
startKoin {
|
||||||
androidContext(this@NextcloudTalkApplication)
|
androidContext(this@NextcloudTalkApplication)
|
||||||
androidLogger()
|
androidLogger()
|
||||||
|
@ -27,6 +27,7 @@ import android.widget.EditText;
|
|||||||
|
|
||||||
import com.nextcloud.talk.R;
|
import com.nextcloud.talk.R;
|
||||||
import com.nextcloud.talk.models.json.mention.Mention;
|
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.newarch.local.models.UserNgEntity;
|
||||||
import com.nextcloud.talk.utils.BetterImageSpan;
|
import com.nextcloud.talk.utils.BetterImageSpan;
|
||||||
import com.nextcloud.talk.utils.DisplayUtils;
|
import com.nextcloud.talk.utils.DisplayUtils;
|
||||||
@ -38,10 +39,10 @@ import com.vanniktech.emoji.EmojiUtils;
|
|||||||
|
|
||||||
public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> {
|
public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> {
|
||||||
private Context context;
|
private Context context;
|
||||||
private UserNgEntity conversationUser;
|
private User conversationUser;
|
||||||
private EditText editText;
|
private EditText editText;
|
||||||
|
|
||||||
public MentionAutocompleteCallback(Context context, UserNgEntity conversationUser,
|
public MentionAutocompleteCallback(Context context, User conversationUser,
|
||||||
EditText editText) {
|
EditText editText) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.conversationUser = conversationUser;
|
this.conversationUser = conversationUser;
|
||||||
|
@ -32,6 +32,7 @@ import coil.api.load
|
|||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
|
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
|
||||||
import com.nextcloud.talk.interfaces.SelectionInterface
|
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.UserNgEntity
|
||||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
@ -47,7 +48,7 @@ import org.koin.core.inject
|
|||||||
|
|
||||||
class BrowserFileItem(
|
class BrowserFileItem(
|
||||||
val model: BrowserFile,
|
val model: BrowserFile,
|
||||||
private val activeUser: UserNgEntity,
|
private val activeUser: User,
|
||||||
private val selectionInterface: SelectionInterface
|
private val selectionInterface: SelectionInterface
|
||||||
) : AbstractFlexibleItem<BrowserFileItem.ViewHolder>(), IFilterable<String>, KoinComponent {
|
) : AbstractFlexibleItem<BrowserFileItem.ViewHolder>(), IFilterable<String>, KoinComponent {
|
||||||
val context: Context by inject()
|
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.controllers.base.BaseController
|
||||||
import com.nextcloud.talk.interfaces.SelectionInterface
|
import com.nextcloud.talk.interfaces.SelectionInterface
|
||||||
import com.nextcloud.talk.jobs.ShareOperationWorker
|
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.newarch.local.models.UserNgEntity
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||||
import eu.davidea.fastscroller.FastScroller
|
import eu.davidea.fastscroller.FastScroller
|
||||||
@ -83,13 +84,13 @@ class BrowserController(args: Bundle) : BaseController(), ListingInterface, Flex
|
|||||||
private var listingAbstractClass: ListingAbstractClass? = null
|
private var listingAbstractClass: ListingAbstractClass? = null
|
||||||
private val browserType: BrowserType
|
private val browserType: BrowserType
|
||||||
private var currentPath: String? = null
|
private var currentPath: String? = null
|
||||||
private val activeUser: UserNgEntity
|
private val activeUser: User
|
||||||
private val roomToken: String?
|
private val roomToken: String?
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
browserType = Parcels.unwrap(args.getParcelable(BundleKeys.KEY_BROWSER_TYPE))
|
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)
|
roomToken = args.getString(BundleKeys.KEY_CONVERSATION_TOKEN)
|
||||||
|
|
||||||
currentPath = "/"
|
currentPath = "/"
|
||||||
@ -130,7 +131,7 @@ class BrowserController(args: Bundle) : BaseController(), ListingInterface, Flex
|
|||||||
iterator.remove()
|
iterator.remove()
|
||||||
if (paths.size == 10 || !iterator.hasNext()) {
|
if (paths.size == 10 || !iterator.hasNext()) {
|
||||||
data = Data.Builder()
|
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)
|
.putString(BundleKeys.KEY_CONVERSATION_TOKEN, roomToken)
|
||||||
.putStringArray(BundleKeys.KEY_FILE_PATHS, paths.toTypedArray())
|
.putStringArray(BundleKeys.KEY_FILE_PATHS, paths.toTypedArray())
|
||||||
.build()
|
.build()
|
||||||
|
@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
|
import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
|
||||||
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
||||||
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
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 com.nextcloud.talk.newarch.local.models.UserNgEntity;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
@ -43,7 +44,7 @@ public class DavListing extends ListingAbstractClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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>() {
|
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
||||||
@Override
|
@Override
|
||||||
public ReadFilesystemOperation call() {
|
public ReadFilesystemOperation call() {
|
||||||
|
@ -25,7 +25,7 @@ import android.os.Handler;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.nextcloud.talk.components.filebrowser.interfaces.ListingInterface;
|
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;
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ public abstract class ListingAbstractClass {
|
|||||||
this.listingInterface = listingInterface;
|
this.listingInterface = listingInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void getFiles(String path, UserNgEntity currentUser,
|
public abstract void getFiles(String path, User currentUser,
|
||||||
@Nullable OkHttpClient okHttpClient);
|
@Nullable OkHttpClient okHttpClient);
|
||||||
|
|
||||||
public void cancelAllJobs() {
|
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.BrowserFile;
|
||||||
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
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.local.models.UserNgEntity;
|
||||||
import com.nextcloud.talk.newarch.utils.NetworkUtils;
|
import com.nextcloud.talk.newarch.utils.NetworkUtils;
|
||||||
import com.nextcloud.talk.utils.ApiUtils;
|
import com.nextcloud.talk.utils.ApiUtils;
|
||||||
@ -44,7 +45,7 @@ public class ReadFilesystemOperation {
|
|||||||
private final int depth;
|
private final int depth;
|
||||||
private final String basePath;
|
private final String basePath;
|
||||||
|
|
||||||
public ReadFilesystemOperation(OkHttpClient okHttpClient, UserNgEntity currentUser, String path,
|
public ReadFilesystemOperation(OkHttpClient okHttpClient, User currentUser, String path,
|
||||||
int depth) {
|
int depth) {
|
||||||
OkHttpClient.Builder okHttpClientBuilder = okHttpClient.newBuilder();
|
OkHttpClient.Builder okHttpClientBuilder = okHttpClient.newBuilder();
|
||||||
okHttpClientBuilder.followRedirects(false);
|
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.features.contactsflow.contacts.ContactsView
|
||||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
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.newarch.utils.Images
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
import com.nextcloud.talk.utils.DateUtils
|
import com.nextcloud.talk.utils.DateUtils
|
||||||
@ -268,7 +269,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
|||||||
if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type
|
if (conversationUser!!.hasSpreedFeatureCapability("webinary-lobby") && (conversation!!.type
|
||||||
== Conversation.ConversationType.GROUP_CONVERSATION || conversation!!.type ==
|
== Conversation.ConversationType.GROUP_CONVERSATION || conversation!!.type ==
|
||||||
PUBLIC_CONVERSATION) && conversation!!.canModerate(
|
PUBLIC_CONVERSATION) && conversation!!.canModerate(
|
||||||
conversationUser
|
conversationUser.toUser()
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
conversationInfoWebinar.visibility = View.VISIBLE
|
conversationInfoWebinar.visibility = View.VISIBLE
|
||||||
@ -652,7 +653,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
|||||||
|
|
||||||
|
|
||||||
if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
|
if (isAttached && (!isBeingDestroyed || !isDestroyed)) {
|
||||||
if (conversationCopy!!.canModerate(conversationUser)) {
|
if (conversationCopy!!.canModerate(conversationUser.toUser())) {
|
||||||
actionTextView.visibility = View.VISIBLE
|
actionTextView.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
actionTextView.visibility = View.GONE
|
actionTextView.visibility = View.GONE
|
||||||
@ -663,13 +664,13 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
|||||||
setupGeneralSettings()
|
setupGeneralSettings()
|
||||||
setupWebinaryView()
|
setupWebinaryView()
|
||||||
|
|
||||||
if (!conversation!!.canLeave(conversationUser)) {
|
if (!conversation!!.canLeave(conversationUser.toUser())) {
|
||||||
leaveConversationAction.visibility = View.GONE
|
leaveConversationAction.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
leaveConversationAction.visibility = View.VISIBLE
|
leaveConversationAction.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!conversation!!.canModerate(conversationUser)) {
|
if (!conversation!!.canModerate(conversationUser.toUser())) {
|
||||||
deleteConversationAction.visibility = View.GONE
|
deleteConversationAction.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
deleteConversationAction.visibility = View.VISIBLE
|
deleteConversationAction.visibility = View.VISIBLE
|
||||||
@ -709,7 +710,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
|||||||
if (conversation != null && conversationUser != null) {
|
if (conversation != null && conversationUser != null) {
|
||||||
changeConversationName.value = conversation!!.displayName
|
changeConversationName.value = conversation!!.displayName
|
||||||
|
|
||||||
if (conversation!!.isNameEditable(conversationUser)) {
|
if (conversation!!.isNameEditable(conversationUser.toUser())) {
|
||||||
changeConversationName.visibility = View.VISIBLE
|
changeConversationName.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
changeConversationName.visibility = View.GONE
|
changeConversationName.visibility = View.GONE
|
||||||
@ -873,7 +874,7 @@ class ConversationInfoController(args: Bundle) : BaseController(),
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!conversation!!.canModerate(conversationUser)) {
|
if (!conversation!!.canModerate(conversationUser.toUser())) {
|
||||||
items = mutableListOf()
|
items = mutableListOf()
|
||||||
} else {
|
} else {
|
||||||
if (participant.type == Participant.ParticipantType.MODERATOR || participant.type == Participant.ParticipantType.OWNER) {
|
if (participant.type == Participant.ParticipantType.MODERATOR || participant.type == Participant.ParticipantType.OWNER) {
|
||||||
|
@ -289,7 +289,7 @@ class NotificationWorker(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val request = Images().getRequestForUrl(
|
val request = Images().getRequestForUrl(
|
||||||
Coil.loader(), applicationContext, avatarUrl!!, signatureVerification.userEntity,
|
Coil.loader(), applicationContext, avatarUrl!!, signatureVerification.userEntity!!.toUser(),
|
||||||
target, null, CircleCropTransformation()
|
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.chat.ChatMessage
|
||||||
import com.nextcloud.talk.models.json.converters.*
|
import com.nextcloud.talk.models.json.converters.*
|
||||||
import com.nextcloud.talk.models.json.participants.Participant
|
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 kotlinx.serialization.Serializable
|
||||||
import lombok.Data
|
import lombok.Data
|
||||||
import org.parceler.Parcel
|
import org.parceler.Parcel
|
||||||
@ -152,36 +153,35 @@ class Conversation {
|
|||||||
return resources.getString(R.string.nc_delete_conversation_default)
|
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
|
return type == ConversationType.ONE_TO_ONE_CONVERSATION && conversationUser
|
||||||
.hasSpreedFeatureCapability(
|
.hasSpreedFeatureCapability(
|
||||||
"locked-one-to-one-rooms"
|
"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(
|
return (Participant.ParticipantType.OWNER == participantType || Participant.ParticipantType.MODERATOR == participantType) && !isLockedOneToOne(
|
||||||
conversationUser
|
conversationUser
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldShowLobby(conversationUser: UserNgEntity): Boolean {
|
fun shouldShowLobby(conversationUser: User): Boolean {
|
||||||
return LobbyState.LOBBY_STATE_MODERATORS_ONLY == lobbyState && !canModerate(
|
return LobbyState.LOBBY_STATE_MODERATORS_ONLY == lobbyState && !canModerate(conversationUser
|
||||||
conversationUser
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLobbyViewApplicable(conversationUser: UserNgEntity): Boolean {
|
fun isLobbyViewApplicable(conversationUser: User): Boolean {
|
||||||
return !canModerate(
|
return !canModerate(
|
||||||
conversationUser
|
conversationUser
|
||||||
) && (type == ConversationType.GROUP_CONVERSATION || type == ConversationType.PUBLIC_CONVERSATION)
|
) && (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
|
return canModerate(conversationUser) && ConversationType.ONE_TO_ONE_CONVERSATION != type
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canLeave(conversationUser: UserNgEntity): Boolean {
|
fun canLeave(conversationUser: User): Boolean {
|
||||||
return !canModerate(
|
return !canModerate(
|
||||||
conversationUser
|
conversationUser
|
||||||
) || type != ConversationType.ONE_TO_ONE_CONVERSATION && participants!!.size > 1
|
) || 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
|
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.content.res.Resources
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.InputFilter
|
import android.text.InputFilter
|
||||||
import android.text.TextUtils
|
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.AbsListView
|
import android.widget.ImageView
|
||||||
import androidx.lifecycle.observe
|
import androidx.lifecycle.observe
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import coil.ImageLoader
|
||||||
import coil.api.load
|
import coil.api.load
|
||||||
import coil.target.Target
|
import coil.target.Target
|
||||||
import coil.transform.CircleCropTransformation
|
import coil.transform.CircleCropTransformation
|
||||||
@ -43,61 +46,61 @@ import com.bluelinelabs.conductor.archlifecycle.ControllerLifecycleOwner
|
|||||||
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
|
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
|
||||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
|
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.adapters.messages.*
|
|
||||||
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
|
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
|
||||||
import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
|
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.chat.ChatMessage
|
||||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||||
import com.nextcloud.talk.models.json.mention.Mention
|
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.getCredentials
|
||||||
import com.nextcloud.talk.newarch.local.models.getMaxMessageLength
|
import com.nextcloud.talk.newarch.local.models.getMaxMessageLength
|
||||||
import com.nextcloud.talk.newarch.mvvm.BaseView
|
import com.nextcloud.talk.newarch.mvvm.BaseView
|
||||||
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
||||||
import com.nextcloud.talk.newarch.utils.Images
|
import com.nextcloud.talk.newarch.utils.Images
|
||||||
|
import com.nextcloud.talk.newarch.utils.NetworkComponents
|
||||||
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
|
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
|
||||||
import com.nextcloud.talk.utils.*
|
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
|
||||||
|
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_CONVERSATION_PASSWORD
|
||||||
|
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
|
||||||
import com.nextcloud.talk.utils.text.Spans
|
import com.nextcloud.talk.utils.text.Spans
|
||||||
import com.otaliastudios.autocomplete.Autocomplete
|
import com.otaliastudios.autocomplete.Autocomplete
|
||||||
import com.stfalcon.chatkit.commons.models.IMessage
|
import com.otaliastudios.elements.Adapter
|
||||||
import com.stfalcon.chatkit.messages.MessageHolders
|
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.messages.MessagesListAdapter
|
||||||
import com.stfalcon.chatkit.utils.DateFormatter
|
|
||||||
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
|
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
|
||||||
import kotlinx.android.synthetic.main.controller_chat.view.*
|
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.lobby_view.view.*
|
||||||
import kotlinx.android.synthetic.main.view_message_input.view.*
|
import kotlinx.android.synthetic.main.view_message_input.view.*
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.parceler.Parcels
|
import org.parceler.Parcels
|
||||||
import java.util.*
|
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
|
class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
|
||||||
.OnMessageLongClickListener<IMessage>, MessagesListAdapter.Formatter<Date> {
|
|
||||||
override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
||||||
override val lifecycleOwner = ControllerLifecycleOwner(this)
|
override val lifecycleOwner = ControllerLifecycleOwner(this)
|
||||||
|
|
||||||
lateinit var viewModel: ChatViewModel
|
private lateinit var viewModel: ChatViewModel
|
||||||
val factory: ChatViewModelFactory by inject()
|
val factory: ChatViewModelFactory by inject()
|
||||||
val imageLoader: CoilImageLoader by inject()
|
private val networkComponents: NetworkComponents by inject()
|
||||||
|
|
||||||
var conversationInfoMenuItem: MenuItem? = null
|
var conversationInfoMenuItem: MenuItem? = null
|
||||||
var conversationVoiceCallMenuItem: MenuItem? = null
|
var conversationVoiceCallMenuItem: MenuItem? = null
|
||||||
var conversationVideoMenuItem: MenuItem? = null
|
var conversationVideoMenuItem: MenuItem? = null
|
||||||
|
|
||||||
private var newMessagesCount = 0
|
|
||||||
|
|
||||||
private lateinit var recyclerViewAdapter: MessagesListAdapter<ChatMessage>
|
private lateinit var recyclerViewAdapter: MessagesListAdapter<ChatMessage>
|
||||||
private lateinit var mentionAutocomplete: Autocomplete<*>
|
private lateinit var mentionAutocomplete: Autocomplete<*>
|
||||||
|
|
||||||
private var shouldShowLobby: Boolean = false
|
private var shouldShowLobby: Boolean = false
|
||||||
private var isReadOnlyConversation: Boolean = false
|
private var isReadOnlyConversation: Boolean = false
|
||||||
|
|
||||||
|
private lateinit var messagesAdapter: Adapter
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup
|
container: ViewGroup
|
||||||
@ -105,12 +108,22 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
actionBar?.show()
|
actionBar?.show()
|
||||||
viewModel = viewModelProvider(factory).get(ChatViewModel::class.java)
|
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 {
|
viewModel.apply {
|
||||||
conversation.observe(this@ChatView) { conversation ->
|
conversation.observe(this@ChatView) { conversation ->
|
||||||
setTitle()
|
setTitle()
|
||||||
setupAdapter()
|
|
||||||
|
|
||||||
if (Conversation.ConversationType.ONE_TO_ONE_CONVERSATION == conversation?.type) {
|
if (Conversation.ConversationType.ONE_TO_ONE_CONVERSATION == conversation?.type) {
|
||||||
loadAvatar()
|
loadAvatar()
|
||||||
@ -124,36 +137,100 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
|||||||
activity?.invalidateOptionsMenu()
|
activity?.invalidateOptionsMenu()
|
||||||
|
|
||||||
if (shouldShowLobby) {
|
if (shouldShowLobby) {
|
||||||
view?.messagesListView?.visibility = View.GONE
|
view.messagesListView?.visibility = View.GONE
|
||||||
view?.messageInputView?.visibility = View.GONE
|
view.messageInputView?.visibility = View.GONE
|
||||||
view?.lobbyView?.visibility = View.VISIBLE
|
view.lobbyView?.visibility = View.VISIBLE
|
||||||
val timer = conversation.lobbyTimer
|
val timer = conversation.lobbyTimer
|
||||||
if (timer != null && timer != 0L) {
|
val unit = if (timer != null && timer != 0L) {
|
||||||
view?.lobbyTextView?.text = String.format(
|
view.lobbyTextView?.text = String.format(
|
||||||
resources!!.getString(R.string.nc_lobby_waiting_with_date),
|
resources!!.getString(R.string.nc_lobby_waiting_with_date),
|
||||||
DateUtils.getLocalDateStringFromTimestampForLobby(
|
DateUtils.getLocalDateStringFromTimestampForLobby(
|
||||||
conversation.lobbyTimer!!
|
conversation.lobbyTimer!!
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
view?.lobbyTextView?.setText(R.string.nc_lobby_waiting)
|
view.lobbyTextView?.setText(R.string.nc_lobby_waiting)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
view?.messagesListView?.visibility = View.GONE
|
view.messagesListView?.visibility = View.GONE
|
||||||
view?.lobbyView?.visibility = View.GONE
|
view.lobbyView?.visibility = View.GONE
|
||||||
|
|
||||||
if (isReadOnlyConversation) {
|
if (isReadOnlyConversation) {
|
||||||
view?.messageInputView?.visibility = View.GONE
|
view.messageInputView?.visibility = View.GONE
|
||||||
} else {
|
} 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) {
|
override fun onAttach(view: View) {
|
||||||
super.onAttach(view)
|
super.onAttach(view)
|
||||||
@ -185,56 +262,15 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
|||||||
|
|
||||||
private fun setupViews() {
|
private fun setupViews() {
|
||||||
view?.let { view ->
|
view?.let { view ->
|
||||||
view.recyclerView.initRecyclerView(
|
view.messagesRecyclerView.initRecyclerView(
|
||||||
LinearLayoutManager(view.context), recyclerViewAdapter, false
|
LinearLayoutManager(view.context), recyclerViewAdapter, false
|
||||||
)
|
)
|
||||||
|
|
||||||
recyclerViewAdapter.setLoadMoreListener(this)
|
|
||||||
recyclerViewAdapter.setDateHeadersFormatter { format(it) }
|
|
||||||
recyclerViewAdapter.setOnMessageLongClickListener { onMessageLongClick(it) }
|
|
||||||
|
|
||||||
view.popupBubbleView.setRecyclerView(view.messagesListView)
|
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 filters = arrayOfNulls<InputFilter>(1)
|
||||||
val lengthFilter = viewModel.user.getMaxMessageLength()
|
val lengthFilter = viewModel.user.getMaxMessageLength()
|
||||||
|
|
||||||
|
|
||||||
filters[0] = InputFilter.LengthFilter(lengthFilter)
|
filters[0] = InputFilter.LengthFilter(lengthFilter)
|
||||||
view.messageInput.filters = filters
|
view.messageInput.filters = filters
|
||||||
|
|
||||||
@ -365,9 +401,9 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
|||||||
viewModel.conversation.value?.let {
|
viewModel.conversation.value?.let {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putParcelable(
|
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)
|
bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, it.token)
|
||||||
router.pushController(
|
router.pushController(
|
||||||
RouterTransaction.with(BrowserController(bundle))
|
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() {
|
private fun loadAvatar() {
|
||||||
|
val imageLoader = networkComponents.getImageLoader(viewModel.user)
|
||||||
val avatarSize = DisplayUtils.convertDpToPixel(
|
val avatarSize = DisplayUtils.convertDpToPixel(
|
||||||
conversationVoiceCallMenuItem?.icon!!
|
conversationVoiceCallMenuItem?.icon!!
|
||||||
.intrinsicWidth.toFloat(), activity!!
|
.intrinsicWidth.toFloat(), activity!!
|
||||||
@ -459,9 +438,7 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
|||||||
), viewModel.user, target, this,
|
), viewModel.user, target, this,
|
||||||
CircleCropTransformation()
|
CircleCropTransformation()
|
||||||
)
|
)
|
||||||
|
|
||||||
imageLoader.load(avatarRequest)
|
imageLoader.load(avatarRequest)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,35 +451,30 @@ class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesLi
|
|||||||
return viewModel.conversation.value?.displayName
|
return viewModel.conversation.value?.displayName
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasContentFor(message: IMessage, type: Byte): Boolean {
|
override fun getImageLoader(): ImageLoader {
|
||||||
when (type) {
|
return networkComponents.getImageLoader(viewModel.user)
|
||||||
ChatController.CONTENT_TYPE_SYSTEM_MESSAGE -> return !TextUtils.isEmpty(message.systemMessage)
|
|
||||||
ChatController.CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> return message.id == "-1"
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun format(date: Date): String {
|
override fun loadImage(imageView: ImageView, url: String, payload: MutableMap<String, String>?) {
|
||||||
return when {
|
val imageLoader = networkComponents.getImageLoader(viewModel.user)
|
||||||
DateFormatter.isToday(date) -> {
|
|
||||||
resources!!.getString(R.string.nc_date_header_today)
|
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)
|
target(imageView)
|
||||||
}
|
val needsAuthBasedOnUrl = url.contains("index.php/core/preview?fileId=") || url.contains("index.php/avatar/")
|
||||||
else -> {
|
if (url.startsWith(viewModel.user.baseUrl) && needsAuthBasedOnUrl) {
|
||||||
DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR)
|
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.repository.offline.MessagesRepository
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.ExitConversationUseCase
|
import com.nextcloud.talk.newarch.domain.usecases.ExitConversationUseCase
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
|
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.local.models.UserNgEntity
|
||||||
import com.nextcloud.talk.newarch.services.GlobalService
|
import com.nextcloud.talk.newarch.services.GlobalService
|
||||||
import com.nextcloud.talk.newarch.services.GlobalServiceInterface
|
import com.nextcloud.talk.newarch.services.GlobalServiceInterface
|
||||||
@ -43,7 +44,7 @@ class ChatViewModel constructor(application: Application,
|
|||||||
private val conversationsRepository: ConversationsRepository,
|
private val conversationsRepository: ConversationsRepository,
|
||||||
private val messagesRepository: MessagesRepository,
|
private val messagesRepository: MessagesRepository,
|
||||||
private val globalService: GlobalService) : BaseViewModel<ChatView>(application), GlobalServiceInterface {
|
private val globalService: GlobalService) : BaseViewModel<ChatView>(application), GlobalServiceInterface {
|
||||||
lateinit var user: UserNgEntity
|
lateinit var user: User
|
||||||
val conversation: MutableLiveData<Conversation?> = MutableLiveData()
|
val conversation: MutableLiveData<Conversation?> = MutableLiveData()
|
||||||
var initConversation: Conversation? = null
|
var initConversation: Conversation? = null
|
||||||
val messagesLiveData = Transformations.switchMap(conversation) {
|
val messagesLiveData = Transformations.switchMap(conversation) {
|
||||||
@ -54,10 +55,10 @@ class ChatViewModel constructor(application: Application,
|
|||||||
var conversationPassword: String? = null
|
var conversationPassword: String? = null
|
||||||
|
|
||||||
|
|
||||||
fun init(user: UserNgEntity, conversationToken: String, conversationPassword: String?) {
|
fun init(user: User, conversationToken: String, conversationPassword: String?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
this@ChatViewModel.user = user
|
this@ChatViewModel.user = user
|
||||||
this@ChatViewModel.initConversation = conversationsRepository.getConversationForUserWithToken(user.id, conversationToken)
|
this@ChatViewModel.initConversation = conversationsRepository.getConversationForUserWithToken(user.id!!, conversationToken)
|
||||||
this@ChatViewModel.conversationPassword = conversationPassword
|
this@ChatViewModel.conversationPassword = conversationPassword
|
||||||
globalService.getConversation(conversationToken, this@ChatViewModel)
|
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) {
|
override suspend fun gotConversationInfoForUser(userNgEntity: UserNgEntity, conversation: Conversation?, operationStatus: GlobalServiceInterface.OperationStatus) {
|
||||||
if (operationStatus == GlobalServiceInterface.OperationStatus.STATUS_OK) {
|
if (operationStatus == GlobalServiceInterface.OperationStatus.STATUS_OK) {
|
||||||
if (userNgEntity.id == user.id && conversation!!.token == initConversation?.token) {
|
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 ->
|
conversation.token?.let { conversationToken ->
|
||||||
globalService.joinConversation(conversationToken, conversationPassword, this)
|
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.autodispose.ControllerScopeProvider
|
||||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.controllers.ChatController
|
|
||||||
import com.nextcloud.talk.models.json.participants.Participant
|
import com.nextcloud.talk.models.json.participants.Participant
|
||||||
import com.nextcloud.talk.newarch.data.presenters.AdvancedEmptyPresenter
|
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.ContactsViewOperationState
|
||||||
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
|
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
|
||||||
import com.nextcloud.talk.newarch.features.contactsflow.groupconversation.GroupConversationView
|
import com.nextcloud.talk.newarch.features.contactsflow.groupconversation.GroupConversationView
|
||||||
@ -180,7 +180,7 @@ class ContactsView(private val bundle: Bundle? = null) : BaseView() {
|
|||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
if (!hasToken || isNewGroupConversation) {
|
if (!hasToken || isNewGroupConversation) {
|
||||||
bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, operationState.conversationToken)
|
bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, operationState.conversationToken)
|
||||||
router.replaceTopController(RouterTransaction.with(ChatController(bundle))
|
router.replaceTopController(RouterTransaction.with(ChatView(bundle))
|
||||||
.popChangeHandler(HorizontalChangeHandler())
|
.popChangeHandler(HorizontalChangeHandler())
|
||||||
.pushChangeHandler(HorizontalChangeHandler()))
|
.pushChangeHandler(HorizontalChangeHandler()))
|
||||||
} else {
|
} 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.data.presenters.AdvancedEmptyPresenter
|
||||||
import com.nextcloud.talk.newarch.features.contactsflow.contacts.ContactsView
|
import com.nextcloud.talk.newarch.features.contactsflow.contacts.ContactsView
|
||||||
import com.nextcloud.talk.newarch.features.search.DebouncingTextWatcher
|
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.BaseView
|
||||||
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
||||||
import com.nextcloud.talk.utils.ConductorRemapping
|
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(
|
items.add(
|
||||||
BasicListItemWithImage(
|
BasicListItemWithImage(
|
||||||
drawable.ic_exit_to_app_black_24dp, context.getString
|
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(
|
items.add(
|
||||||
BasicListItemWithImage(
|
BasicListItemWithImage(
|
||||||
drawable.ic_delete_grey600_24dp, context.getString(
|
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.push.PushConfiguration
|
||||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings
|
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings
|
||||||
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
||||||
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@ -24,6 +25,16 @@ data class User(
|
|||||||
var status: UserStatus? = null
|
var status: UserStatus? = null
|
||||||
) : Parcelable
|
) : 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 {
|
fun User.toUserEntity(): UserNgEntity {
|
||||||
var userNgEntity: UserNgEntity? = null
|
var userNgEntity: UserNgEntity? = null
|
||||||
this.id?.let {
|
this.id?.let {
|
||||||
|
@ -91,11 +91,3 @@ fun UserNgEntity.toUser(): User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun UserNgEntity.getCredentials(): String = ApiUtils.getCredentials(username, token)
|
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()))
|
.setContentTitle(EmojiCompat.get().process(decryptedPushMessage.subject.toString()))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setOngoing(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)
|
.setContentIntent(fullScreenPendingIntent)
|
||||||
.setFullScreenIntent(fullScreenPendingIntent, true)
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||||
.setSound(NotificationUtils.getCallSoundUri(applicationContext, appPreferences), AudioManager.STREAM_RING)
|
.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 imageLoader = networkComponents.getImageLoader(signatureVerification.userEntity!!.toUser())
|
||||||
|
|
||||||
val request = Images().getRequestForUrl(
|
val request = Images().getRequestForUrl(
|
||||||
imageLoader, applicationContext, avatarUrl, signatureVerification.userEntity,
|
imageLoader, applicationContext, avatarUrl, signatureVerification.userEntity!!.toUser(),
|
||||||
target, null, CircleCropTransformation())
|
target, null, CircleCropTransformation())
|
||||||
|
|
||||||
imageLoader.load(request)
|
imageLoader.load(request)
|
||||||
|
@ -36,6 +36,7 @@ import coil.target.Target
|
|||||||
import coil.transform.Transformation
|
import coil.transform.Transformation
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
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.UserNgEntity
|
||||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||||
import com.nextcloud.talk.utils.DisplayUtils
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
@ -46,7 +47,7 @@ class Images {
|
|||||||
context: Context,
|
context: Context,
|
||||||
url: String,
|
url: String,
|
||||||
userEntity:
|
userEntity:
|
||||||
UserNgEntity?,
|
User?,
|
||||||
target: Target?,
|
target: Target?,
|
||||||
lifecycleOwner: LifecycleOwner?,
|
lifecycleOwner: LifecycleOwner?,
|
||||||
vararg transformations: Transformation
|
vararg transformations: Transformation
|
||||||
|
@ -24,7 +24,7 @@ import android.os.Bundle
|
|||||||
import com.bluelinelabs.conductor.Router
|
import com.bluelinelabs.conductor.Router
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import com.bluelinelabs.conductor.RouterTransaction
|
||||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||||
import com.nextcloud.talk.controllers.ChatController
|
import com.nextcloud.talk.newarch.features.chat.ChatView
|
||||||
|
|
||||||
object ConductorRemapping {
|
object ConductorRemapping {
|
||||||
fun remapChatController(
|
fun remapChatController(
|
||||||
@ -51,13 +51,13 @@ object ConductorRemapping {
|
|||||||
} else {
|
} else {
|
||||||
if (!replaceTop) {
|
if (!replaceTop) {
|
||||||
router.pushController(
|
router.pushController(
|
||||||
RouterTransaction.with(ChatController(bundle))
|
RouterTransaction.with(ChatView(bundle))
|
||||||
.pushChangeHandler(HorizontalChangeHandler())
|
.pushChangeHandler(HorizontalChangeHandler())
|
||||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
router.replaceTopController(
|
router.replaceTopController(
|
||||||
RouterTransaction.with(ChatController(bundle))
|
RouterTransaction.with(ChatView(bundle))
|
||||||
.pushChangeHandler(HorizontalChangeHandler())
|
.pushChangeHandler(HorizontalChangeHandler())
|
||||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||||
)
|
)
|
||||||
|
@ -61,6 +61,7 @@ import com.google.android.material.chip.ChipDrawable
|
|||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
import com.nextcloud.talk.events.UserMentionClickEvent
|
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.local.models.UserNgEntity
|
||||||
import com.nextcloud.talk.newarch.utils.Images
|
import com.nextcloud.talk.newarch.utils.Images
|
||||||
import com.nextcloud.talk.utils.text.Spans
|
import com.nextcloud.talk.utils.text.Spans
|
||||||
@ -186,7 +187,7 @@ object DisplayUtils {
|
|||||||
context: Context,
|
context: Context,
|
||||||
id: String,
|
id: String,
|
||||||
label: CharSequence,
|
label: CharSequence,
|
||||||
conversationUser: UserNgEntity,
|
conversationUser: User,
|
||||||
type: String,
|
type: String,
|
||||||
@XmlRes chipResource: Int,
|
@XmlRes chipResource: Int,
|
||||||
emojiEditText: EditText?
|
emojiEditText: EditText?
|
||||||
@ -264,7 +265,7 @@ object DisplayUtils {
|
|||||||
id: String,
|
id: String,
|
||||||
label: String,
|
label: String,
|
||||||
type: String,
|
type: String,
|
||||||
conversationUser: UserNgEntity,
|
conversationUser: User,
|
||||||
@XmlRes chipXmlRes: Int
|
@XmlRes chipXmlRes: Int
|
||||||
): Spannable {
|
): Spannable {
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ object BundleKeys {
|
|||||||
val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD"
|
val KEY_CONVERSATION_PASSWORD = "KEY_CONVERSATION_PASSWORD"
|
||||||
val KEY_CONVERSATION_TOKEN = "KEY_CONVERSATION_TOKEN"
|
val KEY_CONVERSATION_TOKEN = "KEY_CONVERSATION_TOKEN"
|
||||||
val KEY_USER_ENTITY = "KEY_USER_ENTITY"
|
val KEY_USER_ENTITY = "KEY_USER_ENTITY"
|
||||||
|
val KEY_USER = "KEY_USER"
|
||||||
val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION"
|
val KEY_NEW_CONVERSATION = "KEY_NEW_CONVERSATION"
|
||||||
val KEY_NEW_GROUP_CONVERSATION = "KEY_NEW_GROUP_CONVERSATION"
|
val KEY_NEW_GROUP_CONVERSATION = "KEY_NEW_GROUP_CONVERSATION"
|
||||||
val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS"
|
val KEY_ADD_PARTICIPANTS = "KEY_ADD_PARTICIPANTS"
|
||||||
|
@ -72,6 +72,14 @@
|
|||||||
app:inputTextSize="16sp"
|
app:inputTextSize="16sp"
|
||||||
app:showAttachmentButton="true" />
|
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
|
<com.stfalcon.chatkit.messages.MessagesList
|
||||||
android:id="@+id/messagesListView"
|
android:id="@+id/messagesListView"
|
||||||
android:layout_width="match_parent"
|
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"/>
|
<include layout="@layout/item_message_quote" android:visibility="gone"/>
|
||||||
|
|
||||||
<androidx.emoji.widget.EmojiTextView
|
<androidx.emoji.widget.EmojiTextView
|
||||||
android:id="@id/messageText"
|
android:id="@+id/incomingMessageText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:lineSpacingMultiplier="1.2"
|
android:lineSpacingMultiplier="1.2"
|
||||||
@ -68,10 +68,10 @@
|
|||||||
app:layout_wrapBefore="true" />
|
app:layout_wrapBefore="true" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@id/messageTime"
|
android:id="@+id/incomingMessageTime"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/messageText"
|
android:layout_below="@+id/outgoingMessageText"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
app:layout_alignSelf="center" />
|
app:layout_alignSelf="center" />
|
||||||
|
|
@ -41,7 +41,7 @@
|
|||||||
app:justifyContent="flex_end">
|
app:justifyContent="flex_end">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@id/image"
|
android:id="@+id/outgoingPreviewImage"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
@ -52,7 +52,7 @@
|
|||||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
tools:src="@tools:sample/backgrounds/scenic"/>
|
||||||
|
|
||||||
<androidx.emoji.widget.EmojiTextView
|
<androidx.emoji.widget.EmojiTextView
|
||||||
android:id="@id/messageText"
|
android:id="@+id/outgoingPreviewMessageText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:autoLink="all"
|
android:autoLink="all"
|
||||||
@ -65,7 +65,7 @@
|
|||||||
app:layout_wrapBefore="true" />
|
app:layout_wrapBefore="true" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@id/messageTime"
|
android:id="@+id/outgoingPreviewTime"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
@ -41,7 +41,7 @@
|
|||||||
<include layout="@layout/item_message_quote" android:visibility="gone"/>
|
<include layout="@layout/item_message_quote" android:visibility="gone"/>
|
||||||
|
|
||||||
<androidx.emoji.widget.EmojiTextView
|
<androidx.emoji.widget.EmojiTextView
|
||||||
android:id="@id/messageText"
|
android:id="@+id/outgoingMessageText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignWithParentIfMissing="true"
|
android:layout_alignWithParentIfMissing="true"
|
||||||
@ -54,10 +54,10 @@
|
|||||||
android:autoLink="all"/>
|
android:autoLink="all"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@id/messageTime"
|
android:id="@+id/outgoingMessageTime"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/messageText"
|
android:layout_below="@id/outgoingMessageText"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
app:layout_alignSelf="center" />
|
app:layout_alignSelf="center" />
|
||||||
|
|
@ -40,7 +40,7 @@
|
|||||||
app:justifyContent="flex_end">
|
app:justifyContent="flex_end">
|
||||||
|
|
||||||
<androidx.emoji.widget.EmojiTextView
|
<androidx.emoji.widget.EmojiTextView
|
||||||
android:id="@+id/messageText"
|
android:id="@+id/systemMessageText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
@ -54,7 +54,7 @@
|
|||||||
app:layout_wrapBefore="true"/>
|
app:layout_wrapBefore="true"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@id/messageTime"
|
android:id="@+id/systemItemTime"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/warm_grey_four"
|
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_search_for_more">Vyhledat další účastníky</string>
|
||||||
<string name="nc_new_group">Nová skupina</string>
|
<string name="nc_new_group">Nová skupina</string>
|
||||||
<string name="nc_search_empty_contacts">Kam se všichni schovali?</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>
|
</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_search_for_more">Weitere Teilnehmer suchen</string>
|
||||||
<string name="nc_new_group">Neue Gruppe</string>
|
<string name="nc_new_group">Neue Gruppe</string>
|
||||||
<string name="nc_search_empty_contacts">Wo haben sie sich alle versteckt?</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>
|
</resources>
|
||||||
|
@ -318,5 +318,5 @@
|
|||||||
<string name="nc_search_for_more">Αναζήτηση περισσότερων συμμετεχόντων</string>
|
<string name="nc_search_for_more">Αναζήτηση περισσότερων συμμετεχόντων</string>
|
||||||
<string name="nc_new_group">Νέα ομάδα</string>
|
<string name="nc_new_group">Νέα ομάδα</string>
|
||||||
<string name="nc_search_empty_contacts">Πού κρύβονταν όλοι;</string>
|
<string name="nc_search_empty_contacts">Πού κρύβονταν όλοι;</string>
|
||||||
<string name="reject_call">Απόρριψη</string>
|
<string name="nc_reject_call">Απόρριψη</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -316,5 +316,5 @@
|
|||||||
<string name="nc_search_for_more">Buscar más participantes</string>
|
<string name="nc_search_for_more">Buscar más participantes</string>
|
||||||
<string name="nc_new_group">Nuevo grupo</string>
|
<string name="nc_new_group">Nuevo grupo</string>
|
||||||
<string name="nc_search_empty_contacts">¿Dónde se escondieron todos?</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>
|
</resources>
|
||||||
|
@ -321,5 +321,5 @@
|
|||||||
<string name="nc_search_for_more">Bilatu parte-hartzaile gehiago</string>
|
<string name="nc_search_for_more">Bilatu parte-hartzaile gehiago</string>
|
||||||
<string name="nc_new_group">Talde berria</string>
|
<string name="nc_new_group">Talde berria</string>
|
||||||
<string name="nc_search_empty_contacts">Non ezkutatu dira denak?</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>
|
</resources>
|
||||||
|
@ -270,5 +270,5 @@
|
|||||||
<string name="nc_search_for_more">Etsi lisää osallistujia</string>
|
<string name="nc_search_for_more">Etsi lisää osallistujia</string>
|
||||||
<string name="nc_new_group">Uusi ryhmä</string>
|
<string name="nc_new_group">Uusi ryhmä</string>
|
||||||
<string name="nc_search_empty_contacts">Mihin he kaikki piiloutuivat?</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>
|
</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_search_for_more">Rechercher d\'autres participants</string>
|
||||||
<string name="nc_new_group">Nouveau groupe</string>
|
<string name="nc_new_group">Nouveau groupe</string>
|
||||||
<string name="nc_search_empty_contacts">Où se cachent-ils tous ?</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>
|
</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_search_for_more">Buscar máis participantes</string>
|
||||||
<string name="nc_new_group">Grupo novo</string>
|
<string name="nc_new_group">Grupo novo</string>
|
||||||
<string name="nc_search_empty_contacts">Onde se agocharon todos?</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>
|
</resources>
|
||||||
|
@ -320,5 +320,5 @@
|
|||||||
<string name="nc_search_for_more">Cerca altri partecipanti</string>
|
<string name="nc_search_for_more">Cerca altri partecipanti</string>
|
||||||
<string name="nc_new_group">Nuovo gruppo</string>
|
<string name="nc_new_group">Nuovo gruppo</string>
|
||||||
<string name="nc_search_empty_contacts">Dove si sono nascosti?</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>
|
</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_search_for_more">Zoek meer deelnemers</string>
|
||||||
<string name="nc_new_group">Nieuwe groep</string>
|
<string name="nc_new_group">Nieuwe groep</string>
|
||||||
<string name="nc_search_empty_contacts">Waar zijn ze allemaal naar toe?</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>
|
</resources>
|
||||||
|
@ -320,5 +320,5 @@
|
|||||||
<string name="nc_search_for_more">Wyszukaj więcej uczestników</string>
|
<string name="nc_search_for_more">Wyszukaj więcej uczestników</string>
|
||||||
<string name="nc_new_group">Nowa grupa</string>
|
<string name="nc_new_group">Nowa grupa</string>
|
||||||
<string name="nc_search_empty_contacts">Gdzie oni wszyscy się schowali?</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>
|
</resources>
|
||||||
|
@ -320,5 +320,5 @@
|
|||||||
<string name="nc_search_for_more">Procurar por mais participantes</string>
|
<string name="nc_search_for_more">Procurar por mais participantes</string>
|
||||||
<string name="nc_new_group">Novo grupo</string>
|
<string name="nc_new_group">Novo grupo</string>
|
||||||
<string name="nc_search_empty_contacts">Onde eles todos se esconderam?</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>
|
</resources>
|
||||||
|
@ -315,5 +315,5 @@
|
|||||||
<string name="nc_search_for_more">Искать дополнительных участников</string>
|
<string name="nc_search_for_more">Искать дополнительных участников</string>
|
||||||
<string name="nc_new_group">Новая группа</string>
|
<string name="nc_new_group">Новая группа</string>
|
||||||
<string name="nc_search_empty_contacts">Где все?</string>
|
<string name="nc_search_empty_contacts">Где все?</string>
|
||||||
<string name="reject_call">Отклонить</string>
|
<string name="nc_reject_call">Отклонить</string>
|
||||||
</resources>
|
</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_search_for_more">Vyhľadanie ďalších účastníkov</string>
|
||||||
<string name="nc_new_group">Nová skupina</string>
|
<string name="nc_new_group">Nová skupina</string>
|
||||||
<string name="nc_search_empty_contacts">Kde sa všetci schovali?</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>
|
</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_search_for_more">Poišči več udeležencev</string>
|
||||||
<string name="nc_new_group">Nova skupina</string>
|
<string name="nc_new_group">Nova skupina</string>
|
||||||
<string name="nc_search_empty_contacts">Kam so se vsi skrili?</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>
|
</resources>
|
||||||
|
@ -319,5 +319,5 @@
|
|||||||
<string name="nc_search_for_more">Тражи још учесника</string>
|
<string name="nc_search_for_more">Тражи још учесника</string>
|
||||||
<string name="nc_new_group">Нова група</string>
|
<string name="nc_new_group">Нова група</string>
|
||||||
<string name="nc_search_empty_contacts">Где су сви нестали?</string>
|
<string name="nc_search_empty_contacts">Где су сви нестали?</string>
|
||||||
<string name="reject_call">Одбиј</string>
|
<string name="nc_reject_call">Одбиј</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -320,5 +320,5 @@
|
|||||||
<string name="nc_search_for_more">Sök efter fler deltagare</string>
|
<string name="nc_search_for_more">Sök efter fler deltagare</string>
|
||||||
<string name="nc_new_group">Ny grupp</string>
|
<string name="nc_new_group">Ny grupp</string>
|
||||||
<string name="nc_search_empty_contacts">Var gömmer sig alla?</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>
|
</resources>
|
||||||
|
@ -320,5 +320,5 @@
|
|||||||
<string name="nc_search_for_more">Başka katılımcılar arayın</string>
|
<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_new_group">Yeni grup</string>
|
||||||
<string name="nc_search_empty_contacts">Hepsi nereye gizlendiler?</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>
|
</resources>
|
||||||
|
@ -316,5 +316,5 @@
|
|||||||
<string name="nc_search_for_more">搜索更多参与者</string>
|
<string name="nc_search_for_more">搜索更多参与者</string>
|
||||||
<string name="nc_new_group">新建群组</string>
|
<string name="nc_new_group">新建群组</string>
|
||||||
<string name="nc_search_empty_contacts">他们都藏在哪里?</string>
|
<string name="nc_search_empty_contacts">他们都藏在哪里?</string>
|
||||||
<string name="reject_call">拒绝</string>
|
<string name="nc_reject_call">拒绝</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -344,5 +344,5 @@
|
|||||||
<string name="nc_search_for_more">Search for more participants</string>
|
<string name="nc_search_for_more">Search for more participants</string>
|
||||||
<string name="nc_new_group">New group</string>
|
<string name="nc_new_group">New group</string>
|
||||||
<string name="nc_search_empty_contacts">Where did they all hide?</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>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user