Browse Source

Add "device unreachable" dialog and improve welcome page

The texts of the welcome page have been updated and are being mostly
reused for the "device unreachable" dialog.

Further changes:

- Removed fixed height requirement for the welcome page.
- Removed dead code in welcome page CSS
Lennart Grahl 6 years ago
parent
commit
16ca4cd482

+ 7 - 0
public/i18n/de.json

@@ -8,6 +8,7 @@
         "PLEASE_SCAN": "Scannen Sie den QR-Code mit Ihrer Threema-App",
         "PLEASE_UNLOCK": "Verbindung wiederaufbauen",
         "WAITING": "Auf Verbindung warten",
+        "RETRY": "Erneut versuchen",
         "PLEASE_RELOAD": "Bitte laden Sie die Seite neu.",
         "RELOAD": "Seite neu laden",
         "PASSWORD": "Passwort",
@@ -59,6 +60,8 @@
         "APP_STARTED": "Ist die Threema-App gestartet?",
         "SESSION_DELETED": "Wurde diese Sitzung in der Threema-App gelöscht?",
         "PHONE_ONLINE": "Ist Ihr Mobilgerät mit dem Internet verbunden?",
+        "UNLOCK_OR_CHARGE": "Es kann helfen, Ihr Mobilgerät zu entsperren oder an ein Ladegerät anzuschliessen.",
+        "PUSH_FAQ": "Möglicherweise liegt ein Problem bei der Verarbeitung von Push-Benachrichtigungen vor. Die FAQ-Artikel für <a target=\"_blank\" href=\"https://threema.ch/faq/push_andr\">Android</a> und <a target=\"_blank\" href=\"https://threema.ch/faq/push_ios\">iOS</a> helfen bei der Fehlersuche.",
         "WEBCLIENT_ENABLED": "Ist Threema Web in der Threema-App aktiviert?",
         "PLUGIN": "Ist in Ihrem Browser ein Plugin zum Blockieren von WebRTC installiert?",
         "ADBLOCKER": "Ist in Ihrem Browser ein Ad-Blocker installiert?",
@@ -290,6 +293,10 @@
             "PLAY_SOUND": "Ton abspielen"
         }
     },
+    "deviceUnreachable": {
+        "DEVICE_UNREACHABLE": "Mobilgerät nicht erreichbar",
+        "UNABLE_TO_CONNECT": "Eine Verbindung mit Ihrem Mobilgerät konnte nicht hergestellt werden …"
+    },
     "version": {
         "NEW_VERSION": "Neue Version Verfügbar",
         "NEW_VERSION_BODY": "Eine neue Version von Threema Web ({version}) ist verfügbar. Mehr Informationen finden Sie im {changelog}. Drücken Sie \"OK\", um das Update zu aktivieren."

+ 7 - 0
public/i18n/en.json

@@ -8,6 +8,7 @@
         "PLEASE_SCAN": "Scan this QR code with your Threema app",
         "PLEASE_UNLOCK": "Reconnecting session",
         "WAITING": "Waiting for connection",
+        "RETRY": "Retry",
         "PLEASE_RELOAD": "Please reload the page to try again.",
         "RELOAD": "Reload page",
         "PASSWORD": "Password",
@@ -59,6 +60,8 @@
         "APP_STARTED": "Is the Threema app started?",
         "SESSION_DELETED": "Did you delete this session on your phone?",
         "PHONE_ONLINE": "Is your phone connected to the internet?",
+        "UNLOCK_OR_CHARGE": "It may help to unlock your device or connect it to a charger.",
+        "PUSH_FAQ": "Your device may be affected by Push Notification issues. See the FAQ articles for <a target=\"_blank\" href=\"https://threema.ch/faq/push_andr\">Android</a> and <a target=\"_blank\" href=\"https://threema.ch/faq/push_ios\">iOS</a> for troubleshooting.",
         "WEBCLIENT_ENABLED": "Is Threema Web enabled in the Threema app?",
         "PLUGIN": "Is a privacy plugin installed in your browser which blocks WebRTC communication?",
         "ADBLOCKER": "Do you use an ad blocker which also blocks WebRTC communication?",
@@ -290,6 +293,10 @@
             "PLAY_SOUND": "Play sound"
         }
     },
+    "deviceUnreachable": {
+        "DEVICE_UNREACHABLE": "Device Unreachable",
+        "UNABLE_TO_CONNECT": "Unable to connect to your device …"
+    },
     "version": {
         "NEW_VERSION": "New Version Available",
         "NEW_VERSION_BODY": "A new version of Threema Web ({version}) is available. Check out the {changelog} for more information. Click \"OK\" to activate the update."

+ 43 - 0
src/partials/dialog.device-unreachable.html

@@ -0,0 +1,43 @@
+<md-dialog aria-label="Device Unreachable">
+    <form ng-cloak>
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>deviceUnreachable.DEVICE_UNREACHABLE</h2>
+            </div>
+        </md-toolbar>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <h3 translate>deviceUnreachable.UNABLE_TO_CONNECT</h3>
+                <ul class="material-icons-list">
+                    <li class="help">
+                        <span translate>troubleshooting.PHONE_ONLINE</span>
+                    </li>
+                    <li class="help">
+                        <span translate>troubleshooting.APP_STARTED</span>
+                    </li>
+                    <li class="help">
+                        <span translate>troubleshooting.WEBCLIENT_ENABLED</span>
+                    </li>
+                    <li class="info">
+                        <span translate>troubleshooting.UNLOCK_OR_CHARGE</span>
+                    </li>
+                    <li class="info">
+                        <span translate>troubleshooting.PUSH_FAQ</span>
+                    </li>
+                </ul>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button role="button" class="md-primary reload-btn" ng-click="ctrl.reload()" aria-labelledby="aria-label-reload">
+                <span translate id="aria-label-reload">welcome.RELOAD</span>
+            </md-button>
+            <md-button role="button" class="md-primary reload-btn circular-progress-button" ng-click="ctrl.retry()" ng-disabled="ctrl.retrying" aria-labelledby="aria-label-retry">
+                <md-progress-circular ng-if="ctrl.retrying" md-mode="determinate" md-diameter="20" value="{{ctrl.progress}}"></md-progress-circular>
+                <i ng-if="!ctrl.retrying" class="material-icons">refresh</i>
+                <span translate id="aria-label-retry">welcome.RETRY</span>
+            </md-button>
+        </md-dialog-actions>
+    </form>
+</md-dialog>
+

+ 52 - 4
src/partials/messenger.ts

@@ -24,7 +24,7 @@ import {
 } from '@uirouter/angularjs';
 
 import {ContactControllerModel} from '../controller_model/contact';
-import {bufferToUrl, filter, hasValue, logAdapter, supportsPassive, throttle, u8aToHex} from '../helpers';
+import {bufferToUrl, hasValue, logAdapter, supportsPassive, throttle, u8aToHex} from '../helpers';
 import {emojify} from '../helpers/emoji';
 import {ContactService} from '../services/contact';
 import {ControllerService} from '../services/controller';
@@ -61,7 +61,7 @@ class DialogController {
         this.done();
     }
 
-    protected hide(data: any): void {
+    protected hide(data?: any): void {
         this.$mdDialog.hide(data);
         this.done();
     }
@@ -127,11 +127,60 @@ class SendFileController extends DialogController {
     }
 }
 
+/**
+ * Handle device unreachable
+ */
+export class DeviceUnreachableController extends DialogController {
+    public static readonly $inject = ['$rootScope', '$window', '$mdDialog', 'CONFIG', 'WebClientService'];
+    private readonly $rootScope: any;
+    private readonly $window: ng.IWindowService;
+    private readonly webClientService: WebClientService;
+    public retrying: boolean = false;
+    public progress: number = 0;
+
+    constructor(
+        $rootScope: any, $window: ng.IWindowService, $mdDialog: ng.material.IDialogService,
+        CONFIG: threema.Config, webClientService: WebClientService,
+    ) {
+        super($mdDialog, CONFIG);
+        this.$rootScope = $rootScope;
+        this.$window = $window;
+        this.webClientService = webClientService;
+    }
+
+    /**
+     * Retry wakeup of the device via a push session.
+     */
+    public async retry(): Promise<any> {
+        // Schedule sending a push
+        const [expectedPeriodMaxMs, pushSessionPromise] = this.webClientService.sendPush();
+
+        // Initialise progress circle
+        this.retrying = true;
+        this.progress = 0;
+        const interval = setInterval(() => this.$rootScope.$apply(() => ++this.progress), expectedPeriodMaxMs / 100);
+
+        // Wait for push to succeed/reject and reset the progress circle
+        try {
+            await pushSessionPromise;
+        } finally {
+            clearInterval(interval);
+            this.$rootScope.$apply(() => this.retrying = false);
+        }
+    }
+
+    /**
+     * Reload the page.
+     */
+    public reload(): void {
+        this.$window.location.reload();
+    }
+}
+
 /**
  * Handle settings
  */
 class SettingsController {
-
     public static $inject = ['$mdDialog', '$window', 'SettingsService', 'NotificationService'];
 
     public $mdDialog: ng.material.IDialogService;
@@ -190,7 +239,6 @@ class SettingsController {
     public setWantsSound(notificationSound: boolean) {
         this.notificationService.setWantsSound(notificationSound);
     }
-
 }
 
 interface ConversationStateParams extends UiStateParams {

+ 13 - 13
src/partials/welcome.html

@@ -66,29 +66,29 @@
                     <p ng-if="ctrl.state === 'loading' || ctrl.state === 'done'" translate>welcome.LOADING_INITIAL_DATA</p>
                     <div class="troubleshoot" ng-if="ctrl.slowConnect">
                         <h3 translate>troubleshooting.SLOW_CONNECT</h3>
-                        <ul>
-                            <li>
-                                <i class="material-icons md-dark md-14">help</i>
+                        <ul class="material-icons-list">
+                            <li class="help">
                                 <span translate>troubleshooting.PHONE_ONLINE</span>
                             </li>
-                            <li ng-if="ctrl.state === 'push'">
-                                <i class="material-icons md-dark md-14">help</i>
+                            <li ng-if="ctrl.state === 'push'" class="help">
                                 <span translate>troubleshooting.APP_STARTED</span>
                             </li>
-                            <li ng-if="ctrl.state === 'push'">
-                                <i class="material-icons md-dark md-14">help</i>
+                            <li ng-if="ctrl.state === 'push'" class="help">
                                 <span translate>troubleshooting.WEBCLIENT_ENABLED</span>
                             </li>
-                            <li ng-if="ctrl.state === 'push'">
-                                <i class="material-icons md-dark md-14">help</i>
+                            <li ng-if="ctrl.state === 'push'" class="help">
                                 <span translate>troubleshooting.SESSION_DELETED</span>
                             </li>
-                            <li ng-if="ctrl.state === 'peer_handshake' && ctrl.showWebrtcTroubleshooting">
-                                <i class="material-icons md-dark md-14">help</i>
+                            <li ng-if="ctrl.state === 'push'" class="info">
+                                <span translate>troubleshooting.UNLOCK_OR_CHARGE</span>
+                            </li>
+                            <li ng-if="ctrl.state === 'push'" class="info">
+                                <span translate>troubleshooting.PUSH_FAQ</span>
+                            </li>
+                            <li ng-if="ctrl.state === 'peer_handshake' && ctrl.showWebrtcTroubleshooting" class="help">
                                 <span translate>troubleshooting.PLUGIN</span>
                             </li>
-                            <li ng-if="ctrl.state === 'peer_handshake' && ctrl.showWebrtcTroubleshooting">
-                                <i class="material-icons md-dark md-14">help</i>
+                            <li ng-if="ctrl.state === 'peer_handshake' && ctrl.showWebrtcTroubleshooting" class="help">
                                 <span translate>troubleshooting.ADBLOCKER</span>
                             </li>
                         </ul>

+ 32 - 0
src/sass/layout/_main.scss

@@ -250,7 +250,32 @@
             opacity: 1;
         }
     }
+}
+
+ul.material-icons-list {
+    list-style-type: none;
+
+    li {
+        $list-style-width: 1.3em;
+        margin-left: $list-style-width;
+        text-indent: -$list-style-width;
+
+        &::before {
+            font-family: 'Material Icons';
+        }
+
+        &.help {
+            &::before {
+                content: 'help';
+            }
+        }
 
+        &.info {
+            &::before {
+                content: 'info';
+            }
+        }
+    }
 }
 
 .md-dialog-content {
@@ -259,6 +284,13 @@
     }
 }
 
+.circular-progress-button {
+    md-progress-circular {
+        float: left;
+        margin: 8px 4px;
+    }
+}
+
 input.threema-id {
     text-transform: uppercase;
 }

+ 10 - 33
src/sass/sections/_welcome.scss

@@ -112,10 +112,13 @@
     }
 
     .loading {
+        $progress-height: 250px;
+        $progress-overlap: 70px;
         margin-top: 48px;
 
         md-progress-circular {
-            margin: 0 auto;
+            margin: 0 auto calc(-#{$progress-height} + #{$progress-overlap});
+            height: $progress-height;
 
             svg path {
                 stroke-width: 12px !important;
@@ -123,13 +126,6 @@
         }
 
         .info {
-            display: flex;
-            position: relative;
-            top: -250px;
-            flex-direction: column;
-            justify-content: center;
-            height: 250px;
-
             .percentage {
                 margin-bottom: 8px;
                 vertical-align: center;
@@ -140,11 +136,7 @@
         }
 
         .troubleshoot {
-            $troubleshoot-height: 190px;
-            position: absolute;
-            bottom: -$troubleshoot-height - 32px;
-            width: 100%;
-            height: $troubleshoot-height;
+            margin-top: calc(#{$progress-overlap} + 40px);
 
             h3 {
                 margin-bottom: 8px;
@@ -152,13 +144,13 @@
             }
 
             ul {
+                text-align: left;
                 font-size: .9em;
-                list-style-type: none;
-            }
 
-            li {
-                padding-bottom: .3em;
-                line-height: 1.2em;
+                li {
+                    padding: 0 1em .3em;
+                    line-height: 1.2em;
+                }
             }
 
             .forget {
@@ -172,19 +164,4 @@
             }
         }
     }
-
-    .notification {
-        flex-direction: horizontal;
-        margin-bottom: 16px;
-        background-color: #ff9800;
-        padding: 8px;
-
-        p {
-            width: 100%;
-            text-align: center;
-            line-height: 1.4em;
-            font-size: .8em;
-            font-weight: bold;
-        }
-    }
 }

+ 49 - 21
src/services/webclient.ts

@@ -46,6 +46,7 @@ import {TitleService} from './title';
 import {VersionService} from './version';
 
 import {TimeoutError} from '../exceptions';
+import {DeviceUnreachableController} from '../partials/messenger';
 import {ChunkCache} from '../protocol/cache';
 import {SequenceNumber} from '../protocol/sequence_number';
 
@@ -217,11 +218,12 @@ export class WebClientService {
     public alerts: threema.Alert[] = [];
 
     // Push
+    private readonly pushExpectedPeriodMaxMs: number = PushSession.expectedPeriodMaxMs();
     private pushToken: string = null;
     private pushTokenType: threema.PushTokenType = null;
-    private pushSession: PushSession = null;
-    private pushPromise: Promise<any> = null;
-    private readonly pushExpectedPeriodMaxMs: number = PushSession.expectedPeriodMaxMs();
+    private pushSession: PushSession | null = null;
+    private pushPromise: Promise<any> | null = null;
+    private deviceUnreachableDialog: ng.IPromise<any> | null = null;
 
     // Timeouts
     private batteryStatusTimeout: ng.IPromise<void> = null;
@@ -490,10 +492,7 @@ export class WebClientService {
         this.salty.on('new-responder', () => {
             if (!this.startupDone) {
                 // Pushing complete
-                if (this.pushSession !== null) {
-                    this.pushSession.done();
-                    this.pushSession = null;
-                }
+                this.resetPushSession(true);
 
                 // Peer handshake
                 this.stateService.updateConnectionBuildupState('peer_handshake');
@@ -1038,16 +1037,30 @@ export class WebClientService {
         if (this.pushSession === null) {
             this.pushSession = this.pushService.createSession(this.salty.permanentKeyBytes);
 
-            // Start and handle errors
-            this.pushPromise = this.pushSession.start().catch((error) => {
-                if (error instanceof TimeoutError) {
-                    // TODO: Show device unreachable dialog
-                    // TODO: If unreachable dialog is already shown, set .retrying to false (with root scope)
-                    // this.showDeviceUnreachableDialog();
-                } else {
-                    this.failSession();
-                }
-            });
+            // Start and handle success/error
+            this.pushPromise = this.pushSession.start()
+                .then(() => this.resetPushSession(true))
+                .catch((error) => {
+                    // Reset push session
+                    this.resetPushSession(false);
+
+                    // Handle error
+                    if (error instanceof TimeoutError) {
+                        // Show device unreachable dialog (if we were already
+                        // connected and if not already visible).
+                        if (this.$state.includes('messenger') && this.deviceUnreachableDialog === null) {
+                            this.deviceUnreachableDialog = this.$mdDialog.show({
+                                controller: DeviceUnreachableController,
+                                controllerAs: 'ctrl',
+                                templateUrl: 'partials/dialog.device-unreachable.html',
+                                parent: angular.element(document.body),
+                                escapeToClose: false,
+                            }).finally(() => this.deviceUnreachableDialog = null);
+                        }
+                    } else {
+                        this.failSession();
+                    }
+                });
 
             // Update state
             if (!this.$rootScope.$$phase) {
@@ -1061,6 +1074,24 @@ export class WebClientService {
         return [this.pushExpectedPeriodMaxMs, this.pushPromise];
     }
 
+    /**
+     * Reset push session (if any) and hide the *device unreachable* dialog
+     * (if any and if requested).
+     */
+    private resetPushSession(hideDeviceUnreachableDialog: boolean = true): void {
+        // Hide unreachable dialog (if any)
+        if (hideDeviceUnreachableDialog && this.deviceUnreachableDialog !== null) {
+            this.$mdDialog.hide();
+        }
+
+        // Reset push session (if any)
+        if (this.pushSession !== null) {
+            this.pushSession.done();
+            this.pushSession = null;
+            this.pushPromise = null;
+        }
+    }
+
     /**
      * Start the webclient service.
      * Return a promise that resolves once connected.
@@ -1121,10 +1152,7 @@ export class WebClientService {
         let remove = false;
 
         // Stop push session
-        if (this.pushSession !== null) {
-            this.pushSession.done();
-            this.pushSession = null;
-        }
+        this.resetPushSession(true);
 
         // Session deleted: Force close and delete
         if (args.reason === DisconnectReason.SessionDeleted) {