Merge "Remove unused opt_ctx param from API interface"
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
index 48b01f6..14e5e6f 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
@@ -16,7 +16,9 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+
 <link rel="import" href="../../../behaviors/docs-url-behavior/docs-url-behavior.html">
+<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
 <link rel="import" href="../../../styles/gr-form-styles.html">
 <link rel="import" href="../../../styles/gr-menu-page-styles.html">
 <link rel="import" href="../../../styles/gr-page-nav-styles.html">
@@ -52,12 +54,19 @@
       #email {
         margin-bottom: 1em;
       }
-      .filters p {
+      .filters p,
+      .darkToggle p {
         margin-bottom: 1em;
       }
       .queryExample em {
         color: violet;
       }
+      .toggle {
+        align-items: center;
+        display: flex;
+        margin-bottom: 1rem;
+        margin-right: 1rem;
+      }
     </style>
     <style include="gr-form-styles"></style>
     <style include="gr-menu-page-styles"></style>
@@ -95,6 +104,19 @@
       </gr-page-nav>
       <main class="gr-form-styles">
         <h1>User Settings</h1>
+        <section class="darkToggle">
+          <div class="toggle">
+            <paper-toggle-button
+                checked="[[_isDark]]"
+                on-change="_handleToggleDark"></paper-toggle-button>
+            <div>Dark theme (alpha)</div>
+          </div>
+          <p>
+            Gerrit's dark theme is in early alpha, and almost definitely will
+            not play nicely with themes set by specific Gerrit hosts. Filing
+            feedback via the link in the app footer is strongly encouraged!
+          </p>
+        </section>
         <h2
             id="Profile"
             class$="[[_computeHeaderClass(_accountInfoChanged)]]">Profile</h2>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index 215aaa1..213ab65 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -35,6 +35,8 @@
   const ABSOLUTE_URL_PATTERN = /^https?:/;
   const TRAILING_SLASH_PATTERN = /\/$/;
 
+  const RELOAD_MESSAGE = 'Reloading...';
+
   Polymer({
     is: 'gr-settings-view',
 
@@ -45,7 +47,7 @@
      */
 
     /**
-     * Fired with email confirmation text.
+     * Fired with email confirmation text, or when the page reloads.
      *
      * @event show-alert
      */
@@ -132,6 +134,11 @@
       _loadingPromise: Object,
 
       _showNumber: Boolean,
+
+      _isDark: {
+        type: Boolean,
+        value: false,
+      },
     },
 
     behaviors: [
@@ -149,6 +156,8 @@
     attached() {
       this.fire('title-change', {title: 'Settings'});
 
+      this._isDark = !!window.localStorage.getItem('dark-theme');
+
       const promises = [
         this.$.accountInfo.loadData(),
         this.$.watchedProjectsEditor.loadData(),
@@ -410,5 +419,20 @@
 
       return base + GERRIT_DOCS_FILTER_PATH;
     },
+
+    _handleToggleDark() {
+      if (this._isDark) {
+        window.localStorage.removeItem('dark-theme');
+      } else {
+        window.localStorage.setItem('dark-theme', 'true');
+      }
+      this.dispatchEvent(new CustomEvent('show-alert', {
+        detail: {message: RELOAD_MESSAGE},
+        bubbles: true,
+      }));
+      this.async(() => {
+        window.location.reload();
+      }, 1);
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.js
index 84b7f0a..5ac8773 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context.js
@@ -93,7 +93,14 @@
     }
     this.plugin.restApi()
         .send(this.action.method, this.action.__url, payload)
-        .then(onSuccess);
+        .then(onSuccess)
+        .catch(error => {
+          document.dispatchEvent(new CustomEvent('show-alert', {
+            detail: {
+              message: `Plugin network error: ${error}`,
+            },
+          }));
+        });
   };
 
   window.GrPluginActionContext = GrPluginActionContext;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
index 7c18a99..bf6a046 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
@@ -128,5 +128,26 @@
       assert.isTrue(sendStub.calledWith(
           'METHOD', '/changes/1/revisions/2/foo~bar', payload));
     });
+
+    test('call error', done => {
+      instance.action = {
+        method: 'METHOD',
+        __key: 'key',
+        __url: '/changes/1/revisions/2/foo~bar',
+      };
+      const sendStub = sandbox.stub().returns(Promise.reject('boom'));
+      sandbox.stub(plugin, 'restApi').returns({
+        send: sendStub,
+      });
+      const errorStub = sandbox.stub();
+      document.addEventListener('network-error', errorStub);
+      instance.call();
+      flush(() => {
+        assert.isTrue(errorStub.calledOnce);
+        assert.equal(errorStub.args[0][0].detail.message,
+            'Plugin network error: boom');
+        done();
+      });
+    });
   });
 </script>