Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand Down
44 changes: 42 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -9,11 +15,18 @@ import { SnapcastService } from './services/snapcast.service';
standalone: false,
})
export class AppComponent {
readonly SERVICE_SNAPCAST = '_snapcast._tcp.';
services$: Observable<ZeroConfServiceModel[]>;
scanForDevicesOnStartup: boolean = false;


constructor(
private ngZone: NgZone,
private snapcastService: SnapcastService
private snapcastService: SnapcastService,
private zeroconfService: ZeroconfService
) {
this.initializeApp();

}

initializeApp() {
Expand All @@ -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 {
Expand All @@ -37,4 +54,27 @@ export class AppComponent {
});
});
}


async scanForZeroConfServices(): Promise<void> {
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<void> {
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.');
}
}
}
31 changes: 19 additions & 12 deletions src/app/components/choose-speakers/choose-speakers.component.html
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@


<ion-header>
<ion-toolbar>
<ion-title>Choose Speakers</ion-title>
<ion-buttons slot="end">
<ion-buttons slot="start">
<ion-button (click)="closeModal()">
<ion-icon name="close"></ion-icon>
</ion-button>
</ion-buttons>
<ion-buttons slot="end">
<ion-button (click)="saveSelection()">
<!-- <ion-icon slot="end" name="save"></ion-icon> -->
<ion-label>Done</ion-label>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-searchbar (ionInput)="filterSpeakers($event)" placeholder="Search speakers by brand or model..."></ion-searchbar>
<div class="choose-speakers-container">
<p>Select the speakers you want to use:</p>
<!-- Add your speaker selection logic here -->
<div class="row">
<div class="col-6" *ngFor="let speaker of speakers">
<div class="row">
<div class="col-6" *ngFor="let speaker of filteredSpeakers" (click)="selectSpeaker(speaker.id)">
<img [src]="speaker.image" alt="{{ speaker.manufacturer }} {{ speaker.model }}" class="speaker-image">
<ion-item lines="none" (click)="selectSpeaker(speaker.id)">
<ion-icon slot="end" [name]="'checkmark-circle'" *ngIf="selectedId === speaker.id"></ion-icon>
<ion-label> <h2>{{ speaker.manufacturer }} {{ speaker.model }}</h2></ion-label>
<ion-item lines="none">
<ion-icon [name]="'checkmark-circle'" *ngIf="selectedId === speaker.id"></ion-icon>
<ion-label>
<h2>{{ speaker.manufacturer }} {{ speaker.model }}</h2>
</ion-label>
</ion-item>

</div>
</div>
</div>

<ion-button expand="full" color="primary" (click)="saveSelection()">
Save Selection
</ion-button>
</div>
</ion-content>
</ion-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.choose-speakers-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
}
17 changes: 17 additions & 0 deletions src/app/components/choose-speakers/choose-speakers.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class ChooseSpeakersComponent implements OnInit {

selectedId: string | undefined;
speakers: Speaker[] = [];
filteredSpeakers: Speaker[] = [];

constructor(
private modalController: ModalController,
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
});
}

}
1 change: 1 addition & 0 deletions src/app/enum/user-preference.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export enum UserPreference {
SERVER_URL = 'serverUrl',
VOLUME_PRESETS = 'volumePresets',
STREAM_PRESETS = 'streamPresets',
SCAN_FOR_DEVICES_ON_STARTUP = 'scanForDevicesOnStartup',
}
78 changes: 39 additions & 39 deletions src/app/pages/dashboard/dashboard.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<ion-list-header>
<ion-label><span *ngIf="userPreeferenceUsername as name">{{name ? name+"s": 'Your'}}</span>
Devices</ion-label>
<ion-button size="small" fill="clear" [routerLink]="'/ble-wifi-setup'">
<ion-button fill="clear" [routerLink]="'/ble-wifi-setup'">
<ion-icon slot="icon-only" name="add-circle-outline"></ion-icon>
</ion-button>
</ion-list-header>
Expand Down Expand Up @@ -112,43 +112,43 @@
<!-- <div class="page-padding-bottom">
</div> -->

</ion-content>

<ng-template #noState>
<div class="no-state-container">
<div *ngIf="isLoading else noServer" class="ion-text-center">
<ion-spinner name="crescent" class="ion-padding"></ion-spinner>
<ion-card-header>
<ion-card-title>Loading...</ion-card-title>
</ion-card-header>
<ion-card-content>
Please wait while the server status is being loaded. Checking url: <b>{{userPreferenceServerUrl?
userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
</ion-card-content>
</div>
<ng-template #noServer>
<div class="ion-text-center">
<ion-card-header>
<ion-card-title>No Server Available</ion-card-title>
</ion-card-header>
<ion-card-content>
Please check your Beatnik Pi server connection. No Server found at <b>{{userPreferenceServerUrl?
userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
<br>
Wanna use a different server? Go to the <a routerLink="/tabs/menu/settings">Settings</a> page and change the
Snapcast server URL.
<br>
Just wana test test? Enable the Demo mode:


</ion-card-content>
<ion-button fill="clear" color="primary" (click)="enableDemoMode()">
<ion-icon name="play"></ion-icon> Enable Demo Mode
</ion-button>
<ion-button fill="clear" color="primary" routerLink="/setup-server">
<ion-icon name="settings"></ion-icon> Setup Server
</ion-button>
<ng-template #noState>
<div class="no-state-container">
<div *ngIf="isLoading else noServer" class="ion-text-center">
<ion-spinner name="crescent" class="ion-padding"></ion-spinner>
<ion-card-header>
<ion-card-title>Loading...</ion-card-title>
</ion-card-header>
<ion-card-content>
Please wait while the server status is being loaded. Checking url: <b>{{userPreferenceServerUrl?
userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
</ion-card-content>
</div>
<ng-template #noServer>
<div class="ion-text-center">
<ion-card-header>
<ion-card-title>No Server Available</ion-card-title>
</ion-card-header>
<ion-card-content>
Please check your Beatnik Pi server connection. No Server found at <b>{{userPreferenceServerUrl?
userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
<br>
Wanna use a different server? Go to the <a routerLink="/zeroconf">Device Search</a> or set it manually <a
routerLink="/tabs/menu/settings">Settings</a>
<br>
Just wanna test test? Enable the Demo mode:

</ion-card-content>
<ion-button fill="clear" color="primary" routerLink="/zeroconf">
<ion-icon name="settings"></ion-icon> Setup Server
</ion-button>
<ion-button fill="clear" color="primary" (click)="enableDemoMode()">
<ion-icon name="play"></ion-icon> Enable Demo Mode
</ion-button>

</div>
</ng-template>
</div>
</ng-template>
</div>
</ng-template>

</ion-content>
3 changes: 2 additions & 1 deletion src/app/pages/dashboard/dashboard.page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
flex-direction: column;
align-items: center;
justify-content: center;
height: 40vh;
// height: 40vh;
padding-bottom: 32px;
}

.fab-label{
Expand Down
Loading
Loading