react to given reactions inside message

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-11-24 14:43:20 +01:00
parent bcd35ac66c
commit 6b97197c80
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
26 changed files with 849 additions and 581 deletions

View File

@ -3,6 +3,7 @@ package com.nextcloud.talk.adapters.messages
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
interface CommonMessageInterface { interface CommonMessageInterface {
fun onClickReactions(chatMessage: ChatMessage) fun onLongClickReactions(chatMessage: ChatMessage)
fun onClickReaction(chatMessage: ChatMessage, emoji: String)
fun onOpenMessageActionsDialog(chatMessage: ChatMessage) fun onOpenMessageActionsDialog(chatMessage: ChatMessage)
} }

View File

@ -98,18 +98,21 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : M
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageTime.context, binding.messageTime.context,
false, false,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) { private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {

View File

@ -103,18 +103,21 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : Mess
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageText.context, binding.messageText.context,
false, false,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) { private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {

View File

@ -89,18 +89,21 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageTime.context, binding.messageTime.context,
false, false,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun setPollPreview(message: ChatMessage) { private fun setPollPreview(message: ChatMessage) {

View File

@ -35,7 +35,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.emoji.widget.EmojiTextView; import androidx.emoji.widget.EmojiTextView;
public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHolder { public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
private final ItemCustomIncomingPreviewMessageBinding binding; private final ItemCustomIncomingPreviewMessageBinding binding;
public IncomingPreviewMessageViewHolder(View itemView, Object payload) { public IncomingPreviewMessageViewHolder(View itemView, Object payload) {
@ -63,11 +63,6 @@ public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHol
return binding.progressBar; return binding.progressBar;
} }
@Override
public SimpleDraweeView getImage() {
return binding.image;
}
@Override @Override
public View getPreviewContainer() { public View getPreviewContainer() {
return binding.previewContainer; return binding.previewContainer;
@ -95,4 +90,5 @@ public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHol
@Override @Override
public ReactionsInsideMessageBinding getReactionsBinding(){ return binding.reactions; } public ReactionsInsideMessageBinding getReactionsBinding(){ return binding.reactions; }
} }

View File

@ -147,18 +147,21 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : Message
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageTime.context, binding.messageTime.context,
false, false,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun updateDownloadState(message: ChatMessage) { private fun updateDownloadState(message: ChatMessage) {

View File

@ -119,18 +119,21 @@ class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : Message
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageText.context, binding.messageText.context,
false, false,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun processAuthor(message: ChatMessage) { private fun processAuthor(message: ChatMessage) {

View File

@ -121,14 +121,23 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.replyable) itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.replyable)
Reaction().showReactions(message, binding.reactions, context, true, viewThemeUtils) Reaction().showReactions(
binding.reactions.reactionsEmojiWrapper.setOnClickListener { message,
commonMessageInterface.onClickReactions(message) ::clickOnReaction,
::longClickOnReaction,
binding.reactions,
context,
true,
viewThemeUtils
)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun processParentMessage(message: ChatMessage) { private fun processParentMessage(message: ChatMessage) {

View File

@ -1,375 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Marcel Hibbe
* @author Andy Scherzinger
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.messages;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Handler;
import android.util.Base64;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.android.material.card.MaterialCardView;
import com.nextcloud.talk.R;
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.data.user.model.User;
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.ui.theme.ViewThemeUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.DrawableUtils;
import com.nextcloud.talk.utils.FileViewerUtils;
import com.stfalcon.chatkit.messages.MessageHolders;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.content.ContextCompat;
import androidx.emoji.widget.EmojiTextView;
import autodagger.AutoInjector;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import static com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback.REPLYABLE_VIEW_TAG;
@AutoInjector(NextcloudTalkApplication.class)
public abstract class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageMessageViewHolder<ChatMessage> {
private static final String TAG = "PreviewMsgViewHolder";
public static final String KEY_CONTACT_NAME = "contact-name";
public static final String KEY_CONTACT_PHOTO = "contact-photo";
public static final String KEY_MIMETYPE = "mimetype";
public static final String KEY_ID = "id";
public static final String KEY_PATH = "path";
public static final String ACTOR_TYPE_BOTS = "bots";
public static final String ACTOR_ID_CHANGELOG = "changelog";
public static final String KEY_NAME = "name";
@Inject
Context context;
@Inject
ViewThemeUtils viewThemeUtils;
@Inject
OkHttpClient okHttpClient;
ProgressBar progressBar;
ReactionsInsideMessageBinding reactionsBinding;
FileViewerUtils fileViewerUtils;
View clickView;
CommonMessageInterface commonMessageInterface;
PreviewMessageInterface previewMessageInterface;
public MagicPreviewMessageViewHolder(View itemView, Object payload) {
super(itemView, payload);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
}
@SuppressLint("SetTextI18n")
@Override
public void onBind(ChatMessage message) {
super.onBind(message);
if (userAvatar != null) {
if (message.isGrouped() || message.isOneToOneConversation()) {
if (message.isOneToOneConversation()) {
userAvatar.setVisibility(View.GONE);
} else {
userAvatar.setVisibility(View.INVISIBLE);
}
} else {
userAvatar.setVisibility(View.VISIBLE);
userAvatar.setOnClickListener(v -> {
if (payload instanceof MessagePayload) {
((MessagePayload) payload).getProfileBottomSheet().showFor(message.getActorId(),
v.getContext());
}
});
if (ACTOR_TYPE_BOTS.equals(message.getActorType()) && ACTOR_ID_CHANGELOG.equals(message.getActorId())) {
if (context != null) {
Drawable[] layers = new Drawable[2];
layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background);
layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground);
LayerDrawable layerDrawable = new LayerDrawable(layers);
userAvatar.getHierarchy().setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable));
}
}
}
}
progressBar = getProgressBar();
viewThemeUtils.platform.colorCircularProgressBar(getProgressBar());
image = getImage();
clickView = getImage();
getMessageText().setVisibility(View.VISIBLE);
if (message.getCalculateMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
fileViewerUtils = new FileViewerUtils(context, message.getActiveUser());
String fileName = message.getSelectedIndividualHashMap().get(KEY_NAME);
getMessageText().setText(fileName);
if (message.getSelectedIndividualHashMap().containsKey(KEY_CONTACT_NAME)) {
getPreviewContainer().setVisibility(View.GONE);
getPreviewContactName().setText(message.getSelectedIndividualHashMap().get(KEY_CONTACT_NAME));
progressBar = getPreviewContactProgressBar();
getMessageText().setVisibility(View.INVISIBLE);
clickView = getPreviewContactContainer();
viewThemeUtils.talk.colorContactChatItemBackground(getPreviewContactContainer());
viewThemeUtils.talk.colorContactChatItemName(getPreviewContactName());
viewThemeUtils.platform.colorCircularProgressBarOnPrimaryContainer(getPreviewContactProgressBar());
} else {
getPreviewContainer().setVisibility(View.VISIBLE);
getPreviewContactContainer().setVisibility(View.GONE);
}
if (message.getSelectedIndividualHashMap().containsKey(KEY_CONTACT_PHOTO)) {
image = getPreviewContactPhoto();
Drawable drawable = getDrawableFromContactDetails(
context,
message.getSelectedIndividualHashMap().get(KEY_CONTACT_PHOTO));
image.getHierarchy().setPlaceholderImage(drawable);
} else if (message.getSelectedIndividualHashMap().containsKey(KEY_MIMETYPE)) {
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
int drawableResourceId = DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(mimetype);
Drawable drawable = ContextCompat.getDrawable(context, drawableResourceId);
if (drawable != null &&
(drawableResourceId == R.drawable.ic_mimetype_folder ||
drawableResourceId == R.drawable.ic_mimetype_package_x_generic)) {
drawable.setColorFilter(viewThemeUtils.getScheme(image.getContext()).getPrimary(),
PorterDuff.Mode.SRC_ATOP);
}
image.getHierarchy().setPlaceholderImage(drawable);
} else {
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get(KEY_PATH),
message.getActiveUser());
}
if (message.getActiveUser() != null &&
message.getActiveUser().getUsername() != null &&
message.getActiveUser().getBaseUrl() != null) {
clickView.setOnClickListener(v ->
fileViewerUtils.openFile(
message,
new FileViewerUtils.ProgressUi(progressBar, getMessageText(), image)
)
);
clickView.setOnLongClickListener(l -> {
onMessageViewLongClick(message);
return true;
});
} else {
Log.e(TAG, "failed to set click listener because activeUser, username or baseUrl were null");
}
fileViewerUtils.resumeToUpdateViewsByProgress(
Objects.requireNonNull(message.getSelectedIndividualHashMap().get(MagicPreviewMessageViewHolder.KEY_NAME)),
Objects.requireNonNull(message.getSelectedIndividualHashMap().get(MagicPreviewMessageViewHolder.KEY_ID)),
message.getSelectedIndividualHashMap().get(MagicPreviewMessageViewHolder.KEY_MIMETYPE),
new FileViewerUtils.ProgressUi(progressBar, getMessageText(), image));
} else if (message.getCalculateMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
getMessageText().setText("GIPHY");
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", getMessageText());
} else if (message.getCalculateMessageType() == ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE) {
getMessageText().setText("Tenor");
DisplayUtils.setClickableString("Tenor", "https://tenor.com", getMessageText());
} else {
if (message.getMessageType().equals(ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE)) {
clickView.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(message.getImageUrl()));
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(browserIntent);
});
} else {
clickView.setOnClickListener(null);
}
getMessageText().setText("");
}
itemView.setTag(REPLYABLE_VIEW_TAG, message.getReplyable());
reactionsBinding = getReactionsBinding();
new Reaction().showReactions(message,
reactionsBinding,
getMessageText().getContext(),
true,
viewThemeUtils);
reactionsBinding.reactionsEmojiWrapper.setOnClickListener(l -> {
commonMessageInterface.onClickReactions(message);
});
reactionsBinding.reactionsEmojiWrapper.setOnLongClickListener(l -> {
commonMessageInterface.onOpenMessageActionsDialog(message);
return true;
});
}
private Drawable getDrawableFromContactDetails(Context context, String base64) {
Drawable drawable = null;
if (!base64.equals("")) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(
Base64.decode(base64.getBytes(), Base64.DEFAULT));
drawable = Drawable.createFromResourceStream(context.getResources(),
null, inputStream, null, null);
try {
inputStream.close();
} catch (IOException e) {
int drawableResourceId = DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType("text/vcard");
drawable = ContextCompat.getDrawable(context, drawableResourceId);
}
}
return drawable;
}
private void onMessageViewLongClick(ChatMessage message) {
if (fileViewerUtils.isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
previewMessageInterface.onPreviewMessageLongClick(message);
return;
}
Context viewContext;
if (itemView != null && itemView.getContext() != null) {
viewContext = itemView.getContext();
} else {
viewContext = this.context;
}
PopupMenu popupMenu = new PopupMenu(
new ContextThemeWrapper(viewContext, R.style.appActionBarPopupMenu),
itemView,
Gravity.START
);
popupMenu.inflate(R.menu.chat_preview_message_menu);
popupMenu.setOnMenuItemClickListener(item -> {
if (item.getItemId()== R.id.openInFiles){
String keyID = message.getSelectedIndividualHashMap().get(KEY_ID);
String link = message.getSelectedIndividualHashMap().get("link");
fileViewerUtils.openFileInFilesApp(link, keyID);
}
return true;
});
popupMenu.show();
}
private void fetchFileInformation(String url, User activeUser) {
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
@Override
public ReadFilesystemOperation call() {
return new ReadFilesystemOperation(okHttpClient, activeUser, url, 0);
}
}).observeOn(Schedulers.io())
.subscribe(new SingleObserver<ReadFilesystemOperation>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
// unused atm
}
@Override
public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
DavResponse davResponse = readFilesystemOperation.readRemotePath();
if (davResponse.data != null) {
List<BrowserFile> browserFileList = (List<BrowserFile>) davResponse.data;
if (!browserFileList.isEmpty()) {
new Handler(context.getMainLooper()).post(() -> {
int resourceId = DrawableUtils
.INSTANCE
.getDrawableResourceIdForMimeType(browserFileList.get(0).getMimeType());
Drawable drawable = ContextCompat.getDrawable(context, resourceId);
image.getHierarchy().setPlaceholderImage(drawable);
});
}
}
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "Error reading file information", e);
}
});
}
public void assignCommonMessageInterface(CommonMessageInterface commonMessageInterface) {
this.commonMessageInterface = commonMessageInterface;
}
public void assignPreviewMessageInterface(PreviewMessageInterface previewMessageInterface) {
this.previewMessageInterface = previewMessageInterface;
}
public abstract EmojiTextView getMessageText();
public abstract ProgressBar getProgressBar();
public abstract SimpleDraweeView getImage();
public abstract View getPreviewContainer();
public abstract MaterialCardView getPreviewContactContainer();
public abstract SimpleDraweeView getPreviewContactPhoto();
public abstract EmojiTextView getPreviewContactName();
public abstract ProgressBar getPreviewContactProgressBar();
public abstract ReactionsInsideMessageBinding getReactionsBinding();
}

View File

@ -116,18 +116,21 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) :
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageTime.context, binding.messageTime.context,
true, true,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun setParentMessageDataOnMessageItem(message: ChatMessage) { private fun setParentMessageDataOnMessageItem(message: ChatMessage) {

View File

@ -122,18 +122,21 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageText.context, binding.messageText.context,
true, true,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
@SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility") @SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")

View File

@ -108,18 +108,21 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageTime.context, binding.messageTime.context,
true, true,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun setPollPreview(message: ChatMessage) { private fun setPollPreview(message: ChatMessage) {

View File

@ -35,7 +35,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.emoji.widget.EmojiTextView; import androidx.emoji.widget.EmojiTextView;
public class OutcomingPreviewMessageViewHolder extends MagicPreviewMessageViewHolder { public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
private final ItemCustomOutcomingPreviewMessageBinding binding; private final ItemCustomOutcomingPreviewMessageBinding binding;
@ -64,11 +64,6 @@ public class OutcomingPreviewMessageViewHolder extends MagicPreviewMessageViewHo
return binding.progressBar; return binding.progressBar;
} }
@Override
public SimpleDraweeView getImage() {
return binding.image;
}
@Override @Override
public View getPreviewContainer() { public View getPreviewContainer() {
return binding.previewContainer; return binding.previewContainer;

View File

@ -140,18 +140,21 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : MessageHolders
Reaction().showReactions( Reaction().showReactions(
message, message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions, binding.reactions,
binding.messageTime.context, binding.messageTime.context,
true, true,
viewThemeUtils viewThemeUtils
) )
binding.reactions.reactionsEmojiWrapper.setOnClickListener {
commonMessageInterface.onClickReactions(message)
} }
binding.reactions.reactionsEmojiWrapper.setOnLongClickListener { l: View? ->
commonMessageInterface.onOpenMessageActionsDialog(message) private fun longClickOnReaction(chatMessage: ChatMessage) {
true commonMessageInterface.onLongClickReactions(chatMessage)
} }
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
} }
private fun handleResetVoiceMessageState(message: ChatMessage) { private fun handleResetVoiceMessageState(message: ChatMessage) {

View File

@ -0,0 +1,341 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Marcel Hibbe
* @author Andy Scherzinger
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Handler
import android.util.Base64
import android.util.Log
import android.view.Gravity
import android.view.MenuItem
import android.view.View
import android.widget.PopupMenu
import android.widget.ProgressBar
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat
import androidx.emoji.widget.EmojiTextView
import autodagger.AutoInjector
import com.facebook.drawee.view.SimpleDraweeView
import com.google.android.material.card.MaterialCardView
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
import com.nextcloud.talk.utils.FileViewerUtils
import com.nextcloud.talk.utils.FileViewerUtils.ProgressUi
import com.stfalcon.chatkit.messages.MessageHolders.IncomingImageMessageViewHolder
import io.reactivex.Single
import io.reactivex.SingleObserver
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import java.io.ByteArrayInputStream
import java.io.IOException
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
IncomingImageMessageViewHolder<ChatMessage>(itemView, payload) {
@JvmField
@Inject
var context: Context? = null
@JvmField
@Inject
var viewThemeUtils: ViewThemeUtils? = null
@JvmField
@Inject
var okHttpClient: OkHttpClient? = null
open var progressBar: ProgressBar? = null
open var reactionsBinding: ReactionsInsideMessageBinding? = null
var fileViewerUtils: FileViewerUtils? = null
var clickView: View? = null
lateinit var commonMessageInterface: CommonMessageInterface
var previewMessageInterface: PreviewMessageInterface? = null
init {
sharedApplication!!.componentApplication.inject(this)
}
@SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) {
super.onBind(message)
if (userAvatar != null) {
if (message.isGrouped || message.isOneToOneConversation) {
if (message.isOneToOneConversation) {
userAvatar.visibility = View.GONE
} else {
userAvatar.visibility = View.INVISIBLE
}
} else {
userAvatar.visibility = View.VISIBLE
userAvatar.setOnClickListener { v: View ->
if (payload is MessagePayload) {
(payload as MessagePayload).profileBottomSheet.showFor(
message.actorId!!,
v.context
)
}
}
if (ACTOR_TYPE_BOTS == message.actorType && ACTOR_ID_CHANGELOG == message.actorId) {
if (context != null) {
val layers = arrayOfNulls<Drawable>(2)
layers[0] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_background)
layers[1] = ContextCompat.getDrawable(context!!, R.drawable.ic_launcher_foreground)
val layerDrawable = LayerDrawable(layers)
userAvatar.hierarchy.setPlaceholderImage(DisplayUtils.getRoundedDrawable(layerDrawable))
}
}
}
}
viewThemeUtils!!.platform.colorCircularProgressBar(progressBar!!)
clickView = image
messageText.visibility = View.VISIBLE
if (message.getCalculateMessageType() === ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
fileViewerUtils = FileViewerUtils(context!!, message.activeUser!!)
val fileName = message.selectedIndividualHashMap!![KEY_NAME]
messageText.text = fileName
if (message.selectedIndividualHashMap!!.containsKey(KEY_CONTACT_NAME)) {
previewContainer.visibility = View.GONE
previewContactName.text = message.selectedIndividualHashMap!![KEY_CONTACT_NAME]
progressBar = previewContactProgressBar
messageText.visibility = View.INVISIBLE
clickView = previewContactContainer
viewThemeUtils!!.talk.colorContactChatItemBackground(previewContactContainer)
viewThemeUtils!!.talk.colorContactChatItemName(previewContactName)
viewThemeUtils!!.platform.colorCircularProgressBarOnPrimaryContainer(previewContactProgressBar!!)
} else {
previewContainer.visibility = View.VISIBLE
previewContactContainer.visibility = View.GONE
}
if (message.selectedIndividualHashMap!!.containsKey(KEY_CONTACT_PHOTO)) {
image = previewContactPhoto
val drawable = getDrawableFromContactDetails(
context,
message.selectedIndividualHashMap!![KEY_CONTACT_PHOTO]
)
image.hierarchy.setPlaceholderImage(drawable)
} else if (message.selectedIndividualHashMap!!.containsKey(KEY_MIMETYPE)) {
val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE]
val drawableResourceId = getDrawableResourceIdForMimeType(mimetype)
val drawable = ContextCompat.getDrawable(context!!, drawableResourceId)
if (drawable != null &&
(
drawableResourceId == R.drawable.ic_mimetype_folder ||
drawableResourceId == R.drawable.ic_mimetype_package_x_generic
)
) {
drawable.setColorFilter(
viewThemeUtils!!.getScheme(image.context).primary,
PorterDuff.Mode.SRC_ATOP
)
}
image.hierarchy.setPlaceholderImage(drawable)
} else {
fetchFileInformation(
"/" + message.selectedIndividualHashMap!![KEY_PATH],
message.activeUser
)
}
if (message.activeUser != null &&
message.activeUser!!.username != null &&
message.activeUser!!.baseUrl != null
) {
clickView!!.setOnClickListener { v: View? ->
fileViewerUtils!!.openFile(
message,
ProgressUi(progressBar, messageText, image)
)
}
clickView!!.setOnLongClickListener { l: View? ->
onMessageViewLongClick(message)
true
}
} else {
Log.e(TAG, "failed to set click listener because activeUser, username or baseUrl were null")
}
fileViewerUtils!!.resumeToUpdateViewsByProgress(
message.selectedIndividualHashMap!![KEY_NAME]!!,
message.selectedIndividualHashMap!![KEY_ID]!!,
message.selectedIndividualHashMap!![KEY_MIMETYPE],
ProgressUi(progressBar, messageText, image)
)
} else if (message.getCalculateMessageType() === ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
messageText.text = "GIPHY"
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", messageText)
} else if (message.getCalculateMessageType() === ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE) {
messageText.text = "Tenor"
DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText)
} else {
if (message.messageType == ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE.name) {
(clickView as SimpleDraweeView?)?.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(message.imageUrl))
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context!!.startActivity(browserIntent)
}
} else {
(clickView as SimpleDraweeView?)?.setOnClickListener(null)
}
messageText.text = ""
}
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.replyable)
Reaction().showReactions(
message,
::clickOnReaction,
::longClickOnReaction,
reactionsBinding!!,
messageText.context,
true,
viewThemeUtils!!
)
}
private fun longClickOnReaction(chatMessage: ChatMessage) {
commonMessageInterface.onLongClickReactions(chatMessage)
}
private fun clickOnReaction(chatMessage: ChatMessage, emoji: String) {
commonMessageInterface.onClickReaction(chatMessage, emoji)
}
private fun getDrawableFromContactDetails(context: Context?, base64: String?): Drawable? {
var drawable: Drawable? = null
if (base64 != "") {
val inputStream = ByteArrayInputStream(
Base64.decode(base64!!.toByteArray(), Base64.DEFAULT)
)
drawable = Drawable.createFromResourceStream(
context!!.resources,
null, inputStream, null, null
)
try {
inputStream.close()
} catch (e: IOException) {
val drawableResourceId = getDrawableResourceIdForMimeType("text/vcard")
drawable = ContextCompat.getDrawable(context, drawableResourceId)
}
}
return drawable
}
private fun onMessageViewLongClick(message: ChatMessage) {
if (fileViewerUtils!!.isSupportedForInternalViewer(message.selectedIndividualHashMap!![KEY_MIMETYPE])) {
previewMessageInterface!!.onPreviewMessageLongClick(message)
return
}
val viewContext: Context? = if (itemView.context != null) {
itemView.context
} else {
context
}
val popupMenu = PopupMenu(
ContextThemeWrapper(viewContext, R.style.appActionBarPopupMenu),
itemView,
Gravity.START
)
popupMenu.inflate(R.menu.chat_preview_message_menu)
popupMenu.setOnMenuItemClickListener { item: MenuItem ->
if (item.itemId == R.id.openInFiles) {
val keyID = message.selectedIndividualHashMap!![KEY_ID]
val link = message.selectedIndividualHashMap!!["link"]
fileViewerUtils!!.openFileInFilesApp(link!!, keyID!!)
}
true
}
popupMenu.show()
}
private fun fetchFileInformation(url: String, activeUser: User?) {
Single.fromCallable { ReadFilesystemOperation(okHttpClient, activeUser, url, 0) }
.observeOn(Schedulers.io())
.subscribe(object : SingleObserver<ReadFilesystemOperation> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onSuccess(readFilesystemOperation: ReadFilesystemOperation) {
val davResponse = readFilesystemOperation.readRemotePath()
if (davResponse.data != null) {
val browserFileList = davResponse.data as List<BrowserFile>
if (browserFileList.isNotEmpty()) {
Handler(context!!.mainLooper).post {
val resourceId = getDrawableResourceIdForMimeType(browserFileList[0].mimeType)
val drawable = ContextCompat.getDrawable(context!!, resourceId)
image.hierarchy.setPlaceholderImage(drawable)
}
}
}
}
override fun onError(e: Throwable) {
Log.e(TAG, "Error reading file information", e)
}
})
}
fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) {
this.commonMessageInterface = commonMessageInterface
}
fun assignPreviewMessageInterface(previewMessageInterface: PreviewMessageInterface?) {
this.previewMessageInterface = previewMessageInterface
}
abstract val messageText: EmojiTextView
abstract val previewContainer: View
abstract val previewContactContainer: MaterialCardView
abstract val previewContactPhoto: SimpleDraweeView
abstract val previewContactName: EmojiTextView
abstract val previewContactProgressBar: ProgressBar?
companion object {
private const val TAG = "PreviewMsgViewHolder"
const val KEY_CONTACT_NAME = "contact-name"
const val KEY_CONTACT_PHOTO = "contact-photo"
const val KEY_MIMETYPE = "mimetype"
const val KEY_ID = "id"
const val KEY_PATH = "path"
const val ACTOR_TYPE_BOTS = "bots"
const val ACTOR_ID_CHANGELOG = "changelog"
const val KEY_NAME = "name"
}
}

View File

@ -37,17 +37,22 @@ class Reaction {
fun showReactions( fun showReactions(
message: ChatMessage, message: ChatMessage,
clickOnReaction: (message: ChatMessage, emoji: String) -> Unit,
longClickOnReaction: (message: ChatMessage) -> Unit,
binding: ReactionsInsideMessageBinding, binding: ReactionsInsideMessageBinding,
context: Context, context: Context,
isOutgoingMessage: Boolean, isOutgoingMessage: Boolean,
viewThemeUtils: ViewThemeUtils viewThemeUtils: ViewThemeUtils
) { ) {
binding.reactionsEmojiWrapper.removeAllViews() binding.reactionsEmojiWrapper.removeAllViews()
if (message.reactions != null && message.reactions!!.isNotEmpty()) { if (message.reactions != null && message.reactions!!.isNotEmpty()) {
binding.reactionsEmojiWrapper.visibility = View.VISIBLE binding.reactionsEmojiWrapper.visibility = View.VISIBLE
var remainingEmojisToDisplay = MAX_EMOJIS_TO_DISPLAY binding.reactionsEmojiWrapper.setOnLongClickListener {
val showInfoAboutMoreEmojis = message.reactions!!.size > MAX_EMOJIS_TO_DISPLAY longClickOnReaction(message)
true
}
val amountParams = getAmountLayoutParams(context) val amountParams = getAmountLayoutParams(context)
val wrapperParams = getWrapperLayoutParams(context) val wrapperParams = getWrapperLayoutParams(context)
@ -78,13 +83,15 @@ class Reaction {
), ),
) )
binding.reactionsEmojiWrapper.addView(emojiWithAmountWrapper) emojiWithAmountWrapper.setOnClickListener {
clickOnReaction(message, emoji)
remainingEmojisToDisplay--
if (remainingEmojisToDisplay == 0 && showInfoAboutMoreEmojis) {
binding.reactionsEmojiWrapper.addView(getMoreReactionsTextView(context, textColor))
break
} }
emojiWithAmountWrapper.setOnLongClickListener {
longClickOnReaction(message)
false
}
binding.reactionsEmojiWrapper.addView(emojiWithAmountWrapper)
} }
} else { } else {
binding.reactionsEmojiWrapper.visibility = View.GONE binding.reactionsEmojiWrapper.visibility = View.GONE
@ -132,13 +139,6 @@ class Reaction {
return emojiWithAmountWrapper return emojiWithAmountWrapper
} }
private fun getMoreReactionsTextView(context: Context, textColor: Int): TextView {
val infoAboutMoreEmojis = TextView(context)
infoAboutMoreEmojis.setTextColor(textColor)
infoAboutMoreEmojis.text = EMOJI_MORE
return infoAboutMoreEmojis
}
private fun getEmojiTextView(context: Context, emoji: String): EmojiTextView { private fun getEmojiTextView(context: Context, emoji: String): EmojiTextView {
val reactionEmoji = EmojiTextView(context) val reactionEmoji = EmojiTextView(context)
reactionEmoji.text = emoji reactionEmoji.text = emoji
@ -202,12 +202,10 @@ class Reaction {
) )
companion object { companion object {
const val MAX_EMOJIS_TO_DISPLAY = 4
const val AMOUNT_START_MARGIN: Float = 2F const val AMOUNT_START_MARGIN: Float = 2F
const val EMOJI_END_MARGIN: Float = 6F const val EMOJI_END_MARGIN: Float = 6F
const val EMOJI_AND_AMOUNT_PADDING_SIDE: Float = 4F const val EMOJI_AND_AMOUNT_PADDING_SIDE: Float = 4F
const val WRAPPER_PADDING_TOP: Float = 2F const val WRAPPER_PADDING_TOP: Float = 2F
const val WRAPPER_PADDING_BOTTOM: Float = 3F const val WRAPPER_PADDING_BOTTOM: Float = 3F
const val EMOJI_MORE = ""
} }
} }

View File

@ -71,9 +71,9 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
((OutcomingVoiceMessageViewHolder) holder).assignVoiceMessageInterface(chatController); ((OutcomingVoiceMessageViewHolder) holder).assignVoiceMessageInterface(chatController);
((OutcomingVoiceMessageViewHolder) holder).assignCommonMessageInterface(chatController); ((OutcomingVoiceMessageViewHolder) holder).assignCommonMessageInterface(chatController);
} else if (holder instanceof MagicPreviewMessageViewHolder) { } else if (holder instanceof PreviewMessageViewHolder) {
((MagicPreviewMessageViewHolder) holder).assignPreviewMessageInterface(chatController); ((PreviewMessageViewHolder) holder).assignPreviewMessageInterface(chatController);
((MagicPreviewMessageViewHolder) holder).assignCommonMessageInterface(chatController); ((PreviewMessageViewHolder) holder).assignCommonMessageInterface(chatController);
} }
} }
} }

View File

@ -7,7 +7,7 @@
* @author Tim Krüger * @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me> * Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -138,6 +138,8 @@ import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
import com.nextcloud.talk.jobs.ShareOperationWorker import com.nextcloud.talk.jobs.ShareOperationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.messagesearch.MessageSearchActivity import com.nextcloud.talk.messagesearch.MessageSearchActivity
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
@ -150,6 +152,7 @@ import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.polls.ui.PollCreateDialogFragment import com.nextcloud.talk.polls.ui.PollCreateDialogFragment
import com.nextcloud.talk.presenters.MentionAutocompletePresenter import com.nextcloud.talk.presenters.MentionAutocompletePresenter
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.ui.dialog.AttachmentDialog import com.nextcloud.talk.ui.dialog.AttachmentDialog
@ -235,6 +238,9 @@ class ChatController(args: Bundle) :
@Inject @Inject
lateinit var eventBus: EventBus lateinit var eventBus: EventBus
@Inject
lateinit var reactionsRepository: ReactionsRepository
@Inject @Inject
lateinit var permissionUtil: PlatformPermissionUtil lateinit var permissionUtil: PlatformPermissionUtil
@ -1225,7 +1231,7 @@ class ChatController(args: Bundle) :
} }
} }
fun vibrate() { private fun vibrate() {
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= O) { if (Build.VERSION.SDK_INT >= O) {
vibrator.vibrate(VibrationEffect.createOneShot(SHORT_VIBRATE, VibrationEffect.DEFAULT_AMPLITUDE)) vibrator.vibrate(VibrationEffect.createOneShot(SHORT_VIBRATE, VibrationEffect.DEFAULT_AMPLITUDE))
@ -2788,7 +2794,22 @@ class ChatController(args: Bundle) :
} }
} }
override fun onClickReactions(chatMessage: ChatMessage) { override fun onClickReaction(chatMessage: ChatMessage, emoji: String) {
vibrate()
if (chatMessage.reactionsSelf?.contains(emoji) == true) {
reactionsRepository.deleteReaction(currentConversation!!, chatMessage, emoji)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionDeletedObserver())
} else {
reactionsRepository.addReaction(currentConversation!!, chatMessage, emoji)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionAddedObserver())
}
}
override fun onLongClickReactions(chatMessage: ChatMessage) {
activity?.let { activity?.let {
ShowReactionsDialog( ShowReactionsDialog(
activity!!, activity!!,
@ -2801,6 +2822,52 @@ class ChatController(args: Bundle) :
} }
} }
inner class ReactionAddedObserver : Observer<ReactionAddedModel> {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(reactionAddedModel: ReactionAddedModel) {
Log.d(TAG, "onNext")
if (reactionAddedModel.success) {
updateUiToAddReaction(
reactionAddedModel.chatMessage,
reactionAddedModel.emoji
)
}
}
override fun onError(e: Throwable) {
Log.d(TAG, "onError")
}
override fun onComplete() {
Log.d(TAG, "onComplete")
}
}
inner class ReactionDeletedObserver : Observer<ReactionDeletedModel> {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(reactionDeletedModel: ReactionDeletedModel) {
Log.d(TAG, "onNext")
if (reactionDeletedModel.success) {
updateUiToDeleteReaction(
reactionDeletedModel.chatMessage,
reactionDeletedModel.emoji
)
}
}
override fun onError(e: Throwable) {
Log.d(TAG, "onError")
}
override fun onComplete() {
Log.d(TAG, "onComplete")
}
}
override fun onOpenMessageActionsDialog(chatMessage: ChatMessage) { override fun onOpenMessageActionsDialog(chatMessage: ChatMessage) {
openMessageActionsDialog(chatMessage) openMessageActionsDialog(chatMessage)
} }
@ -2823,8 +2890,7 @@ class ChatController(args: Bundle) :
conversationUser, conversationUser,
currentConversation, currentConversation,
isShowMessageDeletionButton(message), isShowMessageDeletionButton(message),
participantPermissions.hasChatPermission(), participantPermissions.hasChatPermission()
ncApi
).show() ).show()
} }
} }
@ -3126,7 +3192,7 @@ class ChatController(args: Bundle) :
adapter?.update(messageTemp) adapter?.update(messageTemp)
} }
fun updateAdapterAfterSendReaction(message: ChatMessage, emoji: String) { fun updateUiToAddReaction(message: ChatMessage, emoji: String) {
if (message.reactions == null) { if (message.reactions == null) {
message.reactions = LinkedHashMap() message.reactions = LinkedHashMap()
} }
@ -3144,6 +3210,24 @@ class ChatController(args: Bundle) :
adapter?.update(message) adapter?.update(message)
} }
fun updateUiToDeleteReaction(message: ChatMessage, emoji: String) {
if (message.reactions == null) {
message.reactions = LinkedHashMap()
}
if (message.reactionsSelf == null) {
message.reactionsSelf = ArrayList<String>()
}
var amount = message.reactions!![emoji]
if (amount == null) {
amount = 0
}
message.reactions!![emoji] = amount - 1
message.reactionsSelf!!.remove(emoji)
adapter?.update(message)
}
private fun isShowMessageDeletionButton(message: ChatMessage): Boolean { private fun isShowMessageDeletionButton(message: ChatMessage): Boolean {
if (conversationUser == null) return false if (conversationUser == null) return false

View File

@ -3,6 +3,8 @@
* *
* @author Álvaro Brey * @author Álvaro Brey
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2022 Álvaro Brey * Copyright (C) 2022 Álvaro Brey
* Copyright (C) 2022 Nextcloud GmbH * Copyright (C) 2022 Nextcloud GmbH
@ -35,6 +37,8 @@ import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsR
import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepositoryImpl import com.nextcloud.talk.remotefilebrowser.repositories.RemoteFileBrowserItemsRepositoryImpl
import com.nextcloud.talk.repositories.conversations.ConversationsRepository import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.repositories.conversations.ConversationsRepositoryImpl import com.nextcloud.talk.repositories.conversations.ConversationsRepositoryImpl
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.repositories.reactions.ReactionsRepositoryImpl
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
@ -82,4 +86,9 @@ class RepositoryModule {
fun provideArbitraryStoragesRepository(database: TalkDatabase): ArbitraryStoragesRepository { fun provideArbitraryStoragesRepository(database: TalkDatabase): ArbitraryStoragesRepository {
return ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao()) return ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao())
} }
@Provides
fun provideReactionsRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ReactionsRepository {
return ReactionsRepositoryImpl(ncApi, userProvider)
}
} }

View File

@ -0,0 +1,29 @@
/*
* 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.domain
import com.nextcloud.talk.models.json.chat.ChatMessage
data class ReactionAddedModel(
var chatMessage: ChatMessage,
var emoji: String,
var success: Boolean
)

View File

@ -0,0 +1,29 @@
/*
* 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.domain
import com.nextcloud.talk.models.json.chat.ChatMessage
data class ReactionDeletedModel(
var chatMessage: ChatMessage,
var emoji: String,
var success: Boolean
)

View File

@ -0,0 +1,42 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.repositories.reactions
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import io.reactivex.Observable
interface ReactionsRepository {
fun addReaction(
currentConversation: Conversation,
message: ChatMessage,
emoji: String
): Observable<ReactionAddedModel>
fun deleteReaction(
currentConversation: Conversation,
message: ChatMessage,
emoji: String
): Observable<ReactionDeletedModel>
}

View File

@ -0,0 +1,102 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.repositories.reactions
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericMeta
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observable
class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: CurrentUserProviderNew) :
ReactionsRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
override fun addReaction(
currentConversation: Conversation,
message: ChatMessage,
emoji: String
): Observable<ReactionAddedModel> {
return ncApi.sendReaction(
credentials,
ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl,
currentConversation.token,
message.id
),
emoji
).map { mapToReactionAddedModel(message, emoji, it.ocs?.meta!!) }
}
override fun deleteReaction(
currentConversation: Conversation,
message: ChatMessage,
emoji: String
): Observable<ReactionDeletedModel> {
return ncApi.deleteReaction(
credentials,
ApiUtils.getUrlForMessageReaction(
currentUser.baseUrl,
currentConversation.token,
message.id
),
emoji
).map { mapToReactionDeletedModel(message, emoji, it.ocs?.meta!!) }
}
private fun mapToReactionAddedModel(
message: ChatMessage,
emoji: String,
reactionResponse: GenericMeta
): ReactionAddedModel {
val success = reactionResponse.statusCode == HTTP_CREATED
return ReactionAddedModel(
message,
emoji,
success
)
}
private fun mapToReactionDeletedModel(
message: ChatMessage,
emoji: String,
reactionResponse: GenericMeta
): ReactionDeletedModel {
val success = reactionResponse.statusCode == HTTP_OK
return ReactionDeletedModel(
message,
emoji,
success
)
}
companion object {
private const val HTTP_OK: Int = 200
private const val HTTP_CREATED: Int = 201
}
}

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -25,7 +27,6 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -35,16 +36,16 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.controllers.ChatController import com.nextcloud.talk.controllers.ChatController
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogMessageActionsBinding import com.nextcloud.talk.databinding.DialogMessageActionsBinding
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.vanniktech.emoji.EmojiPopup import com.vanniktech.emoji.EmojiPopup
import com.vanniktech.emoji.EmojiTextView import com.vanniktech.emoji.EmojiTextView
@ -63,13 +64,15 @@ class MessageActionsDialog(
private val user: User?, private val user: User?,
private val currentConversation: Conversation?, private val currentConversation: Conversation?,
private val showMessageDeletionButton: Boolean, private val showMessageDeletionButton: Boolean,
private val hasChatPermission: Boolean, private val hasChatPermission: Boolean
private val ncApi: NcApi
) : BottomSheetDialog(chatController.activity!!) { ) : BottomSheetDialog(chatController.activity!!) {
@Inject @Inject
lateinit var viewThemeUtils: ViewThemeUtils lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var reactionsRepository: ReactionsRepository
private lateinit var dialogMessageActionsBinding: DialogMessageActionsBinding private lateinit var dialogMessageActionsBinding: DialogMessageActionsBinding
private lateinit var popup: EmojiPopup private lateinit var popup: EmojiPopup
@ -138,7 +141,7 @@ class MessageActionsDialog(
}, },
onEmojiClickListener = { onEmojiClickListener = {
popup.dismiss() popup.dismiss()
sendReaction(message, it.unicode) clickOnEmoji(message, it.unicode)
}, },
onEmojiPopupDismissListener = { onEmojiPopupDismissListener = {
dialogMessageActionsBinding.emojiMore.clearFocus() dialogMessageActionsBinding.emojiMore.clearFocus()
@ -180,27 +183,27 @@ class MessageActionsDialog(
) { ) {
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiThumbsUp) checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiThumbsUp)
dialogMessageActionsBinding.emojiThumbsUp.setOnClickListener { dialogMessageActionsBinding.emojiThumbsUp.setOnClickListener {
sendReaction(message, dialogMessageActionsBinding.emojiThumbsUp.text.toString()) clickOnEmoji(message, dialogMessageActionsBinding.emojiThumbsUp.text.toString())
} }
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiThumbsDown) checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiThumbsDown)
dialogMessageActionsBinding.emojiThumbsDown.setOnClickListener { dialogMessageActionsBinding.emojiThumbsDown.setOnClickListener {
sendReaction(message, dialogMessageActionsBinding.emojiThumbsDown.text.toString()) clickOnEmoji(message, dialogMessageActionsBinding.emojiThumbsDown.text.toString())
} }
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiLaugh) checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiLaugh)
dialogMessageActionsBinding.emojiLaugh.setOnClickListener { dialogMessageActionsBinding.emojiLaugh.setOnClickListener {
sendReaction(message, dialogMessageActionsBinding.emojiLaugh.text.toString()) clickOnEmoji(message, dialogMessageActionsBinding.emojiLaugh.text.toString())
} }
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiHeart) checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiHeart)
dialogMessageActionsBinding.emojiHeart.setOnClickListener { dialogMessageActionsBinding.emojiHeart.setOnClickListener {
sendReaction(message, dialogMessageActionsBinding.emojiHeart.text.toString()) clickOnEmoji(message, dialogMessageActionsBinding.emojiHeart.text.toString())
} }
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiConfused) checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiConfused)
dialogMessageActionsBinding.emojiConfused.setOnClickListener { dialogMessageActionsBinding.emojiConfused.setOnClickListener {
sendReaction(message, dialogMessageActionsBinding.emojiConfused.text.toString()) clickOnEmoji(message, dialogMessageActionsBinding.emojiConfused.text.toString())
} }
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiSad) checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiSad)
dialogMessageActionsBinding.emojiSad.setOnClickListener { dialogMessageActionsBinding.emojiSad.setOnClickListener {
sendReaction(message, dialogMessageActionsBinding.emojiSad.text.toString()) clickOnEmoji(message, dialogMessageActionsBinding.emojiSad.text.toString())
} }
dialogMessageActionsBinding.emojiMore.setOnClickListener { dialogMessageActionsBinding.emojiMore.setOnClickListener {
@ -302,88 +305,66 @@ class MessageActionsDialog(
} }
} }
private fun sendReaction(message: ChatMessage, emoji: String) { private fun clickOnEmoji(message: ChatMessage, emoji: String) {
if (message.reactionsSelf?.contains(emoji) == true) { if (message.reactionsSelf?.contains(emoji) == true) {
deleteReaction(message, emoji) reactionsRepository.deleteReaction(currentConversation!!, message, emoji)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(ReactionDeletedObserver())
} else { } else {
addReaction(message, emoji) reactionsRepository.addReaction(currentConversation!!, message, emoji)
} .subscribeOn(Schedulers.io())
}
private fun addReaction(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()) ?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<GenericOverall> { ?.subscribe(ReactionAddedObserver())
override fun onSubscribe(d: Disposable) { }
// unused atm
} }
override fun onNext(genericOverall: GenericOverall) { inner class ReactionAddedObserver : Observer<ReactionAddedModel> {
val statusCode = genericOverall.ocs?.meta?.statusCode override fun onSubscribe(d: Disposable) {
if (statusCode == HTTP_CREATED) { }
chatController.updateAdapterAfterSendReaction(message, emoji)
override fun onNext(reactionAddedModel: ReactionAddedModel) {
if (reactionAddedModel.success) {
chatController.updateUiToAddReaction(
reactionAddedModel.chatMessage,
reactionAddedModel.emoji
)
} }
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
Log.e(TAG, "error while sending reaction: $emoji")
} }
override fun onComplete() { override fun onComplete() {
dismiss() dismiss()
} }
})
} }
private fun deleteReaction(message: ChatMessage, emoji: String) { inner class ReactionDeletedObserver : Observer<ReactionDeletedModel> {
val credentials = ApiUtils.getCredentials(user?.username, user?.token)
ncApi.deleteReaction(
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) { override fun onSubscribe(d: Disposable) {
// unused atm
} }
override fun onNext(genericOverall: GenericOverall) { override fun onNext(reactionDeletedModel: ReactionDeletedModel) {
Log.d(TAG, "deleted reaction: $emoji") if (reactionDeletedModel.success) {
chatController.updateUiToDeleteReaction(
reactionDeletedModel.chatMessage,
reactionDeletedModel.emoji
)
}
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
Log.e(TAG, "error while deleting reaction: $emoji")
} }
override fun onComplete() { override fun onComplete() {
dismiss() dismiss()
} }
})
} }
companion object { companion object {
private const val TAG = "MessageActionsDialog" private val TAG = MessageActionsDialog::class.java.simpleName
private const val ACTOR_LENGTH = 6 private const val ACTOR_LENGTH = 6
private const val NO_PREVIOUS_MESSAGE_ID: Int = -1 private const val NO_PREVIOUS_MESSAGE_ID: Int = -1
private const val HTTP_CREATED: Int = 201
private const val DELAY: Long = 200 private const val DELAY: Long = 200
} }
} }

View File

@ -41,7 +41,7 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.activities.FullScreenImageActivity import com.nextcloud.talk.activities.FullScreenImageActivity
import com.nextcloud.talk.activities.FullScreenMediaActivity import com.nextcloud.talk.activities.FullScreenMediaActivity
import com.nextcloud.talk.activities.FullScreenTextViewerActivity import com.nextcloud.talk.activities.FullScreenTextViewerActivity
import com.nextcloud.talk.adapters.messages.MagicPreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
@ -78,12 +78,12 @@ class FileViewerUtils(private val context: Context, private val user: User) {
message: ChatMessage, message: ChatMessage,
progressUi: ProgressUi progressUi: ProgressUi
) { ) {
val fileName = message.selectedIndividualHashMap!![MagicPreviewMessageViewHolder.KEY_NAME]!! val fileName = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_NAME]!!
val mimetype = message.selectedIndividualHashMap!![MagicPreviewMessageViewHolder.KEY_MIMETYPE]!! val mimetype = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_MIMETYPE]!!
val link = message.selectedIndividualHashMap!!["link"]!! val link = message.selectedIndividualHashMap!!["link"]!!
val fileId = message.selectedIndividualHashMap!![MagicPreviewMessageViewHolder.KEY_ID]!! val fileId = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_ID]!!
val path = message.selectedIndividualHashMap!![MagicPreviewMessageViewHolder.KEY_PATH]!! val path = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_PATH]!!
var size = message.selectedIndividualHashMap!!["size"] var size = message.selectedIndividualHashMap!!["size"]
if (size == null) { if (size == null) {

View File

@ -17,20 +17,20 @@
You should have received a copy of the GNU Affero General Public License 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/>. 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" <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/reactions_emoji_horizontal_scroller"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:fillViewport="true"
android:measureAllChildren="false"
android:scrollbars="none" >
<LinearLayout
android:id="@+id/reactions_emoji_wrapper" android:id="@+id/reactions_emoji_wrapper"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="5dp" android:gravity="center_vertical"
app:layout_alignSelf="flex_start" android:orientation="horizontal" >
app:layout_flexGrow="1" </LinearLayout>
app:layout_wrapBefore="true"> </HorizontalScrollView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="emojis">
</TextView>
</LinearLayout>