UI: improvements to footer nav for app and PWAs

Adds support for iPad and Chrome PWAs

Better scroll direction logic when reaching bottom of the page
This commit is contained in:
Penar Musaraj 2019-04-11 14:11:26 -04:00
parent d21dd521d2
commit f060c9b3ff
9 changed files with 97 additions and 48 deletions

View File

@ -5,14 +5,14 @@ import { observes } from "ember-addons/ember-computed-decorators";
const MOBILE_SCROLL_DIRECTION_CHECK_THROTTLE = 150;
const MobileFooterComponent = MountWidget.extend(
const FooterNavComponent = MountWidget.extend(
Scrolling,
MobileScrollDirection,
{
widget: "mobile-footer-nav",
widget: "footer-nav",
mobileScrollDirection: null,
scrollEventDisabled: false,
classNames: ["mobile-footer", "visible"],
classNames: ["footer-nav", "visible"],
routeHistory: [],
currentRouteIndex: 0,
canGoBack: false,
@ -28,20 +28,22 @@ const MobileFooterComponent = MountWidget.extend(
didInsertElement() {
this._super(...arguments);
this.bindScrolling({ name: "mobile-footer" });
$(window).on("resize.mobile-footer-on-scroll", () => this.scrolled());
this.bindScrolling({ name: "footer-nav" });
$(window).on("resize.footer-nav-on-scroll", () => this.scrolled());
this.appEvents.on("page:changed", this, "_routeChanged");
this.appEvents.on("composer:opened", this, "_composerOpened");
this.appEvents.on("composer:closed", this, "_composerClosed");
$("body").addClass("with-footer-nav");
},
willDestroyElement() {
this._super(...arguments);
this.unbindScrolling("mobile-footer");
$(window).unbind("resize.mobile-footer-on-scroll");
this.unbindScrolling("footer-nav");
$(window).unbind("resize.footer-nav-on-scroll");
this.appEvents.off("page:changed", this, "_routeChanged");
this.appEvents.off("composer:opened", this, "_composerOpened");
this.appEvents.off("composer:closed", this, "_composerClosed");
$("body").removeClass("with-footer-nav");
},
// The user has scrolled the window, or it is finished rendering and ready for processing.
@ -75,7 +77,7 @@ const MobileFooterComponent = MountWidget.extend(
);
// body class used to adjust positioning of #topic-progress-wrapper
$("body").toggleClass(
"mobile-footer-nav-visible",
"footer-nav-visible",
this.mobileScrollDirection === null ? true : false
);
},
@ -119,10 +121,10 @@ const MobileFooterComponent = MountWidget.extend(
setBackForward() {
let index = this.get("currentRouteIndex");
this.set("canGoBack", index > 1 ? true : false);
this.set("canGoBack", (index > 1 || document.referrer) ? true : false);
this.set("canGoForward", index < this.routeHistory.length ? true : false);
}
}
);
export default MobileFooterComponent;
export default FooterNavComponent;

View File

@ -1,5 +1,5 @@
import computed from "ember-addons/ember-computed-decorators";
import { isAppWebview, isiOSPWA } from "discourse/lib/utilities";
import { isAppWebview, isiOSPWA, isChromePWA } from "discourse/lib/utilities";
export default Ember.Controller.extend({
showTop: true,
@ -20,7 +20,11 @@ export default Ember.Controller.extend({
},
@computed
showMobileFooterNav() {
return isAppWebview() || isiOSPWA();
showFooterNav() {
return (
isAppWebview() ||
isiOSPWA() ||
(!this.site.isMobileDevice && isChromePWA())
);
}
});

View File

@ -653,5 +653,14 @@ export function isiOSPWA() {
export function isAppWebview() {
return window.ReactNativeWebView !== undefined;
}
export function isChromePWA() {
// Watch out: this doesn't distinguish between mobile or desktop PWAs
return (
window.matchMedia("(display-mode: standalone)").matches &&
navigator.userAgent.match(/(Chrome)/g)
);
}
// This prevents a mini racer crash
export default {};

View File

@ -2,11 +2,12 @@
const MOBILE_SCROLL_TOLERANCE = 5;
export default Ember.Mixin.create({
_mobileLastScroll: null,
_lastScroll: null,
_bottomHit: 0,
calculateDirection(offset) {
// Difference between this scroll and the one before it.
const delta = Math.floor(offset - this._mobileLastScroll);
const delta = Math.floor(offset - this._lastScroll);
// This is a tiny scroll, so we ignore it.
if (delta <= MOBILE_SCROLL_TOLERANCE && delta >= -MOBILE_SCROLL_TOLERANCE)
@ -15,26 +16,35 @@ export default Ember.Mixin.create({
const prevDirection = this.mobileScrollDirection;
const currDirection = delta > 0 ? "down" : null;
// Handle Safari overscroll first
if (offset < 0) {
this.set("mobileScrollDirection", null);
} else if (currDirection !== prevDirection) {
this.set("mobileScrollDirection", currDirection);
}
// We store this to compare against it the next time the user scrolls
this._mobileLastScroll = Math.floor(offset);
// If the user reaches the very bottom of the topic, we want to reset the
// scroll direction in order for the header to switch back.
const distanceToBottom = Math.floor(
$("body").height() - offset - $(window).height()
);
// Not at the bottom yet
if (distanceToBottom > 0) return;
// Handle Safari top overscroll first
if (offset < 0) {
this.set("mobileScrollDirection", null);
} else if (currDirection !== prevDirection && distanceToBottom > 0) {
this.set("mobileScrollDirection", currDirection);
}
// We're at the bottom now, so we reset the direction.
this.set("mobileScrollDirection", null);
// We store this to compare against it the next time the user scrolls
this._lastScroll = Math.floor(offset);
// Not at the bottom yet
if (distanceToBottom > 0) {
this._bottomHit = 0;
return;
}
// If the user reaches the very bottom of the topic, we only want to reset
// this scroll direction after a second scrolldown. This is a nicer event
// similar to what Safari and Chrome do.
Ember.run.debounce(() => {
this._bottomHit = 1;
}, 1000);
if (this._bottomHit === 1) {
this.set("mobileScrollDirection", null);
}
}
});

View File

@ -34,6 +34,6 @@
{{topic-entrance}}
{{outlet "composer"}}
{{#if showMobileFooterNav}}
{{mobile-footer}}
{{#if showFooterNav}}
{{footer-nav}}
{{/if}}

View File

@ -1,8 +1,8 @@
import { createWidget } from "discourse/widgets/widget";
import { isAppWebview } from "discourse/lib/utilities";
import { isAppWebview, isChromePWA } from "discourse/lib/utilities";
createWidget("mobile-footer-nav", {
tagName: "div.mobile-footer-nav",
createWidget("footer-nav", {
tagName: "div.footer-nav-widget",
html(attrs) {
const buttons = [];
@ -43,6 +43,15 @@ createWidget("mobile-footer-nav", {
);
}
if (isChromePWA()) {
buttons.push(
this.attach("flat-button", {
action: "refresh",
icon: "sync",
className: "btn-large"
})
);
}
return buttons;
},
@ -54,5 +63,9 @@ createWidget("mobile-footer-nav", {
window.ReactNativeWebView.postMessage(
JSON.stringify({ shareUrl: window.location.href })
);
},
refresh() {
window.location.reload();
}
});

View File

@ -1,38 +1,42 @@
// --------------------------------------------------
// Mobile footer (displayed in DiscourseHub app and PWAs)
// Footer nav bar (displayed in DiscourseHub app and PWAs)
// --------------------------------------------------
$footer-nav-height: 55px;
body.mobile-footer-nav-visible {
body.with-footer-nav {
padding-bottom: $footer-nav-height + 15;
}
body.footer-nav-visible {
#topic-progress-wrapper,
#reply-control.draft {
bottom: $footer-nav-height;
}
}
.mobile-footer {
background-color: $header_background;
box-shadow: shadow("mobile-footer");
.footer-nav {
background-color: rgba($header_background, 0.9);
box-shadow: shadow("footer-nav");
height: $footer-nav-height;
position: fixed;
bottom: -$footer-nav-height;
left: 0;
width: 100%;
z-index: z("mobile-footer");
transition: all linear 0.15s;
z-index: z("footer-nav");
transition: all linear 0.1s;
.d-icon {
color: $header_primary-low-mid;
color: $header_primary-medium;
}
&.visible {
bottom: 0px;
padding-bottom: env(safe-area-inset-bottom);
}
.mobile-footer-nav {
.footer-nav-widget {
display: flex;
justify-content: "space-evenly";
@include unselectable;
@ -45,3 +49,11 @@ body.mobile-footer-nav-visible {
}
}
}
@supports (-webkit-backdrop-filter: blur(10px)) {
.footer-nav {
background-color: rgba($header_background, 0.7);
-webkit-backdrop-filter: blur(10px);
}
}

View File

@ -80,7 +80,7 @@ $z-layers: (
"overlay": 1200
),
"fullscreen": 1150,
"mobile-footer": 1140,
"footer-nav": 1140,
"mobile-composer": 1100,
"header": 1000,
"tooltip": 600,
@ -130,7 +130,7 @@ $box-shadow: (
"card": 0 4px 14px rgba(0, 0, 0, 0.15),
"dropdown": 0 2px 3px 0 rgba(0, 0, 0, 0.2),
"header": 0 2px 4px -1px rgba(0, 0, 0, 0.25),
"mobile-footer": 0 2px 4px 1px rgba(0, 0, 0, 0.25),
"footer-nav": 0 2px 4px 1px rgba(0, 0, 0, 0.25),
"kbd": (
0 2px 0 rgba(0, 0, 0, 0.2),
0 0 0 1px dark-light-choose(#fff, #000) inset

View File

@ -30,7 +30,6 @@
@import "mobile/admin_report_counters";
@import "mobile/menu-panel";
@import "mobile/reviewables";
@import "mobile/footer";
// Import all component-specific files
@import "mobile/components/*";