Merge pull request #1659 from nextcloud/feature/1644/avatar-hovercard

Avatar "hovercard"
This commit is contained in:
Tim Krueger 2021-11-10 14:04:33 +01:00 committed by GitHub
commit 6b13d0b818
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 659 additions and 41 deletions

View File

@ -4,6 +4,8 @@
* @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>
@ -48,6 +50,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
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.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
@ -56,8 +59,8 @@ import java.net.URLEncoder
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class IncomingLocationMessageViewHolder(incomingView: View) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView) {
class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView, payload) {
private val binding: ItemCustomIncomingLocationMessageBinding =
ItemCustomIncomingLocationMessageBinding.bind(itemView)
@ -102,6 +105,9 @@ class IncomingLocationMessageViewHolder(incomingView: View) : MessageHolders
val author: String = message.actorDisplayName
if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
(payload as? ProfileBottomSheet)?.showFor(message.actorId, itemView.context)
}
} else {
binding.messageAuthor.setText(R.string.nc_nick_guest)
}

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application
*
* @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>
*
* 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 {
private final ItemCustomIncomingPreviewMessageBinding binding;
public IncomingPreviewMessageViewHolder(View itemView) {
super(itemView);
public IncomingPreviewMessageViewHolder(View itemView, Object payload) {
super(itemView, payload);
binding = ItemCustomIncomingPreviewMessageBinding.bind(itemView);
}

View File

@ -4,6 +4,8 @@
* @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>
@ -46,6 +48,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemCustomIncomingVoiceMessageBinding
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.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
@ -54,8 +57,8 @@ import java.util.concurrent.ExecutionException
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class IncomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView) {
class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView, payload) {
private val binding: ItemCustomIncomingVoiceMessageBinding =
ItemCustomIncomingVoiceMessageBinding.bind(itemView)
@ -192,6 +195,9 @@ class IncomingVoiceMessageViewHolder(incomingView: View) : MessageHolders
val author: String = message.actorDisplayName
if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
(payload as? ProfileBottomSheet)?.showFor(message.actorId, itemView.context)
}
} else {
binding.messageAuthor.setText(R.string.nc_nick_guest)
}

View File

@ -3,6 +3,8 @@
*
* @author Mario Danic
* @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) 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.databinding.ItemCustomIncomingTextMessageBinding
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.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
@ -53,8 +56,8 @@ import com.stfalcon.chatkit.messages.MessageHolders
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(itemView) {
class MagicIncomingTextMessageViewHolder(itemView: View, payload: Any) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(itemView, payload) {
private val binding: ItemCustomIncomingTextMessageBinding = ItemCustomIncomingTextMessageBinding.bind(itemView)
@ -72,6 +75,9 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
val author: String = message.actorDisplayName
if (!TextUtils.isEmpty(author)) {
binding.messageAuthor.text = author
binding.messageUserAvatar.setOnClickListener {
(payload as? ProfileBottomSheet)?.showFor(message.actorId, itemView.context)
}
} else {
binding.messageAuthor.setText(R.string.nc_nick_guest)
}

View File

@ -4,6 +4,8 @@
* @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>
@ -54,6 +56,7 @@ import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
import com.nextcloud.talk.models.database.CapabilitiesUtil;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet;
import com.nextcloud.talk.utils.AccountUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.DrawableUtils;
@ -110,8 +113,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
View clickView;
public MagicPreviewMessageViewHolder(View itemView) {
super(itemView);
public MagicPreviewMessageViewHolder(View itemView, Object payload) {
super(itemView, payload);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
}
@ -128,6 +131,11 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
}
} else {
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 (context != null) {

View File

@ -2,6 +2,8 @@
* Nextcloud Talk application
*
* @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>
*
* 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;
public OutcomingPreviewMessageViewHolder(View itemView) {
super(itemView);
super(itemView, null);
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.generic.GenericOverall;
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.notifications.NotificationOverall;
import com.nextcloud.talk.models.json.participants.AddParticipantOverall;
@ -425,4 +426,7 @@ public interface NcApi {
@POST
Observable<GenericOverall> notificationCalls(@Header("Authorization") String authorization, @Url String url,
@Field("level") Integer level);
@GET
Observable<HoverCardOverall> hoverCard(@Header("Authorization") String authorization, @Url String url);
}

View File

@ -68,32 +68,32 @@ public class DavUtils {
public static final String PROPERTY_QUOTA_AVAILABLE_BYTES = "quota-available-bytes";
static Property.Name[] getAllPropSet() {
List<Property.Name> propSet = new ArrayList<>();
List<Property.Name> props = new ArrayList<>();
propSet.add(DisplayName.NAME);
propSet.add(GetContentType.NAME);
propSet.add(GetContentLength.NAME);
propSet.add(GetContentType.NAME);
propSet.add(GetContentLength.NAME);
propSet.add(GetLastModified.NAME);
propSet.add(CreationDate.NAME);
propSet.add(GetETag.NAME);
propSet.add(ResourceType.NAME);
props.add(DisplayName.NAME);
props.add(GetContentType.NAME);
props.add(GetContentLength.NAME);
props.add(GetContentType.NAME);
props.add(GetContentLength.NAME);
props.add(GetLastModified.NAME);
props.add(CreationDate.NAME);
props.add(GetETag.NAME);
props.add(ResourceType.NAME);
propSet.add(NCPermission.NAME);
propSet.add(OCId.NAME);
propSet.add(OCSize.NAME);
propSet.add(OCFavorite.NAME);
propSet.add(new Property.Name(OC_NAMESPACE, EXTENDED_PROPERTY_OWNER_ID));
propSet.add(new Property.Name(OC_NAMESPACE, EXTENDED_PROPERTY_OWNER_DISPLAY_NAME));
propSet.add(new Property.Name(OC_NAMESPACE, EXTENDED_PROPERTY_UNREAD_COMMENTS));
props.add(NCPermission.NAME);
props.add(OCId.NAME);
props.add(OCSize.NAME);
props.add(OCFavorite.NAME);
props.add(new Property.Name(OC_NAMESPACE, EXTENDED_PROPERTY_OWNER_ID));
props.add(new Property.Name(OC_NAMESPACE, EXTENDED_PROPERTY_OWNER_DISPLAY_NAME));
props.add(new Property.Name(OC_NAMESPACE, EXTENDED_PROPERTY_UNREAD_COMMENTS));
propSet.add(NCEncrypted.NAME);
propSet.add(new Property.Name(NC_NAMESPACE, EXTENDED_PROPERTY_MOUNT_TYPE));
propSet.add(NCPreview.NAME);
propSet.add(new Property.Name(NC_NAMESPACE, EXTENDED_PROPERTY_NOTE));
props.add(NCEncrypted.NAME);
props.add(new Property.Name(NC_NAMESPACE, EXTENDED_PROPERTY_MOUNT_TYPE));
props.add(NCPreview.NAME);
props.add(new Property.Name(NC_NAMESPACE, EXTENDED_PROPERTY_NOTE));
return propSet.toArray(new Property.Name[0]);
return props.toArray(new Property.Name[0]);
}
public static void registerCustomFactories() {

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.mention.Mention
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.recyclerview.MessageSwipeActions
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
@ -427,9 +428,12 @@ class ChatController(args: Bundle) :
adapterWasNull = true
val messageHolders = MessageHolders()
val profileBottomSheet = ProfileBottomSheet(ncApi!!, conversationUser!!, router)
messageHolders.setIncomingTextConfig(
MagicIncomingTextMessageViewHolder::class.java,
R.layout.item_custom_incoming_text_message
R.layout.item_custom_incoming_text_message,
profileBottomSheet
)
messageHolders.setOutcomingTextConfig(
MagicOutcomingTextMessageViewHolder::class.java,
@ -438,7 +442,8 @@ class ChatController(args: Bundle) :
messageHolders.setIncomingImageConfig(
IncomingPreviewMessageViewHolder::class.java,
R.layout.item_custom_incoming_preview_message
R.layout.item_custom_incoming_preview_message,
profileBottomSheet
)
messageHolders.setOutcomingImageConfig(
@ -460,14 +465,17 @@ class ChatController(args: Bundle) :
MagicUnreadNoticeMessageViewHolder::class.java,
R.layout.item_date_header,
MagicUnreadNoticeMessageViewHolder::class.java,
R.layout.item_date_header, this
R.layout.item_date_header,
this
)
messageHolders.registerContentType(
CONTENT_TYPE_LOCATION,
IncomingLocationMessageViewHolder::class.java,
profileBottomSheet,
R.layout.item_custom_incoming_location_message,
OutcomingLocationMessageViewHolder::class.java,
null,
R.layout.item_custom_outcoming_location_message,
this
)
@ -475,8 +483,10 @@ class ChatController(args: Bundle) :
messageHolders.registerContentType(
CONTENT_TYPE_VOICE_MESSAGE,
IncomingVoiceMessageViewHolder::class.java,
profileBottomSheet,
R.layout.item_custom_incoming_voice_message,
OutcomingVoiceMessageViewHolder::class.java,
null,
R.layout.item_custom_outcoming_voice_message,
this
)
@ -585,7 +595,7 @@ class ChatController(args: Bundle) :
if (layoutManager!!.findFirstCompletelyVisibleItemPosition() < newMessagesCount) {
newMessagesCount = 0
if (binding.popupBubbleView.isShown == true) {
if (binding.popupBubbleView.isShown) {
binding.popupBubbleView.hide()
}
}
@ -640,7 +650,7 @@ class ChatController(args: Bundle) :
}
} catch (npe: NullPointerException) {
// view binding can be null
// since this is called asynchrously and UI might have been destroyed in the meantime
// since this is called asynchronously and UI might have been destroyed in the meantime
Log.i(TAG, "UI destroyed - view binding already gone")
}
}
@ -2178,7 +2188,7 @@ class ChatController(args: Bundle) :
bundle.putBoolean(BundleKeys.KEY_FORWARD_MSG_FLAG, true)
bundle.putString(BundleKeys.KEY_FORWARD_MSG_TEXT, message?.text)
bundle.putString(BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM, roomId)
getRouter().pushController(
router.pushController(
RouterTransaction.with(ConversationsListController(bundle))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())

View File

@ -1007,8 +1007,14 @@ class ConversationInfoController(args: Bundle) :
R.drawable.ic_lock_grey600_24px,
context!!.getString(R.string.nc_attendee_pin, participant.attendeePin)
),
BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context!!.getString(R.string.nc_promote)),
BasicListItemWithImage(R.drawable.ic_pencil_grey600_24dp, context!!.getString(R.string.nc_demote)),
BasicListItemWithImage(
R.drawable.ic_pencil_grey600_24dp,
context!!.getString(R.string.nc_promote)
),
BasicListItemWithImage(
R.drawable.ic_pencil_grey600_24dp,
context!!.getString(R.string.nc_demote)
),
BasicListItemWithImage(
R.drawable.ic_delete_grey600_24dp,
context!!.getString(R.string.nc_remove_participant)

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) {
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>