Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aa49e47
Add cloud skeleton
digimezzo Dec 22, 2025
d44d04f
Sizing kind of works
digimezzo Dec 22, 2025
fa9314c
Middle margin is dynamic
digimezzo Dec 22, 2025
61ce9e7
Start renaming squares
digimezzo Dec 23, 2025
49ae77f
All numbers added
digimezzo Dec 24, 2025
0ac0a51
Finished squares
digimezzo Dec 25, 2025
c1d7103
Show album images
digimezzo Dec 25, 2025
c435755
Improving the layout
digimezzo Dec 25, 2025
5c84f86
Radial background
digimezzo Dec 25, 2025
12927b8
Improve backdrop gradient
digimezzo Dec 25, 2025
c8d6066
Add shadow
digimezzo Dec 26, 2025
80e8445
Clickable
digimezzo Dec 26, 2025
0129ee3
Add clickable
digimezzo Dec 26, 2025
fda0135
Add album playback on click
digimezzo Dec 26, 2025
caa1074
Add album playback on click
digimezzo Dec 26, 2025
ad9d507
Add translations
digimezzo Dec 26, 2025
75260c3
Merge branch 'feature/961_albumCloud' of github.com:digimezzo/dopamin…
digimezzo Dec 26, 2025
9e04b1b
Many fixes
digimezzo Dec 27, 2025
3fa606b
Refactor album placeholder component for improved structure and styling
digimezzo Dec 28, 2025
4e0ac7e
Fix drop shadow
digimezzo Dec 28, 2025
a6c809c
Refactor album placeholder component to enhance animations and structure
digimezzo Dec 28, 2025
8d9e90e
Enhance HighlightsComponent with animation key and subscription manag…
digimezzo Dec 28, 2025
1abb8a7
Refactor HighlightsComponent to manage playback state and trigger ani…
digimezzo Dec 29, 2025
2bb2b4b
Update screenshots
digimezzo Dec 29, 2025
b7fba29
Add new screenshot assets for album feature
digimezzo Dec 29, 2025
c478e49
Refactor square base unit calculation for responsive design in highli…
digimezzo Dec 29, 2025
c15ad42
Remove unnecessary *ngIf directive from app-highlights__content for c…
digimezzo Dec 29, 2025
81b9565
Refactor app-highlights__content background gradients to use responsi…
digimezzo Dec 29, 2025
2cb5f6f
Remove padding
digimezzo Dec 29, 2025
d322317
Center album cloud
digimezzo Dec 30, 2025
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
File renamed without changes
Binary file added Dopamine.screenshot.4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ Dopamine icons created by <a href="https://www.itssharl.ee/">Sharlee</a>.
[![Issues](https://img.shields.io/github/issues/digimezzo/dopamine.svg?style=flat-square)](https://github.com/digimezzo/dopamine/issues)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MQALEWTEZ7HX8)

![Dopaminescreenshot](Dopamine.screenshot.png)
![Dopaminescreenshot.1](Dopamine.screenshot.1.png)

![Dopaminescreenshot 2](Dopamine.screenshot.2.png)

![Dopaminescreenshot 4](Dopamine.screenshot.4.png)

![Dopaminescreenshot 3](Dopamine.screenshot.3.png)

## Debugging
Expand Down
5 changes: 5 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NowPlayingComponent } from './ui/components/now-playing/now-playing.com
import { SettingsComponent } from './ui/components/settings/settings.component';
import { WelcomeComponent } from './ui/components/welcome/welcome.component';
import { CoverPlayerComponent } from './ui/components/mini-players/cover-player/cover-player.component';
import { HighlightsComponent } from './ui/components/highlights/highlights.component';

const routes: Routes = [
{
Expand All @@ -30,6 +31,10 @@ const routes: Routes = [
path: 'nowplaying',
component: NowPlayingComponent,
},
{
path: 'highlights',
component: HighlightsComponent,
},
{
path: 'managecollection',
component: ManageCollectionComponent,
Expand Down
4 changes: 4 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ import { VolumeIconComponent } from './ui/components/volume-icon/volume-icon.com
import { EditTracksDialogComponent } from './ui/components/dialogs/edit-tracks-dialog/edit-tracks-dialog.component';
import { InfoDialogComponent } from './ui/components/dialogs/info-dialog/info-dialog.component';
import { IterableMenuComponent } from './ui/components/common/iterable-menu.component';
import { HighlightsComponent } from './ui/components/highlights/highlights.component';
import { AlbumPlaceholderComponent } from './ui/components/highlights/album-placeholder.component';

export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
Expand Down Expand Up @@ -413,6 +415,8 @@ export function settingsInitializerFactory(settings: SettingsBase) {
TrackBrowserComponent,
PlaybackQueueComponent,
NowPlayingComponent,
HighlightsComponent,
AlbumPlaceholderComponent,
NowPlayingPlaybackPaneComponent,
SearchBoxComponent,
PlaylistFolderBrowserComponent,
Expand Down
1 change: 1 addition & 0 deletions src/app/common/application/i18n.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ describe('validate i18n', () => {
'expanded-view',
'cannot-play-audio-file',
'cannot-play-m4a-file',
'album-cloud',
].sort();

Constants.languages.forEach((language) => {
Expand Down
1 change: 1 addition & 0 deletions src/app/data/entities/album-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export class AlbumData {
public dateFileCreated: number | undefined;
public dateAdded: number | undefined;
public dateLastPlayed: number | undefined;
public playCount: number | undefined;
}
3 changes: 2 additions & 1 deletion src/app/data/query-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export class QueryParts {
GROUP_CONCAT(distinct t.Genres) AS genres,
MAX(t.DateFileCreated) AS dateFileCreated,
MAX(t.DateAdded) AS dateAdded,
MAX(t.DateLastPlayed) AS dateLastPlayed FROM Track t
MAX(t.DateLastPlayed) AS dateLastPlayed,
SUM(t.PlayCount) AS playCount FROM Track t
LEFT JOIN AlbumArtwork a ON t.AlbumKey${albumKeyIndex}=a.AlbumKey`;

if (onlyVisibleAlbumData) {
Expand Down
1 change: 1 addition & 0 deletions src/app/data/repositories/track-repository.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export abstract class TrackRepositoryBase {
public abstract getTracksForPaths(paths: string[]): Track[] | undefined;
public abstract getAlbumDataForAlbumKey(albumKeyIndex: string, albumKey: string): AlbumData[] | undefined;
public abstract getAllAlbumData(albumKeyIndex: string): AlbumData[] | undefined;
public abstract getMostPlayedAlbumData(albumKeyIndex: string, limit: number): AlbumData[] | undefined;
public abstract getAlbumDataForTrackArtists(albumKeyIndex: string, trackArtists: string[]): AlbumData[] | undefined;
public abstract getAlbumDataForAlbumArtists(albumKeyIndex: string, albumArtists: string[]): AlbumData[] | undefined;
public abstract getAlbumDataForGenres(albumKeyIndex: string, genres: string[]): AlbumData[] | undefined;
Expand Down
16 changes: 16 additions & 0 deletions src/app/data/repositories/track-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,22 @@ export class TrackRepository implements TrackRepositoryBase {
return albumData;
}

public getMostPlayedAlbumData(albumKeyIndex: string, limit: number): AlbumData[] | undefined {
const database: any = this.databaseFactory.create();

const statement = database.prepare(
`${QueryParts.selectAlbumDataQueryPart(albumKeyIndex, true)}
AND t.playCount > 0 AND t.AlbumKey IS NOT NULL AND t.AlbumKey <> ''
GROUP BY t.AlbumKey${albumKeyIndex}
ORDER BY playCount DESC
LIMIT @limit;`,
);

const albumData: AlbumData[] | undefined = statement.all({ limit: limit });

return albumData;
}

public getAlbumDataForTrackArtists(albumKeyIndex: string, trackArtists: string[]): AlbumData[] | undefined {
const database: any = this.databaseFactory.create();

Expand Down
4 changes: 4 additions & 0 deletions src/app/services/album/album-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,8 @@ export class AlbumModel implements ISelectable {
public get dateLastPlayedInTicks(): number {
return this.albumData.dateLastPlayed ?? 0;
}

public get playCount(): number {
return this.albumData.playCount ?? 0;
}
}
3 changes: 2 additions & 1 deletion src/app/services/album/album-service.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export abstract class AlbumServiceBase {
public abstract getAllAlbums(): AlbumModel[];
public abstract getAlbumsForArtists(artists: ArtistModel[], artistType: ArtistType): AlbumModel[];
public abstract getAlbumsForGenres(genres: string[]): AlbumModel[];
}
public abstract getMostPlayedAlbums(numberOfAlbums: number): AlbumModel[];
}
14 changes: 14 additions & 0 deletions src/app/services/album/album-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ export class AlbumService implements AlbumServiceBase {
return albums;
}

public getMostPlayedAlbums(numberOfAlbums: number): AlbumModel[] {
const timer = new Timer();
timer.start();

const albumDatas: AlbumData[] = this.trackRepository.getMostPlayedAlbumData(this.settings.albumKeyIndex, numberOfAlbums) ?? [];
const albums: AlbumModel[] = this.createAlbumsFromAlbumData(albumDatas);

timer.stop();

this.logger.info(`Finished getting most played albums. Time required: ${timer.elapsedMilliseconds} ms`, 'AlbumService', 'getMostPlayedAlbums');

return albums;
}

private addAlbumsForTrackOrAllArtists(albumKeyIndex: string, artists: string[], albumDatas: AlbumData[]): void {
const trackArtistsAlbumDatas: AlbumData[] = this.trackRepository.getAlbumDataForTrackArtists(albumKeyIndex, artists) ?? [];

Expand Down
1 change: 1 addition & 0 deletions src/app/services/navigation/navigation.service.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export abstract class NavigationServiceBase {
public abstract navigateToWelcomeAsync(): Promise<void>;
public abstract navigateToManageCollectionAsync(): Promise<void>;
public abstract navigateToNowPlayingAsync(): Promise<void>;
public abstract navigateToHighlightsAsync(): Promise<void>;
public abstract navigateToCoverPlayerAsync(): Promise<void>;
public abstract showPlaybackQueue(): void;
public abstract refreshPlaybackQueueList(): void;
Expand Down
4 changes: 4 additions & 0 deletions src/app/services/navigation/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export class NavigationService implements NavigationServiceBase {
await this.router.navigate(['/nowplaying']);
}

public async navigateToHighlightsAsync(): Promise<void> {
await this.router.navigate(['/highlights']);
}

public async navigateToCoverPlayerAsync(): Promise<void> {
await this.router.navigate(['/coverplayer']);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
<div class="app-collection-playback-pane-controls__right">
<app-playback-time class="mr-4"></app-playback-time>
<i
class="app-collection-playback-pane-controls__buttons las la-music pointer mr-2"
(click)="this.showNowPlayingAsync()"
matTooltip="{{ 'now-playing' | translate }}"
class="app-collection-playback-pane-controls__buttons las la-chart-pie pointer mr-2"
(click)="this.showHighlightsAsync()"
matTooltip="{{ 'highlights' | translate }}"
></i>
<i
class="app-collection-playback-pane-controls__buttons las la-tasks pointer mr-4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ export class CollectionPlaybackPaneComponent {
public async showNowPlayingAsync(): Promise<void> {
await this.navigationService.navigateToNowPlayingAsync();
}

public async showHighlightsAsync(): Promise<void> {
await this.navigationService.navigateToHighlightsAsync();
}
}
35 changes: 35 additions & 0 deletions src/app/ui/components/highlights/album-placeholder.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div
class="square"
[ngClass]="squareClass"
[class.clickable]="album?.artworkPath"
[style.animation-delay.s]="animationDelay"
style="
animation:
shadowFadeIn 2.4s ease-out forwards,
albumFadeZoom 1.2s ease-out forwards;
"
(click)="onAlbumClick()"
>
<div class="app-album__cover__placeholder" *ngIf="!album?.artworkPath">
<svg width="256" height="256" viewBox="0 0 256 256">
<defs>
<linearGradient id="coverGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="30%" stop-color="var(--theme-primary-color)" />
<stop offset="100%" stop-color="var(--theme-secondary-color)" />
</linearGradient>
</defs>

<path
d="M 208.79492 0 L 208.79492 85.669922 A 104.26329 104.26329 0 0 0 128.05273 47.373047 A 104.26329 104.26329 0 0 0 23.789062 151.63672 A 104.26329 104.26329 0 0 0 128.05273 255.90039 A 104.26329 104.26329 0 0 0 232.31641 151.63672 A 104.26329 104.26329 0 0 0 232.30859 150.50391 L 232.31055 150.50391 L 232.31055 0 L 208.79492 0 z M 128.05273 71.195312 A 80.750832 80.441704 0 0 1 208.79492 150.50391 A 80.750832 80.441704 0 0 1 208.80273 151.63672 A 80.750832 80.441704 0 0 1 128.05273 232.07812 A 80.750832 80.441704 0 0 1 47.302734 151.63672 A 80.750832 80.441704 0 0 1 128.05273 71.195312 z M 127.9707 134.98047 A 16.65657 16.65657 0 0 0 111.39648 151.63672 A 16.65657 16.65657 0 0 0 128.05273 168.29297 A 16.65657 16.65657 0 0 0 144.70898 151.63672 A 16.65657 16.65657 0 0 0 127.9707 134.98047 A 16.65657 16.65657 0 0 0 127.9707 134.98047 z"
fill="url(#coverGradient)"
/>
</svg>
</div>
<div class="square-image-container">
<img *ngIf="album?.artworkPath" class="square-image" [src]="album?.artworkPath" />
<div class="play-count-overlay" *ngIf="album?.playCount">
<i class="las la-play"></i>
<span>{{ album?.playCount }}</span>
</div>
</div>
</div>
121 changes: 121 additions & 0 deletions src/app/ui/components/highlights/album-placeholder.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
.square {
position: absolute;
background: transparent;
aspect-ratio: 1 / 1;
overflow: hidden;
border-radius: 8px;
box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0),
0 6px 20px 0 rgba(0, 0, 0, 0);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform: scale(0.8);
}

.square.clickable {
cursor: pointer;
transition: transform 0.1s ease;
}

.square.clickable:active {
transform: scale(0.95) !important;
}

.square-image-container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: block;
}

.square-image {
position: absolute;
object-fit: cover;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
}

.play-count-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
opacity: 0;
transition: opacity 0.2s ease;
border-radius: 8px;
}

.play-count-overlay span {
color: white;
font-size: 16px;
}

.square:hover .play-count-overlay {
opacity: 1;
}

.play-count-overlay i {
font-size: 22px;
}

@keyframes albumFadeZoom {
0% {
opacity: 0;
transform: scale(0.8);
}
50% {
opacity: 1;
}
100% {
opacity: 1;
transform: scale(1);
}
}

@keyframes shadowFadeIn {
0% {
box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0),
0 6px 20px 0 rgba(0, 0, 0, 0);
}
50% {
box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0),
0 6px 20px 0 rgba(0, 0, 0, 0);
}
100% {
box-shadow:
0 4px 8px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.2);
}
}

.app-album__cover__placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--theme-album-cover-background);
}

.app-album__cover__placeholder svg {
width: 60%;
height: 60%;
}
20 changes: 20 additions & 0 deletions src/app/ui/components/highlights/album-placeholder.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
import { AlbumModel } from '../../../services/album/album-model';

@Component({
selector: 'app-album-placeholder',
templateUrl: './album-placeholder.component.html',
styleUrls: ['./album-placeholder.component.scss'],
host: { style: 'display: block; width: 100%;height: 100%' },
encapsulation: ViewEncapsulation.None,
})
export class AlbumPlaceholderComponent {
@Input() album: AlbumModel | undefined;
@Input() animationDelay: number = 0;
@Input() squareClass: string = '';
@Output() albumClick = new EventEmitter<AlbumModel | undefined>();

public onAlbumClick(): void {
this.albumClick.emit(this.album);
}
}
Loading
Loading