From c973541aa48b0c7c09bb46fbb51d5210d368c226 Mon Sep 17 00:00:00 2001 From: 69pmb Date: Sun, 29 Oct 2023 00:08:51 +0200 Subject: [PATCH] feat: movie manager --- .../movie-detail/movie-detail.component.html | 500 +++++++++--------- .../movie-detail/movie-detail.component.ts | 236 +++++---- .../release/component/release.component.html | 1 - .../season-detail/season-detail.component.ts | 18 +- .../serie-detail/serie-detail.component.ts | 14 +- src/app/manager/abstract.manager.ts | 6 +- src/app/manager/movie.manager.ts | 40 ++ src/app/manager/season.manager.ts | 9 +- src/app/manager/serie.manager.ts | 5 +- src/app/service/movie.service.ts | 109 ++-- 10 files changed, 495 insertions(+), 443 deletions(-) create mode 100644 src/app/manager/movie.manager.ts diff --git a/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.html b/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.html index 93ec5d4d4..ba5974d0c 100644 --- a/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.html +++ b/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.html @@ -1,272 +1,278 @@ -
- - - -
-

- {{ movie.title }} ({{ movie.date | date: 'yyyy' }}) -

- -

- {{ movie.time | convertToHHmm: 'true' }} -

-
- - - - - -
-
- -
- - - -
- {{ movie.id }} -
- -
- {{ movie.original_title | capitalizeWord }} -
-
-
- + +
+ + + +
+

+ {{ movie.title }} ({{ movie.date | date: 'yyyy' }}) +

+ +

+ {{ movie.time | convertToHHmm: 'true' }} +

+
+ + + class="btn btn-outline-primary" + appAddCollection + [datas]="[movie]" + [label]="'global.add_collection'" + [isSingleData]="true" + [isMovie]="true" + > +
- -
- {{ alternative.lang }}: {{ alternative.title }} -
-
- -
- - -

-
-
- -
- + +
+ + + +
+ {{ movie.id }} +
+ +
+ {{ movie.original_title | capitalizeWord }} +
+
+
+ + + +
+ +
+ {{ alternative.lang }}: {{ alternative.title }} +
+
+
+
-
+ + +

+
+
+ +
+ +
+
+

+
+
+

-
+
- -

-
-
- -
- - -
- {{ 'release_type.' + release.type | translate }}: - {{ - release.date - | date: 'dd MMMM + +
+ + +
+ {{ 'release_type.' + release.type | translate }}: + {{ + release.date + | date: 'dd MMMM yyyy':'':translate.currentLang + | capitalizeWord + }} +
+
+ + {{ + movie.date + | date: 'EEEE dd MMMM yyyy':'':translate.currentLang | capitalizeWord }} -
- - - {{ - movie.date - | date: 'EEEE dd MMMM yyyy':'':translate.currentLang - | capitalizeWord - }} - -
- -
- {{ movie.budget | currency: 'USD':'':'1.0-0':translate.currentLang }} -
- -
- {{ movie.recette | currency: 'USD':'':'1.0-0':translate.currentLang }} -
- -
- - -
- -
- -
- + +
+ +
+ {{ movie.budget | currency: 'USD':'':'1.0-0':translate.currentLang }} +
+ +
+ {{ movie.recette | currency: 'USD':'':'1.0-0':translate.currentLang }} +
+ +
+ + + + +
+ +
+ +
{{ genre.name | capitalizeWord }} - / - + {{ genre.name | capitalizeWord }} + / + +
-
- -
- -
- + +
+ +
{{ keyword.name | capitalizeWord }} - / - + {{ keyword.name | capitalizeWord }} + / + +
+ +
+ +
{{ movie.overview }}
+
+ +
+ +
+ + + + + +
- -
- -
{{ movie.overview }}
-
- -
- +
+ + + + + + + +
- - - - - - -
-
- - - - - - - -
-
+
diff --git a/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.ts b/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.ts index 6d11c8c66..5241ee711 100644 --- a/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.ts +++ b/src/app/application-modules/movie-detail/component/movie-detail/movie-detail.component.ts @@ -1,17 +1,14 @@ -import {TranslateService, LangChangeEvent} from '@ngx-translate/core'; +import {TranslateService} from '@ngx-translate/core'; +import {Component, Input, Output, EventEmitter} from '@angular/core'; +import {ActivatedRoute, Router, Params} from '@angular/router'; import { - Component, - OnInit, - OnDestroy, - Input, - OnChanges, - SimpleChanges, - Output, - EventEmitter, -} from '@angular/core'; -import {ActivatedRoute, Router, ParamMap} from '@angular/router'; -import {filter} from 'rxjs/operators'; -import {combineLatest, Subscription} from 'rxjs'; + filter, + tap, + map, + switchMap, + distinctUntilChanged, +} from 'rxjs/operators'; +import {combineLatest, merge, ReplaySubject} from 'rxjs'; import { faImage, faChevronCircleRight, @@ -22,32 +19,127 @@ import { import {Tag} from './../../../../model/tag'; import {DuckDuckGo} from './../../../../constant/duck-duck-go'; import {Movie} from '../../../../model/movie'; -import {Keyword, Genre, DetailConfig} from '../../../../model/model'; -import {MovieService} from '../../../../service/movie.service'; +import {DetailConfig, Id} from '../../../../model/model'; import {TitleService} from '../../../../service/title.service'; import {TabsService} from '../../../../service/tabs.service'; import {MyTagsService} from '../../../../service/my-tags.service'; import {MyDatasService} from '../../../../service/my-datas.service'; import {MenuService} from '../../../../service/menu.service'; +import {MovieManager} from '../../../../manager/movie.manager'; @Component({ selector: 'app-movie-detail', styleUrls: ['./movie-detail.component.scss'], templateUrl: './movie-detail.component.html', }) -export class MovieDetailComponent implements OnInit, OnChanges, OnDestroy { - @Input() id!: number; - @Input() config!: DetailConfig; +export class MovieDetailComponent { + private readonly id$ = new ReplaySubject(1); + private readonly config$ = new ReplaySubject(1); + + @Input() + set id(value: number) { + this.isDetail = false; + this.config$.next( + new DetailConfig( + true, + true, + false, + false, + false, + false, + true, + false, + false, + undefined + ) + ); + this.loaded.emit(false); + this.id$.next(value); + } + + ids$ = merge( + this.movieManager.listenParam(this.route.paramMap, 'id').pipe( + filter(id => id !== 0), + tap(() => { + this.isDetail = true; + this.config$.next( + new DetailConfig( + true, + true, + true, + true, + true, + true, + true, + true, + false, + undefined + ) + ); + }) + ), + this.id$ + ); + + movie$ = this.config$.pipe( + distinctUntilChanged( + (prev, curr) => + prev.similar === curr.similar && prev.keywords === curr.keywords + ), + switchMap(config => + this.movieManager.find(this.ids$, config).pipe( + tap(movie => { + this.loaded.emit(true); + if (this.isDetail) { + this.title.setTitle(movie.title); + this.menuService.scrollTo$.next(0); + } + }) + ) + ) + ); + + loading$ = combineLatest([this.ids$, this.movie$]).pipe( + map(([i, m]) => !m || i !== m.id) + ); + + tags$ = combineLatest([ + this.myTagsService.myTags$, + this.myDatasService.myMovies$, + this.movie$, + ]).pipe( + filter( + ([tags, movies, movie]) => + tags !== undefined && + tags.length > 0 && + movies !== undefined && + movie !== undefined + ), + map(([tags, movies, movie]) => { + this.showTags = false; + if (movies.map(m => m.id).includes(movie.id)) { + this.showTags = true; + return tags.filter(t => + t.datas + .filter(d => !d.movie) + .map(d => d.id) + .includes(movie.id) + ); + } else { + return []; + } + }) + ); + @Output() loaded = new EventEmitter(); movie!: Movie; tags: Tag[] = []; showTags = false; isImagesVisible = false; - isDetail!: boolean; + isDetail: boolean; showTitles = false; - sc!: string; + sc: string; Url = DuckDuckGo; - subs: Subscription[] = []; faChevronCircleRight = faChevronCircleRight; faImage = faImage; @@ -55,9 +147,9 @@ export class MovieDetailComponent implements OnInit, OnChanges, OnDestroy { faMinus = faMinus; constructor( - private movieService: MovieService, + private movieManager: MovieManager, private route: ActivatedRoute, - private translate: TranslateService, + protected translate: TranslateService, private title: TitleService, private router: Router, public tabsService: TabsService, @@ -66,101 +158,11 @@ export class MovieDetailComponent implements OnInit, OnChanges, OnDestroy { private myDatasService: MyDatasService ) {} - ngOnInit(): void { - this.subs.push( - this.route.paramMap.subscribe((params: ParamMap) => { - if (params) { - const idParam = +params.get('id'); - if (idParam && idParam !== 0) { - this.id = idParam; - this.isDetail = true; - this.getMovie(this.id); - } - } - }) - ); - this.subs.push( - this.translate.onLangChange.subscribe((event: LangChangeEvent) => { - this.config.lang = event.lang; - this.getMovie(this.id); - }) - ); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.id) { - this.id = changes.id.currentValue ? changes.id.currentValue : this.id; - this.isDetail = false; - } - this.getMovie(this.id); - } - - getMovie(id: number): void { - if (this.id && this.id !== 0) { - this.loaded.emit(false); - this.config = - this.config === undefined - ? new DetailConfig( - true, - true, - true, - true, - true, - true, - true, - true, - false, - this.translate.currentLang - ) - : this.config; - this.movieService.getMovie(id, this.config, true).then(movie => { - this.movie = movie; - this.loaded.emit(true); - if (this.isDetail) { - this.title.setTitle(movie.title); - this.menuService.scrollTo$.next(0); - } - }); - this.subs.push( - combineLatest([ - this.myTagsService.myTags$, - this.myDatasService.myMovies$, - ]) - .pipe( - filter( - ([tags, movies]) => tags !== undefined && movies !== undefined - ) - ) - .subscribe(([tags, movies]) => { - this.tags = []; - this.showTags = false; - if (movies.map(m => m.id).includes(this.id)) { - this.showTags = true; - this.tags = tags.filter(t => - t.datas - .filter(d => d.movie) - .map(m => m.id) - .includes(this.id) - ); - } - }) - ); - } - } - - redirectGenreToDiscover(genre: Genre): void { + toDiscover(item: T, key: string): void { + const params: Params = {}; + params[key] = JSON.stringify([item.id]); this.router.navigate(['discover'], { - queryParams: {genre: JSON.stringify([genre.id])}, + queryParams: params, }); } - - redirectKeywordToDiscover(keyword: Keyword): void { - this.router.navigate(['discover'], { - queryParams: {keyword: JSON.stringify([keyword.id])}, - }); - } - - ngOnDestroy(): void { - this.subs.forEach(subscription => subscription.unsubscribe()); - } } diff --git a/src/app/application-modules/release/component/release.component.html b/src/app/application-modules/release/component/release.component.html index 3c090d46e..de29e0a99 100644 --- a/src/app/application-modules/release/component/release.component.html +++ b/src/app/application-modules/release/component/release.component.html @@ -63,7 +63,6 @@

diff --git a/src/app/application-modules/serie-detail/season-detail/season-detail.component.ts b/src/app/application-modules/serie-detail/season-detail/season-detail.component.ts index c0cb706d6..21e348d58 100644 --- a/src/app/application-modules/serie-detail/season-detail/season-detail.component.ts +++ b/src/app/application-modules/serie-detail/season-detail/season-detail.component.ts @@ -28,16 +28,18 @@ export class SeasonDetailComponent { faArrowCircleRight = faArrowCircleRight; serie$ = this.serieManager - .find(this.route.paramMap, 'id') + .find(this.serieManager.listenParam(this.route.paramMap, 'id')) .pipe(tap(s => this.title.setTitle(s.title))); - season$ = this.seasonManager.find(this.route.paramMap, 'season').pipe( - map(season => { - season.images.push(...season.episodes.map(e => e.poster)); - season.images = season.images.filter(i => Utils.isNotBlank(i)); - return season; - }) - ); + season$ = this.seasonManager + .find(this.serieManager.listenParam(this.route.paramMap, 'season')) + .pipe( + map(season => { + season.images.push(...season.episodes.map(e => e.poster)); + season.images = season.images.filter(i => Utils.isNotBlank(i)); + return season; + }) + ); constructor( private serieManager: SerieManager, diff --git a/src/app/application-modules/serie-detail/serie-detail/serie-detail.component.ts b/src/app/application-modules/serie-detail/serie-detail/serie-detail.component.ts index 5defdcdea..b401f26d4 100644 --- a/src/app/application-modules/serie-detail/serie-detail/serie-detail.component.ts +++ b/src/app/application-modules/serie-detail/serie-detail/serie-detail.component.ts @@ -33,12 +33,14 @@ export class SerieDetailComponent { imageSize = ImageSize; protected sc!: string; - serie$ = this.serieManager.find(this.route.paramMap, 'id').pipe( - tap(serie => { - this.title.setTitle(serie.title); - this.menuService.scrollTo$.next(0); - }) - ); + serie$ = this.serieManager + .find(this.serieManager.listenParam(this.route.paramMap, 'id')) + .pipe( + tap(serie => { + this.title.setTitle(serie.title); + this.menuService.scrollTo$.next(0); + }) + ); loading$ = combineLatest([ this.serieManager.listenParam(this.route.paramMap, 'id'), diff --git a/src/app/manager/abstract.manager.ts b/src/app/manager/abstract.manager.ts index 5f0468f47..7d60e00c8 100644 --- a/src/app/manager/abstract.manager.ts +++ b/src/app/manager/abstract.manager.ts @@ -9,6 +9,7 @@ import { map, startWith, } from 'rxjs/operators'; +import {DetailConfig} from '../model/model'; export abstract class AbstractService { private readonly id$ = new BehaviorSubject(undefined); @@ -51,8 +52,7 @@ export abstract class AbstractService { } public abstract find( - paramMap: Observable, - key: string, - ...args: (number | string)[] + id$: Observable, + ...args: DetailConfig[] ): Observable; } diff --git a/src/app/manager/movie.manager.ts b/src/app/manager/movie.manager.ts new file mode 100644 index 000000000..b36f8d536 --- /dev/null +++ b/src/app/manager/movie.manager.ts @@ -0,0 +1,40 @@ +import {Injectable} from '@angular/core'; +import {AbstractService} from './abstract.manager'; +import {DetailConfig} from '../model/model'; +import {TranslateService} from '@ngx-translate/core'; +import {Observable, combineLatest} from 'rxjs'; +import {switchMap, tap} from 'rxjs/operators'; +import {MovieService} from '../service/movie.service'; +import {Movie} from '../model/movie'; + +@Injectable({ + providedIn: 'root', +}) +export class MovieManager extends AbstractService< + Movie, + {id: number; config: DetailConfig} +> { + constructor( + private readonly movieService: MovieService, + translate: TranslateService + ) { + super( + movieId => this.movieService.getMovie$(movieId.id, movieId.config, true), + (prev, curr) => + prev.id === curr.id && + prev.config.lang === curr.config.lang && + prev.config.reco === curr.config.reco, + translate + ); + } + + find(id$: Observable, config: DetailConfig): Observable { + return combineLatest([id$, this.lang$]).pipe( + tap(([id, lang]) => { + config.lang = lang; + this.update({id, config}); + }), + switchMap(() => this.listen()) + ); + } +} diff --git a/src/app/manager/season.manager.ts b/src/app/manager/season.manager.ts index 10f6d227c..724cef00d 100644 --- a/src/app/manager/season.manager.ts +++ b/src/app/manager/season.manager.ts @@ -4,7 +4,6 @@ import {Season, SeasonId} from '../model/season'; import {TranslateService} from '@ngx-translate/core'; import {SerieService} from '../service/serie.service'; import {Observable, combineLatest} from 'rxjs'; -import {ParamMap} from '@angular/router'; import {switchMap, tap} from 'rxjs/operators'; import {SerieManager} from './serie.manager'; @@ -30,12 +29,8 @@ export class SeasonManager extends AbstractService { ); } - find(paramMap: Observable, key: string): Observable { - return combineLatest([ - this.listenParam(paramMap, key), - this.lang$, - this.serieManager.listen(), - ]).pipe( + find(id$: Observable): Observable { + return combineLatest([id$, this.lang$, this.serieManager.listen()]).pipe( tap(([id, lang, serie]) => this.update({id: id, serieId: serie.id, lang: lang}) ), diff --git a/src/app/manager/serie.manager.ts b/src/app/manager/serie.manager.ts index 2aad68e2e..accd0b2c5 100644 --- a/src/app/manager/serie.manager.ts +++ b/src/app/manager/serie.manager.ts @@ -5,7 +5,6 @@ import {SerieService} from '../service/serie.service'; import {DetailConfig} from '../model/model'; import {TranslateService} from '@ngx-translate/core'; import {Observable, combineLatest} from 'rxjs'; -import {ParamMap} from '@angular/router'; import {switchMap, tap} from 'rxjs/operators'; @Injectable({ @@ -39,8 +38,8 @@ export class SerieManager extends AbstractService { ); } - find(paramMap: Observable, key: string): Observable { - return combineLatest([this.listenParam(paramMap, key), this.lang$]).pipe( + find(id$: Observable): Observable { + return combineLatest([id$, this.lang$]).pipe( tap(([id, lang]) => this.update({id, lang})), switchMap(() => this.listen()) ); diff --git a/src/app/service/movie.service.ts b/src/app/service/movie.service.ts index d24145801..4ecb61103 100644 --- a/src/app/service/movie.service.ts +++ b/src/app/service/movie.service.ts @@ -1,4 +1,4 @@ -import {forkJoin} from 'rxjs'; +import {Observable, forkJoin, iif} from 'rxjs'; import {Injectable} from '@angular/core'; import {DiscoverCriteria} from '../model/discover-criteria'; @@ -13,6 +13,7 @@ import {OmdbService} from './omdb.service'; import {ToastService} from './toast.service'; import {UrlBuilder} from '../shared/urlBuilder'; import {Utils} from '../shared/utils'; +import {map, mergeMap, catchError} from 'rxjs/operators'; @Injectable({ providedIn: 'root', @@ -57,8 +58,16 @@ export class MovieService { } getMovie(id: number, config: DetailConfig, detail: boolean): Promise { + return this.getMovie$(id, config, detail).toPromise(); + } + + getMovie$( + id: number, + config: DetailConfig, + detail: boolean + ): Observable { return this.serviceUtils - .getPromise( + .getObservable( UrlBuilder.detailUrlBuilder( true, id, @@ -74,58 +83,56 @@ export class MovieService { config.lang ) ) - .then(response => { - const movie = MapMovie.mapForMovie(response, this.mockService); - movie.lang_version = config.lang ?? movie.lang_version; - if ( - detail && - (!movie.overview || - ((movie.videos === undefined || movie.videos.length === 0) && - config.video) || - !movie.original_title) - ) { - return this.serviceUtils - .getPromise( - UrlBuilder.detailUrlBuilder( - true, - id, + .pipe( + map(response => { + const movie = MapMovie.mapForMovie(response, this.mockService); + movie.lang_version = config.lang ?? movie.lang_version; + return movie; + }), + mergeMap(movie => + iif( + () => + detail && + (!movie.overview || + ((movie.videos === undefined || movie.videos.length === 0) && + config.video) || + !movie.original_title), + this.getMovie$( + id, + new DetailConfig( + false, + false, + false, + false, config.video, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, + false, + false, + false, false, 'en' - ) - ) - .then(enMovie => { - const resp = MapMovie.mapForMovie(enMovie, this.mockService); - resp.lang_version = config.lang ?? movie.lang_version; - return resp; - }) - .then(enMovie => { - movie.overview = Utils.isBlank(movie.overview) - ? enMovie.overview - : movie.overview; - movie.videos = - movie.videos && movie.videos.length > 0 - ? movie.videos - : enMovie.videos; - movie.original_title = Utils.isBlank(movie.original_title) - ? enMovie.original_title - : movie.original_title; - movie.score = enMovie.score; - return movie; - }); - } else { - return movie; - } - }) - .then(movie => this.omdb.getImdbScore(movie).toPromise()) - .catch(err => this.serviceUtils.handlePromiseError(err, this.toast)); + ), + false + ).pipe( + map(enMovie => { + movie.overview = Utils.isBlank(movie.overview) + ? enMovie.overview + : movie.overview; + movie.videos = + movie.videos && movie.videos.length > 0 + ? movie.videos + : enMovie.videos; + movie.original_title = Utils.isBlank(movie.original_title) + ? enMovie.original_title + : movie.original_title; + movie.score = enMovie.score; + return movie; + }) + ), + this.omdb.getImdbScore(movie).toPromise() + ) + ), + catchError(err => this.serviceUtils.handleObsError(err, this.toast)) + ); } getMoviesByReleaseDates(