mirror of
https://github.com/standardnotes/mobile.git
synced 2026-04-19 13:49:03 -04:00
410 lines
11 KiB
JavaScript
410 lines
11 KiB
JavaScript
var _ = require('lodash')
|
|
|
|
import { StyleSheet, StatusBar, Alert, Platform } from 'react-native';
|
|
import App from "./app"
|
|
import ModelManager from "./lib/modelManager"
|
|
import Server from "./lib/server"
|
|
import Sync from "./lib/sync"
|
|
import Storage from "./lib/storage"
|
|
import Auth from "./lib/auth"
|
|
import Theme from "./models/app/theme"
|
|
import KeysManager from './lib/keysManager'
|
|
|
|
export default class GlobalStyles {
|
|
|
|
static instance = null;
|
|
|
|
static get() {
|
|
if (this.instance == null) {
|
|
this.instance = new GlobalStyles();
|
|
}
|
|
|
|
return this.instance;
|
|
}
|
|
|
|
async resolveInitialTheme() {
|
|
// Get the active theme from storage rather than waiting for local database to load
|
|
return Storage.getItem("activeTheme").then(function(theme) {
|
|
if(theme) {
|
|
theme = JSON.parse(theme);
|
|
theme.isSwapIn = true;
|
|
var constants = _.merge(this.defaultConstants(), theme.mobileRules.constants);
|
|
var rules = _.merge(this.defaultRules(constants), theme.mobileRules.rules);
|
|
this.setStyles(rules, constants, theme.mobileRules.statusBar);
|
|
|
|
this.activeTheme = theme;
|
|
} else {
|
|
var theme = this.systemTheme();
|
|
theme.active = true;
|
|
this.activeTheme = theme;
|
|
var constants = this.defaultConstants();
|
|
this.setStyles(this.defaultRules(constants), constants, theme.mobileRules.statusBar);
|
|
}
|
|
}.bind(this));
|
|
}
|
|
|
|
constructor() {
|
|
KeysManager.get().registerAccountRelatedStorageKeys(["activeTheme"]);
|
|
|
|
ModelManager.getInstance().addItemSyncObserver("themes", "SN|Theme", function(items){
|
|
if(this.activeTheme && this.activeTheme.isSwapIn) {
|
|
this.activeTheme.isSwapIn = false;
|
|
this.activeTheme = _.find(this.themes(), {uuid: this.activeTheme.uuid});
|
|
this.activeTheme.active = true;
|
|
}
|
|
}.bind(this));
|
|
}
|
|
|
|
static styles() {
|
|
return this.get().styles.rules;
|
|
}
|
|
|
|
static constants() {
|
|
return this.get().styles.constants;
|
|
}
|
|
|
|
static stylesForKey(key) {
|
|
var rules = this.get().styles.rules;
|
|
var styles = [rules[key]];
|
|
var platform = Platform.OS == "android" ? "Android" : "IOS";
|
|
var platformRules = rules[key+platform];
|
|
if(platformRules) {
|
|
styles.push(platformRules);
|
|
}
|
|
return styles;
|
|
}
|
|
|
|
static constantForKey(key) {
|
|
var value = this.get().constants[key];
|
|
|
|
// For the platform value, if the active theme does not have a specific value, but the defaults do, we don't
|
|
// want to use the defaults, but instead just look at the activeTheme. Because default platform values only apply
|
|
// to the default theme
|
|
var platform = Platform.OS == "android" ? "Android" : "IOS";
|
|
var platformValue = this.get().activeTheme.mobileRules.constants[key+platform];
|
|
|
|
if(platformValue) {
|
|
return platformValue;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
systemTheme() {
|
|
if(this._systemTheme) {
|
|
return this._systemTheme;
|
|
}
|
|
var constants = this.defaultConstants();
|
|
this._systemTheme = new Theme({
|
|
name: "Default",
|
|
default: true,
|
|
uuid: 0,
|
|
mobileRules: {
|
|
name: "Default",
|
|
rules: this.defaultRules(constants),
|
|
constants: constants,
|
|
statusBar: Platform.OS == "android" ? "light-content" : "dark-content"
|
|
}
|
|
});
|
|
return this._systemTheme;
|
|
}
|
|
|
|
themes() {
|
|
return [this.systemTheme()].concat(ModelManager.getInstance().themes);
|
|
}
|
|
|
|
isThemeActive(theme) {
|
|
return this.activateThemeId === theme.uuid;
|
|
}
|
|
|
|
activateTheme(theme, writeToStorage = true) {
|
|
if(this.activeTheme) {
|
|
this.activeTheme.active = false;
|
|
}
|
|
|
|
var run = () => {
|
|
var constants = _.merge(this.defaultConstants(), theme.mobileRules.constants);
|
|
var rules = _.merge(this.defaultRules(constants), theme.mobileRules.rules);
|
|
this.setStyles(rules, constants, theme.mobileRules.statusBar);
|
|
|
|
this.activeTheme = theme;
|
|
theme.active = true;
|
|
|
|
if(theme.default) {
|
|
Storage.removeItem("activeTheme");
|
|
} else if(writeToStorage) {
|
|
Storage.setItem("activeTheme", JSON.stringify(theme));
|
|
}
|
|
|
|
App.get().reload();
|
|
}
|
|
|
|
if(!theme.mobileRules) {
|
|
this.downloadTheme(theme, function(){
|
|
if(theme.notAvailableOnMobile) {
|
|
Alert.alert("Not Available", "This theme is not available on mobile.");
|
|
} else {
|
|
run();
|
|
}
|
|
});
|
|
} else {
|
|
run();
|
|
}
|
|
}
|
|
|
|
async downloadTheme(theme, callback) {
|
|
var url;
|
|
if(theme.url.includes("?")) {
|
|
url = theme.url.replace("?", ".json?");
|
|
} else {
|
|
url = theme.url + ".json";
|
|
}
|
|
|
|
if(App.isAndroid && url.includes("localhost")) {
|
|
url = url.replace("localhost", "10.0.2.2");
|
|
}
|
|
|
|
return Server.getInstance().getAbsolute(url, {}, function(response){
|
|
// success
|
|
if(response !== theme.mobileRules) {
|
|
theme.mobileRules = response;
|
|
theme.setDirty(true);
|
|
}
|
|
|
|
if(theme.notAvailableOnMobile) {
|
|
theme.notAvailableOnMobile = false;
|
|
theme.setDirty(true);
|
|
}
|
|
|
|
if(callback) {
|
|
callback();
|
|
}
|
|
}, function(response) {
|
|
// error
|
|
if(!theme.notAvailableOnMobile) {
|
|
theme.notAvailableOnMobile = true;
|
|
theme.setDirty(true);
|
|
}
|
|
if(callback) {
|
|
callback();
|
|
}
|
|
|
|
console.log("Theme download error", response);
|
|
})
|
|
}
|
|
|
|
downloadThemeAndReload(theme) {
|
|
this.downloadTheme(theme, function(){
|
|
Sync.getInstance().sync(function(){
|
|
this.activateTheme(theme);
|
|
}.bind(this));
|
|
}.bind(this))
|
|
}
|
|
|
|
setStyles(rules, constants, statusBar) {
|
|
if(!statusBar) {
|
|
statusBar = "light-content";
|
|
}
|
|
this.statusBar = statusBar;
|
|
this.constants = constants;
|
|
this.styles = {
|
|
rules: StyleSheet.create(rules),
|
|
constants: constants
|
|
}
|
|
|
|
// On Android, a time out is required, especially during app startup
|
|
setTimeout(function () {
|
|
StatusBar.setBarStyle(statusBar, true);
|
|
}, Platform.OS == "android" ? 100 : 0);
|
|
}
|
|
|
|
defaultConstants() {
|
|
var tintColor = "#fb0206";
|
|
return {
|
|
composeBorderColor: "#F5F5F5",
|
|
mainBackgroundColor: "#ffffff",
|
|
mainTintColor: tintColor,
|
|
mainDimColor: "#9d9d9d",
|
|
mainTextColor: "#000000",
|
|
mainTextFontSize: 16,
|
|
mainHeaderFontSize: 16,
|
|
|
|
navBarColor: "white",
|
|
navBarTextColor: tintColor,
|
|
|
|
navBarColorAndroid: tintColor,
|
|
navBarTextColorAndroid: "#ffffff",
|
|
|
|
paddingLeft: 14,
|
|
plainCellBorderColor: "#efefef",
|
|
sectionedCellHorizontalPadding: 14,
|
|
selectedBackgroundColor: "#efefef",
|
|
|
|
maxSettingsCellHeight: 45
|
|
}
|
|
}
|
|
|
|
defaultRules(constants) {
|
|
return {
|
|
container: {
|
|
backgroundColor: constants.mainBackgroundColor,
|
|
height: "100%",
|
|
},
|
|
|
|
flexContainer: {
|
|
flex: 1,
|
|
flexDirection: 'column',
|
|
backgroundColor: constants.mainBackgroundColor,
|
|
},
|
|
|
|
centeredContainer: {
|
|
flex: 1,
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
alignItems: 'center'
|
|
},
|
|
|
|
flexedItem: {
|
|
flexGrow: 1
|
|
},
|
|
|
|
uiText: {
|
|
color: constants.mainTextColor,
|
|
fontSize: constants.mainTextFontSize,
|
|
},
|
|
|
|
view: {
|
|
backgroundColor: constants.mainBackgroundColor,
|
|
},
|
|
|
|
tableSection: {
|
|
marginTop: 10,
|
|
marginBottom: 10,
|
|
backgroundColor: constants.mainBackgroundColor
|
|
},
|
|
|
|
sectionHeader: {
|
|
fontSize: constants.mainTextFontSize - 4,
|
|
paddingLeft: constants.paddingLeft,
|
|
paddingBottom: 10,
|
|
paddingTop: 10,
|
|
color: constants.mainDimColor,
|
|
fontWeight: Platform.OS == "android" ? "bold" : "normal"
|
|
},
|
|
|
|
sectionHeaderAndroid: {
|
|
fontSize: constants.mainTextFontSize - 2,
|
|
},
|
|
|
|
sectionedTableCell: {
|
|
borderBottomColor: constants.plainCellBorderColor,
|
|
borderBottomWidth: 1,
|
|
paddingLeft: constants.paddingLeft,
|
|
paddingRight: constants.paddingLeft,
|
|
paddingTop: 13,
|
|
paddingBottom: 12,
|
|
backgroundColor: constants.mainBackgroundColor,
|
|
flex: 1,
|
|
},
|
|
|
|
textInputCell: {
|
|
maxHeight: 50
|
|
},
|
|
|
|
sectionedTableCellTextInput: {
|
|
fontSize: constants.mainTextFontSize,
|
|
padding: 0,
|
|
color: constants.mainTextColor,
|
|
},
|
|
|
|
sectionedTableCellFirst: {
|
|
borderTopColor: constants.plainCellBorderColor,
|
|
borderTopWidth: 1,
|
|
},
|
|
|
|
sectionedAccessoryTableCell: {
|
|
paddingTop: 0,
|
|
paddingBottom: 0,
|
|
minHeight: 47,
|
|
},
|
|
|
|
sectionedAccessoryTableCellLabel: {
|
|
fontSize: constants.mainTextFontSize,
|
|
color: constants.mainTextColor,
|
|
minWidth: "80%"
|
|
},
|
|
|
|
buttonCell: {
|
|
paddingTop: 0,
|
|
paddingBottom: 0,
|
|
flex: 1,
|
|
justifyContent: 'center'
|
|
},
|
|
|
|
buttonCellButton: {
|
|
textAlign: "center",
|
|
textAlignVertical: "center",
|
|
color: Platform.OS == "android" ? constants.mainTextColor : constants.mainTintColor,
|
|
fontSize: constants.mainTextFontSize,
|
|
},
|
|
|
|
buttonCellButtonLeft: {
|
|
textAlign: "left",
|
|
},
|
|
|
|
noteText: {
|
|
flexGrow: 1,
|
|
marginTop: 0,
|
|
paddingTop: 10,
|
|
color: constants.mainTextColor,
|
|
paddingLeft: constants.paddingLeft,
|
|
paddingRight: constants.paddingLeft,
|
|
paddingBottom: 10,
|
|
},
|
|
|
|
noteTextIOS: {
|
|
paddingLeft: constants.paddingLeft - 5,
|
|
paddingRight: constants.paddingLeft - 5,
|
|
},
|
|
|
|
bold: {
|
|
fontWeight: "bold"
|
|
},
|
|
}
|
|
}
|
|
|
|
static shadeBlend(p,c0,c1) {
|
|
var n=p<0?p*-1:p,u=Math.round,w=parseInt;
|
|
if(c0.length>7){
|
|
var f=c0.split(","),t=(c1?c1:p<0?"rgb(0,0,0)":"rgb(255,255,255)").split(","),R=w(f[0].slice(4)),G=w(f[1]),B=w(f[2]);
|
|
return "rgb("+(u((w(t[0].slice(4))-R)*n)+R)+","+(u((w(t[1])-G)*n)+G)+","+(u((w(t[2])-B)*n)+B)+")"
|
|
} else{
|
|
var f=w(c0.slice(1),16),t=w((c1?c1:p<0?"#000000":"#FFFFFF").slice(1),16),R1=f>>16,G1=f>>8&0x00FF,B1=f&0x0000FF;
|
|
return "#"+(0x1000000+(u(((t>>16)-R1)*n)+R1)*0x10000+(u(((t>>8&0x00FF)-G1)*n)+G1)*0x100+(u(((t&0x0000FF)-B1)*n)+B1)).toString(16).slice(1)
|
|
}
|
|
}
|
|
|
|
static darken(color, value = -0.15) {
|
|
return this.shadeBlend(value, color);
|
|
}
|
|
|
|
static lighten(color, value = 0.25) {
|
|
return this.shadeBlend(value, color);
|
|
}
|
|
|
|
static hexToRGBA(hex, alpha) {
|
|
var c;
|
|
if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
|
|
c= hex.substring(1).split('');
|
|
if(c.length== 3){
|
|
c= [c[0], c[0], c[1], c[1], c[2], c[2]];
|
|
}
|
|
c= '0x'+c.join('');
|
|
return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+',' + alpha + ')';
|
|
} else {
|
|
throw new Error('Bad Hex');
|
|
}
|
|
}
|
|
|
|
}
|