瀏覽代碼

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 年之前
父節點
當前提交
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) {