Merge pull request #279 from Johennes/feature/safari-viewport

Manually adapt UI when keyboard shows or hides on mobile Safari
This commit is contained in:
Bruno Windels 2021-04-28 11:04:43 +02:00 committed by GitHub
commit 25e0211ca1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 7 deletions

View file

@ -351,7 +351,11 @@ async function buildCssLegacy(entryPath, urlMapper = null) {
const preCss = await fs.readFile(entryPath, "utf8"); const preCss = await fs.readFile(entryPath, "utf8");
const options = [ const options = [
postcssImport, postcssImport,
cssvariables(), cssvariables({
preserve: (declaration) => {
return declaration.value.indexOf("var(--ios-") == 0;
}
}),
autoprefixer({overrideBrowserslist: ["IE 11"], grid: "no-autoplace"}), autoprefixer({overrideBrowserslist: ["IE 11"], grid: "no-autoplace"}),
flexbugsFixes() flexbugsFixes()
]; ];

View file

@ -35,6 +35,7 @@ import {WorkerPool} from "./dom/WorkerPool.js";
import {BlobHandle} from "./dom/BlobHandle.js"; import {BlobHandle} from "./dom/BlobHandle.js";
import {hasReadPixelPermission, ImageHandle, VideoHandle} from "./dom/ImageHandle.js"; import {hasReadPixelPermission, ImageHandle, VideoHandle} from "./dom/ImageHandle.js";
import {downloadInIframe} from "./dom/download.js"; import {downloadInIframe} from "./dom/download.js";
import {Disposables} from "../../utils/Disposables.js";
function addScript(src) { function addScript(src) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@ -83,6 +84,44 @@ async function loadOlmWorker(config) {
return olmWorker; return olmWorker;
} }
// needed for mobile Safari which shifts the layout viewport up without resizing it
// when the keyboard shows (see https://bugs.webkit.org/show_bug.cgi?id=141832)
function adaptUIOnVisualViewportResize(container) {
if (!window.visualViewport) {
return;
}
const handler = () => {
const sessionView = container.querySelector('.SessionView');
if (!sessionView) {
return;
}
const scrollable = container.querySelector('.bottom-aligned-scroll');
let scrollTopBefore, heightBefore, heightAfter;
if (scrollable) {
scrollTopBefore = scrollable.scrollTop;
heightBefore = scrollable.offsetHeight;
}
// Ideally we'd use window.visualViewport.offsetTop but that seems to occasionally lag
// behind (last tested on iOS 14.4 simulator) so we have to compute the offset manually
const offsetTop = sessionView.offsetTop + sessionView.offsetHeight - window.visualViewport.height;
container.style.setProperty('--ios-viewport-height', window.visualViewport.height.toString() + 'px');
container.style.setProperty('--ios-viewport-top', offsetTop.toString() + 'px');
if (scrollable) {
heightAfter = scrollable.offsetHeight;
scrollable.scrollTop = scrollTopBefore + heightBefore - heightAfter;
}
};
window.visualViewport.addEventListener('resize', handler);
return () => {
window.visualViewport.removeEventListener('resize', handler);
};
}
export class Platform { export class Platform {
constructor(container, config, cryptoExtras = null, options = null) { constructor(container, config, cryptoExtras = null, options = null) {
this._config = config; this._config = config;
@ -115,6 +154,10 @@ export class Platform {
} }
const isIE11 = !!window.MSInputMethodContext && !!document.documentMode; const isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
this.isIE11 = isIE11; this.isIE11 = isIE11;
// From https://stackoverflow.com/questions/9038625/detect-if-device-is-ios/9039885
const isIOS = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) && !window.MSStream;
this.isIOS = isIOS;
this._disposables = new Disposables();
} }
get updateService() { get updateService() {
@ -139,6 +182,13 @@ export class Platform {
if (this.isIE11) { if (this.isIE11) {
this._container.className += " legacy"; this._container.className += " legacy";
} }
if (this.isIOS) {
this._container.className += " ios";
const disposable = adaptUIOnVisualViewportResize(this._container);
if (disposable) {
this._disposables.track(disposable);
}
}
window.__hydrogenViewModel = vm; window.__hydrogenViewModel = vm;
const view = new RootView(vm); const view = new RootView(vm);
this._container.appendChild(view.mount()); this._container.appendChild(view.mount());
@ -156,7 +206,7 @@ export class Platform {
if (navigator.msSaveBlob) { if (navigator.msSaveBlob) {
navigator.msSaveBlob(blobHandle.nativeBlob, filename); navigator.msSaveBlob(blobHandle.nativeBlob, filename);
} else { } else {
downloadInIframe(this._container, this._config.downloadSandbox, blobHandle, filename); downloadInIframe(this._container, this._config.downloadSandbox, blobHandle, filename, this.isIOS);
} }
} }
@ -205,4 +255,8 @@ export class Platform {
get version() { get version() {
return window.HYDROGEN_VERSION; return window.HYDROGEN_VERSION;
} }
dispose() {
this._disposables.dispose();
}
} }

View file

@ -14,10 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// From https://stackoverflow.com/questions/9038625/detect-if-device-is-ios/9039885 export async function downloadInIframe(container, iframeSrc, blobHandle, filename, isIOS) {
const isIOS = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) && !window.MSStream;
export async function downloadInIframe(container, iframeSrc, blobHandle, filename) {
let iframe = container.querySelector("iframe.downloadSandbox"); let iframe = container.querySelector("iframe.downloadSandbox");
if (!iframe) { if (!iframe) {
iframe = document.createElement("iframe"); iframe = document.createElement("iframe");

View file

@ -54,6 +54,13 @@ main {
min-width: 0; min-width: 0;
} }
/* resize and reposition session view to account for mobile Safari which shifts
the layout viewport up without resizing it when the keyboard shows */
.hydrogen.ios .SessionView {
height: var(--ios-viewport-height, 100%);
top: var(--ios-viewport-top, 0);
}
/* hide back button in middle section by default */ /* hide back button in middle section by default */
.middle .close-middle { display: none; } .middle .close-middle { display: none; }
/* mobile layout */ /* mobile layout */

View file

@ -40,7 +40,7 @@ function viewClassForEntry(entry) {
export class TimelineList extends ListView { export class TimelineList extends ListView {
constructor(viewModel) { constructor(viewModel) {
const options = { const options = {
className: "Timeline", className: "Timeline bottom-aligned-scroll",
list: viewModel.tiles, list: viewModel.tiles,
} }
super(options, entry => { super(options, entry => {