mirror of
https://github.com/nextcloud/talk-android
synced 2025-03-06 14:27:24 +00: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>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" static="false" />
|
||||
</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" />
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
@ -212,8 +203,7 @@
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
|
@ -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'`.
|
||||
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
|
||||
|
||||
Contribute your code targeting/based-on the branch ```master```.
|
||||
|
@ -334,6 +334,14 @@ dependencies {
|
||||
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 {
|
||||
reports {
|
||||
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
|
||||
var appPreferences: AppPreferences? = null
|
||||
|
||||
lateinit var reactionsInterface: ReactionsInterface
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
@ -93,13 +95,21 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
|
||||
val textSize = context?.resources!!.getDimension(R.dimen.chat_text_size)
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageText.text = message.text
|
||||
binding.messageText.isEnabled = false
|
||||
|
||||
// parent message handling
|
||||
setParentMessageDataOnMessageItem(message)
|
||||
|
||||
// geo-location
|
||||
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) {
|
||||
@ -270,6 +280,10 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
|
||||
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
||||
}
|
||||
|
||||
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||
this.reactionsInterface = reactionsInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocInMessageView"
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import android.widget.ProgressBar;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
|
||||
import androidx.emoji.widget.EmojiTextView;
|
||||
|
||||
@ -77,4 +78,7 @@ public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHol
|
||||
public ProgressBar getPreviewContactProgressBar() {
|
||||
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 voiceMessageInterface: VoiceMessageInterface
|
||||
lateinit var reactionsInterface: ReactionsInterface
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
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) {
|
||||
@ -302,10 +312,14 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
|
||||
}
|
||||
}
|
||||
|
||||
fun assignAdapter(voiceMessageInterface: VoiceMessageInterface) {
|
||||
fun assignVoiceMessageInterface(voiceMessageInterface: VoiceMessageInterface) {
|
||||
this.voiceMessageInterface = voiceMessageInterface
|
||||
}
|
||||
|
||||
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||
this.reactionsInterface = reactionsInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VoiceInMessageView"
|
||||
private const val SEEKBAR_START: Int = 0
|
||||
|
@ -71,6 +71,8 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
|
||||
@Inject
|
||||
var appPreferences: AppPreferences? = null
|
||||
|
||||
lateinit var reactionsInterface: ReactionsInterface
|
||||
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
@ -119,6 +121,15 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -260,6 +271,10 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
|
||||
return messageStringInternal
|
||||
}
|
||||
|
||||
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||
this.reactionsInterface = reactionsInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TEXT_SIZE_MULTIPLIER = 2.5
|
||||
}
|
||||
|
@ -61,6 +61,8 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
@Inject
|
||||
var context: Context? = null
|
||||
|
||||
lateinit var reactionsInterface: ReactionsInterface
|
||||
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
@ -118,6 +120,15 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
||||
|
||||
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) {
|
||||
@ -204,6 +215,10 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
return messageString1
|
||||
}
|
||||
|
||||
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||
this.reactionsInterface = reactionsInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
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.DavResponse;
|
||||
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
|
||||
import com.nextcloud.talk.models.database.CapabilitiesUtil;
|
||||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
@ -111,8 +112,13 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||
|
||||
ProgressBar progressBar;
|
||||
|
||||
ReactionsInsideMessageBinding reactionsBinding;
|
||||
|
||||
View clickView;
|
||||
|
||||
ReactionsInterface reactionsInterface;
|
||||
PreviewMessageInterface previewMessageInterface;
|
||||
|
||||
public MagicPreviewMessageViewHolder(View itemView, Object payload) {
|
||||
super(itemView, payload);
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
||||
@ -185,25 +191,30 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get(KEY_PATH), message.activeUser);
|
||||
}
|
||||
|
||||
String accountString =
|
||||
if(message.activeUser != null && message.activeUser.getUsername() != null && message.activeUser.getBaseUrl() != null){
|
||||
String accountString =
|
||||
message.activeUser.getUsername() + "@" +
|
||||
message.activeUser.getBaseUrl()
|
||||
.replace("https://", "")
|
||||
.replace("http://", "");
|
||||
message.activeUser.getBaseUrl()
|
||||
.replace("https://", "")
|
||||
.replace("http://", "");
|
||||
|
||||
clickView.setOnClickListener(v -> {
|
||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
||||
if (isSupportedForInternalViewer(mimetype) || canBeHandledByExternalApp(mimetype, fileName)) {
|
||||
openOrDownloadFile(message);
|
||||
} else {
|
||||
openFileInFilesApp(message, accountString);
|
||||
}
|
||||
});
|
||||
clickView.setOnClickListener(v -> {
|
||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
||||
if (isSupportedForInternalViewer(mimetype) || canBeHandledByExternalApp(mimetype, fileName)) {
|
||||
openOrDownloadFile(message);
|
||||
} else {
|
||||
openFileInFilesApp(message, accountString);
|
||||
}
|
||||
});
|
||||
|
||||
clickView.setOnLongClickListener(l -> {
|
||||
onMessageViewLongClick(message, accountString);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
Log.e(TAG, "failed to set click listener because activeUser, username or baseUrl were null");
|
||||
}
|
||||
|
||||
clickView.setOnLongClickListener(l -> {
|
||||
onMessageViewLongClick(message, accountString);
|
||||
return true;
|
||||
});
|
||||
|
||||
// check if download worker is already running
|
||||
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());
|
||||
}
|
||||
|
||||
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) {
|
||||
Drawable drawable = null;
|
||||
@ -283,6 +303,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||
|
||||
public abstract ProgressBar getPreviewContactProgressBar();
|
||||
|
||||
public abstract ReactionsInsideMessageBinding getReactionsBinding();
|
||||
|
||||
private void openOrDownloadFile(ChatMessage message) {
|
||||
String filename = message.getSelectedIndividualHashMap().get(KEY_NAME);
|
||||
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) {
|
||||
if (isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
|
||||
previewMessageInterface.onPreviewMessageLongClick(message);
|
||||
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
|
||||
var context: Context? = null
|
||||
|
||||
lateinit var reactionsInterface: ReactionsInterface
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
@ -84,7 +86,6 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageTime.layoutParams = layoutParams
|
||||
binding.messageText.text = message.text
|
||||
binding.messageText.isEnabled = false
|
||||
|
||||
// parent message handling
|
||||
setParentMessageDataOnMessageItem(message)
|
||||
@ -112,6 +113,15 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
||||
|
||||
// geo-location
|
||||
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")
|
||||
@ -245,6 +255,10 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
||||
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
||||
}
|
||||
|
||||
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||
this.reactionsInterface = reactionsInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocOutMessageView"
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import android.widget.ProgressBar;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
|
||||
import androidx.emoji.widget.EmojiTextView;
|
||||
|
||||
@ -77,4 +78,7 @@ public class OutcomingPreviewMessageViewHolder extends MagicPreviewMessageViewHo
|
||||
public ProgressBar getPreviewContactProgressBar() {
|
||||
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 voiceMessageInterface: VoiceMessageInterface
|
||||
lateinit var reactionsInterface: ReactionsInterface
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
@ -129,6 +130,15 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -275,10 +285,14 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
|
||||
}
|
||||
}
|
||||
|
||||
fun assignAdapter(voiceMessageInterface: VoiceMessageInterface) {
|
||||
fun assignVoiceMessageInterface(voiceMessageInterface: VoiceMessageInterface) {
|
||||
this.voiceMessageInterface = voiceMessageInterface
|
||||
}
|
||||
|
||||
fun assignReactionInterface(reactionsInterface: ReactionsInterface) {
|
||||
this.reactionsInterface = reactionsInterface
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "VoiceOutMessageView"
|
||||
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) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
|
||||
if (holder instanceof IncomingVoiceMessageViewHolder) {
|
||||
((IncomingVoiceMessageViewHolder) holder).assignAdapter(chatController);
|
||||
if (holder instanceof MagicIncomingTextMessageViewHolder) {
|
||||
((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) {
|
||||
((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.ParticipantsOverall;
|
||||
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.signaling.SignalingOverall;
|
||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
|
||||
@ -62,6 +63,7 @@ import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.Multipart;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
@ -488,4 +490,17 @@ public interface NcApi {
|
||||
@GET
|
||||
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.OutcomingPreviewMessageViewHolder
|
||||
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.VoiceMessageInterface
|
||||
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.dialog.AttachmentDialog
|
||||
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.MessageSwipeCallback
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
@ -203,7 +206,9 @@ class ChatController(args: Bundle) :
|
||||
MessagesListAdapter.Formatter<Date>,
|
||||
MessagesListAdapter.OnMessageViewLongClickListener<IMessage>,
|
||||
ContentChecker<ChatMessage>,
|
||||
VoiceMessageInterface {
|
||||
VoiceMessageInterface,
|
||||
ReactionsInterface,
|
||||
PreviewMessageInterface {
|
||||
|
||||
private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind)
|
||||
|
||||
@ -2087,7 +2092,7 @@ class ChatController(args: Bundle) :
|
||||
if (response.code() == HTTP_CODE_OK) {
|
||||
|
||||
val chatOverall = response.body() as ChatOverall?
|
||||
val chatMessageList = setDeletionFlagsAndRemoveInfomessages(chatOverall?.ocs!!.data)
|
||||
val chatMessageList = handleSystemMessages(chatOverall?.ocs!!.data)
|
||||
|
||||
if (chatMessageList.isNotEmpty() &&
|
||||
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 chatMessageIterator = chatMessageMap.iterator()
|
||||
while (chatMessageIterator.hasNext()) {
|
||||
val currentMessage = chatMessageIterator.next()
|
||||
|
||||
// setDeletionFlagsAndRemoveInfomessages
|
||||
if (isInfoMessageAboutDeletion(currentMessage)) {
|
||||
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
|
||||
setMessageAsDeleted(currentMessage.value.parentMessage)
|
||||
} else {
|
||||
@ -2351,6 +2358,15 @@ class ChatController(args: Bundle) :
|
||||
}
|
||||
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()
|
||||
}
|
||||
@ -2360,6 +2376,12 @@ class ChatController(args: Bundle) :
|
||||
.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) {
|
||||
if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) {
|
||||
Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
|
||||
@ -2398,21 +2420,50 @@ class ChatController(args: Bundle) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClickReactions(chatMessage: ChatMessage) {
|
||||
activity?.let {
|
||||
ShowReactionsDialog(
|
||||
activity!!,
|
||||
currentConversation,
|
||||
chatMessage,
|
||||
conversationUser,
|
||||
ncApi!!
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClickReactions(chatMessage: ChatMessage) {
|
||||
openMessageActionsDialog(chatMessage)
|
||||
}
|
||||
|
||||
override fun onMessageViewLongClick(view: View?, message: IMessage?) {
|
||||
if (hasVisibleItems(message as ChatMessage)) {
|
||||
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(
|
||||
activity!!,
|
||||
this,
|
||||
message,
|
||||
conversationUser?.userId,
|
||||
conversationUser,
|
||||
currentConversation,
|
||||
isShowMessageDeletionButton(message)
|
||||
isShowMessageDeletionButton(message),
|
||||
ncApi!!
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSystemMessage(message: ChatMessage): Boolean {
|
||||
return ChatMessage.MessageType.SYSTEM_MESSAGE == message.getMessageType()
|
||||
}
|
||||
|
||||
fun deleteMessage(message: IMessage?) {
|
||||
var apiVersion = 1
|
||||
// FIXME Fix API checking with guests?
|
||||
@ -2680,6 +2731,29 @@ class ChatController(args: Bundle) :
|
||||
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 {
|
||||
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) {
|
||||
try {
|
||||
val sslCertificate = error.certificate
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.nextcloud.talk.models.json.chat;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonIgnore;
|
||||
@ -40,6 +41,7 @@ import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -50,6 +52,8 @@ import kotlin.text.Charsets;
|
||||
@Parcel
|
||||
@JsonObject
|
||||
public class ChatMessage implements MessageContentType, MessageContentType.Image {
|
||||
private static String TAG = "ChatMessage";
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isGrouped;
|
||||
@JsonIgnore
|
||||
@ -90,6 +94,8 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
public Enum<ReadStatus> readStatus = ReadStatus.NONE;
|
||||
@JsonField(name = "messageType")
|
||||
public String messageType;
|
||||
@JsonField(name = "reactions")
|
||||
public LinkedHashMap<String, Integer> reactions;
|
||||
|
||||
public boolean isDownloadingVoiceMessage;
|
||||
public boolean resetVoiceMessage;
|
||||
@ -100,21 +106,21 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
|
||||
@JsonIgnore
|
||||
List<MessageType> messageTypesToIgnore = Arrays.asList(
|
||||
MessageType.REGULAR_TEXT_MESSAGE,
|
||||
MessageType.SYSTEM_MESSAGE,
|
||||
MessageType.SINGLE_LINK_VIDEO_MESSAGE,
|
||||
MessageType.SINGLE_LINK_AUDIO_MESSAGE,
|
||||
MessageType.SINGLE_LINK_MESSAGE,
|
||||
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE,
|
||||
MessageType.VOICE_MESSAGE);
|
||||
MessageType.REGULAR_TEXT_MESSAGE,
|
||||
MessageType.SYSTEM_MESSAGE,
|
||||
MessageType.SINGLE_LINK_VIDEO_MESSAGE,
|
||||
MessageType.SINGLE_LINK_AUDIO_MESSAGE,
|
||||
MessageType.SINGLE_LINK_MESSAGE,
|
||||
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE,
|
||||
MessageType.VOICE_MESSAGE);
|
||||
|
||||
public boolean hasFileAttachment() {
|
||||
if (messageParameters != null && messageParameters.size() > 0) {
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
Map<String, String> individualHashMap = entry.getValue();
|
||||
if(MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("file").getBytes(Charsets.UTF_8))) {
|
||||
if (MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("file").getBytes(Charsets.UTF_8))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -127,9 +133,9 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
Map<String, String> individualHashMap = entry.getValue();
|
||||
|
||||
if(MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("geo-location").getBytes(Charsets.UTF_8))) {
|
||||
if (MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("geo-location").getBytes(Charsets.UTF_8))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -144,13 +150,20 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
if (messageParameters != null && messageParameters.size() > 0) {
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
Map<String, String> individualHashMap = entry.getValue();
|
||||
if(MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("file").getBytes(Charsets.UTF_8))) {
|
||||
if (MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("file").getBytes(Charsets.UTF_8))) {
|
||||
selectedIndividualHashMap = individualHashMap;
|
||||
if(!isVoiceMessage()){
|
||||
return (ApiUtils.getUrlForFilePreviewWithFileId(getActiveUser().getBaseUrl(),
|
||||
individualHashMap.get("id"), NextcloudTalkApplication.Companion.getSharedApplication().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size)));
|
||||
if (!isVoiceMessage()) {
|
||||
if (getActiveUser() != null && getActiveUser().getBaseUrl() != null) {
|
||||
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()");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -168,7 +181,7 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
return MessageType.SYSTEM_MESSAGE;
|
||||
}
|
||||
|
||||
if (isVoiceMessage()){
|
||||
if (isVoiceMessage()) {
|
||||
return MessageType.VOICE_MESSAGE;
|
||||
}
|
||||
|
||||
@ -207,20 +220,20 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
return getText();
|
||||
} else {
|
||||
if (MessageType.SINGLE_LINK_GIPHY_MESSAGE == getMessageType()
|
||||
|| MessageType.SINGLE_LINK_TENOR_MESSAGE == getMessageType()
|
||||
|| MessageType.SINGLE_LINK_GIF_MESSAGE == getMessageType()) {
|
||||
|| MessageType.SINGLE_LINK_TENOR_MESSAGE == getMessageType()
|
||||
|| MessageType.SINGLE_LINK_GIF_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_a_gif_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_a_gif),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_an_attachment_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_an_attachment),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (MessageType.SINGLE_NC_GEOLOCATION_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
@ -248,21 +261,21 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_an_audio_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_an_audio),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (MessageType.SINGLE_LINK_VIDEO_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_a_video_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_a_video),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (MessageType.SINGLE_LINK_IMAGE_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_an_image_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_an_image),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,14 +302,16 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
|
||||
@Override
|
||||
public String getAvatar() {
|
||||
if (getActorType().equals("users")) {
|
||||
if (getActiveUser() == null) {
|
||||
return null;
|
||||
} else if (getActorType().equals("users")) {
|
||||
return ApiUtils.getUrlForAvatar(getActiveUser().getBaseUrl(), actorId, true);
|
||||
} else if (getActorType().equals("bridged")) {
|
||||
return ApiUtils.getUrlForAvatar(getActiveUser().getBaseUrl(), "bridge-bot",
|
||||
true);
|
||||
} else {
|
||||
String apiId =
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest);
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest);
|
||||
|
||||
if (!TextUtils.isEmpty(getActorDisplayName())) {
|
||||
apiId = getActorDisplayName();
|
||||
@ -592,7 +607,7 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
return "ChatMessage(isGrouped=" + this.isGrouped() + ", isOneToOneConversation=" + this.isOneToOneConversation() + ", activeUser=" + this.getActiveUser() + ", selectedIndividualHashMap=" + this.getSelectedIndividualHashMap() + ", isDeleted=" + this.isDeleted() + ", jsonMessageId=" + this.getJsonMessageId() + ", token=" + this.getToken() + ", actorType=" + this.getActorType() + ", actorId=" + this.getActorId() + ", actorDisplayName=" + this.getActorDisplayName() + ", timestamp=" + this.getTimestamp() + ", message=" + this.getMessage() + ", messageParameters=" + this.getMessageParameters() + ", systemMessageType=" + this.getSystemMessageType() + ", replyable=" + this.isReplyable() + ", parentMessage=" + this.getParentMessage() + ", readStatus=" + this.getReadStatus() + ", messageTypesToIgnore=" + this.getMessageTypesToIgnore() + ")";
|
||||
}
|
||||
|
||||
public boolean isVoiceMessage(){
|
||||
public boolean isVoiceMessage() {
|
||||
return "voice-message".equals(messageType);
|
||||
}
|
||||
|
||||
@ -657,6 +672,9 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
MATTERBRIDGE_CONFIG_REMOVED,
|
||||
MATTERBRIDGE_CONFIG_ENABLED,
|
||||
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.PASSWORD_REMOVED
|
||||
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_OFF
|
||||
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_disabled" -> return MATTERBRIDGE_CONFIG_DISABLED
|
||||
"history_cleared" -> return CLEARED_CHAT
|
||||
"reaction" -> return REACTION
|
||||
"reaction_deleted" -> return REACTION_DELETED
|
||||
"reaction_revoked" -> return REACTION_REVOKED
|
||||
else -> return DUMMY
|
||||
}
|
||||
}
|
||||
@ -214,6 +220,9 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
||||
MATTERBRIDGE_CONFIG_ENABLED -> return "matterbridge_config_enabled"
|
||||
MATTERBRIDGE_CONFIG_DISABLED -> return "matterbridge_config_disabled"
|
||||
CLEARED_CHAT -> return "clear_history"
|
||||
REACTION -> return "reaction"
|
||||
REACTION_DELETED -> return "reaction_deleted"
|
||||
REACTION_REVOKED -> return "reaction_revoked"
|
||||
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?,
|
||||
@JsonField(name = ["message"])
|
||||
var message: String?,
|
||||
/* TODO: Change to enum */
|
||||
/* TODO Change to enum */
|
||||
@JsonField(name = ["messageId"])
|
||||
var messageId: String?,
|
||||
@JsonField(name = ["messageIsPredefined"])
|
||||
@ -41,7 +41,7 @@ data class Status(
|
||||
var icon: String?,
|
||||
@JsonField(name = ["clearAt"])
|
||||
var clearAt: Long = 0,
|
||||
/* TODO: Change to enum */
|
||||
/* TODO Change to enum */
|
||||
@JsonField(name = ["status"])
|
||||
var status: String = "offline",
|
||||
@JsonField(name = ["statusIsUserDefined"])
|
||||
|
@ -293,7 +293,7 @@ class ConversationsListBottomDialog(
|
||||
|
||||
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... ?!)
|
||||
// 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
|
||||
|
@ -20,42 +20,60 @@
|
||||
|
||||
package com.nextcloud.talk.ui.dialog
|
||||
|
||||
import android.app.Activity
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
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.BottomSheetDialog
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.controllers.ChatController
|
||||
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.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(
|
||||
val activity: Activity,
|
||||
private val chatController: ChatController,
|
||||
private val message: ChatMessage,
|
||||
private val userId: String?,
|
||||
private val user: UserEntity?,
|
||||
private val currentConversation: Conversation?,
|
||||
private val showMessageDeletionButton: Boolean
|
||||
) : BottomSheetDialog(activity) {
|
||||
private val showMessageDeletionButton: Boolean,
|
||||
private val ncApi: NcApi
|
||||
) : BottomSheetDialog(chatController.activity!!, R.style.BottomSheetDialogThemeNoFloating) {
|
||||
|
||||
private lateinit var dialogMessageActionsBinding: DialogMessageActionsBinding
|
||||
|
||||
private lateinit var popup: EmojiPopup
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
dialogMessageActionsBinding = DialogMessageActionsBinding.inflate(layoutInflater)
|
||||
setContentView(dialogMessageActionsBinding.root)
|
||||
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
|
||||
initEmojiBar()
|
||||
initMenuItemCopy(!message.isDeleted)
|
||||
initMenuReplyToMessage(message.replyable)
|
||||
initMenuReplyPrivately(
|
||||
message.replyable &&
|
||||
userId?.isNotEmpty() == true &&
|
||||
userId != "?" &&
|
||||
user?.userId?.isNotEmpty() == true &&
|
||||
user?.userId != "?" &&
|
||||
message.user.id.startsWith("users/") &&
|
||||
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId &&
|
||||
currentConversation?.type != Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||
@ -67,6 +85,69 @@ class MessageActionsDialog(
|
||||
ChatMessage.MessageType.SYSTEM_MESSAGE != message.getMessageType() &&
|
||||
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) {
|
||||
@ -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 {
|
||||
private const val TAG = "MessageActionsDialog"
|
||||
private const val ACTOR_LENGTH = 6
|
||||
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) {
|
||||
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
|
||||
|
||||
// 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.
|
||||
|
||||
object LoggingUtils {
|
||||
|
@ -243,7 +243,7 @@ public interface AppPreferences {
|
||||
@KeyByString("phone_book_integration")
|
||||
void setPhoneBookIntegration(boolean value);
|
||||
|
||||
// TODO: Remove in 13.0.0
|
||||
// TODO Remove in 13.0.0
|
||||
@KeyByString("link_previews")
|
||||
@RemoveMethod
|
||||
void removeLinkPreviews();
|
||||
|
@ -151,7 +151,7 @@ public class MagicWebSocketInstance extends WebSocketListener {
|
||||
public void restartWebSocket() {
|
||||
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);
|
||||
Request request = new Request.Builder().url(connectionUrl).build();
|
||||
okHttpClient.newWebSocket(request, this);
|
||||
|
@ -18,8 +18,14 @@
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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"/>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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>
|
||||
|
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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/bg_bottom_sheet"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
|
||||
|
@ -23,7 +23,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/bg_bottom_sheet"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingTop="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_half_padding">
|
||||
|
@ -25,7 +25,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/bg_bottom_sheet"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
@ -56,7 +55,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_baseline_person_24"
|
||||
app:tint="@color/colorPrimary" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/shareContactText"
|
||||
@ -87,7 +86,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_baseline_location_on_24"
|
||||
app:tint="@color/colorPrimary" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_share_location"
|
||||
@ -118,7 +117,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_baseline_photo_camera_24"
|
||||
app:tint="@color/colorPrimary" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_attach_picture_from_cam"
|
||||
@ -149,7 +148,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/upload"
|
||||
app:tint="@color/colorPrimary" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_attach_file_from_local"
|
||||
@ -180,7 +179,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_share_variant"
|
||||
app:tint="@color/colorPrimary" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_attach_file_from_cloud"
|
||||
|
@ -49,11 +49,11 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/audio_output_bluetooth_icon"
|
||||
android:layout_width="11dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
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
|
||||
android:id="@+id/audio_output_bluetooth_text"
|
||||
@ -84,7 +84,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
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
|
||||
android:id="@+id/audio_output_speaker_text"
|
||||
@ -115,7 +115,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
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
|
||||
android:id="@+id/audio_output_earspeaker_text"
|
||||
@ -146,7 +146,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
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
|
||||
android:id="@+id/audio_output_wired_headset_text"
|
||||
|
@ -23,7 +23,6 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/bg_bottom_sheet"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
|
@ -25,7 +25,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/bg_bottom_sheet"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
@ -60,7 +59,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_star_border_black_24dp"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -87,7 +86,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_star_black_24dp"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -114,7 +113,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_eye"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -141,7 +140,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_pencil_grey600_24dp"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -168,7 +167,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_link_grey600_24px"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -195,7 +194,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_lock_grey600_24px"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -222,7 +221,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_lock_open_grey600_24dp"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -249,7 +248,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_lock_plus_grey600_24dp"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -276,7 +275,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_delete_grey600_24dp"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -303,7 +302,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_link_grey600_24px"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -330,7 +329,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_group_grey600_24px"
|
||||
app:tint="@color/grey_600" />
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
@ -357,7 +356,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
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
|
||||
android:layout_width="match_parent"
|
||||
|
@ -23,195 +23,283 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/bg_bottom_sheet"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
android:paddingBottom="@dimen/standard_half_padding">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_copy_message"
|
||||
android:id="@+id/emojiBar"
|
||||
android:layout_width="match_parent"
|
||||
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:orientation="horizontal"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<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/grey_600" />
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/emojiThumbsUp"
|
||||
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_thumbsUp"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<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" />
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/emojiThumbsDown"
|
||||
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_thumbsDown"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<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
|
||||
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">
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<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"
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_reply_to_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_mark_as_unread"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/menu_icon_reply_to_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_reply"
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<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">
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_reply_to_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_reply"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
|
||||
<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" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_forward_message"
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_reply_privately"
|
||||
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" />
|
||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:id="@+id/menu_icon_reply_privately"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_reply"
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_reply_to_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">
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_reply_privately"
|
||||
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_reply_privately"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_icon_reply_to_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_reply"
|
||||
app:tint="@color/grey_600" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_reply_to_message"
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_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_reply"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
</LinearLayout>
|
||||
<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" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_reply_privately"
|
||||
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">
|
||||
<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" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_icon_reply_privately"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_reply"
|
||||
app:tint="@color/grey_600" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_reply_privately"
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_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_reply_privately"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
</LinearLayout>
|
||||
<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" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_delete_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">
|
||||
<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" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_icon_delete_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_delete"
|
||||
app:tint="@color/grey_600" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_delete_message"
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_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_delete_message"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
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
|
||||
android:id="@+id/menu_delete_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_delete_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_delete"
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_delete_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_delete_message"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
|
||||
</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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/bg_bottom_sheet"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingTop="@dimen/standard_half_padding"
|
||||
|
@ -76,7 +76,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:textAlignment="viewStart"
|
||||
android:textIsSelectable="true"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true" />
|
||||
@ -89,5 +89,8 @@
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
||||
|
@ -173,6 +173,10 @@
|
||||
android:textColor="@color/warm_grey_four"
|
||||
app:layout_alignSelf="center"
|
||||
tools:text="12:38" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -47,8 +47,7 @@
|
||||
android:orientation="vertical"
|
||||
app:alignContent="stretch"
|
||||
app:alignItems="stretch"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
app:flexWrap="wrap">
|
||||
|
||||
<include
|
||||
android:id="@+id/message_quote"
|
||||
@ -87,5 +86,9 @@
|
||||
android:textIsSelectable="false"
|
||||
app:layout_alignSelf="center" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
||||
|
@ -108,5 +108,9 @@
|
||||
app:layout_alignSelf="center"
|
||||
tools:text="12:38"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
||||
|
@ -59,7 +59,7 @@
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColorHighlight="@color/nc_grey"
|
||||
android:textIsSelectable="true"
|
||||
android:textIsSelectable="false"
|
||||
tools:text="Talk to you later!" />
|
||||
|
||||
<TextView
|
||||
@ -80,5 +80,8 @@
|
||||
app:layout_alignSelf="center"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
||||
|
@ -163,6 +163,10 @@
|
||||
android:textColor="@color/warm_grey_four"
|
||||
app:layout_alignSelf="center"
|
||||
tools:text="12:34" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -76,5 +76,9 @@
|
||||
android:contentDescription="@null"
|
||||
app:layout_alignSelf="center" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
||||
|
@ -104,5 +104,9 @@
|
||||
app:layout_alignSelf="center"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</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="medium_emphasis_text">#99ffffff</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_semitransparent">#99121212</color>
|
||||
@ -58,7 +60,7 @@
|
||||
|
||||
<!-- Chat window incoming message text & informational -->
|
||||
<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="textColorMaxContrast">#8c8c8c</color>
|
||||
|
@ -39,12 +39,15 @@
|
||||
<color name="high_emphasis_text">#de000000</color>
|
||||
<color name="medium_emphasis_text">#99000000</color>
|
||||
<color name="low_emphasis_text">#61000000</color>
|
||||
<color name="high_emphasis_text_inverse">#deffffff</color>
|
||||
|
||||
<!-- general text colors for dark background -->
|
||||
<color name="high_emphasis_text_dark_background">#deffffff</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 -->
|
||||
<color name="nc_outcoming_text_default">#FFFFFF</color>
|
||||
<!-- 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_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="call_screen_text">#ffffffff</color>
|
||||
|
||||
|
@ -279,6 +279,12 @@
|
||||
<string name="invisible">Invisible</string>
|
||||
<string translatable="false" name="divider">—</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="today">Today</string>
|
||||
<string name="thirtyMinutes">30 minutes</string>
|
||||
@ -507,4 +513,6 @@
|
||||
<string name="audio_output_dialog_headline">Audio output</string>
|
||||
<string name="audio_output_wired_headset">Wired headset</string>
|
||||
|
||||
<string name="reactions_tab_all">All</string>
|
||||
|
||||
</resources>
|
||||
|
@ -40,6 +40,7 @@
|
||||
<item name="android:navigationBarColor">@color/bg_default</item>
|
||||
<item name="android: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 name="ThemeOverlay.AppTheme.PopupMenu" parent="ThemeOverlay.MaterialComponents.Dark">
|
||||
@ -55,6 +56,14 @@
|
||||
<item name="elevation">1dp</item>
|
||||
</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">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/background_dark</item>
|
||||
@ -235,7 +244,7 @@
|
||||
<item name="android:colorControlNormal">#ffffff</item>
|
||||
</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:windowSoftInputMode">adjustResize</item>
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
build:
|
||||
maxIssues: 98
|
||||
maxIssues: 95
|
||||
weights:
|
||||
# complexity: 2
|
||||
# LongParameterList: 1
|
||||
|
@ -1,2 +1,2 @@
|
||||
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