FEATURE: Allow users to opt out of automatic dark mode (#10377)

FEATURE: Allow users to opt out of automatic dark mode (#10377)

diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
index 9baf458..5d41ab7 100644
--- a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
@@ -33,6 +33,7 @@ export default Controller.extend({
     let attrs = [
       "locale",
       "external_links_in_new_tab",
+      "dark_scheme_id",
       "dynamic_favicon",
       "enable_quoting",
       "enable_defer",
@@ -149,6 +150,20 @@ export default Controller.extend({
     return result;
   },
 
+  @discourseComputed
+  showDarkModeToggle() {
+    return this.siteSettings.default_dark_mode_color_scheme_id > 0;
+  },
+
+  enableDarkMode: computed({
+    set(key, value) {
+      return value;
+    },
+    get() {
+      return this.get("model.user_option.dark_scheme_id") === -1 ? false : true;
+    }
+  }),
+
   actions: {
     save() {
       this.set("saved", false);
@@ -162,6 +177,11 @@ export default Controller.extend({
         this.set("model.user_option.text_size", this.textSize);
       }
 
+      this.set(
+        "model.user_option.dark_scheme_id",
+        this.enableDarkMode ? null : -1
+      );
+
       return this.model
         .save(this.saveAttrNames)
         .then(() => {
diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js
index c4bf6b5..58195f7 100644
--- a/app/assets/javascripts/discourse/app/models/user.js
+++ b/app/assets/javascripts/discourse/app/models/user.js
@@ -300,6 +300,7 @@ const User = RestModel.extend({
       "email_messages_level",
       "email_level",
       "email_previous_replies",
+      "dark_scheme_id",
       "dynamic_favicon",
       "enable_quoting",
       "enable_defer",
diff --git a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs
index 7f198ba..c3d23b1 100644
--- a/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs
+++ b/app/assets/javascripts/discourse/app/templates/preferences/interface.hbs
@@ -20,6 +20,15 @@
   </div>
 {{/if}}
 
+{{#if showDarkModeToggle}}
+  <div class="control-group dark-mode">
+    <label class="control-label">{{i18n "user.dark_mode"}}</label>
+    <div class="controls">
+      {{preference-checkbox labelKey="user.dark_mode_enable" checked=enableDarkMode}}
+    </div>
+  </div>
+{{/if}}
+
 <div class="control-group text-size">
   <label class="control-label">{{i18n "user.text_size.title"}}</label>
   <div class="controls">
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 15513b3..84d7909 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -454,7 +454,9 @@ module ApplicationHelper
     result = +""
     result << Stylesheet::Manager.color_scheme_stylesheet_link_tag(scheme_id)
 
-    dark_scheme_id = SiteSetting.default_dark_mode_color_scheme_id
+    user_dark_scheme_id = current_user&.user_option&.dark_scheme_id
+    dark_scheme_id =  user_dark_scheme_id || SiteSetting.default_dark_mode_color_scheme_id
+
     if dark_scheme_id != -1
       result << Stylesheet::Manager.color_scheme_stylesheet_link_tag(dark_scheme_id, '(prefers-color-scheme: dark)')
     end
diff --git a/app/serializers/user_option_serializer.rb b/app/serializers/user_option_serializer.rb
index c95f454..0187bcc 100644
--- a/app/serializers/user_option_serializer.rb
+++ b/app/serializers/user_option_serializer.rb
@@ -8,6 +8,7 @@ class UserOptionSerializer < ApplicationSerializer
              :email_level,
              :email_messages_level,
              :external_links_in_new_tab,
+             :dark_scheme_id,
              :dynamic_favicon,
              :enable_quoting,
              :enable_defer,
diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb
index 3b252a7..32446e0 100644
--- a/app/services/user_updater.rb
+++ b/app/services/user_updater.rb
@@ -25,6 +25,7 @@ class UserUpdater
     :external_links_in_new_tab,
     :enable_quoting,
     :enable_defer,
+    :dark_scheme_id,
     :dynamic_favicon,
     :automatically_unpin_topics,
     :digest_after_minutes,
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 8695fda..1bb1d5b 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -909,6 +909,8 @@ en:
       first_notification: "Your first notification! Select it to begin."
       dynamic_favicon: "Show counts on browser icon"
       theme_default_on_all_devices: "Make this the default theme on all my devices"
+      dark_mode: "Dark Mode"
+      dark_mode_enable: "Enable automatic dark mode color scheme"
       text_size_default_on_all_devices: "Make this the default text size on all my devices"
       allow_private_messages: "Allow other users to send me personal messages"
       external_links_in_new_tab: "Open all external links in a new tab"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 46699dc..47d8d60 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -248,6 +248,7 @@ basic:
   default_dark_mode_color_scheme_id:
     default: -1
     hidden: true
+    client: true
   relative_date_duration:
     client: true
     default: 30
diff --git a/db/migrate/20200805151752_add_dark_scheme_id_to_user_options.rb b/db/migrate/20200805151752_add_dark_scheme_id_to_user_options.rb
new file mode 100644
index 0000000..26230f5
--- /dev/null
+++ b/db/migrate/20200805151752_add_dark_scheme_id_to_user_options.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddDarkSchemeIdToUserOptions < ActiveRecord::Migration[6.0]
+  def change
+    add_column :user_options, :dark_scheme_id, :integer
+  end
+end
diff --git a/lib/stylesheet/manager.rb b/lib/stylesheet/manager.rb
index 819c4bc..426a670 100644
--- a/lib/stylesheet/manager.rb
+++ b/lib/stylesheet/manager.rb
@@ -93,13 +93,17 @@ class Stylesheet::Manager
     end
   end
 
-  def self.color_scheme_stylesheet_details(color_scheme_id = nil)
+  def self.color_scheme_stylesheet_details(color_scheme_id = nil, media)
     color_scheme = begin
       ColorScheme.find(color_scheme_id)
     rescue
+      # don't load fallback when requesting dark color scheme
+      return false if media != "all"
       Theme.find_by_id(SiteSetting.default_theme_id)&.color_scheme || ColorScheme.base
     end
 
+    return false if !color_scheme
+
     target = COLOR_SCHEME_STYLESHEET.to_sym
     current_hostname = Discourse.current_hostname
     color_scheme_name = Slug.for(color_scheme.name)
@@ -119,7 +123,9 @@ class Stylesheet::Manager
   end
 
   def self.color_scheme_stylesheet_link_tag(color_scheme_id = nil, media = 'all')
-    stylesheet = color_scheme_stylesheet_details(color_scheme_id)
+    stylesheet = color_scheme_stylesheet_details(color_scheme_id, media)
+    return '' if !stylesheet
+
     href = stylesheet[:new_href]
     %[<link href="#{href}" media="#{media}" rel="stylesheet"/>].html_safe
   end
diff --git a/spec/components/stylesheet/manager_spec.rb b/spec/components/stylesheet/manager_spec.rb
index 692b728..efcdaa3 100644
--- a/spec/components/stylesheet/manager_spec.rb
+++ b/spec/components/stylesheet/manager_spec.rb
@@ -177,9 +177,14 @@ describe Stylesheet::Manager do
       expect(link).not_to eq("")
     end
 
-    it "does not crash on missing color scheme" do
+    it "loads base scheme when defined scheme id is missing" do
       link = Stylesheet::Manager.color_scheme_stylesheet_link_tag(125)
-      expect(link).not_to eq("")
+      expect(link).to include("color_definitions_base")
+    end
+
+    it "loads nothing when defined dark scheme id is missing" do
+      link = Stylesheet::Manager.color_scheme_stylesheet_link_tag(125, "(prefers-color-scheme: dark)")
+      expect(link).to eq("")
     end
 

[... diff too long, it was truncated ...]

GitHub sha: 6fdc711b

This commit appears in #10377 which was approved by eviltrout. It was merged by pmusaraj.