Skip to content

Commit dbb3ccb

Browse files
authored
Merge pull request #8 from byrdsandbytes/feature/sc-2034-finalize-dashboard-screen-v1
Feature/sc 2034 finalize dashboard screen v1
2 parents 60884e6 + d1414e7 commit dbb3ccb

File tree

10 files changed

+208
-46
lines changed

10 files changed

+208
-46
lines changed

src/app/components/player-toolbar/player-toolbar.component.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<ion-toolbar class="play-toolbar" color="dark">
22
<ng-container *ngIf="(displayState$|async) as state ">
33
<ng-container *ngFor="let stream of state.server.streams">
4-
<ion-item class="ion-padding-top" *ngIf="stream.status =='playing'" lines="none" color="dark">
4+
<ion-item *ngIf="stream.properties.metadata" class="ion-padding-top" lines="none" color="dark">
55
<ion-thumbnail slot="start">
66
<img *ngIf="stream.properties.metadata.artData as artData" [src]="convertCoverDataBase64(artData.data, artData.extension)" alt="cover image">
77
</ion-thumbnail>
@@ -32,9 +32,7 @@ <h2> {{ stream.properties.metadata.title }}</h2>
3232
<p>Clients: {{ group.clients.length }}</p>
3333
<ng-container *ngFor="let client of group.clients">
3434
<ion-range *ngIf="client.config.volume" color="light" min="0" max="100" step="1"
35-
[(ngModel)]="client.config.volume.percent" (ionKnobMoveStart)="knobMoveStartEvent($event)" (ionKnobMoveEnd)="knobMoveEndEvent($event)" (ionInput)="changeVolumeForClient(client, $event)">
36-
<!-- [(ngModel)]="client.config.volume.percent" (ionKnobMoveStart)="knobMoveStartEvent($event)" (ionInput)="changeVolumeForClient(client, $event)"> -->
37-
35+
[ngModel]="client.config.volume.percent" (ionKnobMoveStart)="knobMoveStartEvent($event)" (ionKnobMoveEnd)="knobMoveEndEvent($event)" (ionInput)="changeVolumeForClient(client, $event)">
3836
<ion-icon slot="start" size="small" name="volume-mute"></ion-icon>
3937
<ion-icon slot="end" size="small" name="volume-high"></ion-icon>
4038
</ion-range>

src/app/components/player-toolbar/player-toolbar.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ export class PlayerToolbarComponent implements OnInit, OnChanges, OnDestroy {
2323
private knobMoveStart = false;
2424
private knobMoveEnd = false;
2525

26+
27+
2628
constructor(
2729
public snapcastService: SnapcastService
2830
) {
2931

30-
this.displayState$ = this.snapcastService.state$.pipe(
31-
tap(state => console.log('%cPlayerToolbarComponent: displayState$ received', 'color: orange; font-weight: bold;', new Date().toLocaleTimeString(), state))
32-
);
32+
3333
}
3434

3535
ngOnInit(): void {
36-
36+
this.displayState$ = this.snapcastService.state$
3737
this.snapcastService.connect();
3838
}
3939

@@ -151,5 +151,7 @@ export class PlayerToolbarComponent implements OnInit, OnChanges, OnDestroy {
151151
// Optionally, you can add haptic feedback here
152152
}
153153

154+
155+
154156

155157
}

src/app/components/snapcast-group-preview/snapcast-group-preview.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class SnapcastGroupPreviewComponent implements OnInit, OnChanges {
4747
}
4848
// Navigate to the group details page using the group's ID
4949
// Assuming you have a route set up for group details like '/group-details/:id'
50-
this.router.navigate(['tabs/devices', this.group.id]);
50+
this.router.navigate(['tabs/dashboard/devices', this.group.id]);
5151
}
5252

5353
checkClientOnlineState(client: any): string {

src/app/components/snapcast-stream-preview/snapcast-stream-preview.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<div *ngIf="stream.properties.metadata as meta"
1414
class="d-flex flex-column align-items-center justify-content-center ion-text-center">
15-
<img *ngIf="meta.album" class="cover-image" [src]="meta.artUrl" alt="Album Art" />
15+
<img *ngIf="meta.artData as artData" class="cover-image" [src]="convertCoverDataBase64(artData.data, artData.extension)" alt="Album Art" />
1616

1717

1818
<p>{{ meta.album }}<br>

src/app/components/snapcast-stream-preview/snapcast-stream-preview.component.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ export class SnapcastStreamPreviewComponent implements OnInit {
1414

1515
ngOnInit() {}
1616

17-
convertBase64ToImage(base64String: string, format: string): string {
18-
return `data:image/${format};base64,${base64String}`;
17+
18+
convertCoverDataBase64(coverData: string, extension: string): string {
19+
if (!coverData) {
20+
return '';
21+
}
22+
// Convert base64 data to a data URL
23+
return `data:image/${extension};base64,${coverData}`;
1924
}
2025

2126
}

src/app/pages/dashboard/dashboard.page.html

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,32 @@
1010
<ion-header collapse="condense">
1111
<span class="header-note">{{today | date:"EEEE, d.MMMM"}}</span>
1212
<ion-toolbar color="light" class="ion-margin-top">
13-
<ion-title size="large"><span *ngIf="userPreeferenceUsername as name">{{name ? name+"s": 'Your'}}</span> Home</ion-title>
13+
<ion-title size="large"><span *ngIf="userPreeferenceUsername as name">{{name ? name+"s": 'Your'}}</span>
14+
Home</ion-title>
1415

1516
<!-- <ion-title size="large"><span>{{user.displayName}}s Zuhause</span></ion-title> -->
1617
</ion-toolbar>
1718
</ion-header>
1819
<div class="fab-row row">
19-
<div class="col">
20-
<ion-fab>
21-
<ion-fab-button class="custom-fab-button" color="light" (click)="openModal()">
22-
<ion-icon name="volume-high-outline"></ion-icon>
23-
</ion-fab-button>
24-
</ion-fab>
20+
<div class="col ">
21+
<ion-fab-button class="custom-fab-button" color="light" (click)="openModal()">
22+
<ion-icon [name]="numberOfPlayingClients>0?'volume-high':'volume-high-outline'"></ion-icon>
23+
</ion-fab-button>
24+
<div class="fab-label">{{numberOfPlayingClients +"/"+ totalClients}} Client(s) playing</div>
25+
2526
</div>
2627
<div class="col">
27-
28-
<ion-fab>
29-
<ion-fab-button class="custom-fab-button" color="light" [routerLink]="'/tabs/menu/server-status'">
30-
<ion-icon name="wifi-outline"></ion-icon>
31-
</ion-fab-button>
32-
</ion-fab>
28+
<ion-fab-button class="custom-fab-button" color="light" [routerLink]="'/tabs/menu/server-status'">
29+
<ion-icon [name]="lastServerResponseDeltaInSeconds < 2400?'wifi':'wifi-outline'"></ion-icon>
30+
</ion-fab-button>
31+
<div class="fab-label">{{lastServerResponseDeltaInSeconds < 240?'Online':'Offline'}}: {{lastServerResponseTime | date: 'dd.MM.yy HH:mm'}}</div>
3332
</div>
3433
<div class="col">
35-
<ion-fab>
36-
<ion-fab-button class="custom-fab-button" color="light" (click)="getPlayerStatus()">
37-
<ion-icon name="musical-note-outline"></ion-icon>
38-
</ion-fab-button>
39-
</ion-fab>
34+
<ion-fab-button class="custom-fab-button" color="light" (click)="getPlayerStatus()">
35+
<ion-icon [name]="numberOfPlayingStreams>0?'musical-note':'musical-note-outline'"></ion-icon>
36+
</ion-fab-button>
37+
<div class="fab-label">{{numberOfPlayingStreams +"/"+ totalStreams}} Stream(s) playing</div>
38+
4039
</div>
4140

4241
</div>
@@ -102,7 +101,8 @@
102101
<ion-card-title>Loading...</ion-card-title>
103102
</ion-card-header>
104103
<ion-card-content>
105-
Please wait while the server status is being loaded. Checking url: <b>{{userPreferenceServerUrl? userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
104+
Please wait while the server status is being loaded. Checking url: <b>{{userPreferenceServerUrl?
105+
userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
106106
</ion-card-content>
107107
</div>
108108
<ng-template #noServer>
@@ -111,9 +111,11 @@
111111
<ion-card-title>No Server Available</ion-card-title>
112112
</ion-card-header>
113113
<ion-card-content>
114-
Please check your Beatnik Pi server connection. No Server found at <b>{{userPreferenceServerUrl? userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
114+
Please check your Beatnik Pi server connection. No Server found at <b>{{userPreferenceServerUrl?
115+
userPreferenceServerUrl:environment.snapcastServerUrl}}</b>
115116
<br>
116-
Wanna use a different server? Go to the <a routerLink="/tabs/menu/settings">Settings</a> page and change the Snapcast server URL.
117+
Wanna use a different server? Go to the <a routerLink="/tabs/menu/settings">Settings</a> page and change the
118+
Snapcast server URL.
117119
<br>
118120

119121
</ion-card-content>

src/app/pages/dashboard/dashboard.page.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,10 @@
3737
align-items: center;
3838
justify-content: center;
3939
height: 40vh;
40+
}
41+
42+
.fab-label{
43+
font-size: 8px;
44+
width: 80%;
45+
text-align: center;
4046
}

src/app/pages/dashboard/dashboard.page.ts

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component, OnInit } from '@angular/core';
22
import { ModalController } from '@ionic/angular';
3-
import { first, firstValueFrom, Observable } from 'rxjs';
4-
import { Group, ServerDetail, SnapCastServerStatusResponse, Stream } from 'src/app/model/snapcast.model';
3+
import { first, firstValueFrom, interval, Observable } from 'rxjs';
4+
import { Client, Group, ServerDetail, SnapCastServerStatusResponse, Stream } from 'src/app/model/snapcast.model';
55
import { SnapcastService } from 'src/app/services/snapcast.service';
66
import { SwiperOptions } from 'swiper';
77
import { environment } from 'src/environments/environment';
@@ -33,9 +33,16 @@ export class DashboardPage implements OnInit {
3333
userPreferenceServerUrl?: string;
3434
userPreeferenceUsername?: string;
3535

36-
// public groups$: Observable<Group[]>;
37-
// public streams$: Observable<Stream[]>;
38-
// public serverDetails$: Observable<ServerDetail | undefined>;
36+
hasAnyPlayingClients = false;
37+
hasPlayingStreams = false;
38+
numberOfPlayingClients = 0;
39+
numberOfPlayingStreams = 0;
40+
totalClients = 0;
41+
totalStreams = 0;
42+
lastServerResponseTime?: Date;
43+
lastServerResponseDeltaInSeconds?: number;
44+
45+
3946

4047
constructor(
4148
private snapcastService: SnapcastService,
@@ -48,7 +55,7 @@ export class DashboardPage implements OnInit {
4855
async ngOnInit() {
4956
// this.snapcastService.connect();
5057

51-
this.userPreferenceServerUrl = await this.getUserPreferenceServerUrl();
58+
this.userPreferenceServerUrl = await this.getUserPreferenceServerUrl();
5259
this.userPreeferenceUsername = await this.getUserName();
5360

5461
this.displayState = this.snapcastService.state$;
@@ -62,6 +69,20 @@ export class DashboardPage implements OnInit {
6269
});
6370
this.noServerTimeout();
6471

72+
this.displayState.subscribe(state => {
73+
if (state) {
74+
this.lastServerResponseTime = new Date();
75+
this.checkForPlayingStreamsAndClients();
76+
} else {
77+
console.warn('SnapcastStateIndicatorService: No state available to check for playing streams and clients.');
78+
}
79+
});
80+
81+
interval(1000).subscribe(() => {
82+
const today = new Date();
83+
this.lastServerResponseDeltaInSeconds = Math.floor((today.getTime() - (this.lastServerResponseTime?.getTime() || today.getTime())) / 1000);
84+
});
85+
6586
}
6687

6788
async ionViewWillEnter() {
@@ -138,6 +159,41 @@ export class DashboardPage implements OnInit {
138159
}
139160
}
140161

162+
async checkForPlayingStreamsAndClients(): Promise<void> {
163+
console.log('PlayerToolbarComponent: Checking for playing streams and clients...');
164+
const state = await firstValueFrom(this.displayState);
165+
if (!state) {
166+
console.warn('PlayerToolbarComponent: No state available to check for playing streams and clients.');
167+
return;
168+
}
169+
// Check if there are any playing streams
170+
this.hasPlayingStreams = state.server.streams.some(stream => stream.status === 'playing');
171+
console.log('PlayerToolbarComponent: hasPlayingStreams:', this.hasPlayingStreams);
172+
// Check if there are any playing clients, i.e., clients that have a playing stream asigned
173+
this.hasAnyPlayingClients = state.server.groups.some((group: Group) =>
174+
group.clients.some((client: Client) => {
175+
const clientStream = state.server.streams.find(stream => stream.id === group.stream_id);
176+
return client.connected && clientStream && clientStream.status === 'playing';
177+
})
178+
);
179+
console.log('PlayerToolbarComponent: hasAnyPlayingClients:', this.hasAnyPlayingClients);
180+
this.numberOfPlayingClients = state.server.groups.reduce((count, group) => {
181+
return count + group.clients.filter(client => client.connected && state.server.streams.some(stream => stream.id === group.stream_id && stream.status === 'playing')).length;
182+
}, 0);
183+
console.log('PlayerToolbarComponent: numberOfPlayingClients:', this.numberOfPlayingClients);
184+
this.numberOfPlayingStreams = state.server.streams.filter(stream => stream.status === 'playing').length;
185+
console.log('PlayerToolbarComponent: numberOfPlayingStreams:', this.numberOfPlayingStreams);
186+
187+
this.totalClients = state.server.groups.reduce((total, group) => total + group.clients.length, 0);
188+
console.log('PlayerToolbarComponent: totalClients:', this.totalClients);
189+
190+
this.totalStreams = state.server.streams.length;
191+
console.log('PlayerToolbarComponent: totalStreams:', this.totalStreams);
192+
193+
}
194+
195+
196+
141197

142198

143199

0 commit comments

Comments
 (0)