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:
commit
25e0211ca1
5 changed files with 69 additions and 7 deletions
|
@ -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()
|
||||||
];
|
];
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
Reference in a new issue