181 lines
5.2 KiB
TypeScript
181 lines
5.2 KiB
TypeScript
import {Component, OnDestroy, OnInit, Renderer2} from "@angular/core";
|
|
import {
|
|
IonBreadcrumb,
|
|
IonBreadcrumbs,
|
|
IonButton,
|
|
IonCard,
|
|
IonContent,
|
|
IonIcon, IonItem, IonLabel, IonList,
|
|
IonProgressBar
|
|
} from "@ionic/angular/standalone";
|
|
import {TushaPageComponent} from "../../../tusha/tusha-page/tusha-page.component";
|
|
import {FocusState} from "./focus-state";
|
|
import {FocusService} from "./focus.service";
|
|
import {Observable} from "rxjs";
|
|
import {FocusStats} from "./focus-stats";
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
@Component({
|
|
templateUrl: 'focus.component.html',
|
|
styleUrls: ['focus.component.css'],
|
|
standalone: true,
|
|
imports: [IonBreadcrumbs, IonBreadcrumb, IonIcon, TushaPageComponent, IonProgressBar, IonContent, IonButton, IonCard, IonItem, IonLabel, IonList, CommonModule]
|
|
})
|
|
export class FocusPage implements OnInit, OnDestroy {
|
|
duration: number = 0; // default duration in minutes
|
|
minutesText: string = '';
|
|
secondsText: string = '';
|
|
private interval: number | undefined;
|
|
private timerStarted: number | null = null;
|
|
state: FocusState = FocusState.STOPPED;
|
|
private focusRuns = 0;
|
|
$stats: Observable<FocusStats> = this.focusService.$stats;
|
|
|
|
private endOfTimerChime: HTMLAudioElement;
|
|
private forcedEndOfTimerChime: HTMLAudioElement;
|
|
private windUpChime: HTMLAudioElement;
|
|
|
|
pathD: string = 'M100 10 A90 90 0 1 1 0 0';
|
|
|
|
dashOffset = 0;
|
|
|
|
constructor(private renderer: Renderer2, private focusService: FocusService) {
|
|
this.endOfTimerChime = new Audio('../../../assets/race-start-beeps-125125.mp3');
|
|
this.forcedEndOfTimerChime = new Audio('../../../assets/hardstop.mp3');
|
|
this.windUpChime = new Audio('../../../assets/wind-up.mp3');
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.updateTimeDisplay(this.duration * 60, 0, 1);
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
if (this.interval) {
|
|
clearInterval(this.interval);
|
|
}
|
|
}
|
|
|
|
startTimer(duration: number) {
|
|
this.duration = duration;
|
|
if (this.interval) {
|
|
clearInterval(this.interval);
|
|
}
|
|
|
|
this.timerStarted = Date.now();
|
|
this.update();
|
|
this.interval = setInterval(() => this.update(), 1000);
|
|
}
|
|
|
|
private update() {
|
|
const elapsed = this.timerStarted ? Math.round((Date.now() - (this.timerStarted)) / 1000) : 0; // in seconden
|
|
let progress = elapsed / (this.duration * 60); // from 0 to 1
|
|
|
|
if( progress === 0) {
|
|
progress = 0.0000001; // otherwise no graph
|
|
}
|
|
|
|
if (progress >= 1 || isNaN(progress)) {
|
|
progress = 1;
|
|
if (this.duration === 0) {
|
|
this.forcedEndOfTimerChime.play().catch(error => {
|
|
console.error('Error playing audio:', error);
|
|
});
|
|
clearInterval(this.interval);
|
|
this.timerStarted = null;
|
|
}
|
|
else if (progress === 1) {
|
|
this.endOfTimerChime.play().catch(error => {
|
|
console.error('Error playing audio:', error);
|
|
});
|
|
}
|
|
progress = 1;
|
|
}
|
|
|
|
this.renderer.setStyle(
|
|
document.documentElement,
|
|
'--progress',
|
|
progress.toString()
|
|
);
|
|
|
|
this.updateTimeDisplay(this.duration * 60 - elapsed, elapsed, 1 - progress);
|
|
}
|
|
|
|
private updateTimeDisplay(timeLeft: number, elapsed: number, percentage: number) {
|
|
const theta = 2 * Math.PI * (percentage - 0.25);
|
|
const circleEndX = 100 + 90 * Math.cos(theta);
|
|
const circleEndY = 100 + 90 * Math.sin(theta);
|
|
this.pathD = 'M100 10 A90 90 0 ' + (percentage > 0.5 ? '1' : '0') + ' 1 ' + circleEndX + ' ' + circleEndY;
|
|
|
|
const twoNumbers = (n: number) => n < 10 && n >= 0 ? '0' + n.toString() : n.toString();
|
|
const floorPositiveCeilNegative = (n: number): number => n > 0 ? Math.floor(n) : Math.ceil(n);
|
|
this.minutesText = twoNumbers(Math.abs(floorPositiveCeilNegative(timeLeft / 60)));
|
|
this.secondsText = twoNumbers(Math.abs(floorPositiveCeilNegative(timeLeft % 60)));
|
|
|
|
this.dashOffset = elapsed;
|
|
}
|
|
|
|
startWhereWeAre() {
|
|
switch (this.state) {
|
|
case FocusState.FOCUS:
|
|
this.focusRuns++;
|
|
if (this.focusRuns > 3) {
|
|
this.focusRuns = 0;
|
|
this.recharge();
|
|
} else {
|
|
this.reload();
|
|
}
|
|
break;
|
|
default:
|
|
this.focus();
|
|
break;
|
|
}
|
|
}
|
|
|
|
stopTimer() {
|
|
this.focusRuns = 0;
|
|
this.state = FocusState.STOPPED;
|
|
this.stopStatsCounter();
|
|
this.startTimer(0);
|
|
}
|
|
|
|
private stopStatsCounter() {
|
|
this.focusService.stopCurrent(this.timerStarted !== null);
|
|
}
|
|
|
|
focus() {
|
|
this.windUpChime.play().catch(error => {
|
|
console.error('Error playing audio:', error);
|
|
});
|
|
this.state = FocusState.FOCUS;
|
|
this.stopStatsCounter();
|
|
this.focusService.startFocus();
|
|
this.startTimer(1); // 25
|
|
}
|
|
|
|
reload() {
|
|
this.windUpChime.play().catch(error => {
|
|
console.error('Error playing audio:', error);
|
|
});
|
|
if (!!this.timerStarted) {
|
|
this.focusRuns = 0;
|
|
}
|
|
this.state = FocusState.SHORT_BREAK;
|
|
this.stopStatsCounter();
|
|
this.focusService.startShortBreak();
|
|
this.startTimer(0.1); // 5
|
|
}
|
|
|
|
recharge() {
|
|
this.windUpChime.play().catch(error => {
|
|
console.error('Error playing audio:', error);
|
|
});
|
|
if (!!this.timerStarted) {
|
|
this.focusRuns = 0;
|
|
}
|
|
this.state = FocusState.LONG_BREAK;
|
|
this.stopStatsCounter();
|
|
this.focusService.startLongBreak();
|
|
this.startTimer(0.3); // 15
|
|
}
|
|
}
|