diff --git a/CHANGELOG.md b/CHANGELOG.md index 57fabba..444cf74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [Unreleased] + +### Added +- Add setting for auto scan +- Add zeroconf clearing func + +### Changed +- Improve fonting based on system font + +### Fixed +- Fix volume presets +- Clear services on page leave +- Close modal on nav +- Fix fonting overlay issues + ## [0.5.6] - 2026-06-14 ### Added @@ -11,7 +26,7 @@ ## [0.5.5] - 2026-05-27 ### Added -- Add Mopidy support, control, visuals, and presets +- Add Mopidy (POC) support, control, visuals, and presets ## [0.5.4] - 2026-05-08 diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5543596..0bc82e5 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,12 @@ import { Component, NgZone } from '@angular/core'; import { App } from '@capacitor/app'; import { SnapcastService } from './services/snapcast.service'; +import { ZeroconfService } from './services/zero-conf.service'; +import { Observable } from 'rxjs'; +import { ZeroConf, ZeroConfService as ZeroConfServiceModel } from 'capacitor-zeroconf'; +import { Preferences } from '@capacitor/preferences'; +import { UserPreference } from './enum/user-preference.enum'; + @Component({ selector: 'app-root', @@ -9,11 +15,18 @@ import { SnapcastService } from './services/snapcast.service'; standalone: false, }) export class AppComponent { + readonly SERVICE_SNAPCAST = '_snapcast._tcp.'; + services$: Observable; + scanForDevicesOnStartup: boolean = false; + + constructor( private ngZone: NgZone, - private snapcastService: SnapcastService + private snapcastService: SnapcastService, + private zeroconfService: ZeroconfService ) { this.initializeApp(); + } initializeApp() { @@ -25,7 +38,11 @@ export class AppComponent { // App has come to the foreground console.log('App is in the foreground'); // TODO: Refresh or establish connections to snapcast server - this.snapcastService.connect(); + try { + this.snapcastService.connect(); + } catch (error) { + console.error('Error connecting to Snapcast server:', error); + } // Add your logic here to refresh data, update UI, etc. } else { @@ -37,4 +54,27 @@ export class AppComponent { }); }); } + + + async scanForZeroConfServices(): Promise { + try { + await this.zeroconfService.watch(this.SERVICE_SNAPCAST); + console.log(`Started scanning for services of types: ${this.SERVICE_SNAPCAST}`); + } + catch (error) { + console.error('Error starting service scan:', error); + } + + } + + async checkUserPreferencesForAutomaticScan(): Promise { + const result = await Preferences.get({ key: UserPreference.SCAN_FOR_DEVICES_ON_STARTUP }); + this.scanForDevicesOnStartup = result.value === 'true' ? true : false; + if (this.scanForDevicesOnStartup) { + console.log('User preference indicates to scan for devices on startup. Starting scan...'); + await this.scanForZeroConfServices(); + } else { + console.log('User preference indicates NOT to scan for devices on startup.'); + } + } } diff --git a/src/app/components/choose-speakers/choose-speakers.component.html b/src/app/components/choose-speakers/choose-speakers.component.html index 1515bc3..237267e 100644 --- a/src/app/components/choose-speakers/choose-speakers.component.html +++ b/src/app/components/choose-speakers/choose-speakers.component.html @@ -1,33 +1,40 @@ - - Choose Speakers - + + + + + Done + + +

Select the speakers you want to use:

-
-
+
+
{{ speaker.manufacturer }} {{ speaker.model }} - - -

{{ speaker.manufacturer }} {{ speaker.model }}

+ + + +

{{ speaker.manufacturer }} {{ speaker.model }}

+
- +
-
- +
+ Save Selection
- + \ No newline at end of file diff --git a/src/app/components/choose-speakers/choose-speakers.component.scss b/src/app/components/choose-speakers/choose-speakers.component.scss index e69de29..ffbf91a 100644 --- a/src/app/components/choose-speakers/choose-speakers.component.scss +++ b/src/app/components/choose-speakers/choose-speakers.component.scss @@ -0,0 +1,6 @@ +.choose-speakers-container { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px; +} \ No newline at end of file diff --git a/src/app/components/choose-speakers/choose-speakers.component.ts b/src/app/components/choose-speakers/choose-speakers.component.ts index 24e1496..e4f4cb6 100644 --- a/src/app/components/choose-speakers/choose-speakers.component.ts +++ b/src/app/components/choose-speakers/choose-speakers.component.ts @@ -17,6 +17,7 @@ export class ChooseSpeakersComponent implements OnInit { selectedId: string | undefined; speakers: Speaker[] = []; + filteredSpeakers: Speaker[] = []; constructor( private modalController: ModalController, @@ -49,6 +50,7 @@ export class ChooseSpeakersComponent implements OnInit { this.http.get<{ speakers: Speaker[] }>('assets/speakers/speakers-data.json') ); this.speakers = response.speakers; + this.filteredSpeakers = [...this.speakers]; console.log('Speakers loaded:', this.speakers); return this.speakers; } catch (error) { @@ -71,4 +73,19 @@ export class ChooseSpeakersComponent implements OnInit { }); } + filterSpeakers(event: any) { + const searchTerm = event.target.value.toLowerCase(); + + if (!searchTerm) { + this.filteredSpeakers = [...this.speakers]; + return; + } + + this.filteredSpeakers = this.speakers.filter(speaker => { + const matchManufacturer = speaker.manufacturer?.toLowerCase().includes(searchTerm); + const matchModel = speaker.model?.toLowerCase().includes(searchTerm); + return matchManufacturer || matchModel; + }); + } + } diff --git a/src/app/enum/user-preference.enum.ts b/src/app/enum/user-preference.enum.ts index 2352c5b..c4122e2 100644 --- a/src/app/enum/user-preference.enum.ts +++ b/src/app/enum/user-preference.enum.ts @@ -5,4 +5,5 @@ export enum UserPreference { SERVER_URL = 'serverUrl', VOLUME_PRESETS = 'volumePresets', STREAM_PRESETS = 'streamPresets', + SCAN_FOR_DEVICES_ON_STARTUP = 'scanForDevicesOnStartup', } \ No newline at end of file diff --git a/src/app/pages/dashboard/dashboard.page.html b/src/app/pages/dashboard/dashboard.page.html index 0527920..3c62568 100644 --- a/src/app/pages/dashboard/dashboard.page.html +++ b/src/app/pages/dashboard/dashboard.page.html @@ -57,7 +57,7 @@ {{name ? name+"s": 'Your'}} Devices - + @@ -112,43 +112,43 @@ - - - -
-
- - - Loading... - - - Please wait while the server status is being loaded. Checking url: {{userPreferenceServerUrl? - userPreferenceServerUrl:environment.snapcastServerUrl}} - -
- -
- - No Server Available - - - Please check your Beatnik Pi server connection. No Server found at {{userPreferenceServerUrl? - userPreferenceServerUrl:environment.snapcastServerUrl}} -
- Wanna use a different server? Go to the Settings page and change the - Snapcast server URL. -
- Just wana test test? Enable the Demo mode: - - -
- - Enable Demo Mode - - - Setup Server - + +
+
+ + + Loading... + + + Please wait while the server status is being loaded. Checking url: {{userPreferenceServerUrl? + userPreferenceServerUrl:environment.snapcastServerUrl}} + +
+ +
+ + No Server Available + + + Please check your Beatnik Pi server connection. No Server found at {{userPreferenceServerUrl? + userPreferenceServerUrl:environment.snapcastServerUrl}} +
+ Wanna use a different server? Go to the Device Search or set it manually Settings +
+ Just wanna test test? Enable the Demo mode: + +
+ + Setup Server + + + Enable Demo Mode + + +
+
-
-
\ No newline at end of file + + \ No newline at end of file diff --git a/src/app/pages/dashboard/dashboard.page.scss b/src/app/pages/dashboard/dashboard.page.scss index 4e83d46..cf4016d 100644 --- a/src/app/pages/dashboard/dashboard.page.scss +++ b/src/app/pages/dashboard/dashboard.page.scss @@ -39,7 +39,8 @@ flex-direction: column; align-items: center; justify-content: center; - height: 40vh; + // height: 40vh; + padding-bottom: 32px; } .fab-label{ diff --git a/src/app/pages/dashboard/dashboard.page.ts b/src/app/pages/dashboard/dashboard.page.ts index 6a09d4c..80ee5f2 100644 --- a/src/app/pages/dashboard/dashboard.page.ts +++ b/src/app/pages/dashboard/dashboard.page.ts @@ -1,5 +1,5 @@ import { Component, HostListener, OnInit } from '@angular/core'; -import { ModalController, RefresherCustomEvent } from '@ionic/angular'; +import { AlertController, ModalController, RefresherCustomEvent } from '@ionic/angular'; import { first, firstValueFrom, interval, Observable } from 'rxjs'; import { Client, Group, ServerDetail, SnapCastServerStatusResponse, Stream } from 'src/app/model/snapcast.model'; import { SnapcastService } from 'src/app/services/snapcast.service'; @@ -10,6 +10,7 @@ import { Preferences } from '@capacitor/preferences'; import { UserPreference } from 'src/app/enum/user-preference.enum'; import { Speaker } from 'src/app/model/speaker.model'; import { HttpClient } from '@angular/common/http'; +import { ZeroconfService } from 'src/app/services/zero-conf.service'; @@ -73,11 +74,17 @@ export class DashboardPage implements OnInit { refreshingText: string = 'Refreshing... '; + readonly SERVICE_SNAPCAST = '_snapcast._tcp.'; + scanForDevicesOnStartup: boolean = false; + constructor( private snapcastService: SnapcastService, - private http: HttpClient + private http: HttpClient, + private zeroconfService: ZeroconfService, + private alertController: AlertController + ) { // this.groups$ = this.snapcastService.groups$; // this.streams$ = this.snapcastService.streams$; @@ -160,11 +167,13 @@ export class DashboardPage implements OnInit { // } noServerTimeout(): void { - // wait 10 seconds before timing out search for server + // wait 5 seconds before timing out search for server setTimeout(() => { if (this.isLoading) { this.isLoading = false; - console.error('No Snapcast server found after 10 seconds.'); + console.error('No Snapcast server found after 5 seconds.'); + this.checkUserPreferencesForAutomaticScan(); + } }, 10000); @@ -255,7 +264,8 @@ export class DashboardPage implements OnInit { next: () => { console.log('Server state refreshed successfully'); const now = new Date(); - setTimeout(() => { this.refreshingText = 'Server state refreshed successfully at ' + now.toLocaleTimeString(); + setTimeout(() => { + this.refreshingText = 'Server state refreshed successfully at ' + now.toLocaleTimeString(); }, 500); // this.refreshingText = 'Server state refreshed successfully at ' + now.toLocaleTimeString(); // event.target.complete(); @@ -276,11 +286,73 @@ export class DashboardPage implements OnInit { + async scanForZeroConfServices(): Promise { + try { + this.zeroconfService.watch(this.SERVICE_SNAPCAST) + console.log(`Started scanning for services of types: ${this.SERVICE_SNAPCAST}`); + // let's wait for a few seconds to allow the scan to discover services + await new Promise(resolve => setTimeout(resolve, 1000)); + const firstResult = await firstValueFrom(this.zeroconfService.services$); + console.log('First discovered services:', firstResult); + if (firstResult.length === 0) { + console.log('No services found. Stopping scan.'); + await this.zeroconfService.stop(); + } else { + console.log('Services found. Continuing to watch for more services.'); + const serverIP = firstResult[0].ipv4Addresses[0]; + const serverHostname = firstResult[0].hostname; + console.log('Setting discovered Snapcast server as the server URL:', serverIP); + const userPreferenceServerUrl = await Preferences.get({ key: UserPreference.SERVER_URL }); + if (userPreferenceServerUrl.value !== serverIP && userPreferenceServerUrl.value !== serverHostname) { + const alert = await this.alertController.create({ + header: 'Different Snapcast Server Found', + message: `We discovered a Snapcast server at ${serverIP} (${serverHostname}) that is different from your current preference. Do you want to set this as your Snapcast server?`, + buttons: [ + { + text: 'Cancel', + role: 'cancel' + }, + { + text: 'Set as Server', + handler: async () => { + await Preferences.set({ + key: UserPreference.SERVER_URL, + value: serverIP, + }); + console.log('Server URL set to:', serverIP); + this.ionViewWillEnter(); // Refresh the view to reflect the new server URL + + } + } + ] + }); + await alert.present(); + } else { + console.log('Discovered server IP is the same as the current user preference. No action needed.'); + } + } + } + catch (error) { + console.error('Error starting service scan:', error); + } + + } + + async checkUserPreferencesForAutomaticScan(): Promise { + const result = await Preferences.get({ key: UserPreference.SCAN_FOR_DEVICES_ON_STARTUP }); + this.scanForDevicesOnStartup = result.value === 'true' ? true : false; + if (this.scanForDevicesOnStartup) { + console.log('User preference indicates to scan for devices on startup. Starting scan...'); + await this.scanForZeroConfServices(); + } else { + console.log('User preference indicates NOT to scan for devices on startup.'); + } + } +} -} diff --git a/src/app/pages/menu/menu.page.html b/src/app/pages/menu/menu.page.html index df05c97..3251369 100644 --- a/src/app/pages/menu/menu.page.html +++ b/src/app/pages/menu/menu.page.html @@ -28,7 +28,7 @@ - ZeroConf Scan + Search Devices