From ec68abb78f94711a99b1384464c3ac2bce0244db Mon Sep 17 00:00:00 2001
From: Anris Ceta <anris.ceta@mni.thm.de>
Date: Sat, 13 Apr 2019 20:11:06 +0200
Subject: [PATCH] Add theme switch functionality

---
 src/app/app.component.html                    | 12 ++---
 src/app/app.component.scss                    |  2 +-
 src/app/app.component.ts                      |  4 +-
 src/app/app.module.ts                         |  4 +-
 .../shared/header/header.component.html       | 10 ++++
 .../shared/header/header.component.ts         | 16 ++++++-
 src/styles.scss                               |  7 +++
 src/theme/_dark-theme.scss                    | 39 ++++++++++++++++
 src/theme/_theme.scss                         |  7 ---
 src/theme/_variables.scss                     |  1 +
 src/theme/arsnova-theme.const.ts              | 12 +++++
 src/theme/theme.directive.ts                  | 46 +++++++++++++++++++
 src/theme/theme.module.ts                     | 14 ++++++
 src/theme/theme.service.ts                    | 20 ++++++++
 14 files changed, 177 insertions(+), 17 deletions(-)
 create mode 100644 src/theme/_dark-theme.scss
 create mode 100644 src/theme/arsnova-theme.const.ts
 create mode 100644 src/theme/theme.directive.ts
 create mode 100644 src/theme/theme.module.ts
 create mode 100644 src/theme/theme.service.ts

diff --git a/src/app/app.component.html b/src/app/app.component.html
index b86ef90e2..86bb7600d 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,7 +1,7 @@
-<div fxLayout="column" fxFill>
-  <app-header></app-header>
-  <div fxFlex class="app-component">
-    <router-outlet></router-outlet>
+  <div [ngClass]="header.themeClass" fxLayout="column" fxFill appTheme>
+    <app-header #header></app-header>
+    <div fxFlex class="app-component">
+      <router-outlet></router-outlet>
+    </div>
+    <app-footer></app-footer>
   </div>
-  <app-footer></app-footer>
-</div>
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index 23f30f882..526e030fe 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -2,7 +2,7 @@
 
 .app-component {
   padding: 4%;
-  background-color: #b2dfdb;
+  background-color:var(--background-color);
 }
 
 
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index aae1fa0a6..39c8d57d7 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,5 +1,6 @@
 import { Component } from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
+import { ThemeService } from '../theme/theme.service';
 
 @Component({
   selector: 'app-root',
@@ -8,7 +9,8 @@ import { TranslateService } from '@ngx-translate/core';
 })
 export class AppComponent {
 
-  constructor(private translationService: TranslateService) {
+  constructor(private translationService: TranslateService,
+              private themeService: ThemeService) {
 
     translationService.setDefaultLang(this.translationService.getBrowserLang());
     sessionStorage.setItem('currentLang', this.translationService.getBrowserLang());
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 83b65385d..30c5ab13f 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -26,6 +26,7 @@ import { MarkdownService, MarkedOptions } from 'ngx-markdown';
 import { NewLandingComponent } from './components/home/new-landing/new-landing.component';
 import { HomePageComponent } from './components/home/home-page/home-page.component';
 import { AppConfig } from './app.config';
+import { ThemeModule } from '../theme/theme.module';
 
 export function dialogClose(dialogResult: any) {
 }
@@ -53,7 +54,8 @@ export function initializeApp(appConfig: AppConfig) {
     BrowserModule,
     BrowserAnimationsModule,
     EssentialsModule,
-    SharedModule
+    SharedModule,
+    ThemeModule
   ],
   providers: [
     AppConfig,
diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html
index ad321a5fe..bcaf1c673 100644
--- a/src/app/components/shared/header/header.component.html
+++ b/src/app/components/shared/header/header.component.html
@@ -7,6 +7,16 @@
     <span *ngIf="router.url !== '/home'" class="app-title" (click)="goToHomepage()">ARSnova</span>
     <span class="fill-remaining-space"></span>
 
+
+    <mat-menu #themeMenu="matMenu" [overlapTrigger]="false">
+      <button mat-menu-item (click)="changeTheme('')">{{ 'default' | translate }}</button>
+      <button mat-menu-item (click)="changeTheme('dark')">{{ 'dark' | translate }}</button>
+    </mat-menu>
+
+    <button mat-icon-button [matMenuTriggerFor]="themeMenu">
+      <mat-icon>palette</mat-icon>
+    </button>
+
     <mat-menu #langMenu="matMenu" [overlapTrigger]="false">
       <button mat-menu-item (click)="useLanguage('de')">{{ 'header.german' | translate }}</button>
       <button mat-menu-item (click)="useLanguage('en')">{{ 'header.english' | translate }}</button>
diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts
index b3bb28fb8..1ba5921eb 100644
--- a/src/app/components/shared/header/header.component.ts
+++ b/src/app/components/shared/header/header.component.ts
@@ -9,6 +9,7 @@ import { TranslateService } from '@ngx-translate/core';
 import { LanguageService } from '../../../services/util/language.service';
 import { MatDialog } from '@angular/material';
 import { LoginComponent } from '../login/login.component';
+import { ThemeService } from '../../../../theme/theme.service';
 
 @Component({
   selector: 'app-header',
@@ -17,6 +18,7 @@ import { LoginComponent } from '../login/login.component';
 })
 export class HeaderComponent implements OnInit {
   user: User;
+  themeClass = localStorage.getItem('classNameOfTheme');
 
   constructor(public location: Location,
               private authenticationService: AuthenticationService,
@@ -24,7 +26,9 @@ export class HeaderComponent implements OnInit {
               public router: Router,
               private translationService: TranslateService,
               private langService: LanguageService,
-              public dialog: MatDialog) {
+              public dialog: MatDialog,
+              private themeService: ThemeService
+  ) {
   }
 
   ngOnInit() {
@@ -61,6 +65,16 @@ export class HeaderComponent implements OnInit {
     this.langService.langEmitter.emit(language);
   }
 
+  changeTheme(theme) {
+    this.themeClass = theme;
+    localStorage.setItem('classNameOfTheme', theme);
+    if (theme === '') {
+      this.themeService.setActiveThem('arsnovaTheme');
+    } else {
+      this.themeService.setActiveThem(theme);
+    }
+  }
+
   login(isDozent: boolean) {
     const dialogRef = this.dialog.open(LoginComponent, {
       width: '350px'
diff --git a/src/styles.scss b/src/styles.scss
index 7186b2044..fdfdd9810 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1,4 +1,5 @@
 @import 'theme/_variables.scss';
+@import "theme/_dark-theme.scss";
 // Plus imports for other components in your app.
 
 // Include the common styles for Angular Material. We include this here so that you only
@@ -11,4 +12,10 @@
 // that you are using.
 @include angular-material-theme($arsnova-theme);
 
+
+.dark {
+  @include angular-material-theme($dark-theme);
+}
+
+
 @import 'theme/_theme.scss';
diff --git a/src/theme/_dark-theme.scss b/src/theme/_dark-theme.scss
new file mode 100644
index 000000000..b633de8a8
--- /dev/null
+++ b/src/theme/_dark-theme.scss
@@ -0,0 +1,39 @@
+@import '~@angular/material/theming';
+
+html, body {
+  font-family: 'Roboto', 'Helvetica Neue', sans-serif;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.fill-remaining-space {
+  /* This fills the remaining space, by using flexbox.
+     Every toolbar row uses a flexbox row layout. */
+  flex: 1 1 auto;
+}
+
+mat-form-field.input-block {
+  display: block;
+}
+
+.mat-fab .mat-button-wrapper {
+  padding: 0!important;
+}
+
+.mat-dialog-container {
+  background-color: #e0f2f1;
+}
+
+.mat-tab-header {
+  border-bottom-style: none!important;
+}
+
+
+$dark-primary: mat-palette($mat-gray);
+$dark-accent: mat-palette($mat-blue-gray);
+$dark-warn: mat-palette($mat-red);
+
+$dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
+
diff --git a/src/theme/_theme.scss b/src/theme/_theme.scss
index 24c076011..33dd1ada5 100644
--- a/src/theme/_theme.scss
+++ b/src/theme/_theme.scss
@@ -11,10 +11,3 @@ html, body {
 @import '_util.scss';
 @import '_form.scss';
 
-.progress-theme {
-  $progress-primary: mat-palette($mat-light-green, 300);
-  $progress-accent: mat-palette($mat-amber, 300);
-  $progress-warn:  mat-palette($mat-deep-orange, 300);
-  $progress-theme: mat-light-theme($progress-primary, $progress-accent, $progress-warn);
-  @include angular-material-theme($progress-theme);
-}
diff --git a/src/theme/_variables.scss b/src/theme/_variables.scss
index a4a274efc..18e71b8b2 100644
--- a/src/theme/_variables.scss
+++ b/src/theme/_variables.scss
@@ -10,3 +10,4 @@ $arsnova-warn: mat-palette($mat-red, A100);
 
 // Create the theme object (a Sass map containing all of the palettes).
 $arsnova-theme: mat-light-theme($arsnova-primary, $arsnova-accent, $arsnova-warn);
+
diff --git a/src/theme/arsnova-theme.const.ts b/src/theme/arsnova-theme.const.ts
new file mode 100644
index 000000000..311968dcc
--- /dev/null
+++ b/src/theme/arsnova-theme.const.ts
@@ -0,0 +1,12 @@
+export const themes = {
+  arsnovaTheme: {
+    '--button-color': '#80cbc4',
+    '--background-color': '#b2dfdb',
+    '--black-color' : '#000000'
+  },
+  dark: {
+    '--button-color': '#80cbc4',
+    '--background-color': '#000000',
+    '--black-color' : '#000000'
+  }
+};
diff --git a/src/theme/theme.directive.ts b/src/theme/theme.directive.ts
new file mode 100644
index 000000000..41a493da2
--- /dev/null
+++ b/src/theme/theme.directive.ts
@@ -0,0 +1,46 @@
+import { Directive, ElementRef, Inject, OnDestroy, OnInit, Renderer2 } from '@angular/core';
+import { DOCUMENT } from '@angular/common';
+import { themes } from './arsnova-theme.const';
+import { ThemeService } from './theme.service';
+import { Subscription } from 'rxjs';
+
+@Directive({
+  selector: '[appTheme]'
+})
+
+export class ThemeDirective implements OnInit, OnDestroy {
+
+  private themeName = 'arsnovaTheme';
+  private themServiceSubscription: Subscription;
+
+  constructor(private elementRef: ElementRef,
+              private renderer: Renderer2,
+              @Inject(DOCUMENT) private document: any,
+              private themService: ThemeService) {
+  }
+
+  ngOnInit() {
+    this.updateTheme(this.themeName);
+    this.themService.getActiveTheme()
+      .subscribe(themeName => {
+        this.themeName = themeName;
+        this.updateTheme(this.themeName);
+      });
+  }
+
+  updateTheme(themeName: string) {
+    const them = themes[ themeName ];
+    for (const key in them) {
+      if (them.hasOwnProperty(key)) {
+        this.renderer.setProperty(this.elementRef.nativeElement, key, them[key]);
+        this.document.body.style.setProperty(key, them[key]);
+      }
+    }
+  }
+
+  ngOnDestroy() {
+    if (this.themServiceSubscription) {
+      this.themServiceSubscription.unsubscribe();
+    }
+  }
+}
diff --git a/src/theme/theme.module.ts b/src/theme/theme.module.ts
new file mode 100644
index 000000000..af6d3e09b
--- /dev/null
+++ b/src/theme/theme.module.ts
@@ -0,0 +1,14 @@
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { ThemeDirective } from './theme.directive';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule
+  ],
+  declarations: [ThemeDirective],
+  exports:      [ThemeDirective],
+})
+export class ThemeModule { }
diff --git a/src/theme/theme.service.ts b/src/theme/theme.service.ts
new file mode 100644
index 000000000..9c56dfb37
--- /dev/null
+++ b/src/theme/theme.service.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ThemeService {
+  themeName = localStorage.getItem('classNameOfTheme');
+  private activeThem = new BehaviorSubject(this.themeName);
+
+  constructor() { }
+
+  public getActiveTheme() {
+    return this.activeThem.asObservable();
+  }
+
+  public setActiveThem(name) {
+    this.activeThem.next(name);
+  }
+}
-- 
GitLab