diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
index 801062bff49..c7c9143efcd 100644
--- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
@@ -19,7 +19,6 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import ProfileSettings from "../../ProfileSettings";
-import Field from "../../../elements/Field";
import * as languageHandler from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import SettingsStore from "../../../../../settings/SettingsStore";
@@ -27,7 +26,6 @@ import LanguageDropdown from "../../../elements/LanguageDropdown";
import AccessibleButton from "../../../elements/AccessibleButton";
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
import PropTypes from "prop-types";
-import {enumerateThemes, ThemeWatcher} from "../../../../../theme";
import PlatformPeg from "../../../../../PlatformPeg";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
@@ -60,7 +58,6 @@ export default class GeneralUserSettingsTab extends React.Component {
},
emails: [],
msisdns: [],
- ...this._calculateThemeState(),
customThemeUrl: "",
customThemeMessage: {isError: false, text: ""},
};
@@ -91,39 +88,6 @@ export default class GeneralUserSettingsTab extends React.Component {
dis.unregister(this.dispatcherRef);
}
- _calculateThemeState() {
- // We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
- // show the right values for things.
-
- const themeChoice = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme");
- const systemThemeExplicit = SettingsStore.getValueAt(
- SettingLevel.DEVICE, "use_system_theme", null, false, true);
- const themeExplicit = SettingsStore.getValueAt(
- SettingLevel.DEVICE, "theme", null, false, true);
-
- // If the user has enabled system theme matching, use that.
- if (systemThemeExplicit) {
- return {
- theme: themeChoice,
- useSystemTheme: true,
- };
- }
-
- // If the user has set a theme explicitly, use that (no system theme matching)
- if (themeExplicit) {
- return {
- theme: themeChoice,
- useSystemTheme: false,
- };
- }
-
- // Otherwise assume the defaults for the settings
- return {
- theme: themeChoice,
- useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"),
- };
- }
-
_onAction = (payload) => {
if (payload.action === 'id_server_changed') {
this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())});
@@ -214,33 +178,6 @@ export default class GeneralUserSettingsTab extends React.Component {
PlatformPeg.get().reload();
};
- _onThemeChange = (e) => {
- const newTheme = e.target.value;
- if (this.state.theme === newTheme) return;
-
- // doing getValue in the .catch will still return the value we failed to set,
- // so remember what the value was before we tried to set it so we can revert
- const oldTheme = SettingsStore.getValue('theme');
- SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => {
- dis.dispatch({action: 'recheck_theme'});
- this.setState({theme: oldTheme});
- });
- this.setState({theme: newTheme});
- // The settings watcher doesn't fire until the echo comes back from the
- // server, so to make the theme change immediately we need to manually
- // do the dispatch now
- // XXX: The local echoed value appears to be unreliable, in particular
- // when settings custom themes(!) so adding forceTheme to override
- // the value from settings.
- dis.dispatch({action: 'recheck_theme', forceTheme: newTheme});
- };
-
- _onUseSystemThemeChanged = (checked) => {
- this.setState({useSystemTheme: checked});
- SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked);
- dis.dispatch({action: 'recheck_theme'});
- };
-
_onPasswordChangeError = (err) => {
// TODO: Figure out a design that doesn't involve replacing the current dialog
let errMsg = err.error || "";
@@ -277,41 +214,6 @@ export default class GeneralUserSettingsTab extends React.Component {
});
};
- _onAddCustomTheme = async () => {
- let currentThemes = SettingsStore.getValue("custom_themes");
- if (!currentThemes) currentThemes = [];
- currentThemes = currentThemes.map(c => c); // cheap clone
-
- if (this._themeTimer) {
- clearTimeout(this._themeTimer);
- }
-
- try {
- const r = await fetch(this.state.customThemeUrl);
- const themeInfo = await r.json();
- if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') {
- this.setState({customThemeMessage: {text: _t("Invalid theme schema."), isError: true}});
- return;
- }
- currentThemes.push(themeInfo);
- } catch (e) {
- console.error(e);
- this.setState({customThemeMessage: {text: _t("Error downloading theme information."), isError: true}});
- return; // Don't continue on error
- }
-
- await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes);
- this.setState({customThemeUrl: "", customThemeMessage: {text: _t("Theme added!"), isError: false}});
-
- this._themeTimer = setTimeout(() => {
- this.setState({customThemeMessage: {text: "", isError: false}});
- }, 3000);
- };
-
- _onCustomThemeChange = (e) => {
- this.setState({customThemeUrl: e.target.value});
- };
-
_renderProfileSection() {
return (
@@ -391,77 +293,6 @@ export default class GeneralUserSettingsTab extends React.Component {
);
}
- _renderThemeSection() {
- const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
- const LabelledToggleSwitch = sdk.getComponent("views.elements.LabelledToggleSwitch");
-
- const themeWatcher = new ThemeWatcher();
- let systemThemeSection;
- if (themeWatcher.isSystemThemeSupported()) {
- systemThemeSection =
-
-
;
- }
-
- let customThemeForm;
- if (SettingsStore.isFeatureEnabled("feature_custom_themes")) {
- let messageElement = null;
- if (this.state.customThemeMessage.text) {
- if (this.state.customThemeMessage.isError) {
- messageElement =
{this.state.customThemeMessage.text}
;
- } else {
- messageElement =
{this.state.customThemeMessage.text}
;
- }
- }
- customThemeForm = (
-
- );
- }
-
- const themes = Object.entries(enumerateThemes())
- .map(p => ({id: p[0], name: p[1]})); // convert pairs to objects for code readability
- const builtInThemes = themes.filter(p => !p.id.startsWith("custom-"));
- const customThemes = themes.filter(p => !builtInThemes.includes(p))
- .sort((a, b) => a.name.localeCompare(b.name));
- const orderedThemes = [...builtInThemes, ...customThemes];
- return (
-
- {_t("Theme")}
- {systemThemeSection}
-
- {orderedThemes.map(theme => {
- return {theme.name} ;
- })}
-
- {customThemeForm}
-
-
- );
- }
-
_renderDiscoverySection() {
const SetIdServer = sdk.getComponent("views.settings.SetIdServer");
@@ -547,7 +378,6 @@ export default class GeneralUserSettingsTab extends React.Component {
{this._renderProfileSection()}
{this._renderAccountSection()}
{this._renderLanguageSection()}
- {this._renderThemeSection()}
{discoWarning} {_t("Discovery")}
{this._renderDiscoverySection()}
{this._renderIntegrationManagerSection() /* Has its own title */}
diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
index bdb2a9ffc4a..0cba3bb55bc 100644
--- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
@@ -21,8 +21,11 @@ import {SettingLevel} from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import SettingsStore from "../../../../../settings/SettingsStore";
import Field from "../../../elements/Field";
+import AccessibleButton from "../../../elements/AccessibleButton";
import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
+import {enumerateThemes, ThemeWatcher} from "../../../../../theme";
+import dis from "../../../../../dispatcher";
export default class PreferencesUserSettingsTab extends React.Component {
static ROOM_LIST_SETTINGS = [
@@ -51,6 +54,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
'showAvatarChanges',
'showDisplaynameChanges',
'showImages',
+ 'useCompactLayout',
];
static ADVANCED_SETTINGS = [
@@ -78,6 +82,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
SettingsStore.getValueAt(SettingLevel.DEVICE, 'readMarkerInViewThresholdMs').toString(10),
readMarkerOutOfViewThresholdMs:
SettingsStore.getValueAt(SettingLevel.DEVICE, 'readMarkerOutOfViewThresholdMs').toString(10),
+ ...this._calculateThemeState(),
};
}
@@ -112,6 +117,39 @@ export default class PreferencesUserSettingsTab extends React.Component {
});
}
+ _calculateThemeState() {
+ // We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
+ // show the right values for things.
+
+ const themeChoice = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme");
+ const systemThemeExplicit = SettingsStore.getValueAt(
+ SettingLevel.DEVICE, "use_system_theme", null, false, true);
+ const themeExplicit = SettingsStore.getValueAt(
+ SettingLevel.DEVICE, "theme", null, false, true);
+
+ // If the user has enabled system theme matching, use that.
+ if (systemThemeExplicit) {
+ return {
+ theme: themeChoice,
+ useSystemTheme: true,
+ };
+ }
+
+ // If the user has set a theme explicitly, use that (no system theme matching)
+ if (themeExplicit) {
+ return {
+ theme: themeChoice,
+ useSystemTheme: false,
+ };
+ }
+
+ // Otherwise assume the defaults for the settings
+ return {
+ theme: themeChoice,
+ useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"),
+ };
+ }
+
_onAutoLaunchChange = (checked) => {
PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked}));
};
@@ -139,11 +177,142 @@ export default class PreferencesUserSettingsTab extends React.Component {
SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
};
+ _onThemeChange = (e) => {
+ const newTheme = e.target.value;
+ if (this.state.theme === newTheme) return;
+
+ // doing getValue in the .catch will still return the value we failed to set,
+ // so remember what the value was before we tried to set it so we can revert
+ const oldTheme = SettingsStore.getValue('theme');
+ SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => {
+ dis.dispatch({action: 'recheck_theme'});
+ this.setState({theme: oldTheme});
+ });
+ this.setState({theme: newTheme});
+ // The settings watcher doesn't fire until the echo comes back from the
+ // server, so to make the theme change immediately we need to manually
+ // do the dispatch now
+ // XXX: The local echoed value appears to be unreliable, in particular
+ // when settings custom themes(!) so adding forceTheme to override
+ // the value from settings.
+ dis.dispatch({action: 'recheck_theme', forceTheme: newTheme});
+ };
+
+ _onAddCustomTheme = async () => {
+ let currentThemes = SettingsStore.getValue("custom_themes");
+ if (!currentThemes) currentThemes = [];
+ currentThemes = currentThemes.map(c => c); // cheap clone
+
+ if (this._themeTimer) {
+ clearTimeout(this._themeTimer);
+ }
+
+ try {
+ const r = await fetch(this.state.customThemeUrl);
+ const themeInfo = await r.json();
+ if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') {
+ this.setState({customThemeMessage: {text: _t("Invalid theme schema."), isError: true}});
+ return;
+ }
+ currentThemes.push(themeInfo);
+ } catch (e) {
+ console.error(e);
+ this.setState({customThemeMessage: {text: _t("Error downloading theme information."), isError: true}});
+ return; // Don't continue on error
+ }
+
+ await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes);
+ this.setState({customThemeUrl: "", customThemeMessage: {text: _t("Theme added!"), isError: false}});
+
+ this._themeTimer = setTimeout(() => {
+ this.setState({customThemeMessage: {text: "", isError: false}});
+ }, 3000);
+ };
+
+ _onCustomThemeChange = (e) => {
+ this.setState({customThemeUrl: e.target.value});
+ };
+
+ _onUseSystemThemeChanged = (checked) => {
+ this.setState({useSystemTheme: checked});
+ SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked);
+ dis.dispatch({action: 'recheck_theme'});
+ };
+
_renderGroup(settingIds) {
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
return settingIds.map(i =>
);
}
+ _renderThemeSection() {
+ const LabelledToggleSwitch = sdk.getComponent("views.elements.LabelledToggleSwitch");
+
+ const themeWatcher = new ThemeWatcher();
+ let systemThemeSection;
+ if (themeWatcher.isSystemThemeSupported()) {
+ systemThemeSection =
+
+
;
+ }
+
+ let customThemeForm;
+ if (SettingsStore.isFeatureEnabled("feature_custom_themes")) {
+ let messageElement = null;
+ if (this.state.customThemeMessage.text) {
+ if (this.state.customThemeMessage.isError) {
+ messageElement =
{this.state.customThemeMessage.text}
;
+ } else {
+ messageElement =
{this.state.customThemeMessage.text}
;
+ }
+ }
+ customThemeForm = (
+
+ );
+ }
+
+ const themes = Object.entries(enumerateThemes())
+ .map(p => ({id: p[0], name: p[1]})); // convert pairs to objects for code readability
+ const builtInThemes = themes.filter(p => !p.id.startsWith("custom-"));
+ const customThemes = themes.filter(p => !builtInThemes.includes(p))
+ .sort((a, b) => a.name.localeCompare(b.name));
+ const orderedThemes = [...builtInThemes, ...customThemes];
+ return (
+
+ {_t("Theme")}
+ {systemThemeSection}
+
+ {orderedThemes.map(theme => {
+ return {theme.name} ;
+ })}
+
+ {customThemeForm}
+
+ );
+ }
+
render() {
let autoLaunchOption = null;
if (this.state.autoLaunchSupported) {
@@ -173,6 +342,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
{_t("Preferences")}
+ {this._renderThemeSection()}
+
{_t("Room list")}
{this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 24a6568d823..36b5ecc4d42 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -733,18 +733,12 @@
"Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?",
"Success": "Success",
"Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them",
- "Invalid theme schema.": "Invalid theme schema.",
- "Error downloading theme information.": "Error downloading theme information.",
- "Theme added!": "Theme added!",
"Profile": "Profile",
"Email addresses": "Email addresses",
"Phone numbers": "Phone numbers",
"Set a new account password...": "Set a new account password...",
"Account": "Account",
"Language and region": "Language and region",
- "Custom theme URL": "Custom theme URL",
- "Add theme": "Add theme",
- "Theme": "Theme",
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.",
"Account management": "Account management",
"Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!",
@@ -810,6 +804,12 @@
"Room ID or alias of ban list": "Room ID or alias of ban list",
"Subscribe": "Subscribe",
"Notifications": "Notifications",
+ "Invalid theme schema.": "Invalid theme schema.",
+ "Error downloading theme information.": "Error downloading theme information.",
+ "Theme added!": "Theme added!",
+ "Custom theme URL": "Custom theme URL",
+ "Add theme": "Add theme",
+ "Theme": "Theme",
"Start automatically after system login": "Start automatically after system login",
"Always show the window menu bar": "Always show the window menu bar",
"Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close",