Handle soft keyboard better
This commit is contained in:
parent
bf0f2af59e
commit
70d015e5ce
4 changed files with 93 additions and 30 deletions
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
|||
"roboto-fontface": "*",
|
||||
"v-emoji-picker": "^2.3.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-resize": "^0.5.0",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuetify": "^2.2.11",
|
||||
"vuex": "^3.5.1"
|
||||
|
|
@ -13499,6 +13500,14 @@
|
|||
"vue-class-component": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-resize": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-0.5.0.tgz",
|
||||
"integrity": "sha512-k5gOOwTIGSoWZb133Gx3IfSeiiAmve5GyWI7+pU8CvbNntpSAlrCYwZ26GB63NpxcLPK94+m0BDl5TxuZUI+Hw==",
|
||||
"peerDependencies": {
|
||||
"vue": "^2.6.11"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "3.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
||||
|
|
@ -25652,6 +25661,12 @@
|
|||
"integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"vue-resize": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-0.5.0.tgz",
|
||||
"integrity": "sha512-k5gOOwTIGSoWZb133Gx3IfSeiiAmve5GyWI7+pU8CvbNntpSAlrCYwZ26GB63NpxcLPK94+m0BDl5TxuZUI+Hw==",
|
||||
"requires": {}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "3.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"roboto-fontface": "*",
|
||||
"v-emoji-picker": "^2.3.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-resize": "^0.5.0",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuetify": "^2.2.11",
|
||||
"vuex": "^3.5.1"
|
||||
|
|
|
|||
|
|
@ -7,17 +7,26 @@
|
|||
v-on:sscroll="onScroll"
|
||||
>
|
||||
<div v-for="event in events" :key="event.getId()">
|
||||
<div v-if="!event.isRelation() && !event.isRedacted() && !event.isRedaction()">
|
||||
<div style="position:relative;user-select:none"
|
||||
v-on:touchstart="(e) => { touchStart(e, event) }"
|
||||
v-on:touchend="touchEnd"
|
||||
v-on:touchcancel="touchCancel"
|
||||
v-on:touchmove="touchMove"
|
||||
<div
|
||||
v-if="
|
||||
!event.isRelation() && !event.isRedacted() && !event.isRedaction()
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="position: relative; user-select: none"
|
||||
v-on:touchstart="
|
||||
(e) => {
|
||||
touchStart(e, event);
|
||||
}
|
||||
"
|
||||
v-on:touchend="touchEnd"
|
||||
v-on:touchcancel="touchCancel"
|
||||
v-on:touchmove="touchMove"
|
||||
>
|
||||
<component
|
||||
:is="componentForEvent(event)"
|
||||
:room="room"
|
||||
:event="event"
|
||||
:event="event"
|
||||
:reactions="
|
||||
timelineWindow._timelineSet.getRelationsForEvent(
|
||||
event.getId(),
|
||||
|
|
@ -37,6 +46,12 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
|
||||
<resize-observer
|
||||
ref="chatContainerResizer"
|
||||
@notify="handleChatContainerResize"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Input area -->
|
||||
|
|
@ -196,7 +211,13 @@ export default {
|
|||
currentSendError: null,
|
||||
showEmojiPicker: false,
|
||||
selectedEvent: null,
|
||||
showContextMenu: false
|
||||
showContextMenu: false,
|
||||
/**
|
||||
* Current chat container size. We need to keep track of this so that if and when
|
||||
* a soft keyboard is shown/hidden we can restore the scroll position correctly.
|
||||
* If we don't, the keyboard will simply overflow the message we are answering to etc.
|
||||
*/
|
||||
chatContainerSize: 0,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -205,6 +226,8 @@ export default {
|
|||
this.scrollPosition = new ScrollPosition(container);
|
||||
this.$matrix.on("Room.timeline", this.onEvent);
|
||||
this.$matrix.on("RoomMember.typing", this.onUserTyping);
|
||||
this.chatContainerSize = this.$refs.chatContainerResizer.$el.clientHeight;
|
||||
console.log("resize initial height: ", this.chatContainerSize);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
|
|
@ -258,13 +281,13 @@ export default {
|
|||
|
||||
methods: {
|
||||
touchX(event) {
|
||||
if(event.type.indexOf('mouse') !== -1){
|
||||
if (event.type.indexOf("mouse") !== -1) {
|
||||
return event.clientX;
|
||||
}
|
||||
return event.touches[0].clientX;
|
||||
},
|
||||
touchY(event) {
|
||||
if(event.type.indexOf('mouse') !== -1){
|
||||
if (event.type.indexOf("mouse") !== -1) {
|
||||
return event.clientY;
|
||||
}
|
||||
return event.touches[0].clientY;
|
||||
|
|
@ -291,17 +314,38 @@ export default {
|
|||
this.touchCurrentX = this.touchX(e);
|
||||
this.touchCurrentY = this.touchY(e);
|
||||
var tapTolerance = 4;
|
||||
var touchMoved = Math.abs(this.touchStartX - this.touchCurrentX) > tapTolerance ||
|
||||
Math.abs(this.touchStartY - this.touchCurrentY) > tapTolerance;
|
||||
if(touchMoved){
|
||||
var touchMoved =
|
||||
Math.abs(this.touchStartX - this.touchCurrentX) > tapTolerance ||
|
||||
Math.abs(this.touchStartY - this.touchCurrentY) > tapTolerance;
|
||||
if (touchMoved) {
|
||||
this.touchTimer && clearTimeout(this.touchTimer);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggered when out "long tap" timer hits.
|
||||
*/
|
||||
touchTimerElapsed() {
|
||||
console.log('timer');
|
||||
this.showContextMenu = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* If chat container is shrunk (probably because soft keyboard is shown) adjust
|
||||
* the scroll position so that e.g. if we were looking at the last message when
|
||||
* moving focus to the input field, we would still see the last message. Otherwise
|
||||
* if would be hidden behind the keyboard.
|
||||
*/
|
||||
handleChatContainerResize({ width, height }) {
|
||||
console.log("resized", width, height);
|
||||
const delta = height - this.chatContainerSize;
|
||||
this.chatContainerSize = height;
|
||||
console.log("resized delta " + delta);
|
||||
const container = this.$refs.chatContainer;
|
||||
if (delta < 0) {
|
||||
container.scrollTop -= delta;
|
||||
}
|
||||
},
|
||||
|
||||
componentForEvent(event) {
|
||||
switch (event.getType()) {
|
||||
case "m.room.member":
|
||||
|
|
@ -312,7 +356,7 @@ export default {
|
|||
} else if (event.getContent().membership == "invite") {
|
||||
return ContactInvited;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case "m.room.message":
|
||||
if (event.getSender() != this.$matrix.currentUserId) {
|
||||
|
|
@ -372,7 +416,7 @@ export default {
|
|||
|
||||
// If we are at bottom, scroll to see new events...
|
||||
const container = this.$refs.chatContainer;
|
||||
var scrollToSeeNew = (event.getSender() == this.$matrix.currentUserId); // When we sent, scroll
|
||||
var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll
|
||||
if (
|
||||
container.scrollHeight - container.scrollTop.toFixed(0) ==
|
||||
container.clientHeight
|
||||
|
|
@ -542,25 +586,25 @@ export default {
|
|||
if (this.selectedEvent) {
|
||||
const event = this.selectedEvent;
|
||||
this.selectedEvent = null;
|
||||
this.sendQuickReaction({reaction:e.data, event: event});
|
||||
this.sendQuickReaction({ reaction: e.data, event: event });
|
||||
}
|
||||
},
|
||||
|
||||
sendQuickReaction(e) {
|
||||
util
|
||||
.sendQuickReaction(
|
||||
this.$matrix.matrixClient,
|
||||
this.roomId,
|
||||
e.reaction,
|
||||
e.event
|
||||
)
|
||||
.then(() => {
|
||||
console.log("Quick reaction message");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to send quick reaction:", err);
|
||||
});
|
||||
}
|
||||
.sendQuickReaction(
|
||||
this.$matrix.matrixClient,
|
||||
this.roomId,
|
||||
e.reaction,
|
||||
e.event
|
||||
)
|
||||
.then(() => {
|
||||
console.log("Quick reaction message");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to send quick reaction:", err);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ import matrix from './services/matrix.service'
|
|||
import 'roboto-fontface/css/roboto/roboto-fontface.css'
|
||||
import 'material-design-icons-iconfont/dist/material-design-icons.css'
|
||||
import VEmojiPicker from 'v-emoji-picker';
|
||||
import VueResize from 'vue-resize';
|
||||
import 'vue-resize/dist/vue-resize.css';
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.use(VueResize);
|
||||
Vue.use(VEmojiPicker);
|
||||
Vue.use(matrix, {store: store});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue