Add option to start private chat

By clicking on avatar. Issue #101.
This commit is contained in:
N-Pex 2021-05-10 16:11:03 +02:00
parent f3b37f7479
commit 91dfb0bc8e
6 changed files with 196 additions and 7 deletions

View file

@ -420,7 +420,7 @@ $admin-fg: white;
}
}
.message-operations-strut {
.message-operations-strut, .avatar-operations-strut {
position: relative;
height: 0px;
z-index: 1;
@ -442,6 +442,16 @@ $admin-fg: white;
// }
}
.avatar-operations {
position: absolute;
width: fit-content;
background-color: white;
height: 40px;
border-radius: 20px;
padding: 0px 20px;
box-shadow: 4px 4px 8px #888888;
}
.message-operations-picker {
background-color: white;
text-align: center;

View file

@ -9,14 +9,14 @@
ref="chatContainer"
style="overflow-x: hidden; overflow-y: auto"
v-on:scroll="onScroll"
@click="closeContextMenuIfOpen"
@click="closeContextMenusIfOpen"
>
<div ref="messageOperationsStrut" class="message-operations-strut">
<message-operations
ref="messageOperations"
:style="opStyle"
:emojis="recentEmojis"
v-on:close="showContextMenu = false"
v-on:close="showContextMenu = false;showContextMenuAnchor = null;"
v-if="selectedEvent && showContextMenu"
v-on:addreaction="addReaction"
v-on:addquickreaction="addQuickReaction"
@ -29,6 +29,18 @@
/>
</div>
<div ref="avatarOperationsStrut" class="avatar-operations-strut">
<avatar-operations
ref="avatarOperations"
:style="avatarOpStyle"
v-on:close="showAvatarMenu = false;showAvatarMenuAnchor = null;"
v-on:start-private-chat="startPrivateChat($event)"
v-if="selectedEvent && showAvatarMenu"
:room="room"
:event="selectedEvent"
/>
</div>
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
<resize-observer
ref="chatContainerResizer"
@ -82,6 +94,7 @@
v-on:send-quick-reaction="sendQuickReaction"
v-on:context-menu="showContextMenuForEvent($event)"
v-on:own-avatar-clicked="viewProfile"
v-on:other-avatar-clicked="showAvatarMenuForEvent($event)"
v-on:download="download(event)"
/>
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
@ -355,6 +368,7 @@ import DebugEvent from "./messages/DebugEvent.vue";
import util from "../plugins/utils";
import MessageOperations from "./messages/MessageOperations.vue";
import MessageOperationsPicker from "./messages/MessageOperationsPicker.vue";
import AvatarOperations from "./messages/AvatarOperations.vue";
import ChatHeader from "./ChatHeader";
import VoiceRecorder from "./VoiceRecorder";
import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
@ -430,6 +444,7 @@ export default {
MessageOperationsBottomSheet,
StickerPickerBottomSheet,
BottomSheet,
AvatarOperations,
},
data() {
@ -456,6 +471,8 @@ export default {
replyToEvent: null,
showContextMenu: false,
showContextMenuAnchor: null,
showAvatarMenu: false,
showAvatarMenuAnchor: null,
initialLoadDone: false,
loading: false, // Set this to true during long operations to show a "spinner" overlay
showRecorder: false,
@ -586,6 +603,25 @@ export default {
}
return "top:" + top + "px;left:" + left + "px";
},
avatarOpStyle() {
// Calculate where to show the context menu.
//
const ref = this.selectedEvent && this.$refs[this.selectedEvent.getId()];
var top = 0;
var left = 0;
if (ref && ref[0]) {
if (this.showAvatarMenuAnchor) {
var rectAnchor = this.showAvatarMenuAnchor.getBoundingClientRect();
var rectChat = this.$refs.avatarOperationsStrut.getBoundingClientRect();
top = rectAnchor.top - rectChat.top;
left = rectAnchor.left - rectChat.left;
// if (left + 250 > rectChat.right) {
// left = rectChat.right - 250; // Pretty ugly, but we want to make sure it does not escape the screen, and we don't have the exakt width of it (yet)!
// }
}
}
return "top:" + top + "px;left:" + left + "px";
},
canRecordAudio() {
return util.browserCanRecordAudio();
},
@ -1258,13 +1294,44 @@ export default {
this.showContextMenuAnchor = e.anchor;
},
showAvatarMenuForEvent(e) {
const event = e.event;
this.selectedEvent = event;
this.showAvatarMenu = true;
this.showAvatarMenuAnchor = e.anchor;
},
viewProfile() {
this.$navigation.push({ name: "Profile" }, 1);
},
closeContextMenuIfOpen(e) {
startPrivateChat(e) {
this.$matrix.getOrCreatePrivateChat(e.event.getSender())
.then(room => {
this.$nextTick(() => {
this.$navigation.push(
{
name: "Chat",
params: { roomId: util.sanitizeRoomId(room.getCanonicalAlias() || room.roomId) },
},
-1
);
});
})
.catch(err => {
console.error(err);
})
},
closeContextMenusIfOpen(e) {
if (this.showContextMenu) {
this.showContextMenu = false;
this.showContextMenuAnchor = null;
e.preventDefault();
}
if (this.showAvatarMenu) {
this.showAvatarMenu = false;
this.showAvatarMenuAnchor = null;
e.preventDefault();
}
},

View file

@ -0,0 +1,37 @@
<template>
<div
:class="{
'avatar-operations': true,
incoming: incoming,
outgoing: !incoming,
}"
>
<v-btn v-if="incoming" text @click.stop="startPrivateChat" class="ma-0 pa-0"
>Private chat with this user</v-btn
>
</div>
</template>
<script>
import messageMixin from "./messageMixin";
export default {
mixins: [messageMixin],
mounted() {
// Any items to show?
if (this.room && this.event && this.$matrix.isDirectRoomWith(this.room, this.event.getSender())) {
this.$emit("close");
}
},
methods: {
startPrivateChat() {
this.$emit("close");
this.$emit("start-private-chat", { event: this.event });
},
},
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>

View file

@ -1,7 +1,7 @@
<template>
<!-- BASE CLASS FOR INCOMING MESSAGE -->
<div :class="messageClasses">
<v-avatar class="avatar" size="32" color="#ededed">
<v-avatar class="avatar" ref="avatar" size="32" color="#ededed" @click.stop="otherAvatarClicked($refs.avatar.$el)">
<img v-if="messageEventAvatar(event)" :src="messageEventAvatar(event)" />
<span v-else class="white--text headline">{{
messageEventDisplayName(event).substring(0, 1).toUpperCase()

View file

@ -156,6 +156,10 @@ export default {
this.$emit("own-avatar-clicked", {event: this.event});
},
otherAvatarClicked(avatarRef) {
this.$emit("other-avatar-clicked", {event: this.event, anchor: avatarRef});
},
showContextMenu(buttonRef) {
this.$emit("context-menu", {event: this.event,anchor: buttonRef});
},

View file

@ -90,7 +90,7 @@ export default {
login(user) {
const tempMatrixClient = sdk.createClient(User.homeServerUrl(user.home_server));
var promiseLogin;
const self = this;
if (user.access_token) {
// Logged in on "real" account
@ -520,7 +520,7 @@ export default {
var joined = room.getMembersWithMembership("join");
var invited = room.getMembersWithMembership("invite");
var members = joined.concat(invited);
var kickPromises = [];
members.forEach(member => {
if (member.userId != self.currentUserId) {
@ -539,6 +539,77 @@ export default {
})
},
/**
* Get a private chat room with the given user. Searches through our rooms to see
* if a suitable room already exists. If not, one is created.
* @param {*} userId The user to chat with.
*/
getOrCreatePrivateChat(userId) {
return new Promise((resolve, reject) => {
for (const room of this.rooms) {
// Is the other member the one we are looking for?
if (this.isDirectRoomWith(room, userId)) {
var member = room.getMember(userId);
if (member && member.membership == "invite") {
// TODO Resend invite
}
resolve(room);
return;
}
}
// No room found, create one
//
const createRoomOptions = {
visibility: "private", // Not listed!
//name: this.roomName,
preset: "private_chat",
initial_state: [
{
type: "m.room.encryption",
state_key: "",
content: {
algorithm: "m.megolm.v1.aes-sha2",
},
},
{
type: "m.room.guest_access",
state_key: "",
content: {
guest_access: "forbidden",
},
},
],
invite: [userId]
};
return this.matrixClient
.createRoom(createRoomOptions)
.then(({ room_id, room_alias }) => {
resolve(this.getRoom(room_alias || room_id));
})
.catch((error) => {
reject(error);
});
})
},
/**
* Return true if this room is a direct room with the given user.
* @param { } room
* @param {*} userId
*/
isDirectRoomWith(room, userId) {
if (room._selfMembership == "join" && room.getInvitedAndJoinedMemberCount() == 2) {
// Is the other member the one we are looking for?
if (room.getMembersWithMembership("join").some(item => item.userId == userId)) {
return true;
} else if (room.getMembersWithMembership("invite").some(item => item.userId == userId)) {
return true;
}
}
return false;
},
on(event, handler) {
if (this.matrixClient) {
this.matrixClient.on(event, handler);