canti primo tag
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar class="bg-gradient">
|
||||
<ion-title class="outfit-font">
|
||||
<div class="header-logo-wrapper">
|
||||
<img src="assets/icon/favicon.png" class="header-logo">
|
||||
<div class="header-text-group">
|
||||
<span class="app-name">CantiCristiani</span>
|
||||
<span class="version-badge">v{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<div class="offline-badge-header" *ngIf="!connectivityService.isOnline()">
|
||||
<ion-icon name="cloud-offline-outline"></ion-icon>
|
||||
</div>
|
||||
|
||||
|
||||
<ion-button (click)="importPlaylist()" class="add-btn">
|
||||
<ion-icon slot="icon-only" name="qr-code-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button routerLink="/propose-canto" class="add-btn" *ngIf="!playlistService.selectionMode() && settingsService.showEditor()">
|
||||
<ion-icon slot="icon-only" name="add-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button routerLink="/settings" class="settings-btn">
|
||||
<ion-icon slot="icon-only" name="settings-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar class="bg-gradient" *ngIf="!playlistService.selectionMode() || isAddingSongs()">
|
||||
<div class="search-wrapper-group">
|
||||
<div class="search-row">
|
||||
<div class="search-wrapper glass">
|
||||
<ion-searchbar
|
||||
placeholder="Cerca un canto..."
|
||||
[value]="searchQuery()"
|
||||
(ionInput)="onSearch($event)"
|
||||
class="custom-searchbar">
|
||||
</ion-searchbar>
|
||||
<ion-button fill="clear" (click)="toggleVoiceSearch()" class="voice-search-btn">
|
||||
<ion-icon slot="icon-only" [name]="audioEngine.isSearching() ? 'mic' : 'mic-outline'" [color]="audioEngine.isSearching() ? 'danger' : 'secondary'"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-actions-row">
|
||||
<div class="song-count-card outfit-font">
|
||||
{{ filteredCanti().length }}
|
||||
</div>
|
||||
<div class="filter-buttons">
|
||||
<ion-button
|
||||
*ngIf="settingsService.showEditor()"
|
||||
[fill]="showOnlyMine() ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="toggleOnlyMine()"
|
||||
color="secondary"
|
||||
class="filter-chip">
|
||||
Miei
|
||||
<span class="close-icon-wrapper" *ngIf="showOnlyMine()" (click)="clearOnlyMine($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
|
||||
<ion-button
|
||||
[fill]="activeFilterType() === 'liturgico' || selectedLiturgico() !== null ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="toggleFilterType('liturgico')"
|
||||
class="filter-chip">
|
||||
{{ selectedLiturgico() !== null ? getSelectedLiturgicoLabel() : 'Liturgia' }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="selectedLiturgico() === null"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="selectedLiturgico() !== null" (click)="clearLiturgico($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
|
||||
<ion-button
|
||||
[fill]="activeFilterType() === 'tematico' || selectedTematico() !== null ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="toggleFilterType('tematico')"
|
||||
class="filter-chip">
|
||||
{{ selectedTematico() !== null ? getSelectedTematicoLabel() : 'Periodo' }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="selectedTematico() === null"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="selectedTematico() !== null" (click)="clearTematico($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
|
||||
<ion-button
|
||||
[fill]="activeFilterType() === 'playlist' || playlistService.activeListName() !== null ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="playlistService.playlists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
||||
class="filter-chip">
|
||||
{{ playlistService.activeListName() !== null ? playlistService.activeListName() : 'Playlist' }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="playlistService.activeListName() === null && playlistService.playlists().length > 0"></ion-icon>
|
||||
<ion-icon slot="end" name="add-circle-outline" *ngIf="playlistService.activeListName() === null && playlistService.playlists().length === 0"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="playlistService.activeListName() !== null" (click)="clearSpecialList($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-toolbar>
|
||||
|
||||
|
||||
<!-- Category Scroll (Dropdown style) -->
|
||||
<ion-toolbar class="bg-gradient momentos-toolbar" *ngIf="activeFilterType() || playlistService.selectionMode() || (playlistService.activeListName() && !playlistService.selectionMode())">
|
||||
<div class="momento-scroll">
|
||||
<!-- Selection Mode Actions -->
|
||||
<div class="selection-pill glass" *ngIf="playlistService.selectionMode()">
|
||||
<span class="selection-count">{{ playlistService.selectedIds().size }}</span>
|
||||
<ion-button fill="clear" color="secondary" (click)="finishSelection()" [disabled]="playlistService.selectedIds().size === 0" class="mini-action-btn">
|
||||
<ion-icon slot="icon-only" name="save-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" color="danger" (click)="cancelSelection()" class="mini-action-btn">
|
||||
<ion-icon slot="icon-only" name="close-circle-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<!-- Active Playlist Actions -->
|
||||
<div class="selection-pill glass" *ngIf="playlistService.activeListName() && !playlistService.selectionMode()">
|
||||
<ion-button fill="clear" color="secondary" (click)="editPlaylist()" class="mini-action-btn">
|
||||
<ion-icon slot="icon-only" name="create-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" color="secondary" (click)="shareActivePlaylist()" class="mini-action-btn">
|
||||
<ion-icon slot="icon-only" name="share-social-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" color="danger" (click)="deleteActivePlaylist()" class="mini-action-btn" *ngIf="playlistService.activePlaylistId()">
|
||||
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<!-- Filter chips -->
|
||||
<div
|
||||
*ngFor="let item of (activeFilterType() === 'playlist' ? playlistService.playlists() : (activeFilterType() === 'liturgico' ? cantiService.indiceLiturgico() : (activeFilterType() === 'tematico' ? cantiService.indiceTematico() : [])))"
|
||||
class="momento-chip glass"
|
||||
[class.active]="activeFilterType() === 'playlist' ? playlistService.activePlaylistId() === item.id : isIndexSelected(item.id)"
|
||||
(click)="activeFilterType() === 'playlist' ? selectPlaylist(item) : toggleIndex(item.id, activeFilterType()!)">
|
||||
{{ activeFilterType() === 'playlist' ? item.name : item.tag_name }}
|
||||
</div>
|
||||
</div>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="bg-gradient"
|
||||
(touchstart)="onTouchStart($event); onInteraction()"
|
||||
(touchmove)="onTouchMove($event)"
|
||||
(touchend)="onTouchEnd()"
|
||||
(click)="onInteraction()">
|
||||
|
||||
<div class="ion-padding no-padding-top">
|
||||
<div *ngIf="cantiService.loading() && filteredCanti().length === 0" class="ion-text-center ion-padding loading-container">
|
||||
<div class="loading-wrapper">
|
||||
<ion-spinner name="crescent" color="secondary"></ion-spinner>
|
||||
<div class="percentage-label outfit-font">{{ cantiService.progress() }}%</div>
|
||||
<p class="ion-margin-top" style="color: var(--ion-color-secondary)">Caricamento canti...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wrap in DragDrop only in Reorder mode -->
|
||||
<div cdkDropList
|
||||
[cdkDropListDisabled]="!playlistService.selectionMode() || isAddingSongs()"
|
||||
(cdkDropListDropped)="drop($event)"
|
||||
class="transparent-list">
|
||||
|
||||
<ion-item *ngFor="let canto of visibleCanti()"
|
||||
[id]="'canto-' + canto.id"
|
||||
class="glass ion-margin-bottom custom-item"
|
||||
[class.is-playing]="youtubePlayerService.currentCantoId() === canto.id"
|
||||
cdkDrag
|
||||
[cdkDragDisabled]="!playlistService.selectionMode() || isAddingSongs()">
|
||||
|
||||
<!-- Drag Handle (visible only in reorder mode) -->
|
||||
<div class="drag-handle" cdkDragHandle *ngIf="playlistService.selectionMode() && !isAddingSongs()">
|
||||
<ion-icon name="reorder-two-outline"></ion-icon>
|
||||
</div>
|
||||
|
||||
<div class="item-wrapper">
|
||||
<!-- Selection Area -->
|
||||
<div class="selection-column" (click)="playlistService.toggleSongSelection(canto.id); $event.stopPropagation()">
|
||||
<span class="canto-number" [class.selected-number]="playlistService.selectedIds().has(canto.id)">
|
||||
{{ canto.id.startsWith('my_') ? 'M' : canto.id_canti }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Content Area -->
|
||||
<div class="content-column" (click)="goToCanto(canto.id)">
|
||||
<div class="top-row">
|
||||
<ion-label class="info-section">
|
||||
<h2 class="outfit-font" style="font-weight: 500; color: var(--ion-color-secondary)">
|
||||
{{ canto.titolo }}
|
||||
</h2>
|
||||
<p style="color: rgba(255,255,255,0.6); margin-bottom: 2px;">{{ canto.autore || 'Autore sconosciuto' }}</p>
|
||||
</ion-label>
|
||||
|
||||
<!-- Video Thumbnail Section -->
|
||||
<div *ngIf="canto.link_youtube && canto.link_youtube.length > 5" class="thumb-section" (click)="$event.stopPropagation()">
|
||||
<div class="thumb-container compact">
|
||||
<ng-container *ngIf="connectivityService.isOnline(); else offlineThumb">
|
||||
<div class="thumb-wrapper" (click)="playVideo($event, canto.id)">
|
||||
<img [src]="cantiService.getYoutubeThumb(canto.link_youtube)"
|
||||
class="thumb-img loaded"
|
||||
loading="lazy">
|
||||
<div class="play-overlay youtube-overlay">
|
||||
<ion-icon name="play-sharp"></ion-icon>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #offlineThumb>
|
||||
<div class="thumb-wrapper">
|
||||
<div class="offline-thumb">
|
||||
<ion-icon name="cloud-offline-outline"></ion-icon>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Button (only for local songs) -->
|
||||
<div *ngIf="canto.id.startsWith('my_')" class="delete-section" (click)="$event.stopPropagation()">
|
||||
<ion-button fill="clear" color="danger" (click)="deleteMyCanto(canto.id, $event)">
|
||||
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player bars removed from here -->
|
||||
</div>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<!-- Add songs / Toggle View Button -->
|
||||
<div class="ion-text-center ion-margin-top" *ngIf="playlistService.selectionMode()">
|
||||
<ion-button fill="solid" color="secondary" (click)="toggleAddingSongs()" class="action-mode-btn glass">
|
||||
<ion-icon slot="start" [name]="isAddingSongs() ? 'list-outline' : 'add-circle-outline'"></ion-icon>
|
||||
{{ isAddingSongs() ? 'Torna alla Playlist' : 'Aggiungi altri canti' }}
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!cantiService.loading() && filteredCanti().length === 0" class="ion-text-center ion-padding">
|
||||
<p style="color: rgba(255,255,255,0.5)">Nessun canto trovato.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ion-infinite-scroll (ionInfinite)="loadData($event)" threshold="150px" [disabled]="limit() >= filteredCanti().length || (playlistService.selectionMode() && !isAddingSongs())">
|
||||
<ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="Caricamento altri canti...">
|
||||
</ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
|
||||
</ion-content>
|
||||
|
||||
<ion-footer *ngIf="youtubePlayerService.currentCantoId()" class="ion-no-border">
|
||||
<ion-toolbar class="global-player-toolbar glass">
|
||||
<div class="player-content">
|
||||
<div class="controls-row">
|
||||
<ion-button fill="clear" color="secondary" (click)="playPrevPreview($event)" class="skip-btn">
|
||||
<ion-icon slot="icon-only" name="play-skip-back-sharp"></ion-icon>
|
||||
</ion-button>
|
||||
|
||||
<ion-button fill="clear" color="secondary" (click)="youtubePlayerService.isPlaying() ? stopVideo($event) : playVideo($event, youtubePlayerService.currentCantoId()!)" class="play-btn">
|
||||
<ion-icon slot="icon-only" [name]="youtubePlayerService.isPlaying() ? 'pause-sharp' : 'play-sharp'"></ion-icon>
|
||||
</ion-button>
|
||||
|
||||
<ion-range
|
||||
[min]="0"
|
||||
[max]="youtubePlayerService.videoDuration()"
|
||||
[value]="youtubePlayerService.videoProgress()"
|
||||
(ionChange)="onSeek($event)"
|
||||
(ionKnobMoveStart)="onSeekStart()"
|
||||
(ionKnobMoveEnd)="onSeekEnd()"
|
||||
color="secondary"
|
||||
class="global-range">
|
||||
</ion-range>
|
||||
|
||||
<ion-button fill="clear" color="secondary" (click)="playNextPreview($event)" class="skip-btn">
|
||||
<ion-icon slot="icon-only" name="play-skip-forward-sharp"></ion-icon>
|
||||
</ion-button>
|
||||
|
||||
<ion-button fill="clear" color="medium" (click)="stopVideo($event)" class="close-btn">
|
||||
<ion-icon slot="icon-only" name="close-circle-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
Reference in New Issue
Block a user