diff --git a/apps/web/src/app/services/settings-store.service.ts b/apps/web/src/app/services/settings-store.service.ts
index 4a4172e57..12719bed9 100644
--- a/apps/web/src/app/services/settings-store.service.ts
+++ b/apps/web/src/app/services/settings-store.service.ts
@@ -29,6 +29,7 @@ const DEFAULT_SETTINGS: Settings = {
remoteControl: false,
remoteControlPort: 8765,
epgUrl: [],
+ downloadFolder: '',
};
export const SettingsStore = signalStore(
@@ -79,12 +80,12 @@ export const SettingsStore = signalStore(
remoteControl: store.remoteControl(),
remoteControlPort: store.remoteControlPort(),
epgUrl: store.epgUrl(),
- downloadFolder: store.downloadFolder?.(),
+ downloadFolder: store.downloadFolder(),
};
},
getDownloadFolder() {
- return store.downloadFolder?.();
+ return store.downloadFolder();
},
getPlayer() {
diff --git a/apps/web/src/app/xtream-tauri/downloads/downloads.component.ts b/apps/web/src/app/xtream-tauri/downloads/downloads.component.ts
index 09e9b10c3..eb282a930 100644
--- a/apps/web/src/app/xtream-tauri/downloads/downloads.component.ts
+++ b/apps/web/src/app/xtream-tauri/downloads/downloads.component.ts
@@ -6,9 +6,9 @@ import {
OnInit,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
-import { MatIconModule } from '@angular/material/icon';
+import { MatIcon } from '@angular/material/icon';
import { MatProgressBarModule } from '@angular/material/progress-bar';
-import { MatTooltipModule } from '@angular/material/tooltip';
+import { MatTooltip } from '@angular/material/tooltip';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslatePipe } from '@ngx-translate/core';
import {
@@ -23,9 +23,9 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
MatButtonModule,
- MatIconModule,
+ MatIcon,
MatProgressBarModule,
- MatTooltipModule,
+ MatTooltip,
TranslatePipe,
],
})
diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json
index fb4a0e27b..f93bfe454 100644
--- a/apps/web/src/assets/i18n/en.json
+++ b/apps/web/src/assets/i18n/en.json
@@ -223,11 +223,18 @@
"NO_FAVORITES": "No favorites yet",
"USE_STAR_TO_FAVORITE": "Please use 'star' icon to add channels to your favorites.",
"REMOVE_FAVORITE": "Remove from favorites",
+ "ADD_FAVORITE": "Add to favorites",
"FAVORITES_UPDATED": "Favorites were updated!",
"SORT_BY": "Sort by",
"SORT_BY_DATE": "Sort by Date Added (Newest First)",
"SORT_BY_RATING": "Sort by Rating",
- "SORT_BY_NAME": "Sort by Name (A-Z)"
+ "SORT_BY_NAME": "Sort by Name (A-Z)",
+ "LOAD_MORE": "Load more",
+ "REMAINING": "remaining",
+ "LOADING": "Loading channels...",
+ "LOADING_MORE": "Loading more...",
+ "NO_SEARCH_RESULTS": "No channels found",
+ "TRY_DIFFERENT_SEARCH": "Try a different search term"
},
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "Open channel list",
diff --git a/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.html b/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.html
new file mode 100644
index 000000000..71ed28750
--- /dev/null
+++ b/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+ @if (enrichedChannels().length === 0 && searchTerm()) {
+
+
search_off
+
+ {{ 'CHANNELS.NO_SEARCH_RESULTS' | translate }}
+
+
+ {{ 'CHANNELS.TRY_DIFFERENT_SEARCH' | translate }}
+
+
+ } @else {
+
+
+
+ }
+
diff --git a/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.scss b/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.scss
new file mode 100644
index 000000000..3cb935f40
--- /dev/null
+++ b/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.scss
@@ -0,0 +1,89 @@
+@use '@angular/material' as mat;
+
+.scroll-viewport {
+ min-height: calc(100vh - 190px);
+ overflow-x: hidden;
+}
+
+.search-panel {
+ padding: 10px 0 0 0;
+ display: flex;
+ justify-content: center;
+}
+
+.search-bar {
+ @include mat.form-field-density(-5);
+
+ & {
+ width: 96%;
+ }
+}
+
+#all-channels {
+ overflow: hidden;
+ overflow-x: hidden;
+ max-height: calc(100vh - 167px);
+}
+
+.channel-list {
+ overflow-x: hidden;
+}
+
+::ng-deep {
+ .search-bar {
+ div {
+ border-radius: 20px;
+ }
+ }
+
+ div[matformfieldlineripple],
+ .mat-mdc-form-field-subscript-wrapper {
+ display: none;
+ }
+}
+
+[type='search']::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ cursor: pointer;
+ appearance: none;
+ height: 10px;
+ width: 10px;
+ background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCA5NiA5NiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOTYgOTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBvbHlnb24gZmlsbD0iI2VlZSIgcG9pbnRzPSI5NiwxNCA4MiwwIDQ4LDM0IDE0LDAgMCwxNCAzNCw0OCAwLDgyIDE0LDk2IDQ4LDYyIDgyLDk2IDk2LDgyIDYyLDQ4ICIvPg0KPC9zdmc+DQo=');
+ background-size: 10px 10px;
+ color: #fff;
+}
+
+.no-results {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 48px 24px;
+ text-align: center;
+ height: calc(100vh - 220px);
+}
+
+.no-results-icon {
+ font-size: 48px;
+ width: 48px;
+ height: 48px;
+ opacity: 0.5;
+ margin-bottom: 16px;
+}
+
+.no-results-title {
+ font-size: 1.1rem;
+ font-weight: 500;
+ margin-bottom: 8px;
+}
+
+.no-results-hint {
+ opacity: 0.7;
+ font-size: 0.9rem;
+}
+
+@media only screen and (max-width: 599px) {
+ .scroll-viewport {
+ min-height: calc(50vh - 159px);
+ }
+}
diff --git a/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.ts b/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.ts
new file mode 100644
index 000000000..9ed052b8d
--- /dev/null
+++ b/libs/ui/components/src/lib/channel-list-container/all-channels-tab/all-channels-tab.component.ts
@@ -0,0 +1,181 @@
+import { ScrollingModule } from '@angular/cdk/scrolling';
+import { CommonModule } from '@angular/common';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ ElementRef,
+ HostListener,
+ input,
+ OnDestroy,
+ output,
+ signal,
+ viewChild,
+} from '@angular/core';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { TranslatePipe } from '@ngx-translate/core';
+import { Channel, EpgProgram } from 'shared-interfaces';
+import { ChannelListItemComponent } from '../channel-list-item/channel-list-item.component';
+
+/** Enriched channel with pre-computed EPG and progress data */
+export interface EnrichedChannel extends Channel {
+ epgProgram: EpgProgram | null | undefined;
+ progressPercentage: number;
+}
+
+@Component({
+ selector: 'app-all-channels-tab',
+ templateUrl: './all-channels-tab.component.html',
+ styleUrls: ['./all-channels-tab.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ ChannelListItemComponent,
+ CommonModule,
+ MatFormFieldModule,
+ MatIconModule,
+ MatInputModule,
+ ScrollingModule,
+ TranslatePipe,
+ ],
+})
+export class AllChannelsTabComponent implements OnDestroy {
+ /** All channels (will be filtered by search) */
+ readonly channels = input.required();
+
+ /** EPG map for channel enrichment */
+ readonly channelEpgMap = input.required