mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 12:09:45 +01:00
Merge pull request #1895 from nextcloud/feature/1772/reactions
Reactions to chat messages
This commit is contained in:
commit
d66a6a9578
@ -56,16 +56,7 @@
|
|||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
<value>
|
<value>
|
||||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" static="false" />
|
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
<option name="PACKAGES_IMPORT_LAYOUT">
|
|
||||||
<value>
|
|
||||||
<package name="" alias="false" withSubpackages="true" />
|
|
||||||
<package name="java" alias="false" withSubpackages="true" />
|
|
||||||
<package name="javax" alias="false" withSubpackages="true" />
|
|
||||||
<package name="kotlin" alias="false" withSubpackages="true" />
|
|
||||||
<package name="" alias="true" withSubpackages="true" />
|
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||||
@ -212,7 +203,6 @@
|
|||||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="INDENT_SIZE" value="4" />
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
|
@ -141,6 +141,15 @@ If you set your `user.name` and `user.email` git configs, you can sign your comm
|
|||||||
You can also use git [aliases](https://git-scm.com/book/tr/v2/Git-Basics-Git-Aliases) like `git config --global alias.ci 'commit -s'`.
|
You can also use git [aliases](https://git-scm.com/book/tr/v2/Git-Basics-Git-Aliases) like `git config --global alias.ci 'commit -s'`.
|
||||||
Now you can commit with `git ci` and the commit will be signed.
|
Now you can commit with `git ci` and the commit will be signed.
|
||||||
|
|
||||||
|
### Git hooks
|
||||||
|
|
||||||
|
We provide git hooks to make development process easier for both the developer and the reviewers.
|
||||||
|
To install them, just run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew installGitHooks
|
||||||
|
```
|
||||||
|
|
||||||
## Contribution process
|
## Contribution process
|
||||||
|
|
||||||
Contribute your code targeting/based-on the branch ```master```.
|
Contribute your code targeting/based-on the branch ```master```.
|
||||||
|
@ -334,6 +334,14 @@ dependencies {
|
|||||||
gplayImplementation "com.google.firebase:firebase-messaging:23.0.0"
|
gplayImplementation "com.google.firebase:firebase-messaging:23.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task installGitHooks(type: Copy, group: "development") {
|
||||||
|
description = "Install git hooks"
|
||||||
|
from("../scripts/hooks") {
|
||||||
|
include '*'
|
||||||
|
}
|
||||||
|
into '../.git/hooks'
|
||||||
|
}
|
||||||
|
|
||||||
detekt {
|
detekt {
|
||||||
reports {
|
reports {
|
||||||
xml {
|
xml {
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Andy Scherzinger
|
||||||
|
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionVoter
|
||||||
|
|
||||||
|
data class ReactionItem(
|
||||||
|
val reactionVoter: ReactionVoter,
|
||||||
|
val reaction: String?
|
||||||
|
)
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Andy Scherzinger
|
||||||
|
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
interface ReactionItemClickListener {
|
||||||
|
fun onClick(reactionItem: ReactionItem)
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Andy Scherzinger
|
||||||
|
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.nextcloud.talk.databinding.ReactionItemBinding
|
||||||
|
import com.nextcloud.talk.models.database.UserEntity
|
||||||
|
|
||||||
|
class ReactionsAdapter(
|
||||||
|
private val clickListener: ReactionItemClickListener,
|
||||||
|
private val userEntity: UserEntity?
|
||||||
|
) : RecyclerView.Adapter<ReactionsViewHolder>() {
|
||||||
|
internal var list: MutableList<ReactionItem> = ArrayList<ReactionItem>()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReactionsViewHolder {
|
||||||
|
val itemBinding = ReactionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return ReactionsViewHolder(itemBinding, userEntity?.baseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ReactionsViewHolder, position: Int) {
|
||||||
|
holder.bind(list[position], clickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return list.size
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Andy Scherzinger
|
||||||
|
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
|
import com.facebook.drawee.interfaces.DraweeController
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
|
import com.nextcloud.talk.databinding.ReactionItemBinding
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionVoter
|
||||||
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
|
|
||||||
|
class ReactionsViewHolder(
|
||||||
|
private val binding: ReactionItemBinding,
|
||||||
|
private val baseUrl: String?
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(reactionItem: ReactionItem, clickListener: ReactionItemClickListener) {
|
||||||
|
binding.root.setOnClickListener { clickListener.onClick(reactionItem) }
|
||||||
|
binding.reaction.text = reactionItem.reaction
|
||||||
|
binding.name.text = reactionItem.reactionVoter.actorDisplayName
|
||||||
|
|
||||||
|
if (baseUrl != null && baseUrl.isNotEmpty()) {
|
||||||
|
loadAvatar(reactionItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAvatar(reactionItem: ReactionItem) {
|
||||||
|
if (reactionItem.reactionVoter.actorType == ReactionVoter.ReactionActorType.GUESTS) {
|
||||||
|
var displayName = sharedApplication?.resources?.getString(R.string.nc_guest)
|
||||||
|
if (!TextUtils.isEmpty(reactionItem.reactionVoter.actorDisplayName)) {
|
||||||
|
displayName = reactionItem.reactionVoter.actorDisplayName!!
|
||||||
|
}
|
||||||
|
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setOldController(binding.avatar.controller)
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setImageRequest(
|
||||||
|
DisplayUtils.getImageRequestForUrl(
|
||||||
|
ApiUtils.getUrlForGuestAvatar(
|
||||||
|
baseUrl,
|
||||||
|
displayName,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
binding.avatar.controller = draweeController
|
||||||
|
} else if (reactionItem.reactionVoter.actorType == ReactionVoter.ReactionActorType.USERS) {
|
||||||
|
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setOldController(binding.avatar.controller)
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setImageRequest(
|
||||||
|
DisplayUtils.getImageRequestForUrl(
|
||||||
|
ApiUtils.getUrlForAvatar(
|
||||||
|
baseUrl,
|
||||||
|
reactionItem.reactionVoter.actorId,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
binding.avatar.controller = draweeController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,6 +78,8 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
|
|||||||
@Inject
|
@Inject
|
||||||
var appPreferences: AppPreferences? = null
|
var appPreferences: AppPreferences? = null
|
||||||
|
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBind(message: ChatMessage) {
|
override fun onBind(message: ChatMessage) {
|
||||||
super.onBind(message)
|
super.onBind(message)
|
||||||
@ -93,13 +95,21 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
|
|||||||
val textSize = context?.resources!!.getDimension(R.dimen.chat_text_size)
|
val textSize = context?.resources!!.getDimension(R.dimen.chat_text_size)
|
||||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
binding.messageText.text = message.text
|
binding.messageText.text = message.text
|
||||||
binding.messageText.isEnabled = false
|
|
||||||
|
|
||||||
// parent message handling
|
// parent message handling
|
||||||
setParentMessageDataOnMessageItem(message)
|
setParentMessageDataOnMessageItem(message)
|
||||||
|
|
||||||
// geo-location
|
// geo-location
|
||||||
setLocationDataOnMessageItem(message)
|
setLocationDataOnMessageItem(message)
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, context!!, true)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {
|
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {
|
||||||
@ -270,6 +280,10 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
|
|||||||
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "LocInMessageView"
|
private const val TAG = "LocInMessageView"
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import android.widget.ProgressBar;
|
|||||||
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
|
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
|
||||||
|
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||||
|
|
||||||
import androidx.emoji.widget.EmojiTextView;
|
import androidx.emoji.widget.EmojiTextView;
|
||||||
|
|
||||||
@ -77,4 +78,7 @@ public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHol
|
|||||||
public ProgressBar getPreviewContactProgressBar() {
|
public ProgressBar getPreviewContactProgressBar() {
|
||||||
return binding.contactProgressBar;
|
return binding.contactProgressBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactionsInsideMessageBinding getReactionsBinding(){ return binding.reactions; }
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
|
|||||||
lateinit var message: ChatMessage
|
lateinit var message: ChatMessage
|
||||||
|
|
||||||
lateinit var voiceMessageInterface: VoiceMessageInterface
|
lateinit var voiceMessageInterface: VoiceMessageInterface
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBind(message: ChatMessage) {
|
override fun onBind(message: ChatMessage) {
|
||||||
@ -140,6 +141,15 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, context!!, true)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDownloadState(message: ChatMessage) {
|
private fun updateDownloadState(message: ChatMessage) {
|
||||||
@ -302,10 +312,14 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignAdapter(voiceMessageInterface: VoiceMessageInterface) {
|
fun assignVoiceMessageInterface(voiceMessageInterface: VoiceMessageInterface) {
|
||||||
this.voiceMessageInterface = voiceMessageInterface
|
this.voiceMessageInterface = voiceMessageInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "VoiceInMessageView"
|
private const val TAG = "VoiceInMessageView"
|
||||||
private const val SEEKBAR_START: Int = 0
|
private const val SEEKBAR_START: Int = 0
|
||||||
|
@ -71,6 +71,8 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
|
|||||||
@Inject
|
@Inject
|
||||||
var appPreferences: AppPreferences? = null
|
var appPreferences: AppPreferences? = null
|
||||||
|
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
override fun onBind(message: ChatMessage) {
|
override fun onBind(message: ChatMessage) {
|
||||||
super.onBind(message)
|
super.onBind(message)
|
||||||
sharedApplication!!.componentApplication.inject(this)
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
@ -119,6 +121,15 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
|
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, context!!, true)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processAuthor(message: ChatMessage) {
|
private fun processAuthor(message: ChatMessage) {
|
||||||
@ -260,6 +271,10 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
|
|||||||
return messageStringInternal
|
return messageStringInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TEXT_SIZE_MULTIPLIER = 2.5
|
const val TEXT_SIZE_MULTIPLIER = 2.5
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,8 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
|||||||
@Inject
|
@Inject
|
||||||
var context: Context? = null
|
var context: Context? = null
|
||||||
|
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
override fun onBind(message: ChatMessage) {
|
override fun onBind(message: ChatMessage) {
|
||||||
super.onBind(message)
|
super.onBind(message)
|
||||||
sharedApplication!!.componentApplication.inject(this)
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
@ -118,6 +120,15 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
|||||||
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
||||||
|
|
||||||
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
|
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, context!!, true)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processParentMessage(message: ChatMessage) {
|
private fun processParentMessage(message: ChatMessage) {
|
||||||
@ -204,6 +215,10 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
|||||||
return messageString1
|
return messageString1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TEXT_SIZE_MULTIPLIER = 2.5
|
const val TEXT_SIZE_MULTIPLIER = 2.5
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication;
|
|||||||
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.components.filebrowser.webdav.ReadFilesystemOperation;
|
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
||||||
|
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
|
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
|
||||||
import com.nextcloud.talk.models.database.CapabilitiesUtil;
|
import com.nextcloud.talk.models.database.CapabilitiesUtil;
|
||||||
import com.nextcloud.talk.models.database.UserEntity;
|
import com.nextcloud.talk.models.database.UserEntity;
|
||||||
@ -111,8 +112,13 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
|
|
||||||
ProgressBar progressBar;
|
ProgressBar progressBar;
|
||||||
|
|
||||||
|
ReactionsInsideMessageBinding reactionsBinding;
|
||||||
|
|
||||||
View clickView;
|
View clickView;
|
||||||
|
|
||||||
|
ReactionsInterface reactionsInterface;
|
||||||
|
PreviewMessageInterface previewMessageInterface;
|
||||||
|
|
||||||
public MagicPreviewMessageViewHolder(View itemView, Object payload) {
|
public MagicPreviewMessageViewHolder(View itemView, Object payload) {
|
||||||
super(itemView, payload);
|
super(itemView, payload);
|
||||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
||||||
@ -185,6 +191,7 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get(KEY_PATH), message.activeUser);
|
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get(KEY_PATH), message.activeUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(message.activeUser != null && message.activeUser.getUsername() != null && message.activeUser.getBaseUrl() != null){
|
||||||
String accountString =
|
String accountString =
|
||||||
message.activeUser.getUsername() + "@" +
|
message.activeUser.getUsername() + "@" +
|
||||||
message.activeUser.getBaseUrl()
|
message.activeUser.getBaseUrl()
|
||||||
@ -204,6 +211,10 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
onMessageViewLongClick(message, accountString);
|
onMessageViewLongClick(message, accountString);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "failed to set click listener because activeUser, username or baseUrl were null");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// check if download worker is already running
|
// check if download worker is already running
|
||||||
String fileId = message.getSelectedIndividualHashMap().get(KEY_ID);
|
String fileId = message.getSelectedIndividualHashMap().get(KEY_ID);
|
||||||
@ -246,8 +257,17 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemView.setTag(REPLYABLE_VIEW_TAG, message.isReplyable());
|
itemView.setTag(REPLYABLE_VIEW_TAG, message.isReplyable());
|
||||||
}
|
|
||||||
|
|
||||||
|
reactionsBinding = getReactionsBinding();
|
||||||
|
new Reaction().showReactions(message, reactionsBinding, context, false);
|
||||||
|
reactionsBinding.reactionsEmojiWrapper.setOnClickListener(l -> {
|
||||||
|
reactionsInterface.onClickReactions(message);
|
||||||
|
});
|
||||||
|
reactionsBinding.reactionsEmojiWrapper.setOnLongClickListener(l -> {
|
||||||
|
reactionsInterface.onLongClickReactions(message);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Drawable getDrawableFromContactDetails(Context context, String base64) {
|
private Drawable getDrawableFromContactDetails(Context context, String base64) {
|
||||||
Drawable drawable = null;
|
Drawable drawable = null;
|
||||||
@ -283,6 +303,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
|
|
||||||
public abstract ProgressBar getPreviewContactProgressBar();
|
public abstract ProgressBar getPreviewContactProgressBar();
|
||||||
|
|
||||||
|
public abstract ReactionsInsideMessageBinding getReactionsBinding();
|
||||||
|
|
||||||
private void openOrDownloadFile(ChatMessage message) {
|
private void openOrDownloadFile(ChatMessage message) {
|
||||||
String filename = message.getSelectedIndividualHashMap().get(KEY_NAME);
|
String filename = message.getSelectedIndividualHashMap().get(KEY_NAME);
|
||||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
||||||
@ -410,6 +432,7 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
|
|
||||||
private void onMessageViewLongClick(ChatMessage message, String accountString) {
|
private void onMessageViewLongClick(ChatMessage message, String accountString) {
|
||||||
if (isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
|
if (isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
|
||||||
|
previewMessageInterface.onPreviewMessageLongClick(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,4 +614,12 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void assignReactionInterface(ReactionsInterface reactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assignPreviewMessageInterface(PreviewMessageInterface previewMessageInterface) {
|
||||||
|
this.previewMessageInterface = previewMessageInterface;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,8 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
|||||||
@Inject
|
@Inject
|
||||||
var context: Context? = null
|
var context: Context? = null
|
||||||
|
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBind(message: ChatMessage) {
|
override fun onBind(message: ChatMessage) {
|
||||||
super.onBind(message)
|
super.onBind(message)
|
||||||
@ -84,7 +86,6 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
|||||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
binding.messageTime.layoutParams = layoutParams
|
binding.messageTime.layoutParams = layoutParams
|
||||||
binding.messageText.text = message.text
|
binding.messageText.text = message.text
|
||||||
binding.messageText.isEnabled = false
|
|
||||||
|
|
||||||
// parent message handling
|
// parent message handling
|
||||||
setParentMessageDataOnMessageItem(message)
|
setParentMessageDataOnMessageItem(message)
|
||||||
@ -112,6 +113,15 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
|||||||
|
|
||||||
// geo-location
|
// geo-location
|
||||||
setLocationDataOnMessageItem(message)
|
setLocationDataOnMessageItem(message)
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, context!!, true)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")
|
@SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")
|
||||||
@ -245,6 +255,10 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
|||||||
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "LocOutMessageView"
|
private const val TAG = "LocOutMessageView"
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import android.widget.ProgressBar;
|
|||||||
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
|
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
|
||||||
|
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||||
|
|
||||||
import androidx.emoji.widget.EmojiTextView;
|
import androidx.emoji.widget.EmojiTextView;
|
||||||
|
|
||||||
@ -77,4 +78,7 @@ public class OutcomingPreviewMessageViewHolder extends MagicPreviewMessageViewHo
|
|||||||
public ProgressBar getPreviewContactProgressBar() {
|
public ProgressBar getPreviewContactProgressBar() {
|
||||||
return binding.contactProgressBar;
|
return binding.contactProgressBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactionsInsideMessageBinding getReactionsBinding() { return binding.reactions; }
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
|
|||||||
lateinit var handler: Handler
|
lateinit var handler: Handler
|
||||||
|
|
||||||
lateinit var voiceMessageInterface: VoiceMessageInterface
|
lateinit var voiceMessageInterface: VoiceMessageInterface
|
||||||
|
lateinit var reactionsInterface: ReactionsInterface
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBind(message: ChatMessage) {
|
override fun onBind(message: ChatMessage) {
|
||||||
@ -129,6 +130,15 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
||||||
|
|
||||||
|
Reaction().showReactions(message, binding.reactions, context!!, true)
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
|
||||||
|
reactionsInterface.onClickReactions(message)
|
||||||
|
}
|
||||||
|
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
|
||||||
|
reactionsInterface.onLongClickReactions(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResetVoiceMessageState(message: ChatMessage) {
|
private fun handleResetVoiceMessageState(message: ChatMessage) {
|
||||||
@ -275,10 +285,14 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignAdapter(voiceMessageInterface: VoiceMessageInterface) {
|
fun assignVoiceMessageInterface(voiceMessageInterface: VoiceMessageInterface) {
|
||||||
this.voiceMessageInterface = voiceMessageInterface
|
this.voiceMessageInterface = voiceMessageInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||||
|
this.reactionsInterface = reactionsInterface
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "VoiceOutMessageView"
|
private const val TAG = "VoiceOutMessageView"
|
||||||
private const val SEEKBAR_START: Int = 0
|
private const val SEEKBAR_START: Int = 0
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.nextcloud.talk.adapters.messages
|
||||||
|
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
|
|
||||||
|
interface PreviewMessageInterface {
|
||||||
|
fun onPreviewMessageLongClick(chatMessage: ChatMessage)
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* Parts related to account import were either copied from or inspired by the great work done by David Luhmer at:
|
||||||
|
* https://github.com/nextcloud/ownCloud-Account-Importer
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.adapters.messages
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
|
import com.vanniktech.emoji.EmojiTextView
|
||||||
|
|
||||||
|
class Reaction {
|
||||||
|
fun showReactions(
|
||||||
|
message: ChatMessage,
|
||||||
|
binding: ReactionsInsideMessageBinding,
|
||||||
|
context: Context,
|
||||||
|
useLightColorForText: Boolean
|
||||||
|
) {
|
||||||
|
binding.reactionsEmojiWrapper.removeAllViews()
|
||||||
|
if (message.reactions != null && message.reactions.isNotEmpty()) {
|
||||||
|
|
||||||
|
var remainingEmojisToDisplay = MAX_EMOJIS_TO_DISPLAY
|
||||||
|
val showInfoAboutMoreEmojis = message.reactions.size > MAX_EMOJIS_TO_DISPLAY
|
||||||
|
for ((emoji, amount) in message.reactions) {
|
||||||
|
val reactionEmoji = EmojiTextView(context)
|
||||||
|
reactionEmoji.text = emoji
|
||||||
|
binding.reactionsEmojiWrapper.addView(reactionEmoji)
|
||||||
|
|
||||||
|
val reactionAmount = TextView(context)
|
||||||
|
|
||||||
|
if (amount > 1) {
|
||||||
|
if (useLightColorForText) {
|
||||||
|
reactionAmount.setTextColor(ContextCompat.getColor(context, R.color.nc_grey))
|
||||||
|
}
|
||||||
|
reactionAmount.text = amount.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val params = RelativeLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
params.setMargins(
|
||||||
|
DisplayUtils.convertDpToPixel(EMOJI_START_MARGIN, context).toInt(),
|
||||||
|
0,
|
||||||
|
DisplayUtils.convertDpToPixel(EMOJI_END_MARGIN, context).toInt(),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
reactionAmount.layoutParams = params
|
||||||
|
binding.reactionsEmojiWrapper.addView(reactionAmount)
|
||||||
|
|
||||||
|
remainingEmojisToDisplay--
|
||||||
|
if (remainingEmojisToDisplay == 0 && showInfoAboutMoreEmojis) {
|
||||||
|
val infoAboutMoreEmojis = TextView(context)
|
||||||
|
infoAboutMoreEmojis.text = EMOJI_MORE
|
||||||
|
binding.reactionsEmojiWrapper.addView(infoAboutMoreEmojis)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAX_EMOJIS_TO_DISPLAY = 4
|
||||||
|
const val EMOJI_START_MARGIN: Float = 2F
|
||||||
|
const val EMOJI_END_MARGIN: Float = 8F
|
||||||
|
const val EMOJI_MORE = "…"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.nextcloud.talk.adapters.messages
|
||||||
|
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
|
|
||||||
|
interface ReactionsInterface {
|
||||||
|
fun onClickReactions(chatMessage: ChatMessage)
|
||||||
|
fun onLongClickReactions(chatMessage: ChatMessage)
|
||||||
|
}
|
@ -49,10 +49,26 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
|
|||||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
super.onBindViewHolder(holder, position);
|
super.onBindViewHolder(holder, position);
|
||||||
|
|
||||||
if (holder instanceof IncomingVoiceMessageViewHolder) {
|
if (holder instanceof MagicIncomingTextMessageViewHolder) {
|
||||||
((IncomingVoiceMessageViewHolder) holder).assignAdapter(chatController);
|
((MagicIncomingTextMessageViewHolder) holder).assignReactionInterface(chatController);
|
||||||
|
} else if (holder instanceof MagicOutcomingTextMessageViewHolder) {
|
||||||
|
((MagicOutcomingTextMessageViewHolder) holder).assignReactionInterface(chatController);
|
||||||
|
|
||||||
|
} else if (holder instanceof IncomingLocationMessageViewHolder) {
|
||||||
|
((IncomingLocationMessageViewHolder) holder).assignReactionInterface(chatController);
|
||||||
|
} else if (holder instanceof OutcomingLocationMessageViewHolder) {
|
||||||
|
((OutcomingLocationMessageViewHolder) holder).assignReactionInterface(chatController);
|
||||||
|
|
||||||
|
} else if (holder instanceof IncomingVoiceMessageViewHolder) {
|
||||||
|
((IncomingVoiceMessageViewHolder) holder).assignVoiceMessageInterface(chatController);
|
||||||
|
((IncomingVoiceMessageViewHolder) holder).assignReactionInterface(chatController);
|
||||||
} else if (holder instanceof OutcomingVoiceMessageViewHolder) {
|
} else if (holder instanceof OutcomingVoiceMessageViewHolder) {
|
||||||
((OutcomingVoiceMessageViewHolder) holder).assignAdapter(chatController);
|
((OutcomingVoiceMessageViewHolder) holder).assignVoiceMessageInterface(chatController);
|
||||||
|
((OutcomingVoiceMessageViewHolder) holder).assignReactionInterface(chatController);
|
||||||
|
|
||||||
|
} else if (holder instanceof MagicPreviewMessageViewHolder) {
|
||||||
|
((MagicPreviewMessageViewHolder) holder).assignPreviewMessageInterface(chatController);
|
||||||
|
((MagicPreviewMessageViewHolder) holder).assignReactionInterface(chatController);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ import com.nextcloud.talk.models.json.notifications.NotificationOverall;
|
|||||||
import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
|
import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
|
||||||
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
|
import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
|
||||||
import com.nextcloud.talk.models.json.push.PushRegistrationOverall;
|
import com.nextcloud.talk.models.json.push.PushRegistrationOverall;
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionsOverall;
|
||||||
import com.nextcloud.talk.models.json.search.ContactsByNumberOverall;
|
import com.nextcloud.talk.models.json.search.ContactsByNumberOverall;
|
||||||
import com.nextcloud.talk.models.json.signaling.SignalingOverall;
|
import com.nextcloud.talk.models.json.signaling.SignalingOverall;
|
||||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
|
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
|
||||||
@ -62,6 +63,7 @@ import retrofit2.http.FieldMap;
|
|||||||
import retrofit2.http.FormUrlEncoded;
|
import retrofit2.http.FormUrlEncoded;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Header;
|
import retrofit2.http.Header;
|
||||||
|
import retrofit2.http.Headers;
|
||||||
import retrofit2.http.Multipart;
|
import retrofit2.http.Multipart;
|
||||||
import retrofit2.http.POST;
|
import retrofit2.http.POST;
|
||||||
import retrofit2.http.PUT;
|
import retrofit2.http.PUT;
|
||||||
@ -488,4 +490,17 @@ public interface NcApi {
|
|||||||
@GET
|
@GET
|
||||||
Observable<StatusesOverall> getUserStatuses(@Header("Authorization") String authorization, @Url String url);
|
Observable<StatusesOverall> getUserStatuses(@Header("Authorization") String authorization, @Url String url);
|
||||||
|
|
||||||
|
|
||||||
|
@POST
|
||||||
|
Observable<GenericOverall> sendReaction(@Header("Authorization") String authorization, @Url String url,
|
||||||
|
@Query("reaction") String reaction);
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
Observable<GenericOverall> deleteReaction(@Header("Authorization") String authorization, @Url String url,
|
||||||
|
@Query("reaction") String reaction);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
Observable<ReactionsOverall> getReactions(@Header("Authorization") String authorization,
|
||||||
|
@Url String url,
|
||||||
|
@Query("reaction") String reaction);
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,8 @@ import com.nextcloud.talk.adapters.messages.MagicUnreadNoticeMessageViewHolder
|
|||||||
import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.OutcomingVoiceMessageViewHolder
|
||||||
|
import com.nextcloud.talk.adapters.messages.PreviewMessageInterface
|
||||||
|
import com.nextcloud.talk.adapters.messages.ReactionsInterface
|
||||||
import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter
|
import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter
|
||||||
import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
|
import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
|
||||||
import com.nextcloud.talk.api.NcApi
|
import com.nextcloud.talk.api.NcApi
|
||||||
@ -139,6 +141,7 @@ import com.nextcloud.talk.presenters.MentionAutocompletePresenter
|
|||||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
||||||
import com.nextcloud.talk.ui.dialog.AttachmentDialog
|
import com.nextcloud.talk.ui.dialog.AttachmentDialog
|
||||||
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
|
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
|
||||||
|
import com.nextcloud.talk.ui.dialog.ShowReactionsDialog
|
||||||
import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
|
import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
|
||||||
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
|
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
@ -203,7 +206,9 @@ class ChatController(args: Bundle) :
|
|||||||
MessagesListAdapter.Formatter<Date>,
|
MessagesListAdapter.Formatter<Date>,
|
||||||
MessagesListAdapter.OnMessageViewLongClickListener<IMessage>,
|
MessagesListAdapter.OnMessageViewLongClickListener<IMessage>,
|
||||||
ContentChecker<ChatMessage>,
|
ContentChecker<ChatMessage>,
|
||||||
VoiceMessageInterface {
|
VoiceMessageInterface,
|
||||||
|
ReactionsInterface,
|
||||||
|
PreviewMessageInterface {
|
||||||
|
|
||||||
private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind)
|
private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind)
|
||||||
|
|
||||||
@ -2087,7 +2092,7 @@ class ChatController(args: Bundle) :
|
|||||||
if (response.code() == HTTP_CODE_OK) {
|
if (response.code() == HTTP_CODE_OK) {
|
||||||
|
|
||||||
val chatOverall = response.body() as ChatOverall?
|
val chatOverall = response.body() as ChatOverall?
|
||||||
val chatMessageList = setDeletionFlagsAndRemoveInfomessages(chatOverall?.ocs!!.data)
|
val chatMessageList = handleSystemMessages(chatOverall?.ocs!!.data)
|
||||||
|
|
||||||
if (chatMessageList.isNotEmpty() &&
|
if (chatMessageList.isNotEmpty() &&
|
||||||
ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType
|
ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType
|
||||||
@ -2336,14 +2341,16 @@ class ChatController(args: Bundle) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setDeletionFlagsAndRemoveInfomessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
||||||
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
||||||
val chatMessageIterator = chatMessageMap.iterator()
|
val chatMessageIterator = chatMessageMap.iterator()
|
||||||
while (chatMessageIterator.hasNext()) {
|
while (chatMessageIterator.hasNext()) {
|
||||||
val currentMessage = chatMessageIterator.next()
|
val currentMessage = chatMessageIterator.next()
|
||||||
|
|
||||||
|
// setDeletionFlagsAndRemoveInfomessages
|
||||||
if (isInfoMessageAboutDeletion(currentMessage)) {
|
if (isInfoMessageAboutDeletion(currentMessage)) {
|
||||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage.id)) {
|
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage.id)) {
|
||||||
// if chatMessageMap doesnt't contain message to delete (this happens when lookingIntoFuture),
|
// if chatMessageMap doesn't contain message to delete (this happens when lookingIntoFuture),
|
||||||
// the message to delete has to be modified directly inside the adapter
|
// the message to delete has to be modified directly inside the adapter
|
||||||
setMessageAsDeleted(currentMessage.value.parentMessage)
|
setMessageAsDeleted(currentMessage.value.parentMessage)
|
||||||
} else {
|
} else {
|
||||||
@ -2351,6 +2358,15 @@ class ChatController(args: Bundle) :
|
|||||||
}
|
}
|
||||||
chatMessageIterator.remove()
|
chatMessageIterator.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete reactions system messages
|
||||||
|
else if (isReactionsMessage(currentMessage)) {
|
||||||
|
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage.id)) {
|
||||||
|
updateAdapterForReaction(currentMessage.value.parentMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatMessageIterator.remove()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return chatMessageMap.values.toList()
|
return chatMessageMap.values.toList()
|
||||||
}
|
}
|
||||||
@ -2360,6 +2376,12 @@ class ChatController(args: Bundle) :
|
|||||||
.SystemMessageType.MESSAGE_DELETED
|
.SystemMessageType.MESSAGE_DELETED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isReactionsMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
||||||
|
return currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION ||
|
||||||
|
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_DELETED ||
|
||||||
|
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
|
||||||
|
}
|
||||||
|
|
||||||
private fun startACall(isVoiceOnlyCall: Boolean) {
|
private fun startACall(isVoiceOnlyCall: Boolean) {
|
||||||
if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) {
|
if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) {
|
||||||
Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
|
||||||
@ -2398,19 +2420,48 @@ class ChatController(args: Bundle) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMessageViewLongClick(view: View?, message: IMessage?) {
|
override fun onClickReactions(chatMessage: ChatMessage) {
|
||||||
if (hasVisibleItems(message as ChatMessage)) {
|
|
||||||
activity?.let {
|
activity?.let {
|
||||||
MessageActionsDialog(
|
ShowReactionsDialog(
|
||||||
activity!!,
|
activity!!,
|
||||||
this,
|
|
||||||
message,
|
|
||||||
conversationUser?.userId,
|
|
||||||
currentConversation,
|
currentConversation,
|
||||||
isShowMessageDeletionButton(message)
|
chatMessage,
|
||||||
|
conversationUser,
|
||||||
|
ncApi!!
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLongClickReactions(chatMessage: ChatMessage) {
|
||||||
|
openMessageActionsDialog(chatMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessageViewLongClick(view: View?, message: IMessage?) {
|
||||||
|
openMessageActionsDialog(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreviewMessageLongClick(chatMessage: ChatMessage) {
|
||||||
|
openMessageActionsDialog(chatMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openMessageActionsDialog(iMessage: IMessage?) {
|
||||||
|
val message = iMessage as ChatMessage
|
||||||
|
if (hasVisibleItems(message) && !isSystemMessage(message)) {
|
||||||
|
activity?.let {
|
||||||
|
MessageActionsDialog(
|
||||||
|
this,
|
||||||
|
message,
|
||||||
|
conversationUser,
|
||||||
|
currentConversation,
|
||||||
|
isShowMessageDeletionButton(message),
|
||||||
|
ncApi!!
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSystemMessage(message: ChatMessage): Boolean {
|
||||||
|
return ChatMessage.MessageType.SYSTEM_MESSAGE == message.getMessageType()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteMessage(message: IMessage?) {
|
fun deleteMessage(message: IMessage?) {
|
||||||
@ -2680,6 +2731,29 @@ class ChatController(args: Bundle) :
|
|||||||
adapter?.update(messageTemp)
|
adapter?.update(messageTemp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateAdapterForReaction(message: IMessage?) {
|
||||||
|
val messageTemp = message as ChatMessage
|
||||||
|
|
||||||
|
messageTemp.isOneToOneConversation =
|
||||||
|
currentConversation?.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||||
|
messageTemp.activeUser = conversationUser
|
||||||
|
|
||||||
|
adapter?.update(messageTemp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAdapterAfterSendReaction(message: ChatMessage, emoji: String) {
|
||||||
|
if (message.reactions == null) {
|
||||||
|
message.reactions = LinkedHashMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
var amount = message.reactions[emoji]
|
||||||
|
if (amount == null) {
|
||||||
|
amount = 0
|
||||||
|
}
|
||||||
|
message.reactions[emoji] = amount + 1
|
||||||
|
adapter?.update(message)
|
||||||
|
}
|
||||||
|
|
||||||
private fun isShowMessageDeletionButton(message: ChatMessage): Boolean {
|
private fun isShowMessageDeletionButton(message: ChatMessage): Boolean {
|
||||||
if (conversationUser == null) return false
|
if (conversationUser == null) return false
|
||||||
|
|
||||||
|
@ -277,6 +277,7 @@ class WebViewLoginController(args: Bundle? = null) : NewBaseController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
|
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
|
||||||
try {
|
try {
|
||||||
val sslCertificate = error.certificate
|
val sslCertificate = error.certificate
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
package com.nextcloud.talk.models.json.chat;
|
package com.nextcloud.talk.models.json.chat;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonIgnore;
|
import com.bluelinelabs.logansquare.annotation.JsonIgnore;
|
||||||
@ -40,6 +41,7 @@ import java.security.MessageDigest;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -50,6 +52,8 @@ import kotlin.text.Charsets;
|
|||||||
@Parcel
|
@Parcel
|
||||||
@JsonObject
|
@JsonObject
|
||||||
public class ChatMessage implements MessageContentType, MessageContentType.Image {
|
public class ChatMessage implements MessageContentType, MessageContentType.Image {
|
||||||
|
private static String TAG = "ChatMessage";
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public boolean isGrouped;
|
public boolean isGrouped;
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
@ -90,6 +94,8 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
|||||||
public Enum<ReadStatus> readStatus = ReadStatus.NONE;
|
public Enum<ReadStatus> readStatus = ReadStatus.NONE;
|
||||||
@JsonField(name = "messageType")
|
@JsonField(name = "messageType")
|
||||||
public String messageType;
|
public String messageType;
|
||||||
|
@JsonField(name = "reactions")
|
||||||
|
public LinkedHashMap<String, Integer> reactions;
|
||||||
|
|
||||||
public boolean isDownloadingVoiceMessage;
|
public boolean isDownloadingVoiceMessage;
|
||||||
public boolean resetVoiceMessage;
|
public boolean resetVoiceMessage;
|
||||||
@ -149,8 +155,15 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
|||||||
("file").getBytes(Charsets.UTF_8))) {
|
("file").getBytes(Charsets.UTF_8))) {
|
||||||
selectedIndividualHashMap = individualHashMap;
|
selectedIndividualHashMap = individualHashMap;
|
||||||
if (!isVoiceMessage()) {
|
if (!isVoiceMessage()) {
|
||||||
return (ApiUtils.getUrlForFilePreviewWithFileId(getActiveUser().getBaseUrl(),
|
if (getActiveUser() != null && getActiveUser().getBaseUrl() != null) {
|
||||||
individualHashMap.get("id"), NextcloudTalkApplication.Companion.getSharedApplication().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size)));
|
return (ApiUtils.getUrlForFilePreviewWithFileId(
|
||||||
|
getActiveUser().getBaseUrl(),
|
||||||
|
individualHashMap.get("id"),
|
||||||
|
NextcloudTalkApplication.Companion.getSharedApplication().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size)));
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "getActiveUser() or getActiveUser().getBaseUrl() were null when trying to " +
|
||||||
|
"getImageUrl()");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,7 +302,9 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAvatar() {
|
public String getAvatar() {
|
||||||
if (getActorType().equals("users")) {
|
if (getActiveUser() == null) {
|
||||||
|
return null;
|
||||||
|
} else if (getActorType().equals("users")) {
|
||||||
return ApiUtils.getUrlForAvatar(getActiveUser().getBaseUrl(), actorId, true);
|
return ApiUtils.getUrlForAvatar(getActiveUser().getBaseUrl(), actorId, true);
|
||||||
} else if (getActorType().equals("bridged")) {
|
} else if (getActorType().equals("bridged")) {
|
||||||
return ApiUtils.getUrlForAvatar(getActiveUser().getBaseUrl(), "bridge-bot",
|
return ApiUtils.getUrlForAvatar(getActiveUser().getBaseUrl(), "bridge-bot",
|
||||||
@ -657,6 +672,9 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
|||||||
MATTERBRIDGE_CONFIG_REMOVED,
|
MATTERBRIDGE_CONFIG_REMOVED,
|
||||||
MATTERBRIDGE_CONFIG_ENABLED,
|
MATTERBRIDGE_CONFIG_ENABLED,
|
||||||
MATTERBRIDGE_CONFIG_DISABLED,
|
MATTERBRIDGE_CONFIG_DISABLED,
|
||||||
CLEARED_CHAT
|
CLEARED_CHAT,
|
||||||
|
REACTION,
|
||||||
|
REACTION_DELETED,
|
||||||
|
REACTION_REVOKED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Andy Scherzinger
|
||||||
|
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.models.json.converters
|
||||||
|
|
||||||
|
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionVoter.ReactionActorType.DUMMY
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionVoter.ReactionActorType.GUESTS
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionVoter.ReactionActorType.USERS
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionVoter
|
||||||
|
|
||||||
|
class EnumReactionActorTypeConverter : StringBasedTypeConverter<ReactionVoter.ReactionActorType>() {
|
||||||
|
override fun getFromString(string: String): ReactionVoter.ReactionActorType {
|
||||||
|
return when (string) {
|
||||||
|
"guests" -> GUESTS
|
||||||
|
"users" -> USERS
|
||||||
|
else -> DUMMY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertToString(`object`: ReactionVoter.ReactionActorType?): String {
|
||||||
|
|
||||||
|
if (`object` == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (`object`) {
|
||||||
|
GUESTS -> "guests"
|
||||||
|
USERS -> "users"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,6 +65,9 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MODERAT
|
|||||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.OBJECT_SHARED
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.OBJECT_SHARED
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_REMOVED
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_REMOVED
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_SET
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_SET
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_DELETED
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_REVOKED
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY_OFF
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY_OFF
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_ADDED
|
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_ADDED
|
||||||
@ -161,6 +164,9 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
|||||||
"matterbridge_config_enabled" -> return MATTERBRIDGE_CONFIG_ENABLED
|
"matterbridge_config_enabled" -> return MATTERBRIDGE_CONFIG_ENABLED
|
||||||
"matterbridge_config_disabled" -> return MATTERBRIDGE_CONFIG_DISABLED
|
"matterbridge_config_disabled" -> return MATTERBRIDGE_CONFIG_DISABLED
|
||||||
"history_cleared" -> return CLEARED_CHAT
|
"history_cleared" -> return CLEARED_CHAT
|
||||||
|
"reaction" -> return REACTION
|
||||||
|
"reaction_deleted" -> return REACTION_DELETED
|
||||||
|
"reaction_revoked" -> return REACTION_REVOKED
|
||||||
else -> return DUMMY
|
else -> return DUMMY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,6 +220,9 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
|||||||
MATTERBRIDGE_CONFIG_ENABLED -> return "matterbridge_config_enabled"
|
MATTERBRIDGE_CONFIG_ENABLED -> return "matterbridge_config_enabled"
|
||||||
MATTERBRIDGE_CONFIG_DISABLED -> return "matterbridge_config_disabled"
|
MATTERBRIDGE_CONFIG_DISABLED -> return "matterbridge_config_disabled"
|
||||||
CLEARED_CHAT -> return "clear_history"
|
CLEARED_CHAT -> return "clear_history"
|
||||||
|
REACTION -> return "reaction"
|
||||||
|
REACTION_DELETED -> return "reaction_deleted"
|
||||||
|
REACTION_REVOKED -> return "reaction_revoked"
|
||||||
else -> return ""
|
else -> return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.models.json.reactions
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import com.nextcloud.talk.models.json.converters.EnumReactionActorTypeConverter
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class ReactionVoter(
|
||||||
|
@JsonField(name = ["actorType"], typeConverter = EnumReactionActorTypeConverter::class)
|
||||||
|
var actorType: ReactionActorType?,
|
||||||
|
@JsonField(name = ["actorId"])
|
||||||
|
var actorId: String?,
|
||||||
|
@JsonField(name = ["actorDisplayName"])
|
||||||
|
var actorDisplayName: String?,
|
||||||
|
@JsonField(name = ["timestamp"])
|
||||||
|
var timestamp: Long = 0
|
||||||
|
) : Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this(null, null, null, 0)
|
||||||
|
|
||||||
|
enum class ReactionActorType {
|
||||||
|
DUMMY, GUESTS, USERS
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.models.json.reactions
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import com.nextcloud.talk.models.json.generic.GenericOCS
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class ReactionsOCS(
|
||||||
|
@JsonField(name = ["data"])
|
||||||
|
var data: HashMap<String, List<ReactionVoter>>?
|
||||||
|
) : GenericOCS(), Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this(HashMap())
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.models.json.reactions
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class ReactionsOverall(
|
||||||
|
@JsonField(name = ["ocs"])
|
||||||
|
var ocs: ReactionsOCS?
|
||||||
|
) : Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this(null)
|
||||||
|
}
|
@ -32,7 +32,7 @@ data class Status(
|
|||||||
var userId: String?,
|
var userId: String?,
|
||||||
@JsonField(name = ["message"])
|
@JsonField(name = ["message"])
|
||||||
var message: String?,
|
var message: String?,
|
||||||
/* TODO: Change to enum */
|
/* TODO Change to enum */
|
||||||
@JsonField(name = ["messageId"])
|
@JsonField(name = ["messageId"])
|
||||||
var messageId: String?,
|
var messageId: String?,
|
||||||
@JsonField(name = ["messageIsPredefined"])
|
@JsonField(name = ["messageIsPredefined"])
|
||||||
@ -41,7 +41,7 @@ data class Status(
|
|||||||
var icon: String?,
|
var icon: String?,
|
||||||
@JsonField(name = ["clearAt"])
|
@JsonField(name = ["clearAt"])
|
||||||
var clearAt: Long = 0,
|
var clearAt: Long = 0,
|
||||||
/* TODO: Change to enum */
|
/* TODO Change to enum */
|
||||||
@JsonField(name = ["status"])
|
@JsonField(name = ["status"])
|
||||||
var status: String = "offline",
|
var status: String = "offline",
|
||||||
@JsonField(name = ["statusIsUserDefined"])
|
@JsonField(name = ["statusIsUserDefined"])
|
||||||
|
@ -293,7 +293,7 @@ class ConversationsListBottomDialog(
|
|||||||
|
|
||||||
dialogRouter!!.pushController(
|
dialogRouter!!.pushController(
|
||||||
|
|
||||||
// TODO: refresh conversation list after EntryMenuController finished (throw event? / pass controller
|
// TODO refresh conversation list after EntryMenuController finished (throw event? / pass controller
|
||||||
// into EntryMenuController to execute fetch data... ?!)
|
// into EntryMenuController to execute fetch data... ?!)
|
||||||
// for example if you set a password, the dialog items should be refreshed for the next time you open it
|
// for example if you set a password, the dialog items should be refreshed for the next time you open it
|
||||||
// without to manually have to refresh the conversations list
|
// without to manually have to refresh the conversations list
|
||||||
|
@ -20,42 +20,60 @@
|
|||||||
|
|
||||||
package com.nextcloud.talk.ui.dialog
|
package com.nextcloud.talk.ui.dialog
|
||||||
|
|
||||||
import android.app.Activity
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.annotation.NonNull
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.nextcloud.talk.BuildConfig
|
import com.nextcloud.talk.BuildConfig
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.api.NcApi
|
||||||
import com.nextcloud.talk.controllers.ChatController
|
import com.nextcloud.talk.controllers.ChatController
|
||||||
import com.nextcloud.talk.databinding.DialogMessageActionsBinding
|
import com.nextcloud.talk.databinding.DialogMessageActionsBinding
|
||||||
|
import com.nextcloud.talk.models.database.CapabilitiesUtil
|
||||||
|
import com.nextcloud.talk.models.database.UserEntity
|
||||||
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.generic.GenericOverall
|
||||||
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import com.vanniktech.emoji.EmojiPopup
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
class MessageActionsDialog(
|
class MessageActionsDialog(
|
||||||
val activity: Activity,
|
|
||||||
private val chatController: ChatController,
|
private val chatController: ChatController,
|
||||||
private val message: ChatMessage,
|
private val message: ChatMessage,
|
||||||
private val userId: String?,
|
private val user: UserEntity?,
|
||||||
private val currentConversation: Conversation?,
|
private val currentConversation: Conversation?,
|
||||||
private val showMessageDeletionButton: Boolean
|
private val showMessageDeletionButton: Boolean,
|
||||||
) : BottomSheetDialog(activity) {
|
private val ncApi: NcApi
|
||||||
|
) : BottomSheetDialog(chatController.activity!!, R.style.BottomSheetDialogThemeNoFloating) {
|
||||||
|
|
||||||
private lateinit var dialogMessageActionsBinding: DialogMessageActionsBinding
|
private lateinit var dialogMessageActionsBinding: DialogMessageActionsBinding
|
||||||
|
|
||||||
|
private lateinit var popup: EmojiPopup
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
dialogMessageActionsBinding = DialogMessageActionsBinding.inflate(layoutInflater)
|
dialogMessageActionsBinding = DialogMessageActionsBinding.inflate(layoutInflater)
|
||||||
setContentView(dialogMessageActionsBinding.root)
|
setContentView(dialogMessageActionsBinding.root)
|
||||||
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
|
initEmojiBar()
|
||||||
initMenuItemCopy(!message.isDeleted)
|
initMenuItemCopy(!message.isDeleted)
|
||||||
initMenuReplyToMessage(message.replyable)
|
initMenuReplyToMessage(message.replyable)
|
||||||
initMenuReplyPrivately(
|
initMenuReplyPrivately(
|
||||||
message.replyable &&
|
message.replyable &&
|
||||||
userId?.isNotEmpty() == true &&
|
user?.userId?.isNotEmpty() == true &&
|
||||||
userId != "?" &&
|
user?.userId != "?" &&
|
||||||
message.user.id.startsWith("users/") &&
|
message.user.id.startsWith("users/") &&
|
||||||
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId &&
|
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId &&
|
||||||
currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||||
@ -67,6 +85,69 @@ class MessageActionsDialog(
|
|||||||
ChatMessage.MessageType.SYSTEM_MESSAGE != message.getMessageType() &&
|
ChatMessage.MessageType.SYSTEM_MESSAGE != message.getMessageType() &&
|
||||||
BuildConfig.DEBUG
|
BuildConfig.DEBUG
|
||||||
)
|
)
|
||||||
|
|
||||||
|
initEmojiMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private fun initEmojiMore() {
|
||||||
|
dialogMessageActionsBinding.emojiMore.setOnTouchListener { v, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||||
|
popup.toggle()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
popup = EmojiPopup.Builder
|
||||||
|
.fromRootView(dialogMessageActionsBinding.root)
|
||||||
|
.setOnEmojiPopupShownListener {
|
||||||
|
dialogMessageActionsBinding.emojiMore.clearFocus()
|
||||||
|
dialogMessageActionsBinding.messageActions.visibility = View.GONE
|
||||||
|
}
|
||||||
|
.setOnEmojiClickListener { _, imageView ->
|
||||||
|
popup.dismiss()
|
||||||
|
sendReaction(message, imageView.unicode)
|
||||||
|
}
|
||||||
|
.setOnEmojiPopupDismissListener {
|
||||||
|
dialogMessageActionsBinding.emojiMore.clearFocus()
|
||||||
|
dialogMessageActionsBinding.messageActions.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
val imm: InputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as
|
||||||
|
InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(dialogMessageActionsBinding.emojiMore.windowToken, 0)
|
||||||
|
}
|
||||||
|
.build(dialogMessageActionsBinding.emojiMore)
|
||||||
|
dialogMessageActionsBinding.emojiMore.disableKeyboardInput(popup)
|
||||||
|
dialogMessageActionsBinding.emojiMore.forceSingleEmoji()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEmojiBar() {
|
||||||
|
if (CapabilitiesUtil.hasSpreedFeatureCapability(user, "reactions")) {
|
||||||
|
dialogMessageActionsBinding.emojiThumbsUp.setOnClickListener {
|
||||||
|
sendReaction(message, dialogMessageActionsBinding.emojiThumbsUp.text.toString())
|
||||||
|
}
|
||||||
|
dialogMessageActionsBinding.emojiThumbsDown.setOnClickListener {
|
||||||
|
sendReaction(message, dialogMessageActionsBinding.emojiThumbsDown.text.toString())
|
||||||
|
}
|
||||||
|
dialogMessageActionsBinding.emojiLaugh.setOnClickListener {
|
||||||
|
sendReaction(message, dialogMessageActionsBinding.emojiLaugh.text.toString())
|
||||||
|
}
|
||||||
|
dialogMessageActionsBinding.emojiHeart.setOnClickListener {
|
||||||
|
sendReaction(message, dialogMessageActionsBinding.emojiHeart.text.toString())
|
||||||
|
}
|
||||||
|
dialogMessageActionsBinding.emojiConfused.setOnClickListener {
|
||||||
|
sendReaction(message, dialogMessageActionsBinding.emojiConfused.text.toString())
|
||||||
|
}
|
||||||
|
dialogMessageActionsBinding.emojiSad.setOnClickListener {
|
||||||
|
sendReaction(message, dialogMessageActionsBinding.emojiSad.text.toString())
|
||||||
|
}
|
||||||
|
dialogMessageActionsBinding.emojiMore.setOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
dialogMessageActionsBinding.emojiBar.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
dialogMessageActionsBinding.emojiBar.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMenuMarkAsUnread(visible: Boolean) {
|
private fun initMenuMarkAsUnread(visible: Boolean) {
|
||||||
@ -150,8 +231,47 @@ class MessageActionsDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sendReaction(message: ChatMessage, emoji: String) {
|
||||||
|
val credentials = ApiUtils.getCredentials(user?.username, user?.token)
|
||||||
|
|
||||||
|
ncApi.sendReaction(
|
||||||
|
credentials,
|
||||||
|
ApiUtils.getUrlForMessageReaction(
|
||||||
|
user?.baseUrl,
|
||||||
|
currentConversation!!.token,
|
||||||
|
message.id
|
||||||
|
),
|
||||||
|
emoji
|
||||||
|
)
|
||||||
|
?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(object : Observer<GenericOverall> {
|
||||||
|
override fun onSubscribe(d: Disposable) {
|
||||||
|
// unused atm
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNext(@NonNull genericOverall: GenericOverall) {
|
||||||
|
val statusCode = genericOverall.ocs.meta.statusCode
|
||||||
|
if (statusCode == HTTP_CREATED) {
|
||||||
|
chatController.updateAdapterAfterSendReaction(message, emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
Log.e(TAG, "error while sending reaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "MessageActionsDialog"
|
||||||
private const val ACTOR_LENGTH = 6
|
private const val ACTOR_LENGTH = 6
|
||||||
private const val NO_PREVIOUS_MESSAGE_ID: Int = -1
|
private const val NO_PREVIOUS_MESSAGE_ID: Int = -1
|
||||||
|
private const val HTTP_OK: Int = 200
|
||||||
|
private const val HTTP_CREATED: Int = 201
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,351 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Andy Scherzinger
|
||||||
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||||
|
* Copyright (C) 2022 Marcel Hibbe (dev@mhibbe.de)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* Parts related to account import were either copied from or inspired by the great work done by David Luhmer at:
|
||||||
|
* https://github.com/nextcloud/ownCloud-Account-Importer
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.adapters.ReactionItem
|
||||||
|
import com.nextcloud.talk.adapters.ReactionItemClickListener
|
||||||
|
import com.nextcloud.talk.adapters.ReactionsAdapter
|
||||||
|
import com.nextcloud.talk.api.NcApi
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.databinding.DialogMessageReactionsBinding
|
||||||
|
import com.nextcloud.talk.databinding.ItemReactionsTabBinding
|
||||||
|
import com.nextcloud.talk.models.database.UserEntity
|
||||||
|
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||||
|
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||||
|
import com.nextcloud.talk.models.json.generic.GenericOverall
|
||||||
|
import com.nextcloud.talk.models.json.reactions.ReactionsOverall
|
||||||
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.Comparator
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class ShowReactionsDialog(
|
||||||
|
activity: Activity,
|
||||||
|
private val currentConversation: Conversation?,
|
||||||
|
private val chatMessage: ChatMessage,
|
||||||
|
private val userEntity: UserEntity?,
|
||||||
|
private val ncApi: NcApi
|
||||||
|
) : BottomSheetDialog(activity), ReactionItemClickListener {
|
||||||
|
|
||||||
|
private lateinit var binding: DialogMessageReactionsBinding
|
||||||
|
|
||||||
|
private var adapter: ReactionsAdapter? = null
|
||||||
|
|
||||||
|
private val tagAll: String? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = DialogMessageReactionsBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
adapter = ReactionsAdapter(this, userEntity)
|
||||||
|
binding.reactionsList.adapter = adapter
|
||||||
|
binding.reactionsList.layoutManager = LinearLayoutManager(context)
|
||||||
|
initEmojiReactions()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEmojiReactions() {
|
||||||
|
adapter?.list?.clear()
|
||||||
|
if (chatMessage.reactions != null && chatMessage.reactions.isNotEmpty()) {
|
||||||
|
var reactionsTotal = 0
|
||||||
|
for ((emoji, amount) in chatMessage.reactions) {
|
||||||
|
reactionsTotal = reactionsTotal.plus(amount as Int)
|
||||||
|
val tab: TabLayout.Tab = binding.emojiReactionsTabs.newTab() // Create a new Tab names "First Tab"
|
||||||
|
|
||||||
|
val itemBinding = ItemReactionsTabBinding.inflate(layoutInflater)
|
||||||
|
itemBinding.reactionTab.tag = emoji
|
||||||
|
itemBinding.reactionIcon.text = emoji
|
||||||
|
itemBinding.reactionCount.text = amount.toString()
|
||||||
|
tab.customView = itemBinding.root
|
||||||
|
|
||||||
|
binding.emojiReactionsTabs.addTab(tab)
|
||||||
|
}
|
||||||
|
|
||||||
|
val tab: TabLayout.Tab = binding.emojiReactionsTabs.newTab() // Create a new Tab names "First Tab"
|
||||||
|
|
||||||
|
val itemBinding = ItemReactionsTabBinding.inflate(layoutInflater)
|
||||||
|
itemBinding.reactionTab.tag = tagAll
|
||||||
|
itemBinding.reactionIcon.text = context.getString(R.string.reactions_tab_all)
|
||||||
|
itemBinding.reactionCount.text = reactionsTotal.toString()
|
||||||
|
tab.customView = itemBinding.root
|
||||||
|
|
||||||
|
binding.emojiReactionsTabs.addTab(tab, 0)
|
||||||
|
|
||||||
|
binding.emojiReactionsTabs.getTabAt(0)?.select()
|
||||||
|
|
||||||
|
binding.emojiReactionsTabs.addOnTabSelectedListener(object : OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||||
|
// called when a tab is reselected
|
||||||
|
updateParticipantsForEmoji(chatMessage, tab.customView?.tag as String?)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||||
|
// called when a tab is reselected
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||||
|
// called when a tab is reselected
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateParticipantsForEmoji(chatMessage, tagAll)
|
||||||
|
}
|
||||||
|
adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateParticipantsForEmoji(chatMessage: ChatMessage, emoji: String?) {
|
||||||
|
adapter?.list?.clear()
|
||||||
|
|
||||||
|
val credentials = ApiUtils.getCredentials(userEntity?.username, userEntity?.token)
|
||||||
|
|
||||||
|
ncApi.getReactions(
|
||||||
|
credentials,
|
||||||
|
ApiUtils.getUrlForMessageReaction(
|
||||||
|
userEntity?.baseUrl,
|
||||||
|
currentConversation!!.token,
|
||||||
|
chatMessage.id
|
||||||
|
),
|
||||||
|
emoji
|
||||||
|
)
|
||||||
|
?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(object : Observer<ReactionsOverall> {
|
||||||
|
override fun onSubscribe(d: Disposable) {
|
||||||
|
// unused atm
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNext(@NonNull reactionsOverall: ReactionsOverall) {
|
||||||
|
val reactionVoters: ArrayList<ReactionItem> = ArrayList()
|
||||||
|
if (reactionsOverall.ocs?.data != null) {
|
||||||
|
val map = reactionsOverall.ocs?.data
|
||||||
|
for (key in map!!.keys) {
|
||||||
|
for (reactionVoter in reactionsOverall.ocs?.data!![key]!!) {
|
||||||
|
reactionVoters.add(ReactionItem(reactionVoter, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(reactionVoters, ReactionComparator(userEntity?.userId))
|
||||||
|
|
||||||
|
adapter?.list?.addAll(reactionVoters)
|
||||||
|
adapter?.notifyDataSetChanged()
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "no voters for this reaction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
Log.e(TAG, "failed to retrieve list of reaction voters")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
// unused atm
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(reactionItem: ReactionItem) {
|
||||||
|
if (reactionItem.reactionVoter.actorId?.equals(userEntity?.userId) == true) {
|
||||||
|
deleteReaction(chatMessage, reactionItem.reaction!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteReaction(message: ChatMessage, emoji: String) {
|
||||||
|
val credentials = ApiUtils.getCredentials(userEntity?.username, userEntity?.token)
|
||||||
|
|
||||||
|
ncApi.deleteReaction(
|
||||||
|
credentials,
|
||||||
|
ApiUtils.getUrlForMessageReaction(
|
||||||
|
userEntity?.baseUrl,
|
||||||
|
currentConversation!!.token,
|
||||||
|
message.id
|
||||||
|
),
|
||||||
|
emoji
|
||||||
|
)
|
||||||
|
?.subscribeOn(Schedulers.io())
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribe(object : Observer<GenericOverall> {
|
||||||
|
override fun onSubscribe(d: Disposable) {
|
||||||
|
// unused atm
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNext(@NonNull genericOverall: GenericOverall) {
|
||||||
|
Log.d(TAG, "deleted reaction: $emoji")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
Log.e(TAG, "error while deleting reaction: $emoji")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "ShowReactionsDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReactionComparator(val activeUser: String?) : Comparator<ReactionItem> {
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
override fun compare(reactionItem1: ReactionItem?, reactionItem2: ReactionItem?): Int {
|
||||||
|
// sort by emoji, own account, display-name, timestamp, actor-id
|
||||||
|
|
||||||
|
if (reactionItem1 == null && reactionItem2 == null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (reactionItem1 == null) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if (reactionItem2 == null) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// emoji
|
||||||
|
val reaction = StringComparator().compare(reactionItem1.reaction, reactionItem2.reaction)
|
||||||
|
if (reaction != 0) {
|
||||||
|
return reaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// own account
|
||||||
|
val ownAccount = compareOwnAccount(
|
||||||
|
activeUser,
|
||||||
|
reactionItem1.reactionVoter.actorId,
|
||||||
|
reactionItem2.reactionVoter.actorId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (ownAccount != 0) {
|
||||||
|
return ownAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
// display-name
|
||||||
|
val displayName = StringComparator()
|
||||||
|
.compare(
|
||||||
|
reactionItem1.reactionVoter.actorDisplayName,
|
||||||
|
reactionItem2.reactionVoter.actorDisplayName
|
||||||
|
)
|
||||||
|
|
||||||
|
if (displayName != 0) {
|
||||||
|
return displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
// timestamp
|
||||||
|
val timestamp = LongComparator()
|
||||||
|
.compare(
|
||||||
|
reactionItem1.reactionVoter.timestamp,
|
||||||
|
reactionItem2.reactionVoter.timestamp
|
||||||
|
)
|
||||||
|
|
||||||
|
if (timestamp != 0) {
|
||||||
|
return timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// actor-id
|
||||||
|
val actorId = StringComparator()
|
||||||
|
.compare(
|
||||||
|
reactionItem1.reactionVoter.actorId,
|
||||||
|
reactionItem2.reactionVoter.actorId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (actorId != 0) {
|
||||||
|
return actorId
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
fun compareOwnAccount(activeUser: String?, actorId1: String?, actorId2: String?): Int {
|
||||||
|
val reactionVote1Active = activeUser == actorId1
|
||||||
|
val reactionVote2Active = activeUser == actorId2
|
||||||
|
|
||||||
|
if (!reactionVote1Active && !reactionVote2Active || reactionVote1Active && reactionVote2Active) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeUser == null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reactionVote1Active) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (reactionVote2Active) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class StringComparator : Comparator<String?> {
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
override fun compare(obj1: String?, obj2: String?): Int {
|
||||||
|
if (obj1 === obj2) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (obj1 == null) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return if (obj2 == null) {
|
||||||
|
1
|
||||||
|
} else obj1.lowercase().compareTo(obj2.lowercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LongComparator : Comparator<Long?> {
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
override fun compare(obj1: Long?, obj2: Long?): Int {
|
||||||
|
if (obj1 === obj2) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (obj1 == null) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return if (obj2 == null) {
|
||||||
|
1
|
||||||
|
} else obj1.compareTo(obj2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -441,4 +441,11 @@ public class ApiUtils {
|
|||||||
public static String getUrlForUserStatuses(String baseUrl) {
|
public static String getUrlForUserStatuses(String baseUrl) {
|
||||||
return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/statuses";
|
return baseUrl + ocsApiVersion + "/apps/user_status/api/v1/statuses";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getUrlForMessageReaction(String baseUrl,
|
||||||
|
String roomToken,
|
||||||
|
String messageId) {
|
||||||
|
return baseUrl + ocsApiVersion + spreedApiVersion + "/reaction/" + roomToken + "/" + messageId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ package com.nextcloud.talk.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
// TODO: improve log handling. https://github.com/nextcloud/talk-android/issues/1376
|
// TODO improve log handling. https://github.com/nextcloud/talk-android/issues/1376
|
||||||
// writing logs to a file is temporarily disabled to avoid huge logfiles.
|
// writing logs to a file is temporarily disabled to avoid huge logfiles.
|
||||||
|
|
||||||
object LoggingUtils {
|
object LoggingUtils {
|
||||||
|
@ -243,7 +243,7 @@ public interface AppPreferences {
|
|||||||
@KeyByString("phone_book_integration")
|
@KeyByString("phone_book_integration")
|
||||||
void setPhoneBookIntegration(boolean value);
|
void setPhoneBookIntegration(boolean value);
|
||||||
|
|
||||||
// TODO: Remove in 13.0.0
|
// TODO Remove in 13.0.0
|
||||||
@KeyByString("link_previews")
|
@KeyByString("link_previews")
|
||||||
@RemoveMethod
|
@RemoveMethod
|
||||||
void removeLinkPreviews();
|
void removeLinkPreviews();
|
||||||
|
@ -151,7 +151,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
|||||||
public void restartWebSocket() {
|
public void restartWebSocket() {
|
||||||
reconnecting = true;
|
reconnecting = true;
|
||||||
|
|
||||||
// TODO: when improving logging, keep in mind this issue: https://github.com/nextcloud/talk-android/issues/1013
|
// TODO when improving logging, keep in mind this issue: https://github.com/nextcloud/talk-android/issues/1013
|
||||||
Log.d(TAG, "restartWebSocket: " + connectionUrl);
|
Log.d(TAG, "restartWebSocket: " + connectionUrl);
|
||||||
Request request = new Request.Builder().url(connectionUrl).build();
|
Request request = new Request.Builder().url(connectionUrl).build();
|
||||||
okHttpClient.newWebSocket(request, this);
|
okHttpClient.newWebSocket(request, this);
|
||||||
|
@ -18,8 +18,14 @@
|
|||||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<vector android:autoMirrored="true" android:height="24dp"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
android:width="24dp"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:height="24dp"
|
||||||
<path android:fillColor="@color/medium_emphasis_text" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
android:autoMirrored="true"
|
||||||
|
android:tint="@color/medium_emphasis_text"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
26
app/src/main/res/drawable/ic_dots_horizontal.xml
Normal file
26
app/src/main/res/drawable/ic_dots_horizontal.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!--
|
||||||
|
@author Google LLC
|
||||||
|
Copyright (C) 2021 Google LLC
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="@color/high_emphasis_menu_icon"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z" />
|
||||||
|
</vector>
|
@ -23,7 +23,6 @@
|
|||||||
android:id="@+id/bottom_sheet"
|
android:id="@+id/bottom_sheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
|
||||||
android:paddingStart="@dimen/standard_padding"
|
android:paddingStart="@dimen/standard_padding"
|
||||||
android:paddingTop="@dimen/standard_padding"
|
android:paddingTop="@dimen/standard_padding"
|
||||||
android:paddingEnd="@dimen/standard_half_padding">
|
android:paddingEnd="@dimen/standard_half_padding">
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="@dimen/standard_padding"
|
android:paddingStart="@dimen/standard_padding"
|
||||||
android:paddingEnd="@dimen/standard_padding"
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
@ -56,7 +55,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_baseline_person_24"
|
android:src="@drawable/ic_baseline_person_24"
|
||||||
app:tint="@color/colorPrimary" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/shareContactText"
|
android:id="@+id/shareContactText"
|
||||||
@ -87,7 +86,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_baseline_location_on_24"
|
android:src="@drawable/ic_baseline_location_on_24"
|
||||||
app:tint="@color/colorPrimary" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/txt_share_location"
|
android:id="@+id/txt_share_location"
|
||||||
@ -118,7 +117,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_baseline_photo_camera_24"
|
android:src="@drawable/ic_baseline_photo_camera_24"
|
||||||
app:tint="@color/colorPrimary" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/txt_attach_picture_from_cam"
|
android:id="@+id/txt_attach_picture_from_cam"
|
||||||
@ -149,7 +148,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/upload"
|
android:src="@drawable/upload"
|
||||||
app:tint="@color/colorPrimary" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/txt_attach_file_from_local"
|
android:id="@+id/txt_attach_file_from_local"
|
||||||
@ -180,7 +179,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_share_variant"
|
android:src="@drawable/ic_share_variant"
|
||||||
app:tint="@color/colorPrimary" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/txt_attach_file_from_cloud"
|
android:id="@+id/txt_attach_file_from_cloud"
|
||||||
|
@ -49,11 +49,11 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/audio_output_bluetooth_icon"
|
android:id="@+id/audio_output_bluetooth_icon"
|
||||||
android:layout_width="11dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="12dp"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_baseline_bluetooth_audio_24"
|
android:src="@drawable/ic_baseline_bluetooth_audio_24"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon_inverse" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/audio_output_bluetooth_text"
|
android:id="@+id/audio_output_bluetooth_text"
|
||||||
@ -84,7 +84,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_volume_up_white_24dp"
|
android:src="@drawable/ic_volume_up_white_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon_inverse" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/audio_output_speaker_text"
|
android:id="@+id/audio_output_speaker_text"
|
||||||
@ -115,7 +115,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_baseline_phone_in_talk_24"
|
android:src="@drawable/ic_baseline_phone_in_talk_24"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon_inverse" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/audio_output_earspeaker_text"
|
android:id="@+id/audio_output_earspeaker_text"
|
||||||
@ -146,7 +146,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_baseline_headset_mic_24"
|
android:src="@drawable/ic_baseline_headset_mic_24"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon_inverse" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/audio_output_wired_headset_text"
|
android:id="@+id/audio_output_wired_headset_text"
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="@dimen/standard_padding"
|
android:paddingStart="@dimen/standard_padding"
|
||||||
android:paddingEnd="@dimen/standard_padding"
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="@dimen/standard_padding"
|
android:paddingStart="@dimen/standard_padding"
|
||||||
android:paddingEnd="@dimen/standard_padding"
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
@ -60,7 +59,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_star_border_black_24dp"
|
android:src="@drawable/ic_star_border_black_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -87,7 +86,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_star_black_24dp"
|
android:src="@drawable/ic_star_black_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -114,7 +113,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_eye"
|
android:src="@drawable/ic_eye"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -141,7 +140,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_pencil_grey600_24dp"
|
android:src="@drawable/ic_pencil_grey600_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -168,7 +167,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_link_grey600_24px"
|
android:src="@drawable/ic_link_grey600_24px"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -195,7 +194,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_lock_grey600_24px"
|
android:src="@drawable/ic_lock_grey600_24px"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -222,7 +221,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_lock_open_grey600_24dp"
|
android:src="@drawable/ic_lock_open_grey600_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -249,7 +248,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_lock_plus_grey600_24dp"
|
android:src="@drawable/ic_lock_plus_grey600_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -276,7 +275,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_delete_grey600_24dp"
|
android:src="@drawable/ic_delete_grey600_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -303,7 +302,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_link_grey600_24px"
|
android:src="@drawable/ic_link_grey600_24px"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -330,7 +329,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_group_grey600_24px"
|
android:src="@drawable/ic_group_grey600_24px"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -357,7 +356,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_exit_to_app_black_24dp"
|
android:src="@drawable/ic_exit_to_app_black_24dp"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -23,104 +23,97 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="@dimen/standard_padding"
|
|
||||||
android:paddingEnd="@dimen/standard_padding"
|
|
||||||
android:paddingBottom="@dimen/standard_half_padding">
|
android:paddingBottom="@dimen/standard_half_padding">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_copy_message"
|
android:id="@+id/emojiBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:layout_marginStart="@dimen/standard_eighth_margin"
|
||||||
|
android:layout_marginEnd="@dimen/zero"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal">
|
||||||
tools:ignore="UseCompoundDrawables">
|
|
||||||
|
|
||||||
<ImageView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/menu_icon_copy_message"
|
android:id="@+id/emojiThumbsUp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="@dimen/activity_row_layout_height"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/activity_row_layout_height"
|
||||||
android:contentDescription="@null"
|
android:layout_weight="1"
|
||||||
android:src="@drawable/ic_content_copy"
|
android:cursorVisible="false"
|
||||||
app:tint="@color/grey_600" />
|
android:gravity="center"
|
||||||
|
android:text="@string/emoji_thumbsUp"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/menu_text_copy_message"
|
android:id="@+id/emojiThumbsDown"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="@dimen/activity_row_layout_height"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/activity_row_layout_height"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_weight="1"
|
||||||
android:paddingStart="@dimen/standard_double_padding"
|
android:cursorVisible="false"
|
||||||
android:paddingEnd="@dimen/zero"
|
android:gravity="center"
|
||||||
android:text="@string/nc_copy_message"
|
android:text="@string/emoji_thumbsDown"
|
||||||
android:textAlignment="viewStart"
|
android:textSize="24sp" />
|
||||||
android:textColor="@color/high_emphasis_text"
|
|
||||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/emojiHeart"
|
||||||
|
android:layout_width="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_height="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:cursorVisible="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/default_emoji"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/emojiLaugh"
|
||||||
|
android:layout_width="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_height="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:cursorVisible="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/emoji_heart"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/emojiConfused"
|
||||||
|
android:layout_width="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_height="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:cursorVisible="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/emoji_confused"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/emojiSad"
|
||||||
|
android:layout_width="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_height="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:cursorVisible="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/emoji_sad"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiEditText
|
||||||
|
android:id="@+id/emojiMore"
|
||||||
|
android:layout_width="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_height="@dimen/activity_row_layout_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/emoji_more"
|
||||||
|
android:drawableEnd="@drawable/ic_dots_horizontal"
|
||||||
|
android:paddingStart="@dimen/zero"
|
||||||
|
android:paddingEnd="@dimen/standard_padding" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_mark_as_unread"
|
android:id="@+id/message_actions"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/menu_icon_mark_as_unread"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_eye_off"
|
|
||||||
app:tint="@color/grey_600" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/menu_text_mark_as_unread"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:paddingStart="@dimen/standard_padding"
|
||||||
android:paddingStart="@dimen/standard_double_padding"
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
android:paddingEnd="@dimen/zero"
|
android:orientation="vertical">
|
||||||
android:text="@string/nc_mark_as_unread"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
android:textColor="@color/high_emphasis_text"
|
|
||||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/menu_forward_message"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/menu_icon_forward_message"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_share_action"
|
|
||||||
app:tint="@color/grey_600" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/menu_text_forward_message"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="start|center_vertical"
|
|
||||||
android:paddingStart="@dimen/standard_double_padding"
|
|
||||||
android:paddingEnd="@dimen/zero"
|
|
||||||
android:text="@string/nc_forward_message"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
android:textColor="@color/high_emphasis_text"
|
|
||||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_reply_to_message"
|
android:id="@+id/menu_reply_to_message"
|
||||||
@ -137,7 +130,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_reply"
|
android:src="@drawable/ic_reply"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/menu_text_reply_to_message"
|
android:id="@+id/menu_text_reply_to_message"
|
||||||
@ -168,7 +161,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_reply"
|
android:src="@drawable/ic_reply"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/menu_text_reply_privately"
|
android:id="@+id/menu_text_reply_privately"
|
||||||
@ -184,6 +177,99 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/menu_forward_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/menu_icon_forward_message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_share_action"
|
||||||
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/menu_text_forward_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
|
android:text="@string/nc_forward_message"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/menu_mark_as_unread"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/menu_icon_mark_as_unread"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_eye_off"
|
||||||
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/menu_text_mark_as_unread"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
|
android:text="@string/nc_mark_as_unread"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/menu_copy_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/menu_icon_copy_message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_content_copy"
|
||||||
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/menu_text_copy_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:paddingStart="@dimen/standard_double_padding"
|
||||||
|
android:paddingEnd="@dimen/zero"
|
||||||
|
android:text="@string/nc_copy_message"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="@color/high_emphasis_text"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/menu_delete_message"
|
android:id="@+id/menu_delete_message"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -199,7 +285,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
android:src="@drawable/ic_delete"
|
android:src="@drawable/ic_delete"
|
||||||
app:tint="@color/grey_600" />
|
app:tint="@color/high_emphasis_menu_icon" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/menu_text_delete_message"
|
android:id="@+id/menu_text_delete_message"
|
||||||
@ -216,3 +302,5 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
48
app/src/main/res/layout/dialog_message_reactions.xml
Normal file
48
app/src/main/res/layout/dialog_message_reactions.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Andy Scherzinger
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2022 Andy Scherzinger
|
||||||
|
~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
~
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="288dp">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/reactions_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/reaction_item" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/emoji_reactions_tabs"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="@dimen/min_size_clickable_area"
|
||||||
|
app:tabGravity="fill"
|
||||||
|
app:tabMode="scrollable" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -24,7 +24,6 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/bg_bottom_sheet"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="@dimen/standard_padding"
|
android:paddingStart="@dimen/standard_padding"
|
||||||
android:paddingTop="@dimen/standard_half_padding"
|
android:paddingTop="@dimen/standard_half_padding"
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:lineSpacingMultiplier="1.2"
|
android:lineSpacingMultiplier="1.2"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="false"
|
||||||
app:layout_alignSelf="flex_start"
|
app:layout_alignSelf="flex_start"
|
||||||
app:layout_flexGrow="1"
|
app:layout_flexGrow="1"
|
||||||
app:layout_wrapBefore="true" />
|
app:layout_wrapBefore="true" />
|
||||||
@ -89,5 +89,8 @@
|
|||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
app:layout_alignSelf="center" />
|
app:layout_alignSelf="center" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -173,6 +173,10 @@
|
|||||||
android:textColor="@color/warm_grey_four"
|
android:textColor="@color/warm_grey_four"
|
||||||
app:layout_alignSelf="center"
|
app:layout_alignSelf="center"
|
||||||
tools:text="12:38" />
|
tools:text="12:38" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -47,8 +47,7 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:alignContent="stretch"
|
app:alignContent="stretch"
|
||||||
app:alignItems="stretch"
|
app:alignItems="stretch"
|
||||||
app:flexWrap="wrap"
|
app:flexWrap="wrap">
|
||||||
app:justifyContent="flex_end">
|
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/message_quote"
|
android:id="@+id/message_quote"
|
||||||
@ -87,5 +86,9 @@
|
|||||||
android:textIsSelectable="false"
|
android:textIsSelectable="false"
|
||||||
app:layout_alignSelf="center" />
|
app:layout_alignSelf="center" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
|
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -108,5 +108,9 @@
|
|||||||
app:layout_alignSelf="center"
|
app:layout_alignSelf="center"
|
||||||
tools:text="12:38"/>
|
tools:text="12:38"/>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
|
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
android:lineSpacingMultiplier="1.2"
|
android:lineSpacingMultiplier="1.2"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColorHighlight="@color/nc_grey"
|
android:textColorHighlight="@color/nc_grey"
|
||||||
android:textIsSelectable="true"
|
android:textIsSelectable="false"
|
||||||
tools:text="Talk to you later!" />
|
tools:text="Talk to you later!" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -80,5 +80,8 @@
|
|||||||
app:layout_alignSelf="center"
|
app:layout_alignSelf="center"
|
||||||
android:contentDescription="@null" />
|
android:contentDescription="@null" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -163,6 +163,10 @@
|
|||||||
android:textColor="@color/warm_grey_four"
|
android:textColor="@color/warm_grey_four"
|
||||||
app:layout_alignSelf="center"
|
app:layout_alignSelf="center"
|
||||||
tools:text="12:34" />
|
tools:text="12:34" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -76,5 +76,9 @@
|
|||||||
android:contentDescription="@null"
|
android:contentDescription="@null"
|
||||||
app:layout_alignSelf="center" />
|
app:layout_alignSelf="center" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
|
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -104,5 +104,9 @@
|
|||||||
app:layout_alignSelf="center"
|
app:layout_alignSelf="center"
|
||||||
android:contentDescription="@null" />
|
android:contentDescription="@null" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/reactions"
|
||||||
|
layout="@layout/reactions_inside_message" />
|
||||||
|
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
46
app/src/main/res/layout/item_reactions_tab.xml
Normal file
46
app/src/main/res/layout/item_reactions_tab.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Andy Scherzinger
|
||||||
|
~ Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
|
~
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/reaction_tab"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<androidx.emoji.widget.EmojiTextView
|
||||||
|
android:id="@+id/reaction_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:textIsSelectable="false"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="@string/default_emoji" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/reaction_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
56
app/src/main/res/layout/reaction_item.xml
Normal file
56
app/src/main/res/layout/reaction_item.xml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
|
||||||
|
Nextcloud Talk application
|
||||||
|
|
||||||
|
Copyright (C) 2022 Andy Scherzinger
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/item_height">
|
||||||
|
|
||||||
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="@dimen/avatar_size"
|
||||||
|
android:layout_height="@dimen/avatar_size"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_margin="@dimen/standard_margin"
|
||||||
|
app:roundAsCircle="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:textColor="@color/high_emphasis_text"
|
||||||
|
android:textSize="@dimen/bottom_sheet_text_size"
|
||||||
|
tools:text="Participant Name" />
|
||||||
|
|
||||||
|
<androidx.emoji.widget.EmojiTextView
|
||||||
|
android:id="@+id/reaction"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:layout_marginEnd="@dimen/standard_half_margin"
|
||||||
|
tools:text="@string/default_emoji" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
36
app/src/main/res/layout/reactions_inside_message.xml
Normal file
36
app/src/main/res/layout/reactions_inside_message.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
|
||||||
|
Nextcloud Talk application
|
||||||
|
|
||||||
|
Copyright (C) 2022 Marcel Hibbe
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/reactions_emoji_wrapper"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
app:layout_alignSelf="flex_start"
|
||||||
|
app:layout_flexGrow="1"
|
||||||
|
app:layout_wrapBefore="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="emojis">
|
||||||
|
</TextView>
|
||||||
|
</LinearLayout>
|
@ -38,7 +38,9 @@
|
|||||||
<color name="high_emphasis_text">#deffffff</color>
|
<color name="high_emphasis_text">#deffffff</color>
|
||||||
<color name="medium_emphasis_text">#99ffffff</color>
|
<color name="medium_emphasis_text">#99ffffff</color>
|
||||||
<color name="low_emphasis_text">#61ffffff</color>
|
<color name="low_emphasis_text">#61ffffff</color>
|
||||||
<color name="high_emphasis_text_inverse">#de000000</color>
|
|
||||||
|
<!-- bottom sheet specific icon default color -->
|
||||||
|
<color name="high_emphasis_menu_icon">#8Affffff</color>
|
||||||
|
|
||||||
<color name="bg_default">#121212</color>
|
<color name="bg_default">#121212</color>
|
||||||
<color name="bg_default_semitransparent">#99121212</color>
|
<color name="bg_default_semitransparent">#99121212</color>
|
||||||
@ -58,7 +60,7 @@
|
|||||||
|
|
||||||
<!-- Chat window incoming message text & informational -->
|
<!-- Chat window incoming message text & informational -->
|
||||||
<color name="bg_bottom_sheet">#121212</color>
|
<color name="bg_bottom_sheet">#121212</color>
|
||||||
<color name="bg_message_list_incoming_bubble">#1CFFFFFF</color>
|
<color name="bg_message_list_incoming_bubble">#2A2A2A</color>
|
||||||
<color name="bg_message_list_incoming_bubble_deleted">#14FFFFFF</color>
|
<color name="bg_message_list_incoming_bubble_deleted">#14FFFFFF</color>
|
||||||
|
|
||||||
<color name="textColorMaxContrast">#8c8c8c</color>
|
<color name="textColorMaxContrast">#8c8c8c</color>
|
||||||
|
@ -39,12 +39,15 @@
|
|||||||
<color name="high_emphasis_text">#de000000</color>
|
<color name="high_emphasis_text">#de000000</color>
|
||||||
<color name="medium_emphasis_text">#99000000</color>
|
<color name="medium_emphasis_text">#99000000</color>
|
||||||
<color name="low_emphasis_text">#61000000</color>
|
<color name="low_emphasis_text">#61000000</color>
|
||||||
<color name="high_emphasis_text_inverse">#deffffff</color>
|
|
||||||
|
|
||||||
<!-- general text colors for dark background -->
|
<!-- general text colors for dark background -->
|
||||||
<color name="high_emphasis_text_dark_background">#deffffff</color>
|
<color name="high_emphasis_text_dark_background">#deffffff</color>
|
||||||
<color name="medium_emphasis_text_dark_background">#99ffffff</color>
|
<color name="medium_emphasis_text_dark_background">#99ffffff</color>
|
||||||
|
|
||||||
|
<!-- bottom sheet specific icon default color -->
|
||||||
|
<color name="high_emphasis_menu_icon_inverse">#8Affffff</color>
|
||||||
|
<color name="high_emphasis_menu_icon">#8A000000</color>
|
||||||
|
|
||||||
<!-- Text color of sent messages -->
|
<!-- Text color of sent messages -->
|
||||||
<color name="nc_outcoming_text_default">#FFFFFF</color>
|
<color name="nc_outcoming_text_default">#FFFFFF</color>
|
||||||
<!-- Text color of received messages -->
|
<!-- Text color of received messages -->
|
||||||
@ -82,7 +85,7 @@
|
|||||||
<color name="bg_message_list_outcoming_bubble">@color/colorPrimary</color>
|
<color name="bg_message_list_outcoming_bubble">@color/colorPrimary</color>
|
||||||
<color name="bg_message_list_outcoming_bubble_deleted">#800082C9</color>
|
<color name="bg_message_list_outcoming_bubble_deleted">#800082C9</color>
|
||||||
|
|
||||||
<color name="bg_bottom_sheet">#46ffffff</color>
|
<color name="bg_bottom_sheet">#FFFFFF</color>
|
||||||
<color name="bg_call_screen_dialog">#121212</color>
|
<color name="bg_call_screen_dialog">#121212</color>
|
||||||
<color name="call_screen_text">#ffffffff</color>
|
<color name="call_screen_text">#ffffffff</color>
|
||||||
|
|
||||||
|
@ -279,6 +279,12 @@
|
|||||||
<string name="invisible">Invisible</string>
|
<string name="invisible">Invisible</string>
|
||||||
<string translatable="false" name="divider">—</string>
|
<string translatable="false" name="divider">—</string>
|
||||||
<string translatable="false" name="default_emoji">😃</string>
|
<string translatable="false" name="default_emoji">😃</string>
|
||||||
|
<string translatable="false" name="emoji_thumbsUp">👍</string>
|
||||||
|
<string translatable="false" name="emoji_thumbsDown">👎</string>
|
||||||
|
<string translatable="false" name="emoji_heart">❤️</string>
|
||||||
|
<string translatable="false" name="emoji_confused">😯</string>
|
||||||
|
<string translatable="false" name="emoji_sad">😢</string>
|
||||||
|
<string translatable="false" name="emoji_more">More emojis</string>
|
||||||
<string name="dontClear">Don\'t clear</string>
|
<string name="dontClear">Don\'t clear</string>
|
||||||
<string name="today">Today</string>
|
<string name="today">Today</string>
|
||||||
<string name="thirtyMinutes">30 minutes</string>
|
<string name="thirtyMinutes">30 minutes</string>
|
||||||
@ -507,4 +513,6 @@
|
|||||||
<string name="audio_output_dialog_headline">Audio output</string>
|
<string name="audio_output_dialog_headline">Audio output</string>
|
||||||
<string name="audio_output_wired_headset">Wired headset</string>
|
<string name="audio_output_wired_headset">Wired headset</string>
|
||||||
|
|
||||||
|
<string name="reactions_tab_all">All</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
<item name="android:navigationBarColor">@color/bg_default</item>
|
<item name="android:navigationBarColor">@color/bg_default</item>
|
||||||
<item name="android:seekBarStyle">@style/Nextcloud.Material.Incoming.SeekBar</item>
|
<item name="android:seekBarStyle">@style/Nextcloud.Material.Incoming.SeekBar</item>
|
||||||
<item name="seekBarStyle">@style/Nextcloud.Material.Incoming.SeekBar</item>
|
<item name="seekBarStyle">@style/Nextcloud.Material.Incoming.SeekBar</item>
|
||||||
|
<item name="bottomSheetDialogTheme">@style/ThemeOverlay.App.BottomSheetDialog</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ThemeOverlay.AppTheme.PopupMenu" parent="ThemeOverlay.MaterialComponents.Dark">
|
<style name="ThemeOverlay.AppTheme.PopupMenu" parent="ThemeOverlay.MaterialComponents.Dark">
|
||||||
@ -55,6 +56,14 @@
|
|||||||
<item name="elevation">1dp</item>
|
<item name="elevation">1dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.MaterialComponents.DayNight.BottomSheetDialog">
|
||||||
|
<item name="bottomSheetStyle">@style/Talk.BottomSheetDialog</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Talk.BottomSheetDialog" parent="Widget.MaterialComponents.BottomSheet.Modal">
|
||||||
|
<item name="backgroundTint">@color/bg_bottom_sheet</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="TransparentTheme" parent="Theme.MaterialComponents.NoActionBar.Bridge">
|
<style name="TransparentTheme" parent="Theme.MaterialComponents.NoActionBar.Bridge">
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowBackground">@android:color/background_dark</item>
|
<item name="android:windowBackground">@android:color/background_dark</item>
|
||||||
@ -235,7 +244,7 @@
|
|||||||
<item name="android:colorControlNormal">#ffffff</item>
|
<item name="android:colorControlNormal">#ffffff</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="BottomSheetDialogThemeNoFloating" parent="Theme.Design.Light.BottomSheetDialog">
|
<style name="BottomSheetDialogThemeNoFloating" parent="ThemeOverlay.MaterialComponents.DayNight.BottomSheetDialog">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:windowSoftInputMode">adjustResize</item>
|
<item name="android:windowSoftInputMode">adjustResize</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
build:
|
build:
|
||||||
maxIssues: 98
|
maxIssues: 95
|
||||||
weights:
|
weights:
|
||||||
# complexity: 2
|
# complexity: 2
|
||||||
# LongParameterList: 1
|
# LongParameterList: 1
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
DO NOT TOUCH; GENERATED BY DRONE
|
DO NOT TOUCH; GENERATED BY DRONE
|
||||||
<span class="mdl-layout-title">Lint Report: 1 error and 164 warnings</span>
|
<span class="mdl-layout-title">Lint Report: 1 error and 156 warnings</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user