Browse Source

Auto-detect theme

Sign with QR by default
master
Eduard Kuzmenko 4 years ago
parent
commit
b66d85ed37
  1. 2
      src/components/chat/chat.ts
  2. 6
      src/components/sidebarLeft/index.ts
  3. 4
      src/components/sidebarLeft/tabs/background.ts
  4. 11
      src/components/sidebarLeft/tabs/backgroundColor.ts
  5. 1
      src/index.hbs
  6. 5
      src/index.ts
  7. 12
      src/lib/appManagers/appImManager.ts
  8. 2
      src/lib/appManagers/appMessagesManager.ts
  9. 6
      src/lib/appManagers/appStateManager.ts
  10. 30
      src/lib/rootScope.ts
  11. 72
      src/pages/pageSignQR.ts
  12. 3
      src/scss/partials/_input.scss
  13. 24
      src/scss/partials/pages/_pages.scss
  14. 10
      src/scss/style.scss
  15. 6
      src/types.d.ts

2
src/components/chat/chat.ts

@ -83,7 +83,7 @@ export default class Chat extends EventListenerBase<{ @@ -83,7 +83,7 @@ export default class Chat extends EventListenerBase<{
}
public setBackground(url: string): Promise<void> {
const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme);
const theme = rootScope.getTheme();
let item: HTMLElement;
if(theme.background.type === 'color' && document.documentElement.style.cursor === 'grabbing') {

6
src/components/sidebarLeft/index.ts

@ -101,7 +101,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -101,7 +101,7 @@ export class AppSidebarLeft extends SidebarSlider {
const themeCheckboxField = new CheckboxField({
toggle: true,
checked: rootScope.settings.theme === 'night'
checked: rootScope.getTheme().name === 'night'
});
themeCheckboxField.input.addEventListener('change', () => {
rootScope.settings.theme = themeCheckboxField.input.checked ? 'night' : 'day';
@ -109,6 +109,10 @@ export class AppSidebarLeft extends SidebarSlider { @@ -109,6 +109,10 @@ export class AppSidebarLeft extends SidebarSlider {
appImManager.applyCurrentTheme();
});
rootScope.on('theme_change', () => {
themeCheckboxField.setValueSilently(rootScope.getTheme().name === 'night');
});
const menuButtons: (ButtonMenuItemOptions & {verify?: () => boolean})[] = [{
icon: 'saved',
text: 'SavedMessages',

4
src/components/sidebarLeft/tabs/background.ts

@ -42,7 +42,7 @@ export default class AppBackgroundTab extends SliderSuperTab { @@ -42,7 +42,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
this.container.classList.add('background-container', 'background-image-container');
this.setTitle('ChatBackground');
this.theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme);
this.theme = rootScope.getTheme();
{
const container = generateSection(this.scrollable);
@ -347,7 +347,7 @@ export default class AppBackgroundTab extends SliderSuperTab { @@ -347,7 +347,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
private setActive = () => {
const active = this.grid.querySelector('.active');
const background = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme).background;
const background = this.theme.background;
const target = background.type === 'image' ? this.grid.querySelector(`.grid-item[data-slug="${background.slug}"]`) : null;
if(active === target) {
return;

11
src/components/sidebarLeft/tabs/backgroundColor.ts

@ -5,7 +5,7 @@ import findUpClassName from "../../../helpers/dom/findUpClassName"; @@ -5,7 +5,7 @@ import findUpClassName from "../../../helpers/dom/findUpClassName";
import highlightningColor from "../../../helpers/highlightningColor";
import { throttle } from "../../../helpers/schedulers";
import appImManager from "../../../lib/appManagers/appImManager";
import appStateManager from "../../../lib/appManagers/appStateManager";
import appStateManager, { Theme } from "../../../lib/appManagers/appStateManager";
import rootScope from "../../../lib/rootScope";
import ColorPicker, { ColorPickerColor } from "../../colorPicker";
import { SliderSuperTab } from "../../slider";
@ -14,11 +14,14 @@ export default class AppBackgroundColorTab extends SliderSuperTab { @@ -14,11 +14,14 @@ export default class AppBackgroundColorTab extends SliderSuperTab {
private colorPicker: ColorPicker;
private grid: HTMLElement;
private applyColor: (hex: string, updateColorPicker?: boolean) => void;
private theme: Theme;
init() {
this.container.classList.add('background-container', 'background-color-container');
this.setTitle('SetColor');
this.theme = rootScope.getTheme();
const section = new SettingSection({});
this.colorPicker = new ColorPicker();
@ -79,7 +82,7 @@ export default class AppBackgroundColorTab extends SliderSuperTab { @@ -79,7 +82,7 @@ export default class AppBackgroundColorTab extends SliderSuperTab {
private setActive() {
const active = this.grid.querySelector('.active');
const background = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme).background;
const background = this.theme.background;
const target = background.type === 'color' ? this.grid.querySelector(`.grid-item[data-color="${background.color}"]`) : null;
if(active === target) {
return;
@ -99,7 +102,7 @@ export default class AppBackgroundColorTab extends SliderSuperTab { @@ -99,7 +102,7 @@ export default class AppBackgroundColorTab extends SliderSuperTab {
this.colorPicker.setColor(hex);
} else {
const rgba = hexaToRgba(hex);
const background = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme).background;
const background = this.theme.background;
const hsla = highlightningColor(rgba);
background.color = hex.toLowerCase();
@ -118,7 +121,7 @@ export default class AppBackgroundColorTab extends SliderSuperTab { @@ -118,7 +121,7 @@ export default class AppBackgroundColorTab extends SliderSuperTab {
onOpen() {
setTimeout(() => {
const background = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme).background;
const background = this.theme.background;
// * set active if type is color
if(background.type === 'color') {

1
src/index.hbs

@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
<meta name="msapplication-TileImage" content="/assets/img/mstile-144x144.png?v=jw3mK7G9Ry">
<meta name="msapplication-config" content="/assets/img/browserconfig.xml?v=jw3mK7G9Ry">
<meta name="theme-color" content="#ffffff">
<meta name="color-scheme" content="light dark">
{{# each htmlWebpackPlugin.files.css }}
<link rel="stylesheet" href="{{ this }}">
{{/ each }}

5
src/index.ts

@ -257,6 +257,8 @@ console.timeEnd('get storage1'); */ @@ -257,6 +257,8 @@ console.timeEnd('get storage1'); */
//console.log('got auth:', auth);
//console.timeEnd('get storage');
rootScope.default.setThemeListener();
if(langPack.appVersion !== App.langPackVersion) {
I18n.default.getLangPack(langPack.lang_code);
}
@ -286,6 +288,9 @@ console.timeEnd('get storage1'); */ @@ -286,6 +288,9 @@ console.timeEnd('get storage1'); */
case 'authStateSignIn':
(await import('./pages/pageSignIn')).default.mount();
break;
case 'authStateSignQr':
(await import('./pages/pageSignQR')).default.mount();
break;
case 'authStateAuthCode':
(await import('./pages/pageAuthCode')).default.mount(authState.sentCode);
break;

12
src/lib/appManagers/appImManager.ts

@ -38,7 +38,7 @@ import appDraftsManager from './appDraftsManager'; @@ -38,7 +38,7 @@ import appDraftsManager from './appDraftsManager';
import serverTimeManager from '../mtproto/serverTimeManager';
import sessionStorage from '../sessionStorage';
import appDownloadManager from './appDownloadManager';
import appStateManager, { AppStateManager } from './appStateManager';
import { AppStateManager } from './appStateManager';
import { MOUNT_CLASS_TO } from '../../config/debug';
import appNavigationController from '../../components/appNavigationController';
import appNotificationsManager from './appNotificationsManager';
@ -170,6 +170,10 @@ export class AppImManager { @@ -170,6 +170,10 @@ export class AppImManager {
this.saveChatPosition(chat);
});
rootScope.on('theme_change', () => {
this.applyCurrentTheme();
});
sessionStorage.get('chatPositions').then((c) => {
sessionStorage.setToCache('chatPositions', c || {});
});
@ -247,7 +251,7 @@ export class AppImManager { @@ -247,7 +251,7 @@ export class AppImManager {
}
public setCurrentBackground(broadcastEvent = false) {
const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme);
const theme = rootScope.getTheme();
if(theme.background.type === 'image' || (theme.background.type === 'default' && theme.background.slug)) {
const defaultTheme = AppStateManager.STATE_INIT.settings.themes.find(t => t.name === theme.name);
@ -327,7 +331,7 @@ export class AppImManager { @@ -327,7 +331,7 @@ export class AppImManager {
public applyHighlightningColor() {
let hsla: string;
const theme = rootScope.settings.themes.find(t => t.name === rootScope.settings.theme);
const theme = rootScope.getTheme();
if(theme.background.highlightningColor) {
hsla = theme.background.highlightningColor;
document.documentElement.style.setProperty('--message-highlightning-color', hsla);
@ -352,7 +356,7 @@ export class AppImManager { @@ -352,7 +356,7 @@ export class AppImManager {
public applyCurrentTheme(slug?: string, backgroundUrl?: string, broadcastEvent?: boolean) {
this.applyHighlightningColor();
document.documentElement.classList.toggle('night', rootScope.settings.theme === 'night');
rootScope.setTheme();
if(backgroundUrl) {
this.backgroundPromises[slug] = Promise.resolve(backgroundUrl);

2
src/lib/appManagers/appMessagesManager.ts

@ -4222,7 +4222,7 @@ export class AppMessagesManager { @@ -4222,7 +4222,7 @@ export class AppMessagesManager {
}
};
private setDialogToStateIfMessageIsTop(message: any) {
public setDialogToStateIfMessageIsTop(message: any) {
const dialog = this.getDialogOnly(message.peerId);
if(dialog && dialog.top_message === message.mid) {
this.dialogsStorage.setDialogToState(dialog);

6
src/lib/appManagers/appStateManager.ts

@ -32,7 +32,7 @@ export type Background = { @@ -32,7 +32,7 @@ export type Background = {
};
export type Theme = {
name: 'day' | 'night',
name: 'day' | 'night' | 'system',
background: Background
};
@ -96,7 +96,7 @@ export const STATE_INIT: State = { @@ -96,7 +96,7 @@ export const STATE_INIT: State = {
recentSearch: [],
version: STATE_VERSION,
authState: {
_: 'authStateSignIn'
_: 'authStateSignQr'
},
hiddenPinnedMessages: {},
settings: {
@ -134,7 +134,7 @@ export const STATE_INIT: State = { @@ -134,7 +134,7 @@ export const STATE_INIT: State = {
highlightningColor: 'hsla(0, 0%, 3.82353%, 0.4)'
}
}],
theme: 'day',
theme: 'system',
notifications: {
sound: false
}

30
src/lib/rootScope.ts

@ -13,7 +13,7 @@ import type { ConnectionStatusChange } from "../types"; @@ -13,7 +13,7 @@ import type { ConnectionStatusChange } from "../types";
import type { UserTyping } from "./appManagers/appChatsManager";
import type Chat from "../components/chat/chat";
import type { UserAuth } from "./mtproto/mtproto_config";
import type { State } from "./appManagers/appStateManager";
import type { State, Theme } from "./appManagers/appStateManager";
import type { MyDraftMessage } from "./appManagers/appDraftsManager";
import EventListenerBase from "../helpers/eventListenerBase";
import { MOUNT_CLASS_TO } from "../config/debug";
@ -109,6 +109,8 @@ export type BroadcastEvents = { @@ -109,6 +109,8 @@ export type BroadcastEvents = {
'notify_peer_type_settings': {key: Exclude<NotifyPeer['_'], 'notifyPeer'>, settings: PeerNotifySettings},
'language_change': void,
'theme_change': void,
};
export class RootScope extends EventListenerBase<{
@ -124,6 +126,7 @@ export class RootScope extends EventListenerBase<{ @@ -124,6 +126,7 @@ export class RootScope extends EventListenerBase<{
public connectionStatus: {[name: string]: ConnectionStatusChange} = {};
public settings: State['settings'];
public peerId = 0;
public systemTheme: Theme['name'];
constructor() {
super();
@ -142,6 +145,27 @@ export class RootScope extends EventListenerBase<{ @@ -142,6 +145,27 @@ export class RootScope extends EventListenerBase<{
});
}
public setThemeListener() {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const checkDarkMode = () => {
//const theme = this.getTheme();
this.systemTheme = darkModeMediaQuery.matches ? 'night' : 'day';
//const newTheme = this.getTheme();
if(this.myId) {
this.broadcast('theme_change');
} else {
this.setTheme();
}
};
darkModeMediaQuery.addEventListener('change', checkDarkMode);
checkDarkMode();
}
public setTheme() {
document.documentElement.classList.toggle('night', this.getTheme().name === 'night');
}
get overlayIsActive() {
return this._overlayIsActive;
}
@ -151,6 +175,10 @@ export class RootScope extends EventListenerBase<{ @@ -151,6 +175,10 @@ export class RootScope extends EventListenerBase<{
this.broadcast('overlay_toggle', value);
}
public getTheme(name: Theme['name'] = this.settings.theme === 'system' ? this.systemTheme : this.settings.theme) {
return this.settings.themes.find(t => t.name === name);
}
public broadcast = <T extends keyof BroadcastEvents>(name: T, detail?: BroadcastEvents[T]) => {
/* //if(DEBUG) {
if(name !== 'user_update') {

72
src/pages/pageSignQR.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
//import apiManager from '../lib/mtproto/apiManager';
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
@ -17,11 +16,16 @@ import { pause } from '../helpers/schedulers'; @@ -17,11 +16,16 @@ import { pause } from '../helpers/schedulers';
import App from '../config/app';
import Button from '../components/button';
import { _i18n, i18n, LangPackKey } from '../lib/langPack';
import appStateManager from '../lib/appManagers/appStateManager';
import rootScope from '../lib/rootScope';
import { putPreloader } from '../components/misc';
let onFirstMount = async() => {
const pageElement = page.pageEl;
const imageDiv = pageElement.querySelector('.auth-image') as HTMLDivElement;
let preloader = putPreloader(imageDiv, true);
const inputWrapper = document.createElement('div');
inputWrapper.classList.add('input-wrapper');
@ -54,10 +58,10 @@ let onFirstMount = async() => { @@ -54,10 +58,10 @@ let onFirstMount = async() => {
const QRCodeStyling = results[0].default;
let stop = false;
document.addEventListener('user_auth', () => {
rootScope.addEventListener('user_auth', () => {
stop = true;
cachedPromise = null;
}, {once: true});
}, true);
let options: {dcId?: number, ignoreErrors: true} = {ignoreErrors: true};
let prevToken: Uint8Array | number[];
@ -99,20 +103,46 @@ let onFirstMount = async() => { @@ -99,20 +103,46 @@ let onFirstMount = async() => {
let encoded = bytesToBase64(loginToken.token);
let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
const style = window.getComputedStyle(document.documentElement);
const surfaceColor = style.getPropertyValue('--surface-color').trim();
const textColor = style.getPropertyValue('--primary-text-color').trim();
const primaryColor = style.getPropertyValue('--primary-color').trim();
const logoUrl = await fetch('assets/img/logo_padded.svg')
.then(res => res.text())
.then(text => {
text = text.replace(/(fill:).+?(;)/, `$1${primaryColor}$2`);
const blob = new Blob([text], {type: 'image/svg+xml;charset=utf-8'});
// * because iOS Safari doesn't want to eat objectURL
return new Promise<string>((resolve) => {
const reader = new FileReader();
reader.onload = (e) => {
resolve(e.target.result as string);
};
reader.readAsDataURL(blob);
});
//return URL.createObjectURL(blob);
});
const qrCode = new QRCodeStyling({
width: 240 * window.devicePixelRatio,
height: 240 * window.devicePixelRatio,
data: url,
image: "assets/img/logo_padded.svg",
image: logoUrl,
dotsOptions: {
color: "#000000",
type: "rounded"
color: textColor,
type: 'rounded'
},
cornersSquareOptions: {
type: 'extra-rounded'
},
imageOptions: {
imageSize: .75
imageSize: 1,
margin: 0
},
backgroundOptions: {
color: "#ffffff"
color: surfaceColor
},
qrOptions: {
errorCorrectionLevel: "L"
@ -138,9 +168,25 @@ let onFirstMount = async() => { @@ -138,9 +168,25 @@ let onFirstMount = async() => {
// * это костыль, но библиотека не предоставляет никаких событий
await promise.then(() => {
Array.from(imageDiv.children).slice(0, -1).forEach(el => {
el.remove();
});
if(preloader) {
preloader.style.animation = 'hide-icon .4s forwards';
const c = imageDiv.children[1] as HTMLElement;
c.style.display = 'none';
c.style.animation = 'grow-icon .4s forwards';
setTimeout(() => {
c.style.display = '';
}, 150);
setTimeout(() => {
c.style.animation = '';
}, 500);
preloader = undefined;
} else {
Array.from(imageDiv.children).slice(0, -1).forEach(el => {
el.remove();
});
}
});
}
@ -171,7 +217,7 @@ let onFirstMount = async() => { @@ -171,7 +217,7 @@ let onFirstMount = async() => {
return false;
};
await iterate(false);
//await iterate(false);
return async() => {
stop = false;
@ -198,6 +244,8 @@ const page = new Page('page-signQR', true, () => { @@ -198,6 +244,8 @@ const page = new Page('page-signQR', true, () => {
cachedPromise.then(func => {
func();
});
appStateManager.pushToState('authState', {_: 'authStateSignQr'});
});
export default page;

3
src/scss/partials/_input.scss

@ -117,6 +117,7 @@ @@ -117,6 +117,7 @@
&:-webkit-autofill:active {
font-family: "Roboto", -apple-system, apple color emoji, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !important;
font-size: 1rem !important;
color: var(--primary-text-color) !important;
}
@include respond-to(handhelds) {
@ -428,7 +429,7 @@ input:focus, button:focus { @@ -428,7 +429,7 @@ input:focus, button:focus {
}
@include hover() {
color: #000;
color: var(--primary-text-color);
}
}
}

24
src/scss/partials/pages/_pages.scss

@ -5,8 +5,9 @@ @@ -5,8 +5,9 @@
*/
#auth-pages {
max-width: 720px; // 360 + 360 / 2
max-width: 100%;
overflow: hidden;
background: var(--surface-color);
.btn-primary {
text-transform: uppercase;
@ -42,6 +43,8 @@ @@ -42,6 +43,8 @@
display: flex;
flex-direction: column;
position: relative;
max-width: 720px; // 360 + 360 / 2
margin: 0 auto;
.auth-placeholder {
flex: 1;
@ -73,7 +76,7 @@ @@ -73,7 +76,7 @@
top: 0;
right: 0;
bottom: 0;
background: #fff;
background: var(--surface-color);
z-index: 1;
}
@ -167,14 +170,29 @@ @@ -167,14 +170,29 @@
}
.page-signQR {
overflow: unset !important;
.auth-image {
width: 240px !important;
height: 240px !important;
overflow: hidden;
//overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
.preloader {
transform: none;
left: unset;
top: unset;
}
.qr-canvas {
width: 100%;
height: 100%;
& + .qr-canvas {
display: none;
}
}
}

10
src/scss/style.scss

@ -219,6 +219,14 @@ html.night { @@ -219,6 +219,14 @@ html.night {
// * Night theme end
}
@media (prefers-color-scheme: dark) {
}
@media (prefers-color-scheme: light) {
}
@import "partials/ico";
@import "partials/input";
@import "partials/button";
@ -671,7 +679,7 @@ hr { @@ -671,7 +679,7 @@ hr {
top: calc(100% + .5rem);
left: 0;
overflow: hidden;
background-color: #fff;
background-color: var(--surface-color);
z-index: 3;
border-radius: $border-radius-medium;
display: flex;

6
src/types.d.ts vendored

@ -42,12 +42,16 @@ export type AnyFunction = (...args: any) => any; @@ -42,12 +42,16 @@ export type AnyFunction = (...args: any) => any;
export type AnyToVoidFunction = (...args: any) => void;
export type NoneToVoidFunction = () => void;
export type AuthState = AuthState.signIn | AuthState.authCode | AuthState.password | AuthState.signUp | AuthState.signedIn;
export type AuthState = AuthState.signIn | AuthState.signQr | AuthState.authCode | AuthState.password | AuthState.signUp | AuthState.signedIn;
export namespace AuthState {
export type signIn = {
_: 'authStateSignIn'
};
export type signQr = {
_: 'authStateSignQr'
};
export type authCode = {
_: 'authStateAuthCode',
sentCode: AuthSentCode.authSentCode

Loading…
Cancel
Save