Show profile action on avatar click

For every click on a avatar in a group chat it will be tried to receive a
hover card from the Nextcloud server. The endpoint returns multiple
actions. Those actions will be filtered for the app ids 'spreed',
'email' and 'profile'. Other will be ignored.
The received filtered actions will be shown in a bottom sheet.

Resolves: #1644

Signed-off-by: Tim Krüger <t@timkrueger.me>
This commit is contained in:
Tim Krüger 2021-11-04 15:27:43 +01:00
parent 036cf7c590
commit 9accac3325
No known key found for this signature in database
GPG key ID: FECE3A7222C52A4E
15 changed files with 627 additions and 15 deletions

View file

@ -4,6 +4,8 @@
* @author Mario Danic * @author Mario Danic
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Andy Scherzinger * @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 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
@ -48,6 +50,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
@ -56,8 +59,8 @@ import java.net.URLEncoder
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class IncomingLocationMessageViewHolder(incomingView: View) : MessageHolders class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView) { .IncomingTextMessageViewHolder<ChatMessage>(incomingView, payload) {
private val binding: ItemCustomIncomingLocationMessageBinding = private val binding: ItemCustomIncomingLocationMessageBinding =
ItemCustomIncomingLocationMessageBinding.bind(itemView) ItemCustomIncomingLocationMessageBinding.bind(itemView)
@ -102,6 +105,9 @@ class IncomingLocationMessageViewHolder(incomingView: View) : MessageHolders
val author: String = message.actorDisplayName val author: String = message.actorDisplayName
if (!TextUtils.isEmpty(author)) { if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
(payload as? ProfileBottomSheet)?.showFor(message.actorId, itemView.context)
}
} else { } else {
binding.messageAuthor.setText(R.string.nc_nick_guest) binding.messageAuthor.setText(R.string.nc_nick_guest)
} }

View file

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Andy Scherzinger * @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 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
@ -31,8 +33,8 @@ import androidx.emoji.widget.EmojiTextView;
public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHolder { public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHolder {
private final ItemCustomIncomingPreviewMessageBinding binding; private final ItemCustomIncomingPreviewMessageBinding binding;
public IncomingPreviewMessageViewHolder(View itemView) { public IncomingPreviewMessageViewHolder(View itemView, Object payload) {
super(itemView); super(itemView, payload);
binding = ItemCustomIncomingPreviewMessageBinding.bind(itemView); binding = ItemCustomIncomingPreviewMessageBinding.bind(itemView);
} }

View file

@ -4,6 +4,8 @@
* @author Mario Danic * @author Mario Danic
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Andy Scherzinger * @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 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
@ -46,6 +48,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
@ -54,8 +57,8 @@ import java.util.concurrent.ExecutionException
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class IncomingVoiceMessageViewHolder(incomingView: View) : MessageHolders class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView) { .IncomingTextMessageViewHolder<ChatMessage>(incomingView, payload) {
private val binding: ItemCustomIncomingVoiceMessageBinding = private val binding: ItemCustomIncomingVoiceMessageBinding =
ItemCustomIncomingVoiceMessageBinding.bind(itemView) ItemCustomIncomingVoiceMessageBinding.bind(itemView)
@ -192,6 +195,9 @@ class IncomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
val author: String = message.actorDisplayName val author: String = message.actorDisplayName
if (!TextUtils.isEmpty(author)) { if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
(payload as? ProfileBottomSheet)?.showFor(message.actorId, itemView.context)
}
} else { } else {
binding.messageAuthor.setText(R.string.nc_nick_guest) binding.messageAuthor.setText(R.string.nc_nick_guest)
} }

View file

@ -3,6 +3,8 @@
* *
* @author Mario Danic * @author Mario Danic
* @author Andy Scherzinger * @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 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
* *
@ -44,6 +46,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
@ -53,8 +56,8 @@ import com.stfalcon.chatkit.messages.MessageHolders
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(itemView) { .IncomingTextMessageViewHolder<ChatMessage>(itemView, payload) {
private val binding: ItemCustomIncomingTextMessageBinding = ItemCustomIncomingTextMessageBinding.bind(itemView) private val binding: ItemCustomIncomingTextMessageBinding = ItemCustomIncomingTextMessageBinding.bind(itemView)
@ -72,6 +75,9 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
val author: String = message.actorDisplayName val author: String = message.actorDisplayName
if (!TextUtils.isEmpty(author)) { if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
(payload as? ProfileBottomSheet)?.showFor(message.actorId, itemView.context)
}
} else { } else {
binding.messageAuthor.setText(R.string.nc_nick_guest) binding.messageAuthor.setText(R.string.nc_nick_guest)
} }

View file

@ -4,6 +4,8 @@
* @author Mario Danic * @author Mario Danic
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Andy Scherzinger * @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 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
@ -54,6 +56,7 @@ import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
import com.nextcloud.talk.models.database.CapabilitiesUtil; import com.nextcloud.talk.models.database.CapabilitiesUtil;
import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.chat.ChatMessage; import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet;
import com.nextcloud.talk.utils.AccountUtils; import com.nextcloud.talk.utils.AccountUtils;
import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.DrawableUtils; import com.nextcloud.talk.utils.DrawableUtils;
@ -110,8 +113,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
View clickView; View clickView;
public MagicPreviewMessageViewHolder(View itemView) { public MagicPreviewMessageViewHolder(View itemView, Object payload) {
super(itemView); super(itemView, payload);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this); NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
} }
@ -128,6 +131,11 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
} }
} else { } else {
userAvatar.setVisibility(View.VISIBLE); userAvatar.setVisibility(View.VISIBLE);
userAvatar.setOnClickListener(v -> {
if (payload instanceof ProfileBottomSheet){
((ProfileBottomSheet) payload).showFor(message.actorId, v.getContext());
}
});
if (ACTOR_TYPE_BOTS.equals(message.actorType) && ACTOR_ID_CHANGELOG.equals(message.actorId)) { if (ACTOR_TYPE_BOTS.equals(message.actorType) && ACTOR_ID_CHANGELOG.equals(message.actorId)) {
if (context != null) { if (context != null) {

View file

@ -2,6 +2,8 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Andy Scherzinger * @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 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
@ -32,7 +34,7 @@ public class OutcomingPreviewMessageViewHolder extends MagicPreviewMessageViewHo
private final ItemCustomOutcomingPreviewMessageBinding binding; private final ItemCustomOutcomingPreviewMessageBinding binding;
public OutcomingPreviewMessageViewHolder(View itemView) { public OutcomingPreviewMessageViewHolder(View itemView) {
super(itemView); super(itemView, null);
binding = ItemCustomOutcomingPreviewMessageBinding.bind(itemView); binding = ItemCustomOutcomingPreviewMessageBinding.bind(itemView);
} }

View file

@ -31,6 +31,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall;
import com.nextcloud.talk.models.json.conversations.RoomsOverall; import com.nextcloud.talk.models.json.conversations.RoomsOverall;
import com.nextcloud.talk.models.json.generic.GenericOverall; import com.nextcloud.talk.models.json.generic.GenericOverall;
import com.nextcloud.talk.models.json.generic.Status; import com.nextcloud.talk.models.json.generic.Status;
import com.nextcloud.talk.models.json.hovercard.HoverCardOverall;
import com.nextcloud.talk.models.json.mention.MentionOverall; import com.nextcloud.talk.models.json.mention.MentionOverall;
import com.nextcloud.talk.models.json.notifications.NotificationOverall; import com.nextcloud.talk.models.json.notifications.NotificationOverall;
import com.nextcloud.talk.models.json.participants.AddParticipantOverall; import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
@ -425,4 +426,7 @@ public interface NcApi {
@POST @POST
Observable<GenericOverall> notificationCalls(@Header("Authorization") String authorization, @Url String url, Observable<GenericOverall> notificationCalls(@Header("Authorization") String authorization, @Url String url,
@Field("level") Integer level); @Field("level") Integer level);
@GET
Observable<HoverCardOverall> hoverCard(@Header("Authorization") String authorization, @Url String url);
} }

View file

@ -132,6 +132,7 @@ import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.mention.Mention import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.presenters.MentionAutocompletePresenter 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.AttachmentDialog
import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
@ -427,9 +428,12 @@ class ChatController(args: Bundle) :
adapterWasNull = true adapterWasNull = true
val messageHolders = MessageHolders() val messageHolders = MessageHolders()
val profileBottomSheet = ProfileBottomSheet(ncApi!!, conversationUser!!, router)
messageHolders.setIncomingTextConfig( messageHolders.setIncomingTextConfig(
MagicIncomingTextMessageViewHolder::class.java, MagicIncomingTextMessageViewHolder::class.java,
R.layout.item_custom_incoming_text_message R.layout.item_custom_incoming_text_message,
profileBottomSheet
) )
messageHolders.setOutcomingTextConfig( messageHolders.setOutcomingTextConfig(
MagicOutcomingTextMessageViewHolder::class.java, MagicOutcomingTextMessageViewHolder::class.java,
@ -438,7 +442,8 @@ class ChatController(args: Bundle) :
messageHolders.setIncomingImageConfig( messageHolders.setIncomingImageConfig(
IncomingPreviewMessageViewHolder::class.java, IncomingPreviewMessageViewHolder::class.java,
R.layout.item_custom_incoming_preview_message R.layout.item_custom_incoming_preview_message,
profileBottomSheet
) )
messageHolders.setOutcomingImageConfig( messageHolders.setOutcomingImageConfig(
@ -460,14 +465,17 @@ class ChatController(args: Bundle) :
MagicUnreadNoticeMessageViewHolder::class.java, MagicUnreadNoticeMessageViewHolder::class.java,
R.layout.item_date_header, R.layout.item_date_header,
MagicUnreadNoticeMessageViewHolder::class.java, MagicUnreadNoticeMessageViewHolder::class.java,
R.layout.item_date_header, this R.layout.item_date_header,
this
) )
messageHolders.registerContentType( messageHolders.registerContentType(
CONTENT_TYPE_LOCATION, CONTENT_TYPE_LOCATION,
IncomingLocationMessageViewHolder::class.java, IncomingLocationMessageViewHolder::class.java,
profileBottomSheet,
R.layout.item_custom_incoming_location_message, R.layout.item_custom_incoming_location_message,
OutcomingLocationMessageViewHolder::class.java, OutcomingLocationMessageViewHolder::class.java,
null,
R.layout.item_custom_outcoming_location_message, R.layout.item_custom_outcoming_location_message,
this this
) )
@ -475,8 +483,10 @@ class ChatController(args: Bundle) :
messageHolders.registerContentType( messageHolders.registerContentType(
CONTENT_TYPE_VOICE_MESSAGE, CONTENT_TYPE_VOICE_MESSAGE,
IncomingVoiceMessageViewHolder::class.java, IncomingVoiceMessageViewHolder::class.java,
profileBottomSheet,
R.layout.item_custom_incoming_voice_message, R.layout.item_custom_incoming_voice_message,
OutcomingVoiceMessageViewHolder::class.java, OutcomingVoiceMessageViewHolder::class.java,
null,
R.layout.item_custom_outcoming_voice_message, R.layout.item_custom_outcoming_voice_message,
this this
) )
@ -2178,7 +2188,7 @@ class ChatController(args: Bundle) :
bundle.putBoolean(BundleKeys.KEY_FORWARD_MSG_FLAG, true) bundle.putBoolean(BundleKeys.KEY_FORWARD_MSG_FLAG, true)
bundle.putString(BundleKeys.KEY_FORWARD_MSG_TEXT, message?.text) bundle.putString(BundleKeys.KEY_FORWARD_MSG_TEXT, message?.text)
bundle.putString(BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM, roomId) bundle.putString(BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM, roomId)
getRouter().pushController( router.pushController(
RouterTransaction.with(ConversationsListController(bundle)) RouterTransaction.with(ConversationsListController(bundle))
.pushChangeHandler(HorizontalChangeHandler()) .pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler())

View file

@ -0,0 +1,96 @@
/*
*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
*
* 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.hovercard;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.parceler.Parcel;
import java.util.List;
import java.util.Objects;
@Parcel
@JsonObject
public class HoverCard {
@JsonField(name = "userId")
public String userId;
@JsonField(name = "displayName")
public String displayName;
@JsonField(name = "actions")
public List<HoverCardAction> actions;
public String getUserId() {
return this.userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public List<HoverCardAction> getActions() {
return actions;
}
public void setActions(List<HoverCardAction> actions) {
this.actions = actions;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HoverCard hoverCard = (HoverCard) o;
return Objects.equals(userId, hoverCard.userId) &&
Objects.equals(displayName, hoverCard.displayName) &&
Objects.equals(actions, hoverCard.actions);
}
@Override
public int hashCode() {
return Objects.hash(userId, displayName, actions);
}
@Override
public String toString() {
return "HoverCard{" +
"userId='" + userId + '\'' +
", displayName='" + displayName + '\'' +
", actions=" + actions +
'}';
}
}

View file

@ -0,0 +1,107 @@
/*
*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
*
* 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.hovercard;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.parceler.Parcel;
import java.util.Objects;
@Parcel
@JsonObject
public class HoverCardAction {
@JsonField(name = "title")
public String title;
@JsonField(name = "icon")
public String icon;
@JsonField(name = "hyperlink")
public String hyperlink;
@JsonField(name = "appId")
public String appId;
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getHyperlink() {
return hyperlink;
}
public void setHyperlink(String hyperlink) {
this.hyperlink = hyperlink;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HoverCardAction that = (HoverCardAction) o;
return Objects.equals(title, that.title) &&
Objects.equals(icon, that.icon) &&
Objects.equals(hyperlink, that.hyperlink) &&
Objects.equals(appId, that.appId);
}
@Override
public int hashCode() {
return Objects.hash(title, icon, hyperlink, appId);
}
@Override
public String toString() {
return "HoverCardAction{" +
"title='" + title + '\'' +
", icon='" + icon + '\'' +
", hyper='" + hyperlink + '\'' +
", appId='" + appId + '\'' +
'}';
}
}

View file

@ -0,0 +1,69 @@
/*
*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
*
* 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.hovercard;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.nextcloud.talk.models.json.generic.GenericOCS;
import java.util.Objects;
@JsonObject
public class HoverCardOCS extends GenericOCS {
@JsonField(name = "data")
public HoverCard data;
public HoverCard getData() {
return this.data;
}
public void setData(HoverCard data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
HoverCardOCS that = (HoverCardOCS) o;
return Objects.equals(data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), data);
}
@Override
public String toString() {
return "HoverCardOCS{" +
"data=" + data +
'}';
}
}

View file

@ -0,0 +1,64 @@
/*
*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
*
* 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.hovercard;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import java.util.Objects;
@JsonObject
public class HoverCardOverall {
@JsonField(name = "ocs")
public HoverCardOCS ocs;
public HoverCardOCS getOcs() {
return this.ocs;
}
public void setOcs(HoverCardOCS ocs) {
this.ocs = ocs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HoverCardOverall that = (HoverCardOverall) o;
return Objects.equals(ocs, that.ocs);
}
@Override
public int hashCode() {
return Objects.hash(ocs);
}
@Override
public String toString() {
return "HoverCardOverall{" +
"ocs=" + ocs +
'}';
}
}

View file

@ -0,0 +1,217 @@
/*
*
* Nextcloud Talk application
*
* @author Tim Krüger
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
*
* 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.ui.bottom.sheet
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import com.afollestad.materialdialogs.LayoutMode
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.bluelinelabs.conductor.Router
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.hovercard.HoverCardAction
import com.nextcloud.talk.models.json.hovercard.HoverCardOverall
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet.AllowedAppIds.EMAIL
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet.AllowedAppIds.PROFILE
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet.AllowedAppIds.SPREED
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.bundle.BundleKeys
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.parceler.Parcels
private const val TAG = "ProfileBottomSheet"
class ProfileBottomSheet(val ncApi: NcApi, val userEntity: UserEntity, val router: Router) {
private val allowedAppIds = listOf(SPREED.stringValue, PROFILE.stringValue, EMAIL.stringValue)
fun showFor(user: String, context: Context) {
ncApi.hoverCard(
ApiUtils.getCredentials(userEntity.username, userEntity.token),
ApiUtils.getUrlForHoverCard(userEntity.baseUrl, user)
).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : io.reactivex.Observer<HoverCardOverall> {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(hoverCardOverall: HoverCardOverall) {
bottomSheet(hoverCardOverall.ocs.data.actions, hoverCardOverall.ocs.data.displayName, user, context)
}
override fun onError(e: Throwable) {
Log.e(TAG, "Failed to get hover card for user $user", e)
}
override fun onComplete() {
}
})
}
private fun bottomSheet(actions: List<HoverCardAction>, displayName: String, userId: String, context: Context) {
val filteredActions = actions.filter { allowedAppIds.contains(it.appId) }
val items = filteredActions.map { configureActionListItem(it) }
MaterialDialog(context, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
cornerRadius(res = R.dimen.corner_radius)
title(text = displayName)
listItemsWithImage(items = items) { _, index, _ ->
val action = filteredActions[index]
when (AllowedAppIds.createFor(action)) {
PROFILE -> openProfile(action.hyperlink, context)
EMAIL -> composeEmail(action.title, context)
SPREED -> talkTo(userId)
}
}
}
}
private fun configureActionListItem(action: HoverCardAction): BasicListItemWithImage {
val drawable = when (AllowedAppIds.createFor(action)) {
PROFILE -> R.drawable.ic_user
EMAIL -> R.drawable.ic_email
SPREED -> R.drawable.ic_talk
}
return BasicListItemWithImage(
drawable,
action.title
)
}
private fun talkTo(userId: String) {
val apiVersion =
ApiUtils.getConversationApiVersion(userEntity, intArrayOf(ApiUtils.APIv4, 1))
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
userEntity.baseUrl,
"1",
null,
userId,
null
)
val credentials = ApiUtils.getCredentials(userEntity.username, userEntity.token)
ncApi!!.createRoom(
credentials,
retrofitBucket.getUrl(), retrofitBucket.getQueryMap()
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, userEntity)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.getOcs().getData().getToken())
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.getOcs().getData().getRoomId())
// FIXME once APIv2+ is used only, the createRoom already returns all the data
ncApi!!.getRoom(
credentials,
ApiUtils.getUrlForRoom(
apiVersion, userEntity.baseUrl,
roomOverall.getOcs().getData().getToken()
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<RoomOverall> {
override fun onSubscribe(d: Disposable) {
// unused atm
}
override fun onNext(roomOverall: RoomOverall) {
bundle.putParcelable(
BundleKeys.KEY_ACTIVE_CONVERSATION,
Parcels.wrap(roomOverall.getOcs().getData())
)
ConductorRemapping.remapChatController(
router, userEntity.id,
roomOverall.getOcs().getData().getToken(), bundle, true
)
}
override fun onError(e: Throwable) {
Log.e(TAG, e.message, e)
}
override fun onComplete() {
// unused atm
}
})
}
override fun onError(e: Throwable) {
Log.e(TAG, e.message, e)
}
override fun onComplete() {
// unused atm
}
})
}
private fun composeEmail(address: String, context: Context) {
val addresses = arrayListOf(address)
val intent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("mailto:") // only email apps should handle this
putExtra(Intent.EXTRA_EMAIL, addresses)
}
context.startActivity(intent)
}
private fun openProfile(hyperlink: String, context: Context) {
val webpage: Uri = Uri.parse(hyperlink)
val intent = Intent(Intent.ACTION_VIEW, webpage)
context.startActivity(intent)
}
enum class AllowedAppIds(val stringValue: String) {
SPREED("spreed"),
PROFILE("profile"),
EMAIL("email");
companion object {
fun createFor(action: HoverCardAction): AllowedAppIds = valueOf(action.appId.uppercase())
}
}
}

View file

@ -400,4 +400,7 @@ public class ApiUtils {
public static String getUrlToSendLocation(int version, String baseUrl, String roomToken) { public static String getUrlToSendLocation(int version, String baseUrl, String roomToken) {
return getUrlForChat(version, baseUrl, roomToken) + "/share"; return getUrlForChat(version, baseUrl, roomToken) + "/share";
} }
public static String getUrlForHoverCard(String baseUrl, String userId) { return baseUrl + ocsApiVersion +
"/hovercard/v1/" + userId; }
} }

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector android:autoMirrored="true"
android:height="24dp"
android:viewportHeight="16"
android:viewportWidth="16"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#757575"
android:pathData="m7.9992,0.999a6.9993,6.9994 0,0 0,-6.9992 6.9996,6.9993 6.9994,0 0,0 6.9992,6.9994 6.9993,6.9994 0,0 0,3.6308 -1.024c0.8602,0.3418 2.7871,1.356 3.2457,0.9179 0.4792,-0.4577 -0.5626,-2.6116 -0.8124,-3.412a6.9993,6.9994 0,0 0,0.935 -3.4814,6.9993 6.9994,0 0,0 -6.9991,-6.9993zM8,3.6601a4.34,4.3401 0,0 1,4.34 4.3401,4.34 4.3401,0 0,1 -4.34,4.3398 4.34,4.3401 0,0 1,-4.34 -4.3398,4.34 4.3401,0 0,1 4.34,-4.3401z"
android:strokeWidth=".14" />
</vector>