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": "*",
|
"roboto-fontface": "*",
|
||||||
"v-emoji-picker": "^2.3.1",
|
"v-emoji-picker": "^2.3.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
|
"vue-resize": "^0.5.0",
|
||||||
"vue-router": "^3.2.0",
|
"vue-router": "^3.2.0",
|
||||||
"vuetify": "^2.2.11",
|
"vuetify": "^2.2.11",
|
||||||
"vuex": "^3.5.1"
|
"vuex": "^3.5.1"
|
||||||
|
|
@ -13499,6 +13500,14 @@
|
||||||
"vue-class-component": "*"
|
"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": {
|
"node_modules/vue-router": {
|
||||||
"version": "3.4.9",
|
"version": "3.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
"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==",
|
"integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==",
|
||||||
"requires": {}
|
"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": {
|
"vue-router": {
|
||||||
"version": "3.4.9",
|
"version": "3.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
"v-emoji-picker": "^2.3.1",
|
"v-emoji-picker": "^2.3.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
|
"vue-resize": "^0.5.0",
|
||||||
"vue-router": "^3.2.0",
|
"vue-router": "^3.2.0",
|
||||||
"vuetify": "^2.2.11",
|
"vuetify": "^2.2.11",
|
||||||
"vuex": "^3.5.1"
|
"vuex": "^3.5.1"
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,21 @@
|
||||||
v-on:sscroll="onScroll"
|
v-on:sscroll="onScroll"
|
||||||
>
|
>
|
||||||
<div v-for="event in events" :key="event.getId()">
|
<div v-for="event in events" :key="event.getId()">
|
||||||
<div v-if="!event.isRelation() && !event.isRedacted() && !event.isRedaction()">
|
<div
|
||||||
<div style="position:relative;user-select:none"
|
v-if="
|
||||||
v-on:touchstart="(e) => { touchStart(e, event) }"
|
!event.isRelation() && !event.isRedacted() && !event.isRedaction()
|
||||||
v-on:touchend="touchEnd"
|
"
|
||||||
v-on:touchcancel="touchCancel"
|
>
|
||||||
v-on:touchmove="touchMove"
|
<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
|
<component
|
||||||
:is="componentForEvent(event)"
|
:is="componentForEvent(event)"
|
||||||
|
|
@ -37,6 +46,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
|
||||||
|
<resize-observer
|
||||||
|
ref="chatContainerResizer"
|
||||||
|
@notify="handleChatContainerResize"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Input area -->
|
<!-- Input area -->
|
||||||
|
|
@ -196,7 +211,13 @@ export default {
|
||||||
currentSendError: null,
|
currentSendError: null,
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
selectedEvent: null,
|
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.scrollPosition = new ScrollPosition(container);
|
||||||
this.$matrix.on("Room.timeline", this.onEvent);
|
this.$matrix.on("Room.timeline", this.onEvent);
|
||||||
this.$matrix.on("RoomMember.typing", this.onUserTyping);
|
this.$matrix.on("RoomMember.typing", this.onUserTyping);
|
||||||
|
this.chatContainerSize = this.$refs.chatContainerResizer.$el.clientHeight;
|
||||||
|
console.log("resize initial height: ", this.chatContainerSize);
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyed() {
|
destroyed() {
|
||||||
|
|
@ -258,13 +281,13 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
touchX(event) {
|
touchX(event) {
|
||||||
if(event.type.indexOf('mouse') !== -1){
|
if (event.type.indexOf("mouse") !== -1) {
|
||||||
return event.clientX;
|
return event.clientX;
|
||||||
}
|
}
|
||||||
return event.touches[0].clientX;
|
return event.touches[0].clientX;
|
||||||
},
|
},
|
||||||
touchY(event) {
|
touchY(event) {
|
||||||
if(event.type.indexOf('mouse') !== -1){
|
if (event.type.indexOf("mouse") !== -1) {
|
||||||
return event.clientY;
|
return event.clientY;
|
||||||
}
|
}
|
||||||
return event.touches[0].clientY;
|
return event.touches[0].clientY;
|
||||||
|
|
@ -291,17 +314,38 @@ export default {
|
||||||
this.touchCurrentX = this.touchX(e);
|
this.touchCurrentX = this.touchX(e);
|
||||||
this.touchCurrentY = this.touchY(e);
|
this.touchCurrentY = this.touchY(e);
|
||||||
var tapTolerance = 4;
|
var tapTolerance = 4;
|
||||||
var touchMoved = Math.abs(this.touchStartX - this.touchCurrentX) > tapTolerance ||
|
var touchMoved =
|
||||||
Math.abs(this.touchStartY - this.touchCurrentY) > tapTolerance;
|
Math.abs(this.touchStartX - this.touchCurrentX) > tapTolerance ||
|
||||||
if(touchMoved){
|
Math.abs(this.touchStartY - this.touchCurrentY) > tapTolerance;
|
||||||
|
if (touchMoved) {
|
||||||
this.touchTimer && clearTimeout(this.touchTimer);
|
this.touchTimer && clearTimeout(this.touchTimer);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when out "long tap" timer hits.
|
||||||
|
*/
|
||||||
touchTimerElapsed() {
|
touchTimerElapsed() {
|
||||||
console.log('timer');
|
|
||||||
this.showContextMenu = true;
|
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) {
|
componentForEvent(event) {
|
||||||
switch (event.getType()) {
|
switch (event.getType()) {
|
||||||
case "m.room.member":
|
case "m.room.member":
|
||||||
|
|
@ -312,7 +356,7 @@ export default {
|
||||||
} else if (event.getContent().membership == "invite") {
|
} else if (event.getContent().membership == "invite") {
|
||||||
return ContactInvited;
|
return ContactInvited;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "m.room.message":
|
case "m.room.message":
|
||||||
if (event.getSender() != this.$matrix.currentUserId) {
|
if (event.getSender() != this.$matrix.currentUserId) {
|
||||||
|
|
@ -372,7 +416,7 @@ export default {
|
||||||
|
|
||||||
// If we are at bottom, scroll to see new events...
|
// If we are at bottom, scroll to see new events...
|
||||||
const container = this.$refs.chatContainer;
|
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 (
|
if (
|
||||||
container.scrollHeight - container.scrollTop.toFixed(0) ==
|
container.scrollHeight - container.scrollTop.toFixed(0) ==
|
||||||
container.clientHeight
|
container.clientHeight
|
||||||
|
|
@ -542,25 +586,25 @@ export default {
|
||||||
if (this.selectedEvent) {
|
if (this.selectedEvent) {
|
||||||
const event = this.selectedEvent;
|
const event = this.selectedEvent;
|
||||||
this.selectedEvent = null;
|
this.selectedEvent = null;
|
||||||
this.sendQuickReaction({reaction:e.data, event: event});
|
this.sendQuickReaction({ reaction: e.data, event: event });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sendQuickReaction(e) {
|
sendQuickReaction(e) {
|
||||||
util
|
util
|
||||||
.sendQuickReaction(
|
.sendQuickReaction(
|
||||||
this.$matrix.matrixClient,
|
this.$matrix.matrixClient,
|
||||||
this.roomId,
|
this.roomId,
|
||||||
e.reaction,
|
e.reaction,
|
||||||
e.event
|
e.event
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Quick reaction message");
|
console.log("Quick reaction message");
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log("Failed to send quick reaction:", err);
|
console.log("Failed to send quick reaction:", err);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,12 @@ import matrix from './services/matrix.service'
|
||||||
import 'roboto-fontface/css/roboto/roboto-fontface.css'
|
import 'roboto-fontface/css/roboto/roboto-fontface.css'
|
||||||
import 'material-design-icons-iconfont/dist/material-design-icons.css'
|
import 'material-design-icons-iconfont/dist/material-design-icons.css'
|
||||||
import VEmojiPicker from 'v-emoji-picker';
|
import VEmojiPicker from 'v-emoji-picker';
|
||||||
|
import VueResize from 'vue-resize';
|
||||||
|
import 'vue-resize/dist/vue-resize.css';
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
Vue.use(VueResize);
|
||||||
Vue.use(VEmojiPicker);
|
Vue.use(VEmojiPicker);
|
||||||
Vue.use(matrix, {store: store});
|
Vue.use(matrix, {store: store});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue