diff --git a/assets/localization/en.json b/assets/localization/en.json index 50a40fa2..09020246 100644 --- a/assets/localization/en.json +++ b/assets/localization/en.json @@ -204,7 +204,7 @@ "settings.SystemStatus": "System Status", "settings.SystemStatusDescription": "Status Page for Hosted Services", "settings.TestBuilds": "Test Builds", - "settings.TestBuildsDescription": "Install Pre-Release Builds of LunaSea", + "settings.TestBuildsDescription": "Install Prerelease Builds of LunaSea", "settings.TestConnection": "Test Connection", "settings.TLSCertificateValidation": "TLS Certificate Validation", "settings.TLSCertificateValidationDescription": "Validate Certificates in TLS Connections", diff --git a/lib/core.dart b/lib/core.dart index 2d6dbae4..00e6ddf6 100644 --- a/lib/core.dart +++ b/lib/core.dart @@ -30,6 +30,7 @@ export 'core/models.dart'; export 'core/modules.dart'; export 'core/pages.dart'; export 'core/router.dart'; +export 'core/services.dart'; export 'core/state.dart'; export 'core/system.dart'; export 'core/ui.dart'; diff --git a/lib/core/services.dart b/lib/core/services.dart new file mode 100644 index 00000000..8822fbaf --- /dev/null +++ b/lib/core/services.dart @@ -0,0 +1 @@ +export 'services/the_movie_db.dart'; diff --git a/lib/core/services/the_movie_db.dart b/lib/core/services/the_movie_db.dart new file mode 100644 index 00000000..36ee1ac0 --- /dev/null +++ b/lib/core/services/the_movie_db.dart @@ -0,0 +1 @@ +export 'the_movie_db/api.dart'; diff --git a/lib/core/services/the_movie_db/api.dart b/lib/core/services/the_movie_db/api.dart new file mode 100644 index 00000000..5f700262 --- /dev/null +++ b/lib/core/services/the_movie_db/api.dart @@ -0,0 +1,9 @@ +class TheMovieDB { + static const String _IMAGE_ENDPOINT_URL = 'https://image.tmdb.org/t/p/'; + static const String _IMAGE_POSTER_SIZE = 'w154'; + + static String? getImageURL(String? id) { + if (id != null) return '$_IMAGE_ENDPOINT_URL$_IMAGE_POSTER_SIZE$id'; + return null; + } +} diff --git a/lib/core/services/the_movie_db/types.dart b/lib/core/services/the_movie_db/types.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/core/ui/block/block.dart b/lib/core/ui/block/block.dart index c18b8e9d..6cf37bc8 100644 --- a/lib/core/ui/block/block.dart +++ b/lib/core/ui/block/block.dart @@ -94,6 +94,10 @@ class LunaBlock extends StatelessWidget { @override Widget build(BuildContext context) { + return _buildBlock(context); + } + + Widget _buildBlock(BuildContext context) { double _height = _calculateHeight(); return LunaCard( context: context, diff --git a/lib/modules/overseerr/api/models.dart b/lib/modules/overseerr/api/models.dart index 73793079..095640d9 100644 --- a/lib/modules/overseerr/api/models.dart +++ b/lib/modules/overseerr/api/models.dart @@ -1,3 +1,21 @@ +export 'models/content/collection.dart'; +export 'models/content/content_ratings.dart'; +export 'models/content/created_by.dart'; +export 'models/content/credits.dart'; +export 'models/content/episode.dart'; +export 'models/content/external_ids.dart'; +export 'models/content/genre.dart'; +export 'models/content/keyword.dart'; +export 'models/content/movie.dart'; +export 'models/content/movie_releases.dart'; +export 'models/content/network.dart'; +export 'models/content/production_company.dart'; +export 'models/content/production_country.dart'; +export 'models/content/related_video.dart'; +export 'models/content/season.dart'; +export 'models/content/series.dart'; +export 'models/content/spoken_language.dart'; +export 'models/content/watch_provider.dart'; export 'models/media/media.dart'; export 'models/media/season.dart'; export 'models/requests/request_count.dart'; diff --git a/lib/modules/overseerr/api/models/content/collection.dart b/lib/modules/overseerr/api/models/content/collection.dart new file mode 100644 index 00000000..99e4ebdb --- /dev/null +++ b/lib/modules/overseerr/api/models/content/collection.dart @@ -0,0 +1,33 @@ +import 'package:lunasea/core.dart'; + +part 'collection.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrCollection { + @JsonKey() + int? id; + + @JsonKey() + String? name; + + @JsonKey() + String? posterPath; + + @JsonKey() + String? backdropPath; + + OverseerrCollection({ + this.id, + this.name, + this.posterPath, + this.backdropPath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrCollection.fromJson(Map json) => + _$OverseerrCollectionFromJson(json); + + Map toJson() => _$OverseerrCollectionToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/content_ratings.dart b/lib/modules/overseerr/api/models/content/content_ratings.dart new file mode 100644 index 00000000..353b1c62 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/content_ratings.dart @@ -0,0 +1,43 @@ +import 'package:lunasea/core.dart'; + +part 'content_ratings.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrContentRatings { + @JsonKey() + List? results; + + OverseerrContentRatings({ + this.results, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrContentRatings.fromJson(Map json) => + _$OverseerrContentRatingsFromJson(json); + + Map toJson() => _$OverseerrContentRatingsToJson(this); +} + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrContentRating { + @JsonKey(name: 'iso_3166_1') + String? iso31661; + + @JsonKey() + String? rating; + + OverseerrContentRating({ + this.iso31661, + this.rating, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrContentRating.fromJson(Map json) => + _$OverseerrContentRatingFromJson(json); + + Map toJson() => _$OverseerrContentRatingToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/created_by.dart b/lib/modules/overseerr/api/models/content/created_by.dart new file mode 100644 index 00000000..c5a3ba30 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/created_by.dart @@ -0,0 +1,37 @@ +import 'package:lunasea/core.dart'; + +part 'created_by.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrCreatedBy { + @JsonKey() + int? id; + + @JsonKey(name: 'credit_id') + String? creditId; + + @JsonKey() + String? name; + + @JsonKey() + int? gender; + + @JsonKey(name: 'profile_path') + String? profilePath; + + OverseerrCreatedBy({ + this.id, + this.creditId, + this.name, + this.gender, + this.profilePath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrCreatedBy.fromJson(Map json) => + _$OverseerrCreatedByFromJson(json); + + Map toJson() => _$OverseerrCreatedByToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/credits.dart b/lib/modules/overseerr/api/models/content/credits.dart new file mode 100644 index 00000000..2f47586b --- /dev/null +++ b/lib/modules/overseerr/api/models/content/credits.dart @@ -0,0 +1,113 @@ +import 'package:lunasea/core.dart'; + +part 'credits.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrCredits { + @JsonKey() + List? cast; + + @JsonKey() + List? crew; + + OverseerrCredits({ + this.cast, + this.crew, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrCredits.fromJson(Map json) => + _$OverseerrCreditsFromJson(json); + + Map toJson() => _$OverseerrCreditsToJson(this); +} + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrCast { + @JsonKey() + int? id; + + @JsonKey() + int? castId; + + @JsonKey() + String? character; + + @JsonKey() + String? creditId; + + @JsonKey() + int? gender; + + @JsonKey() + String? name; + + @JsonKey() + int? order; + + @JsonKey() + String? profilePath; + + OverseerrCast({ + this.id, + this.castId, + this.character, + this.creditId, + this.gender, + this.name, + this.order, + this.profilePath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrCast.fromJson(Map json) => + _$OverseerrCastFromJson(json); + + Map toJson() => _$OverseerrCastToJson(this); +} + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrCrew { + @JsonKey() + int? id; + + @JsonKey() + String? creditId; + + @JsonKey() + String? department; + + @JsonKey() + int? gender; + + @JsonKey() + String? job; + + @JsonKey() + String? name; + + @JsonKey() + String? profilePath; + + OverseerrCrew({ + this.id, + this.creditId, + this.department, + this.gender, + this.job, + this.name, + this.profilePath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrCrew.fromJson(Map json) => + _$OverseerrCrewFromJson(json); + + Map toJson() => _$OverseerrCrewToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/episode.dart b/lib/modules/overseerr/api/models/content/episode.dart new file mode 100644 index 00000000..f0c9c909 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/episode.dart @@ -0,0 +1,53 @@ +import 'package:lunasea/core.dart'; + +part 'episode.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrEpisode { + @JsonKey() + int? id; + + @JsonKey() + String? airDate; + + @JsonKey() + int? episodeNumber; + + @JsonKey() + String? name; + + @JsonKey() + String? overview; + + @JsonKey() + String? productionCode; + + @JsonKey() + int? seasonNumber; + + @JsonKey() + double? voteAverage; + + @JsonKey() + String? stillPath; + + OverseerrEpisode({ + this.id, + this.airDate, + this.episodeNumber, + this.name, + this.overview, + this.productionCode, + this.seasonNumber, + this.voteAverage, + this.stillPath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrEpisode.fromJson(Map json) => + _$OverseerrEpisodeFromJson(json); + + Map toJson() => _$OverseerrEpisodeToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/external_ids.dart b/lib/modules/overseerr/api/models/content/external_ids.dart new file mode 100644 index 00000000..9eeed5ea --- /dev/null +++ b/lib/modules/overseerr/api/models/content/external_ids.dart @@ -0,0 +1,49 @@ +import 'package:lunasea/core.dart'; + +part 'external_ids.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrExternalIds { + @JsonKey() + String? imdbId; + + @JsonKey() + String? freebaseMid; + + @JsonKey() + String? freebaseId; + + @JsonKey() + int? tvdbId; + + @JsonKey() + int? tvrageId; + + @JsonKey() + String? facebookId; + + @JsonKey() + String? instagramId; + + @JsonKey() + String? twitterId; + + OverseerrExternalIds({ + this.imdbId, + this.freebaseMid, + this.freebaseId, + this.tvdbId, + this.tvrageId, + this.facebookId, + this.instagramId, + this.twitterId, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrExternalIds.fromJson(Map json) => + _$OverseerrExternalIdsFromJson(json); + + Map toJson() => _$OverseerrExternalIdsToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/genre.dart b/lib/modules/overseerr/api/models/content/genre.dart new file mode 100644 index 00000000..62ff05cd --- /dev/null +++ b/lib/modules/overseerr/api/models/content/genre.dart @@ -0,0 +1,25 @@ +import 'package:lunasea/core.dart'; + +part 'genre.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrGenre { + @JsonKey() + int? id; + + @JsonKey() + String? name; + + OverseerrGenre({ + this.id, + this.name, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrGenre.fromJson(Map json) => + _$OverseerrGenreFromJson(json); + + Map toJson() => _$OverseerrGenreToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/keyword.dart b/lib/modules/overseerr/api/models/content/keyword.dart new file mode 100644 index 00000000..218af96a --- /dev/null +++ b/lib/modules/overseerr/api/models/content/keyword.dart @@ -0,0 +1,25 @@ +import 'package:lunasea/core.dart'; + +part 'keyword.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrKeyword { + @JsonKey() + int? id; + + @JsonKey() + String? name; + + OverseerrKeyword({ + this.id, + this.name, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrKeyword.fromJson(Map json) => + _$OverseerrKeywordFromJson(json); + + Map toJson() => _$OverseerrKeywordToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/movie.dart b/lib/modules/overseerr/api/models/content/movie.dart new file mode 100644 index 00000000..919308e7 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/movie.dart @@ -0,0 +1,145 @@ +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/overseerr.dart'; + +part 'movie.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrMovie { + @JsonKey() + int? id; + + @JsonKey() + String? imdbId; + + @JsonKey() + bool? adult; + + @JsonKey() + String? backdropPath; + + @JsonKey() + int? budget; + + @JsonKey() + List? genres; + + @JsonKey() + String? homepage; + + @JsonKey() + String? originalLanguage; + + @JsonKey() + String? originalTitle; + + @JsonKey() + String? overview; + + @JsonKey() + double? popularity; + + @JsonKey() + List? relatedVideos; + + @JsonKey() + String? posterPath; + + @JsonKey() + List? productionCompanies; + + @JsonKey() + List? productionCountries; + + @JsonKey() + String? releaseDate; + + @JsonKey() + int? revenue; + + @JsonKey() + int? runtime; + + @JsonKey() + String? status; + + @JsonKey() + String? tagline; + + @JsonKey() + String? title; + + @JsonKey() + bool? video; + + @JsonKey() + double? voteAverage; + + @JsonKey() + int? voteCount; + + @JsonKey() + String? plexUrl; + + @JsonKey() + OverseerrCollection? collection; + + @JsonKey() + OverseerrExternalIds? externalIds; + + @JsonKey() + OverseerrMedia? mediaInfo; + + @JsonKey() + List? watchProviders; + + @JsonKey() + OverseerrCredits? credits; + + @JsonKey() + List? spokenLanguages; + + @JsonKey() + OverseerrMovieReleases? releases; + + OverseerrMovie({ + this.id, + this.imdbId, + this.adult, + this.backdropPath, + this.budget, + this.genres, + this.homepage, + this.originalLanguage, + this.originalTitle, + this.overview, + this.popularity, + this.relatedVideos, + this.posterPath, + this.productionCompanies, + this.productionCountries, + this.releaseDate, + this.revenue, + this.runtime, + this.status, + this.tagline, + this.title, + this.video, + this.voteAverage, + this.voteCount, + this.plexUrl, + this.collection, + this.externalIds, + this.mediaInfo, + this.watchProviders, + this.credits, + this.spokenLanguages, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrMovie.fromJson(Map json) => + _$OverseerrMovieFromJson(json); + + Map toJson() => _$OverseerrMovieToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/movie_releases.dart b/lib/modules/overseerr/api/models/content/movie_releases.dart new file mode 100644 index 00000000..5393f829 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/movie_releases.dart @@ -0,0 +1,78 @@ +import 'package:lunasea/core.dart'; + +part 'movie_releases.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrMovieReleases { + @JsonKey() + List? results; + + OverseerrMovieReleases({ + this.results, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrMovieReleases.fromJson(Map json) => + _$OverseerrMovieReleasesFromJson(json); + + Map toJson() => _$OverseerrMovieReleasesToJson(this); +} + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrMovieReleaseResult { + @JsonKey(name: 'iso_3166_1') + String? iso31661; + + @JsonKey(name: 'release_dates') + List? releaseDates; + + OverseerrMovieReleaseResult({ + this.iso31661, + this.releaseDates, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrMovieReleaseResult.fromJson(Map json) => + _$OverseerrMovieReleaseResultFromJson(json); + + Map toJson() => _$OverseerrMovieReleaseResultToJson(this); +} + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrMovieReleaseResultDate { + @JsonKey() + String? certification; + + @JsonKey(name: 'iso_639_1') + String? iso6391; + + @JsonKey() + String? note; + + @JsonKey(name: 'release_date') + String? releaseDate; + + @JsonKey() + int? type; + + OverseerrMovieReleaseResultDate({ + this.certification, + this.iso6391, + this.note, + this.releaseDate, + this.type, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrMovieReleaseResultDate.fromJson(Map json) => + _$OverseerrMovieReleaseResultDateFromJson(json); + + Map toJson() => + _$OverseerrMovieReleaseResultDateToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/network.dart b/lib/modules/overseerr/api/models/content/network.dart new file mode 100644 index 00000000..cc9f02df --- /dev/null +++ b/lib/modules/overseerr/api/models/content/network.dart @@ -0,0 +1,33 @@ +import 'package:lunasea/core.dart'; + +part 'network.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrNetwork { + @JsonKey() + int? id; + + @JsonKey() + String? name; + + @JsonKey() + String? originCountry; + + @JsonKey() + String? logoPath; + + OverseerrNetwork({ + this.id, + this.name, + this.originCountry, + this.logoPath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrNetwork.fromJson(Map json) => + _$OverseerrNetworkFromJson(json); + + Map toJson() => _$OverseerrNetworkToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/production_company.dart b/lib/modules/overseerr/api/models/content/production_company.dart new file mode 100644 index 00000000..9ac8196f --- /dev/null +++ b/lib/modules/overseerr/api/models/content/production_company.dart @@ -0,0 +1,45 @@ +import 'package:lunasea/core.dart'; + +part 'production_company.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrProductionCompany { + @JsonKey() + int? id; + + @JsonKey() + String? logoPath; + + @JsonKey() + String? originCountry; + + @JsonKey() + String? name; + + @JsonKey() + String? description; + + @JsonKey() + String? headquarters; + + @JsonKey() + String? homepage; + + OverseerrProductionCompany({ + this.id, + this.logoPath, + this.originCountry, + this.name, + this.description, + this.headquarters, + this.homepage, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrProductionCompany.fromJson(Map json) => + _$OverseerrProductionCompanyFromJson(json); + + Map toJson() => _$OverseerrProductionCompanyToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/production_country.dart b/lib/modules/overseerr/api/models/content/production_country.dart new file mode 100644 index 00000000..7e2c82c3 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/production_country.dart @@ -0,0 +1,25 @@ +import 'package:lunasea/core.dart'; + +part 'production_country.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrProductionCountry { + @JsonKey(name: 'iso_3166_1') + String? iso31661; + + @JsonKey() + String? name; + + OverseerrProductionCountry({ + this.iso31661, + this.name, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrProductionCountry.fromJson(Map json) => + _$OverseerrProductionCountryFromJson(json); + + Map toJson() => _$OverseerrProductionCountryToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/related_video.dart b/lib/modules/overseerr/api/models/content/related_video.dart new file mode 100644 index 00000000..f34ecfeb --- /dev/null +++ b/lib/modules/overseerr/api/models/content/related_video.dart @@ -0,0 +1,41 @@ +import 'package:lunasea/core.dart'; + +part 'related_video.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrRelatedVideo { + @JsonKey() + String? site; + + @JsonKey() + String? key; + + @JsonKey() + String? name; + + @JsonKey() + int? size; + + @JsonKey() + String? type; + + @JsonKey() + String? url; + + OverseerrRelatedVideo({ + this.site, + this.key, + this.name, + this.size, + this.type, + this.url, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrRelatedVideo.fromJson(Map json) => + _$OverseerrRelatedVideoFromJson(json); + + Map toJson() => _$OverseerrRelatedVideoToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/season.dart b/lib/modules/overseerr/api/models/content/season.dart new file mode 100644 index 00000000..4531fee3 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/season.dart @@ -0,0 +1,45 @@ +import 'package:lunasea/core.dart'; + +part 'season.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrSeason { + @JsonKey() + int? id; + + @JsonKey() + String? airDate; + + @JsonKey() + int? episodeCount; + + @JsonKey() + String? name; + + @JsonKey() + String? overview; + + @JsonKey() + int? seasonNumber; + + @JsonKey() + String? posterPath; + + OverseerrSeason({ + this.id, + this.airDate, + this.episodeCount, + this.name, + this.overview, + this.seasonNumber, + this.posterPath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrSeason.fromJson(Map json) => + _$OverseerrSeasonFromJson(json); + + Map toJson() => _$OverseerrSeasonToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/series.dart b/lib/modules/overseerr/api/models/content/series.dart new file mode 100644 index 00000000..d54c8108 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/series.dart @@ -0,0 +1,167 @@ +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/overseerr.dart'; + +part 'series.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrSeries { + @JsonKey() + int? id; + + @JsonKey() + List? createdBy; + + @JsonKey() + String? homepage; + + @JsonKey() + String? name; + + @JsonKey() + List? episodeRunTime; + + @JsonKey() + String? firstAirDate; + + @JsonKey() + String? lastAirDate; + + @JsonKey() + List? genres; + + @JsonKey() + String? backdropPath; + + @JsonKey() + String? posterPath; + + @JsonKey() + bool? inProduction; + + @JsonKey() + List? languages; + + @JsonKey() + int? numberOfEpisodes; + + @JsonKey() + int? numberOfSeasons; + + @JsonKey() + List? originCountry; + + @JsonKey() + String? originalLanguage; + + @JsonKey() + String? originalName; + + @JsonKey() + String? overview; + + @JsonKey() + double? popularity; + + @JsonKey() + List? productionCompanies; + + @JsonKey() + List? productionCountries; + + @JsonKey() + List? spokenLanguages; + + @JsonKey() + List? relatedVideos; + + @JsonKey() + List? networks; + + @JsonKey() + String? status; + + @JsonKey() + String? type; + + @JsonKey() + double? voteAverage; + + @JsonKey() + int? voteCount; + + @JsonKey() + String? tagline; + + @JsonKey() + OverseerrCredits? credits; + + @JsonKey() + List? watchProviders; + + @JsonKey() + List? keywords; + + @JsonKey() + OverseerrExternalIds? externalIds; + + @JsonKey() + OverseerrContentRatings? contentRatings; + + @JsonKey() + List? seasons; + + @JsonKey() + OverseerrMedia? mediaInfo; + + @JsonKey() + OverseerrEpisode? lastEpisodeToAir; + + @JsonKey() + OverseerrEpisode? nextEpisodeToAir; + + OverseerrSeries({ + this.id, + this.createdBy, + this.homepage, + this.name, + this.episodeRunTime, + this.firstAirDate, + this.lastAirDate, + this.genres, + this.backdropPath, + this.posterPath, + this.inProduction, + this.languages, + this.numberOfEpisodes, + this.numberOfSeasons, + this.originCountry, + this.originalLanguage, + this.originalName, + this.overview, + this.popularity, + this.productionCompanies, + this.productionCountries, + this.relatedVideos, + this.spokenLanguages, + this.networks, + this.status, + this.type, + this.voteAverage, + this.voteCount, + this.tagline, + this.credits, + this.watchProviders, + this.keywords, + this.externalIds, + this.seasons, + this.mediaInfo, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrSeries.fromJson(Map json) => + _$OverseerrSeriesFromJson(json); + + Map toJson() => _$OverseerrSeriesToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/spoken_language.dart b/lib/modules/overseerr/api/models/content/spoken_language.dart new file mode 100644 index 00000000..c4faad3b --- /dev/null +++ b/lib/modules/overseerr/api/models/content/spoken_language.dart @@ -0,0 +1,33 @@ +import 'package:lunasea/core.dart'; + +part 'spoken_language.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrSpokenLanguage { + @JsonKey() + String? englishName; + + @JsonKey(name: 'english_name') + String? englishNameSecondary; + + @JsonKey(name: 'iso_639_1') + String? iso6391; + + @JsonKey() + String? name; + + OverseerrSpokenLanguage({ + this.englishName, + this.englishNameSecondary, + this.iso6391, + this.name, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrSpokenLanguage.fromJson(Map json) => + _$OverseerrSpokenLanguageFromJson(json); + + Map toJson() => _$OverseerrSpokenLanguageToJson(this); +} diff --git a/lib/modules/overseerr/api/models/content/watch_provider.dart b/lib/modules/overseerr/api/models/content/watch_provider.dart new file mode 100644 index 00000000..eab855f9 --- /dev/null +++ b/lib/modules/overseerr/api/models/content/watch_provider.dart @@ -0,0 +1,63 @@ +import 'package:lunasea/core.dart'; + +part 'watch_provider.g.dart'; + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrWatchProvider { + @JsonKey(name: 'iso_3166_1') + String? iso31661; + + @JsonKey() + String? link; + + @JsonKey() + List? buy; + + @JsonKey() + List? flatrate; + + OverseerrWatchProvider({ + this.iso31661, + this.link, + this.buy, + this.flatrate, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrWatchProvider.fromJson(Map json) => + _$OverseerrWatchProviderFromJson(json); + + Map toJson() => _$OverseerrWatchProviderToJson(this); +} + +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class OverseerrWatchProviderDetails { + @JsonKey() + int? id; + + @JsonKey() + String? name; + + @JsonKey() + int? displayPriority; + + @JsonKey() + String? logoPath; + + OverseerrWatchProviderDetails({ + this.id, + this.name, + this.displayPriority, + this.logoPath, + }); + + @override + String toString() => json.encode(this.toJson()); + + factory OverseerrWatchProviderDetails.fromJson(Map json) => + _$OverseerrWatchProviderDetailsFromJson(json); + + Map toJson() => _$OverseerrWatchProviderDetailsToJson(this); +} diff --git a/lib/modules/overseerr/api/models/media/media.dart b/lib/modules/overseerr/api/models/media/media.dart index 5e9007cf..0af19ae0 100644 --- a/lib/modules/overseerr/api/models/media/media.dart +++ b/lib/modules/overseerr/api/models/media/media.dart @@ -5,95 +5,91 @@ part 'media.g.dart'; @JsonSerializable(explicitToJson: true, includeIfNull: false) class OverseerrMedia { - @JsonKey(name: 'id') + @JsonKey() int? id; @JsonKey( - name: 'mediaType', fromJson: OverseerrUtilities.mediaTypeFromJson, toJson: OverseerrUtilities.mediaTypeToJson, ) OverseerrMediaType? mediaType; - @JsonKey(name: 'tmdbId') + @JsonKey() int? tmdbId; - @JsonKey(name: 'tvdbId') + @JsonKey() int? tvdbId; - @JsonKey(name: 'imdbId') + @JsonKey() String? imdbId; @JsonKey( - name: 'status', - fromJson: OverseerrUtilities.requestStatusFromJson, - toJson: OverseerrUtilities.requestStatusToJson, + fromJson: OverseerrUtilities.mediaStatusFromJson, + toJson: OverseerrUtilities.mediaStatusToJson, ) - OverseerrRequestStatus? status; + OverseerrMediaStatus? status; @JsonKey( - name: 'status4k', - fromJson: OverseerrUtilities.requestStatusFromJson, - toJson: OverseerrUtilities.requestStatusToJson, + fromJson: OverseerrUtilities.mediaStatusFromJson, + toJson: OverseerrUtilities.mediaStatusToJson, ) - OverseerrRequestStatus? status4k; + OverseerrMediaStatus? status4k; @JsonKey( - name: 'createdAt', fromJson: OverseerrUtilities.dateTimeFromJson, toJson: OverseerrUtilities.dateTimeToJson, ) DateTime? createdAt; @JsonKey( - name: 'updatedAt', fromJson: OverseerrUtilities.dateTimeFromJson, toJson: OverseerrUtilities.dateTimeToJson, ) DateTime? updatedAt; @JsonKey( - name: 'lastSeasonChange', fromJson: OverseerrUtilities.dateTimeFromJson, toJson: OverseerrUtilities.dateTimeToJson, ) DateTime? lastSeasonChange; @JsonKey( - name: 'mediaAddedAt', fromJson: OverseerrUtilities.dateTimeFromJson, toJson: OverseerrUtilities.dateTimeToJson, ) DateTime? mediaAddedAt; - @JsonKey(name: 'serviceId') + @JsonKey() int? serviceId; - @JsonKey(name: 'serviceId4k') + @JsonKey() int? serviceId4k; - @JsonKey(name: 'externalServiceId') + @JsonKey() int? externalServiceId; - @JsonKey(name: 'externalServiceId4k') + @JsonKey() int? externalServiceId4k; - @JsonKey(name: 'externalServiceSlug') + @JsonKey() String? externalServiceSlug; - @JsonKey(name: 'externalServiceSlug4k') + @JsonKey() String? externalServiceSlug4k; - @JsonKey(name: 'ratingKey') + @JsonKey() String? ratingKey; - @JsonKey(name: 'ratingKey4k') + @JsonKey() String? ratingKey4k; - @JsonKey(name: 'plexUrl') + @JsonKey() + List? seasons; + + @JsonKey() String? plexUrl; - @JsonKey(name: 'serviceUrl') + @JsonKey() String? serviceUrl; OverseerrMedia({ @@ -116,6 +112,7 @@ class OverseerrMedia { this.externalServiceSlug4k, this.ratingKey, this.ratingKey4k, + this.seasons, this.plexUrl, this.serviceUrl, }); diff --git a/lib/modules/overseerr/api/models/media/season.dart b/lib/modules/overseerr/api/models/media/season.dart index 64185ae7..04878eab 100644 --- a/lib/modules/overseerr/api/models/media/season.dart +++ b/lib/modules/overseerr/api/models/media/season.dart @@ -4,7 +4,7 @@ import 'package:lunasea/modules/overseerr.dart'; part 'season.g.dart'; @JsonSerializable(explicitToJson: true, includeIfNull: false) -class OverseerrSeason { +class OverseerrMediaSeason { @JsonKey(name: 'id') int? id; @@ -18,6 +18,13 @@ class OverseerrSeason { ) OverseerrRequestStatus? status; + @JsonKey( + name: 'status4k', + fromJson: OverseerrUtilities.requestStatusFromJson, + toJson: OverseerrUtilities.requestStatusToJson, + ) + OverseerrRequestStatus? status4k; + @JsonKey( name: 'createdAt', fromJson: OverseerrUtilities.dateTimeFromJson, @@ -32,10 +39,11 @@ class OverseerrSeason { ) DateTime? updatedAt; - OverseerrSeason({ + OverseerrMediaSeason({ this.id, this.seasonNumber, this.status, + this.status4k, this.createdAt, this.updatedAt, }); @@ -44,10 +52,10 @@ class OverseerrSeason { @override String toString() => json.encode(this.toJson()); - /// Deserialize a JSON map to a [OverseerrSeason] object. - factory OverseerrSeason.fromJson(Map json) => - _$OverseerrSeasonFromJson(json); + /// Deserialize a JSON map to a [OverseerrMediaSeason] object. + factory OverseerrMediaSeason.fromJson(Map json) => + _$OverseerrMediaSeasonFromJson(json); - /// Serialize a [OverseerrSeason] object to a JSON map. - Map toJson() => _$OverseerrSeasonToJson(this); + /// Serialize a [OverseerrMediaSeason] object to a JSON map. + Map toJson() => _$OverseerrMediaSeasonToJson(this); } diff --git a/lib/modules/overseerr/api/models/requests/request.dart b/lib/modules/overseerr/api/models/requests/request.dart index 80dfad86..0561634a 100644 --- a/lib/modules/overseerr/api/models/requests/request.dart +++ b/lib/modules/overseerr/api/models/requests/request.dart @@ -63,6 +63,9 @@ class OverseerrRequest { @JsonKey(name: 'modifiedBy') OverseerrUser? modifiedBy; + @JsonKey(name: 'seasons') + List? seasons; + @JsonKey(name: 'seasonCount') int? seasonCount; @@ -81,6 +84,7 @@ class OverseerrRequest { this.media, this.requestedBy, this.modifiedBy, + this.seasons, this.seasonCount, }); diff --git a/lib/modules/overseerr/api/models/users/user.dart b/lib/modules/overseerr/api/models/users/user.dart index 6d07a4f6..b4205c4c 100644 --- a/lib/modules/overseerr/api/models/users/user.dart +++ b/lib/modules/overseerr/api/models/users/user.dart @@ -14,6 +14,9 @@ class OverseerrUser { @JsonKey(name: 'email') String? email; + @JsonKey(name: 'plexId') + int? plexId; + @JsonKey(name: 'plexUsername') String? plexUsername; @@ -65,6 +68,7 @@ class OverseerrUser { this.permissions, this.id, this.email, + this.plexId, this.plexUsername, this.username, this.recoveryLinkExpirationDate, diff --git a/lib/modules/overseerr/api/overseerr.dart b/lib/modules/overseerr/api/overseerr.dart index b1e91be7..337e48fb 100644 --- a/lib/modules/overseerr/api/overseerr.dart +++ b/lib/modules/overseerr/api/overseerr.dart @@ -25,6 +25,12 @@ abstract class Overseerr { return _Overseerr(_dio, baseUrl: _baseUrl); } + @GET('movie/{id}') + Future getMovie({ + @Path('id') required int id, + @Query('language') String? language, + }); + @GET('request') Future getRequests({ @Query('take') int? take, @@ -48,6 +54,12 @@ abstract class Overseerr { @GET('status/appdata') Future getStatusAppData(); + @GET('tv/{id}') + Future getSeries({ + @Path('id') required int id, + @Query('language') String? language, + }); + @GET('user') Future getUsers({ @Query('take') int? take, diff --git a/lib/modules/overseerr/routes/requests/widgets/request_tile.dart b/lib/modules/overseerr/routes/requests/widgets/request_tile.dart index 29bafc89..8c0e675b 100644 --- a/lib/modules/overseerr/routes/requests/widgets/request_tile.dart +++ b/lib/modules/overseerr/routes/requests/widgets/request_tile.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/overseerr.dart'; -class OverseerrRequestTile extends StatelessWidget { +class OverseerrRequestTile extends StatefulWidget { final OverseerrRequest request; const OverseerrRequestTile({ @@ -10,18 +10,81 @@ class OverseerrRequestTile extends StatelessWidget { required this.request, }) : super(key: key); + @override + State createState() => _State(); +} + +class _State extends State with LunaLoadCallbackMixin { + OverseerrMovie? _movie; + OverseerrSeries? _series; + + @override + Future loadCallback() async { + // Temporary, for testing purposes + if (widget.request.type == OverseerrMediaType.MOVIE) { + OverseerrMovie movie = await context + .read() + .api! + .getMovie(id: widget.request.media!.tmdbId!); + if (mounted) setState(() => _movie = movie); + } + + if (widget.request.type == OverseerrMediaType.TV) { + OverseerrSeries series = await context + .read() + .api! + .getSeries(id: widget.request.media!.tmdbId!); + if (mounted) setState(() => _series = series); + } + } + @override Widget build(BuildContext context) { - return LunaBlock( - title: request.requestedBy!.displayName ?? 'overseerr.UnknownUser'.tr(), - body: const [ - TextSpan(text: 'Placeholder 1'), - TextSpan(text: 'Placeholder 2'), - ], - posterPlaceholderIcon: LunaIcons.USER, - posterHeaders: context.read().headers, - posterUrl: request.requestedBy!.avatar, - posterIsSquare: true, - ); + // Temporary, for testing purposes + if (_movie != null) { + return LunaBlock( + title: _movie!.title ?? 'lunasea.Unknown'.tr(), + body: [ + const TextSpan(text: 'Placeholder 1'), + TextSpan( + text: 'Requested by ${widget.request.requestedBy?.displayName}', + style: const TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + color: LunaColours.accent, + )), + ], + posterHeaders: context.read().headers, + posterUrl: TheMovieDB.getImageURL(_movie!.posterPath), + posterPlaceholderIcon: LunaIcons.VIDEO_CAM, + ); + } else if (_series != null) { + return LunaBlock( + title: _series!.name ?? 'lunasea.Unknown'.tr(), + body: [ + const TextSpan(text: 'Placeholder 1'), + TextSpan( + text: 'Requested by ${widget.request.requestedBy?.displayName}', + style: const TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + color: LunaColours.accent, + )), + ], + posterHeaders: context.read().headers, + posterUrl: TheMovieDB.getImageURL(_series!.posterPath), + posterPlaceholderIcon: LunaIcons.VIDEO_CAM, + ); + } else { + return LunaBlock( + title: widget.request.requestedBy!.displayName ?? + 'overseerr.UnknownUser'.tr(), + body: const [ + TextSpan(text: 'Placeholder 1'), + TextSpan(text: 'Placeholder 2'), + ], + posterPlaceholderIcon: LunaIcons.USER, + posterHeaders: context.read().headers, + posterUrl: widget.request.requestedBy!.avatar, + ); + } } } diff --git a/localization/settings/en.json b/localization/settings/en.json index daca0650..94df81f4 100644 --- a/localization/settings/en.json +++ b/localization/settings/en.json @@ -204,7 +204,7 @@ "settings.SystemStatus": "System Status", "settings.SystemStatusDescription": "Status Page for Hosted Services", "settings.TestBuilds": "Test Builds", - "settings.TestBuildsDescription": "Install Pre-Release Builds of LunaSea", + "settings.TestBuildsDescription": "Install Prerelease Builds of LunaSea", "settings.TestConnection": "Test Connection", "settings.TLSCertificateValidation": "TLS Certificate Validation", "settings.TLSCertificateValidationDescription": "Validate Certificates in TLS Connections",