From f3d26a27fd48a0aea498fcb87c514594272163d8 Mon Sep 17 00:00:00 2001 From: Lars Saalbach Date: Thu, 16 May 2024 21:46:33 +0200 Subject: [PATCH] #709, #708, #707, #706 Added haptic engine added text to speech for bluetooth scale connection added sort of images added indicator that machines are connected --- package-lock.json | 14 +++ package.json | 4 +- src/app/app.scss | 22 +++++ src/app/brew/brew-add/brew-add.component.html | 22 +++++ src/app/brew/brew-add/brew-add.component.ts | 14 ++- .../brew/brew-edit/brew-edit.component.html | 22 +++++ src/app/brew/brew-edit/brew-edit.component.ts | 13 ++- src/app/settings/settings.page.html | 91 +++++++++++++++++++ src/app/settings/settings.page.ts | 18 +++- .../beanconqueror-meticulous-logo.svg | 13 +++ .../beanconqueror-no-wifi.svg | 1 + .../custom-ion-icons/beanconqueror-wifi.svg | 1 + .../beanconqueror-xenia-logo.svg | 30 ++++++ src/assets/i18n/en.json | 18 +++- src/classes/settings/settings.ts | 19 ++++ .../brew-brewing-graph.component.ts | 87 +++++++++++++++++- ...ew-brewing-preparation-device.component.ts | 15 ++- .../brew-brewing/brew-brewing.component.html | 3 +- .../brew-brewing/brew-brewing.component.ts | 24 ++++- .../photo-add/photo-add.component.html | 6 ++ .../photo-add/photo-add.component.ts | 20 ++++ src/interfaces/settings/iSettings.ts | 9 ++ .../hapticService/haptic.service.spec.ts | 16 ++++ src/services/hapticService/haptic.service.ts | 15 +++ .../text-to-speech.service.spec.ts | 16 ++++ .../textToSpeech/text-to-speech.service.ts | 41 +++++++++ src/services/uiImage.ts | 42 +++------ 27 files changed, 556 insertions(+), 40 deletions(-) create mode 100644 src/assets/custom-ion-icons/beanconqueror-meticulous-logo.svg create mode 100644 src/assets/custom-ion-icons/beanconqueror-no-wifi.svg create mode 100644 src/assets/custom-ion-icons/beanconqueror-wifi.svg create mode 100644 src/assets/custom-ion-icons/beanconqueror-xenia-logo.svg create mode 100644 src/services/hapticService/haptic.service.spec.ts create mode 100644 src/services/hapticService/haptic.service.ts create mode 100644 src/services/textToSpeech/text-to-speech.service.spec.ts create mode 100644 src/services/textToSpeech/text-to-speech.service.ts diff --git a/package-lock.json b/package-lock.json index d09262466..ef74c258d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,6 +116,7 @@ "cordova-plugin-ionic-webview": "^5.0.1", "cordova-plugin-screen-orientation": "github:apache/cordova-plugin-screen-orientation", "cordova-plugin-splashscreen": "^6.0.1", + "cordova-plugin-vibration": "^3.1.1", "cordova-plugin-x-socialsharing": "^6.0.4", "cordova-sqlite-storage": "^5.1.0", "elliptic": ">=6.5.4", @@ -9755,6 +9756,19 @@ } } }, + "node_modules/cordova-plugin-vibration": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cordova-plugin-vibration/-/cordova-plugin-vibration-3.1.1.tgz", + "integrity": "sha512-qgv67Rueo4Pydfant3TwnXeFiN9dl+6lKMM6h5jYg9XewiGAGOr8vfWsTvQssC3m3xMKGS1ap3xPNH+BzZ4RMA==", + "dev": true, + "engines": { + "cordovaDependencies": { + "4.0.0": { + "cordova": ">100" + } + } + } + }, "node_modules/cordova-plugin-x-socialsharing": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/cordova-plugin-x-socialsharing/-/cordova-plugin-x-socialsharing-6.0.4.tgz", diff --git a/package.json b/package.json index fe64e1d76..07459eecb 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "cordova-plugin-ionic-webview": "^5.0.1", "cordova-plugin-screen-orientation": "github:apache/cordova-plugin-screen-orientation", "cordova-plugin-splashscreen": "^6.0.1", + "cordova-plugin-vibration": "^3.1.1", "cordova-plugin-x-socialsharing": "^6.0.4", "cordova-sqlite-storage": "^5.1.0", "elliptic": ">=6.5.4", @@ -233,7 +234,8 @@ "ACCESS_BACKGROUND_LOCATION": "false", "BLUETOOTH_RESTORE_STATE": "false" }, - "cordova-plugin-screen-orientation": {} + "cordova-plugin-screen-orientation": {}, + "cordova-plugin-vibration": {} }, "platforms": [ "android", diff --git a/src/app/app.scss b/src/app/app.scss index a4115c30f..e92f19bd0 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -273,6 +273,19 @@ ion-tab-button.tab-selected { position: absolute; top: 0; right: 10px; + background: rgba(255, 255, 255, 0.8); +} +.position-absolute-button-sort-left { + position: absolute; + top: 0px; + left: 0px; + background: rgba(255, 255, 255, 0.8); +} +.position-absolute-button-sort-right { + position: absolute; + top: 0; + left: 70px; + background: rgba(255, 255, 255, 0.8); } @@ -1035,3 +1048,12 @@ loading-popover { } } } + +ion-chip.machine-connected { + background-color: rgba(5, 199, 147,0.4); +} +ion-chip.machine-disconnected { + + background-color: rgba(204, 51, 17,0.4); + +} diff --git a/src/app/brew/brew-add/brew-add.component.html b/src/app/brew/brew-add/brew-add.component.html index 80963d129..879e5da1a 100644 --- a/src/app/brew/brew-add/brew-add.component.html +++ b/src/app/brew/brew-add/brew-add.component.html @@ -7,6 +7,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/brew/brew-add/brew-add.component.ts b/src/app/brew/brew-add/brew-add.component.ts index 6e4fdbf7d..e0f6bd68c 100644 --- a/src/app/brew/brew-add/brew-add.component.ts +++ b/src/app/brew/brew-add/brew-add.component.ts @@ -39,10 +39,11 @@ import { CoffeeBluetoothDevicesService, CoffeeBluetoothServiceEvent, } from '../../../services/coffeeBluetoothDevices/coffee-bluetooth-devices.service'; -import { PreparationDeviceType } from '../../../classes/preparationDevice'; import { UIHelper } from '../../../services/uiHelper'; import { VisualizerService } from '../../../services/visualizerService/visualizer-service.service'; import { Subscription } from 'rxjs'; +import { HapticService } from '../../../services/hapticService/haptic.service'; +import { PreparationDeviceType } from '../../../classes/preparationDevice'; declare var Plotly; declare var window; @@ -91,7 +92,8 @@ export class BrewAddComponent implements OnInit { private readonly bleManager: CoffeeBluetoothDevicesService, private readonly uiHelper: UIHelper, private readonly visualizerService: VisualizerService, - private readonly changeDetectorRef: ChangeDetectorRef + private readonly changeDetectorRef: ChangeDetectorRef, + private readonly hapticService: HapticService ) { // Initialize to standard in drop down @@ -209,6 +211,12 @@ export class BrewAddComponent implements OnInit { const scale: BluetoothScale = this.bleManager.getScale(); if (scale) { scale.tare(); + if ( + this.settings.haptic_feedback_active && + this.settings.haptic_feedback_tare + ) { + this.hapticService.vibrate(); + } } } @@ -452,4 +460,6 @@ export class BrewAddComponent implements OnInit { this.bluetoothSubscription = undefined; } } + + protected readonly PreparationDeviceType = PreparationDeviceType; } diff --git a/src/app/brew/brew-edit/brew-edit.component.html b/src/app/brew/brew-edit/brew-edit.component.html index 6300c1bce..72bb081fe 100644 --- a/src/app/brew/brew-edit/brew-edit.component.html +++ b/src/app/brew/brew-edit/brew-edit.component.html @@ -7,6 +7,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/brew/brew-edit/brew-edit.component.ts b/src/app/brew/brew-edit/brew-edit.component.ts index a4b8159a8..d4a4f7904 100644 --- a/src/app/brew/brew-edit/brew-edit.component.ts +++ b/src/app/brew/brew-edit/brew-edit.component.ts @@ -17,9 +17,10 @@ import { Settings } from '../../../classes/settings/settings'; import { SettingsPopoverBluetoothActionsComponent } from '../../settings/settings-popover-bluetooth-actions/settings-popover-bluetooth-actions.component'; import { BluetoothScale, SCALE_TIMER_COMMAND } from '../../../classes/devices'; import { CoffeeBluetoothDevicesService } from '../../../services/coffeeBluetoothDevices/coffee-bluetooth-devices.service'; -import { PreparationDeviceType } from '../../../classes/preparationDevice'; import { UIAlert } from '../../../services/uiAlert'; import { VisualizerService } from '../../../services/visualizerService/visualizer-service.service'; +import { HapticService } from '../../../services/hapticService/haptic.service'; +import { PreparationDeviceType } from '../../../classes/preparationDevice'; declare var Plotly; declare var window; @Component({ @@ -36,6 +37,7 @@ export class BrewEditComponent implements OnInit { public showFooter: boolean = true; private initialBeanData: string = ''; private disableHardwareBack; + public readonly PreparationDeviceType = PreparationDeviceType; constructor( private readonly modalController: ModalController, private readonly navParams: NavParams, @@ -50,7 +52,8 @@ export class BrewEditComponent implements OnInit { private readonly insomnia: Insomnia, private readonly bleManager: CoffeeBluetoothDevicesService, private readonly uiAlert: UIAlert, - private readonly visualizerService: VisualizerService + private readonly visualizerService: VisualizerService, + private readonly hapticService: HapticService ) { this.settings = this.uiSettingsStorage.getSettings(); // Moved from ionViewDidEnter, because of Ionic issues with ion-range @@ -155,6 +158,12 @@ export class BrewEditComponent implements OnInit { const scale: BluetoothScale = this.bleManager.getScale(); if (scale) { scale.tare(); + if ( + this.settings.haptic_feedback_active && + this.settings.haptic_feedback_tare + ) { + this.hapticService.vibrate(); + } } } public async updateBrew() { diff --git a/src/app/settings/settings.page.html b/src/app/settings/settings.page.html index 7e71decdd..2ea5c0ec2 100644 --- a/src/app/settings/settings.page.html +++ b/src/app/settings/settings.page.html @@ -459,6 +459,97 @@

{{"VISUALIZER.UPLOAD_AUTOMATIC" | translate}}

+ + + {{"PAGE_SETTINGS_TEXT_TO_SPEECH_SECTION" | translate}} + +

{{"EXPERIMENTAL_FEATURE_DISCLAIMER" | translate}}

+
+ + +
{{"TEXT_TO_SPEECH.ACTIVATE" | translate}}
+
+
+ + + +

{{"TEXT_TO_SPEECH.PITCH" | translate}}

+
+
+ + +
{{settings.text_to_speech_pitch | number : '.0-1'}}
+
+
+ + +

{{"TEXT_TO_SPEECH.RATE" | translate}}

+
+
+ + +
{{settings.text_to_speech_rate | number : '.0-1'}}
+
+
+ + +

{{"TEXT_TO_SPEECH.SPEAK_EVERY_MS" | translate}}

+
+
+ + +
{{settings.text_to_speech_interval_rate | number : '.0'}}
+
+
+ + + +

{{"TEXT_TO_SPEECH.TEST_SPEAK" | translate}}

+

{{"TEXT_TO_SPEECH.FOLLOWING_NUMBERS_WILL_BE_TEST_SPOKEN" | translate}}: 182.5, 28, 1072, 1.2, 0.1, 203.5

+
+ +
+
+
+
+ + + + + {{"PAGE_SETTINGS_HAPTIC_FEEDBACK_SECTION" | translate}} + + +

{{"EXPERIMENTAL_FEATURE_DISCLAIMER" | translate}}

+
+ + +
{{"HAPTIC_FEEDBACK.ACTIVATE" | translate}}
+
+
+ + + +
{{"HAPTIC_FEEDBACK.BREW_STARTED" | translate}}
+
+
+ + +
{{"HAPTIC_FEEDBACK.BREW_STOPPED" | translate}}
+
+
+ + + +
{{"HAPTIC_FEEDBACK.TARE" | translate}}
+
+
+
+
+
+
diff --git a/src/app/settings/settings.page.ts b/src/app/settings/settings.page.ts index 9f7deb0e2..49b6e85e6 100644 --- a/src/app/settings/settings.page.ts +++ b/src/app/settings/settings.page.ts @@ -70,6 +70,7 @@ import { VISUALIZER_SERVER_ENUM } from '../../enums/settings/visualizerServer'; import { VisualizerService } from '../../services/visualizerService/visualizer-service.service'; import { UIGraphStorage } from '../../services/uiGraphStorage.service'; import { Graph } from '../../classes/graph/graph'; +import { TextToSpeechService } from '../../services/textToSpeech/text-to-speech.service'; declare var cordova: any; declare var device: any; @@ -160,7 +161,8 @@ export class SettingsPage implements OnInit { private readonly eventQueue: EventQueueService, private readonly uiFileHelper: UIFileHelper, private readonly uiExportImportHelper: UIExportImportHelper, - private readonly visualizerService: VisualizerService + private readonly visualizerService: VisualizerService, + private readonly textToSpeech: TextToSpeechService ) { this.__initializeSettings(); this.debounceLanguageFilter @@ -822,6 +824,20 @@ export class SettingsPage implements OnInit { } } + public testSpeak() { + this.textToSpeech.readAndSetTTLSettings(); + let speakTestCount: number = 0; + const testSpeakArray = ['182.5', '28', '1072', '1.2', '0.1', '203.5']; + const speakTestIntv = setInterval(() => { + this.textToSpeech.speak(testSpeakArray[speakTestCount]); + + speakTestCount = speakTestCount + 1; + if (speakTestCount > 5) { + clearInterval(speakTestIntv); + } + }, this.settings.text_to_speech_interval_rate); + } + public async uploadBrewsToVisualizer() { const brewEntries = this.uiBrewStorage.getAllEntries(); const uploadShots = brewEntries.filter( diff --git a/src/assets/custom-ion-icons/beanconqueror-meticulous-logo.svg b/src/assets/custom-ion-icons/beanconqueror-meticulous-logo.svg new file mode 100644 index 000000000..e7c612b38 --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-meticulous-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/assets/custom-ion-icons/beanconqueror-no-wifi.svg b/src/assets/custom-ion-icons/beanconqueror-no-wifi.svg new file mode 100644 index 000000000..7a8a484b2 --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-no-wifi.svg @@ -0,0 +1 @@ + diff --git a/src/assets/custom-ion-icons/beanconqueror-wifi.svg b/src/assets/custom-ion-icons/beanconqueror-wifi.svg new file mode 100644 index 000000000..313537a2c --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-wifi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/custom-ion-icons/beanconqueror-xenia-logo.svg b/src/assets/custom-ion-icons/beanconqueror-xenia-logo.svg new file mode 100644 index 000000000..f5d396315 --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-xenia-logo.svg @@ -0,0 +1,30 @@ + +image/svg+xml \ No newline at end of file diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index a66a160b4..6461f3831 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1843,5 +1843,21 @@ "WATER_TYPE_THIRD_WAVE_WATER_ESPRESSO_MACHINE_PROFILE":"Third Wave Water - Espresso Machine Profile", "WATER_TYPE_THIRD_WAVE_WATER_COLD_BREW_PROFILE":"Third Wave Water -Cold Brew Profile", "WATER_TYPE_THIRD_WAVE_WATER_LOW_ACID_PROFILE":"Third Wave Water - Low Acid Profile", - "ADD_WATER": "Add water" + "ADD_WATER": "Add water", + "PAGE_SETTINGS_TEXT_TO_SPEECH_SECTION": "Text to speech", + "TEXT_TO_SPEECH": { + "ACTIVATE": "Activate Text to speech", + "BREW_STARTED": "Brew started", + "BREW_ENDED": "Brew end", + "TIME": "Time", + "SPEAK_EVERY_MS": "Speak every selected millisecond", + "FOLLOWING_NUMBERS_WILL_BE_TEST_SPOKEN": "Following numbers will be test spoken" + }, + "PAGE_SETTINGS_HAPTIC_FEEDBACK_SECTION": "Haptic feedback", + "HAPTIC_FEEDBACK": { + "ACTIVE": "Activate haptic feedback" + }, + "CONNECTED": "Connected", + "DISCONNECTED": "Disconnected", + "EXPERIMENTAL_FEATURE_DISCLAIMER": "This is an experimental feature" } diff --git a/src/classes/settings/settings.ts b/src/classes/settings/settings.ts index 9f0dbcb84..f104471cd 100755 --- a/src/classes/settings/settings.ts +++ b/src/classes/settings/settings.ts @@ -189,6 +189,15 @@ export class Settings implements ISettings { public show_backup_issues: boolean; + public text_to_speech_active: boolean; + public text_to_speech_rate: number; + public text_to_speech_pitch: number; + public text_to_speech_interval_rate: number; + + public haptic_feedback_active: boolean; + public haptic_feedback_brew_started: boolean; + public haptic_feedback_brew_stopped: boolean; + public haptic_feedback_tare: boolean; public GET_BEAN_FILTER(): IBeanPageFilter { const upperRating: number = this.bean_rating; return { @@ -428,6 +437,16 @@ export class Settings implements ISettings { this.visualizer_upload_automatic = false; this.show_backup_issues = true; + + this.text_to_speech_active = false; + this.text_to_speech_rate = 1; + this.text_to_speech_pitch = 3; + this.text_to_speech_interval_rate = 500; + + this.haptic_feedback_active = false; + this.haptic_feedback_brew_started = false; + this.haptic_feedback_brew_stopped = false; + this.haptic_feedback_tare = false; } public initializeByObject(settingsObj: ISettings): void { diff --git a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts index 4897de9c3..a560ab8a8 100644 --- a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts +++ b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts @@ -61,6 +61,7 @@ import { MeticulousShotData } from '../../../classes/preparationDevice/meticulou import { Graph } from '../../../classes/graph/graph'; import { UIGraphStorage } from '../../../services/uiGraphStorage.service'; import regression from 'regression'; +import { TextToSpeechService } from '../../../services/textToSpeech/text-to-speech.service'; declare var Plotly; @@ -149,6 +150,9 @@ export class BrewBrewingGraphComponent implements OnInit { public profileDiv: ElementRef; public chartData = []; + + public textToSpeechWeightInterval: any = undefined; + public textToSpeechTimerInterval: any = undefined; constructor( private readonly platform: Platform, private readonly bleManager: CoffeeBluetoothDevicesService, @@ -166,11 +170,15 @@ export class BrewBrewingGraphComponent implements OnInit { private readonly modalController: ModalController, private readonly uiLog: UILog, public readonly uiBrewHelper: UIBrewHelper, - private readonly uiGraphStorage: UIGraphStorage + private readonly uiGraphStorage: UIGraphStorage, + private readonly textToSpeech: TextToSpeechService ) {} public ngOnInit() { this.settings = this.uiSettingsStorage.getSettings(); + if (this.settings.text_to_speech_active) { + this.textToSpeech.readAndSetTTLSettings(); + } } public async instance() { @@ -229,6 +237,7 @@ export class BrewBrewingGraphComponent implements OnInit { } else if (_type === CoffeeBluetoothServiceEvent.DISCONNECTED_SCALE) { this.deattachToWeightChange(); this.deattachToFlowChange(); + this.deattachToTextToSpeedChange(); this.deattachToScaleEvents(); disconnectTriggered = true; } else if (_type === CoffeeBluetoothServiceEvent.CONNECTED_PRESSURE) { @@ -1633,6 +1642,7 @@ export class BrewBrewingGraphComponent implements OnInit { this.deattachToWeightChange(); this.deattachToFlowChange(); + this.deattachToTextToSpeedChange(); // 551 - Always attach to flow change, even when reset is triggerd this.attachToFlowChange(); } @@ -1717,6 +1727,13 @@ export class BrewBrewingGraphComponent implements OnInit { scale.setTimer(SCALE_TIMER_COMMAND.STOP); this.deattachToWeightChange(); this.deattachToFlowChange(); + this.deattachToTextToSpeedChange(); + if (this.settings.text_to_speech_active) { + this.textToSpeech.speak( + this.translate.instant('TEXT_TO_SPEECH.BREW_ENDED'), + true + ); + } } if (pressureDevice) { this.deattachToPressureChange(); @@ -2043,6 +2060,9 @@ export class BrewBrewingGraphComponent implements OnInit { temperature = Math.floor( (crypto.getRandomValues(new Uint8Array(1))[0] / Math.pow(2, 8)) * 90 ); + if (this.settings.text_to_speech_active) { + this.textToSpeech.speak(weight.toString()); + } this.__setPressureFlow({ actual: pressure, old: pressure }); @@ -2102,6 +2122,13 @@ export class BrewBrewingGraphComponent implements OnInit { if (scale) { this.attachToScaleWeightChange(); this.attachToFlowChange(); + this.attachToTextToSpeechChange(); + + if (this.settings.text_to_speech_active) { + this.textToSpeech.speak( + this.translate.instant('TEXT_TO_SPEECH.BREW_STARTED') + ); + } } if ( pressureDevice && @@ -2290,6 +2317,17 @@ export class BrewBrewingGraphComponent implements OnInit { } } + public deattachToTextToSpeedChange() { + if (this.textToSpeechWeightInterval) { + clearInterval(this.textToSpeechWeightInterval); + this.textToSpeechWeightInterval = undefined; + } + if (this.textToSpeechTimerInterval) { + clearInterval(this.textToSpeechTimerInterval); + this.textToSpeechTimerInterval = undefined; + } + } + public deattachToPressureChange() { if (this.pressureDeviceSubscription) { this.pressureDeviceSubscription.unsubscribe(); @@ -2330,6 +2368,48 @@ export class BrewBrewingGraphComponent implements OnInit { } } + public attachToTextToSpeechChange() { + this.deattachToTextToSpeedChange(); + if (this.settings.text_to_speech_active === true) { + const isEspressoBrew: boolean = + this.data.getPreparation().style_type === + PREPARATION_STYLE_TYPE.ESPRESSO; + this.textToSpeechWeightInterval = setInterval(() => { + this.ngZone.runOutsideAngular(() => { + if (this.flowProfileTempAll.length > 0) { + const actualScaleWeight = + this.flowProfileTempAll.slice(-1)[0].weight; + if (actualScaleWeight !== null && actualScaleWeight !== undefined) { + if (isEspressoBrew) { + this.textToSpeech.speak( + this.uiHelper + .toFixedIfNecessary(actualScaleWeight, 1) + .toString() + ); + } else { + this.textToSpeech.speak( + this.uiHelper + .toFixedIfNecessary(actualScaleWeight, 0) + .toString() + ); + } + } + } + }); + }, this.settings.text_to_speech_interval_rate); + + this.textToSpeechTimerInterval = setInterval(() => { + this.ngZone.runOutsideAngular(() => { + this.textToSpeech.speak( + this.translate.instant('TEXT_TO_SPEECH.TIME') + + ' ' + + this.data.brew_time + ); + }); + }, 5000); + } + } + public attachToPressureChange() { const pressureDevice: PressureDevice = this.bleManager.getPressureDevice(); if (pressureDevice) { @@ -2639,6 +2719,7 @@ export class BrewBrewingGraphComponent implements OnInit { this.deattachToWeightChange(); this.deattachToFlowChange(); + this.deattachToTextToSpeedChange(); this.deattachToPressureChange(); this.deattachToScaleEvents(); this.deattachToTemperatureChange(); @@ -2647,6 +2728,10 @@ export class BrewBrewingGraphComponent implements OnInit { this.deattachToScaleStartTareListening(); this.stopFetchingAndSettingDataFromXenia(); this.stopFetchingDataFromMeticulous(); + + if (this.settings.text_to_speech_active) { + this.textToSpeech.end(); + } } public ngOnDestroy() { diff --git a/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts b/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts index 84ad0e9bb..34998a4d2 100644 --- a/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts +++ b/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts @@ -41,6 +41,7 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { @Input() public brewComponent: BrewBrewingComponent; public preparationDevice: XeniaDevice | MeticulousDevice = undefined; + public preparation: Preparation = undefined; public settings: Settings = undefined; public PREPARATION_DEVICE_TYPE_ENUM = PreparationDeviceType; public PREPARATION_STYLE_TYPE = PREPARATION_STYLE_TYPE; @@ -74,9 +75,21 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { } } + public hasAPreparationDeviceSet() { + return ( + this.preparation?.connectedPreparationDevice.type !== + PreparationDeviceType.NONE + ); + } + + public getDataPreparationDeviceType() { + return this.preparation?.connectedPreparationDevice.type; + } + public async instancePreparationDevice(_brew: Brew = null) { + this.preparation = this.data.getPreparation(); const connectedDevice: PreparationDevice = - this.uiPreparationHelper.getConnectedDevice(this.data.getPreparation()); + this.uiPreparationHelper.getConnectedDevice(this.preparation); if (connectedDevice) { if (connectedDevice instanceof XeniaDevice) { await this.instanceXeniaPreparationDevice(connectedDevice, _brew); diff --git a/src/components/brews/brew-brewing/brew-brewing.component.html b/src/components/brews/brew-brewing/brew-brewing.component.html index 1ca72a333..874b807b9 100644 --- a/src/components/brews/brew-brewing/brew-brewing.component.html +++ b/src/components/brews/brew-brewing/brew-brewing.component.html @@ -192,7 +192,6 @@ {{"BREW_HEADER_WHILE_BREW" | translate }} - @@ -209,6 +208,8 @@ + + 0"> + + + + + + diff --git a/src/components/photo-add/photo-add.component.ts b/src/components/photo-add/photo-add.component.ts index a73b5a83f..273ebc951 100644 --- a/src/components/photo-add/photo-add.component.ts +++ b/src/components/photo-add/photo-add.component.ts @@ -112,4 +112,24 @@ export class PhotoAddComponent implements OnInit { } } } + + public async sortLeft(_index: number) { + this.swap(_index - 1, _index); + } + + public async sortRight(_index: number) { + this.swap(_index + 1, _index); + } + + public swap(index1: number, index2: number) { + // Check if the indices are within the valid range + + // Perform the swap using a temporary variable + const temp = this.data.attachments[index1]; + this.data.attachments[index1] = this.data.attachments[index2]; + this.data.attachments[index2] = temp; + this.emitChanges(); + + this.photoSlides.nativeElement.swiper.slideTo(index1, 200, false); + } } diff --git a/src/interfaces/settings/iSettings.ts b/src/interfaces/settings/iSettings.ts index 3e354af15..25efcb7a7 100755 --- a/src/interfaces/settings/iSettings.ts +++ b/src/interfaces/settings/iSettings.ts @@ -181,4 +181,13 @@ export interface ISettings { visualizer_upload_automatic: boolean; show_backup_issues: boolean; + + text_to_speech_active: boolean; + text_to_speech_rate: number; + text_to_speech_pitch: number; + + haptic_feedback_active: boolean; + haptic_feedback_brew_started: boolean; + haptic_feedback_brew_stopped: boolean; + haptic_feedback_tare: boolean; } diff --git a/src/services/hapticService/haptic.service.spec.ts b/src/services/hapticService/haptic.service.spec.ts new file mode 100644 index 000000000..42a7d4039 --- /dev/null +++ b/src/services/hapticService/haptic.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { HapticService } from './haptic.service'; + +describe('HapticService', () => { + let service: HapticService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(HapticService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/services/hapticService/haptic.service.ts b/src/services/hapticService/haptic.service.ts new file mode 100644 index 000000000..9c7481ffe --- /dev/null +++ b/src/services/hapticService/haptic.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; + +declare var navigator; +@Injectable({ + providedIn: 'root', +}) +export class HapticService { + constructor() {} + + public vibrate() { + try { + navigator.vibrate(1000); + } catch (ex) {} + } +} diff --git a/src/services/textToSpeech/text-to-speech.service.spec.ts b/src/services/textToSpeech/text-to-speech.service.spec.ts new file mode 100644 index 000000000..1f7bd70a2 --- /dev/null +++ b/src/services/textToSpeech/text-to-speech.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { TextToSpeechService } from './text-to-speech.service'; + +describe('TextToSpeechService', () => { + let service: TextToSpeechService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TextToSpeechService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/services/textToSpeech/text-to-speech.service.ts b/src/services/textToSpeech/text-to-speech.service.ts new file mode 100644 index 000000000..40a1e16ed --- /dev/null +++ b/src/services/textToSpeech/text-to-speech.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { UISettingsStorage } from '../uiSettingsStorage'; +import { Settings } from '../../classes/settings/settings'; + +declare var TTS; +declare var window; +@Injectable({ + providedIn: 'root', +}) +export class TextToSpeechService { + private settings: Settings; + + private rate: number = 1; + private pitch: number = 1; + private textToSpeechInstance: SpeechSynthesis; + private selectedVoice; + constructor(private readonly uiSettings: UISettingsStorage) { + this.settings = this.uiSettings.getSettings(); + this.textToSpeechInstance = window.speechSynthesis; + this.selectedVoice = new SpeechSynthesisUtterance(); + } + + public readAndSetTTLSettings() { + this.selectedVoice.rate = this.settings.text_to_speech_rate; + this.selectedVoice.pitch = this.settings.text_to_speech_pitch; + this.selectedVoice.volume = 1; + } + + public speak(_text: string, _end: boolean = false) { + try { + this.selectedVoice.text = _text; + if (_end) { + this.end(); + } + this.textToSpeechInstance.speak(this.selectedVoice); + } catch (ex) {} + } + public end() { + this.textToSpeechInstance.cancel(); + } +} diff --git a/src/services/uiImage.ts b/src/services/uiImage.ts index d8d71af5d..caa312f3a 100755 --- a/src/services/uiImage.ts +++ b/src/services/uiImage.ts @@ -105,6 +105,7 @@ export class UIImage { } } catch (ex) {} } + public async choosePhoto(): Promise { const promise = new Promise(async (resolve, reject) => { this.__checkPermission( @@ -126,7 +127,7 @@ export class UIImage { .then( async (results) => { await this.uiAlert.showLoadingSpinner(); - for (const result of results) { + for await (const result of results) { if ( result && result.length > 0 && @@ -137,20 +138,18 @@ export class UIImage { ) { try { const imageStr: string = `data:image/jpeg;base64,${result}`; - await this.uiFileHelper - .saveBase64File( + const newURL = + await this.uiFileHelper.saveBase64File( 'beanconqueror_image', '.jpg', imageStr - ) - .then( - (_newURL) => { - fileurls.push(_newURL); - }, - () => {} ); - } catch (ex) {} + fileurls.push(newURL); + } catch (ex) { + //nothing + } } else { + //Nothing } } setTimeout(() => { @@ -185,7 +184,7 @@ export class UIImage { async (_files) => { await this.uiAlert.showLoadingSpinner(); - for (const file of _files) { + for await (const file of _files) { let newFileName = file; try { // We cant copy the file if it doesn't start with file:///, @@ -209,27 +208,14 @@ export class UIImage { imageStr = `data:image/jpeg;base64,${result}`; } - await this.uiFileHelper - .saveBase64File( + try { + const newUrl = await this.uiFileHelper.saveBase64File( 'beanconqueror_image', '.jpg', imageStr - ) - .then( - (_newURL) => { - fileurls.push(_newURL); - }, - () => {} ); - - /**await this.uiFileHelper - .copyFileWithSpecificName(newFileName) - .then( - async (_fullPath) => { - fileurls.push(_fullPath); - }, - () => {} - );**/ + fileurls.push(newUrl); + } catch (ex) {} } catch (ex) { setTimeout(() => { this.uiAlert.hideLoadingSpinner();