rewrite: Refactor model imports and continue work on shikimori integration

This commit is contained in:
Kevin Rodrigues Borges
2026-06-01 23:34:59 +01:00
parent 28d4a18b17
commit 4a0676da80
61 changed files with 3821 additions and 60 deletions

View File

@@ -0,0 +1,174 @@
---
name: fjson-dart-bean-generator
description: Generate Dart bean classes from JSON and regenerate serialization helpers using the fjson CLI. Use this skill whenever the user needs to create Dart models from API JSON responses, generate @JsonSerializable entity classes, or regenerate .g.dart helper files. Also use when the user mentions FlutterJsonBeanFactory, fjson, json_to_dart, JSON serialization, @JSONField, @JsonSerializable, or needs to add new API response DTOs to the unyo project.
---
# fjson CLI — Dart Bean Generator
The fjson CLI generates `@JsonSerializable` Dart entity classes from JSON responses and regenerates `.g.dart` serialization helpers, `json_field.dart`, and `json_convert_content.dart`. It replaces the FlutterJsonBeanFactory IntelliJ plugin for CLI/agent workflows.
The bundled JAR lives inside this skill directory: **`fjson-cli.jar`**. Invoke it as:
```bash
java -jar <path-to-skill>/fjson-cli.jar <command> [options]
```
When running from the unyo project root, the skill is at `.agents/skills/fjson-dart-bean-generator/`:
```bash
java -jar .agents/skills/fjson-dart-bean-generator/fjson-cli.jar <command> [options]
```
If you need to rebuild the JAR (rare — only after modifying fjson-cli source):
```bash
cd ../fjson-cli && ./gradlew jar
# Then copy the new JAR into the skill:
cp ../fjson-cli/build/libs/fjson-cli.jar .agents/skills/fjson-dart-bean-generator/fjson-cli.jar
```
## Two Commands
| Command | Purpose | When to use |
|---------|---------|-------------|
| `fjson bean` | Create a new entity `.dart` file from JSON | User provides a class name + JSON (string or file) |
| `fjson gen` | Regenerate all `.g.dart` helpers, `json_field.dart`, `json_convert_content.dart` | After adding/modifying any `@JsonSerializable` class |
> **Important**: `fjson bean` **automatically runs** `fjson gen` when invoked inside a Flutter project (one with a `pubspec.yaml`). You do NOT need to run `gen` separately after `bean`.
## `fjson bean` — Generate a new entity from JSON
### Required flags
| Flag | Description | Example |
|------|-------------|---------|
| `--class <Name>` | Root class name (use the API response root name) | `--class MediaResponse` |
| `--json <string>` | Raw JSON string | `--json '{"id":1,"title":"..."}'` |
| `--file <path>` | Path to a `.json` file (alternative to `--json`) | `--file response.json` |
### Optional flags
| Flag | Description | Default | When to use |
|------|-------------|---------|-------------|
| `--output <path>` | Directory for the generated `.dart` file | `./lib` | Direct entity to correct DTO subfolder |
| `--suffix <text>` | Suffix appended to generated class names | `entity` | Leave as default for unyo |
| `--nullable` | Make fields nullable (`Type?`) | off | Rarely needed in unyo |
| `--set-default` | Assign default values to primitive fields | off | **Always use this for unyo DTOs** |
| `--project <path>` | Flutter project root | `.` | Usually not needed from unyo root |
### Typical unyo invocation
```bash
java -jar .agents/skills/fjson-dart-bean-generator/fjson-cli.jar bean \
--class MediaCollectionResponse \
--json '<paste JSON here>' \
--output ./lib/core/services/api/dto/anilist/ \
--set-default
```
Or from a file:
```bash
java -jar .agents/skills/fjson-dart-bean-generator/fjson-cli.jar bean \
--class AnizipEpisodeInfo \
--file ./response.json \
--output ./lib/core/services/api/dto/anizip/ \
--set-default
```
### What gets generated
The command produces a `.dart` file containing:
- A root `@JsonSerializable()` class with `--suffix` appended (e.g., `MediaCollectionResponse``MediaCollectionResponseEntity`)
- Nested `@JsonSerializable()` classes for each nested JSON object (e.g., `MediaCollectionResponseDtoPage`)
- `@JSONField(name: 'original_key')` annotations for keys whose Dart naming differs from JSON
- `late` fields with default values (if `--set-default`)
- `fromJson()` factory and `toJson()` method stubs
- `import` of `json_field.dart` and the matching `.g.dart`
- `export` of the matching `.g.dart`
The generated entity **also auto-triggers `fjson gen`**, so the matching `.g.dart`, `json_field.dart`, and `json_convert_content.dart` are all regenerated immediately.
## `fjson gen` — Regenerate all helpers
```bash
java -jar .agents/skills/fjson-dart-bean-generator/fjson-cli.jar gen --project .
```
This scans `lib/**/*.dart` for every `@JsonSerializable` class and:
1. Regenerates each `<entity>.g.dart` file in `lib/generated/json/`
2. Rebuilds `lib/generated/json/base/json_field.dart` (annotation definitions)
3. Rebuilds `lib/generated/json/base/json_convert_content.dart` (the `JsonConvert` runtime and `JsonConvertClassCollection` registry with all entity imports)
4. Deletes any stale `.g.dart` files for entities that no longer exist
Run this **after any manual edit** to an entity file (adding/removing/renaming fields).
## Generated File Layout in unyo
```
unyo/
├── lib/core/services/api/dto/
│ └── anilist/
│ └── media_response_entity.dart ← fjson bean output
└── lib/generated/json/
├── media_response_entity.g.dart ← auto-generated helpers
├── media_collection_graphql_entity.g.dart ← existing
├── ... ← all other .g.dart
└── base/
├── json_field.dart ← annotations
└── json_convert_content.dart ← registry + runtime
```
### Entity file naming convention in unyo
- **Snake_case filenames**: e.g., `media_details_graphql_entity.dart`
- **PascalCase class names**: e.g., `MediaDetailsGraphqlEntity`
- fjson converts PascalCase `--class` names to snake_case filenames automatically
- Always place entities in the correct DTO subfolder: `lib/core/services/api/dto/<provider>/`
- Providers: `anilist/`, `anizip/`, `aniskip/`, `extensions/`
### Default unyo settings that fjson matches
- `--set-default` is always used → all fields get `= ''`, `= 0`, `= false`, etc.
- `--suffix entity` (default) → class names end with `Entity`
- `flutter_json.generated_path` is `generated/json` in `pubspec.yaml`
## Workflow: Adding a new API response model
1. **Get the JSON** — Either from the user, from a file, or from a GraphQL response
2. **Determine the provider** (anilist, anizip, aniskip, extensions)
3. **Run `fjson bean`** with `--class`, `--json/--file`, `--output`, and `--set-default`:
```bash
java -jar .agents/skills/fjson-dart-bean-generator/fjson-cli.jar bean \
--class NewApiResponse \
--json '<the JSON>' \
--output ./lib/core/services/api/dto/anilist/ \
--set-default
```
4. **Verify the output** — Open the generated `.dart` file and check:
- Class names and field types are correct
- Nested objects are properly generated as sub-classes
- `@JSONField(name: ...)` mappings are correct for non-matching keys
5. **Update `json_convert_content.dart`** — Already done automatically by `fjson gen` (triggered by `fjson bean`). Verify the new entity's import is present.
6. **Run analysis** — `flutter analyze` to ensure no issues
7. **If fields need adjustment** — Edit the entity file directly, then re-run `fjson gen`:
```bash
java -jar .agents/skills/fjson-dart-bean-generator/fjson-cli.jar gen --project .
```
## Common Pitfalls
- **Wrong `--output` path**: Entity files must go to the correct provider subfolder (`lib/core/services/api/dto/<provider>/`) for the import in `json_convert_content.dart` and `.g.dart` files to resolve correctly.
- **Forgetting `--set-default`**: Without it, fields use `late` without defaults. In unyo, all DTO fields have defaults (except `dynamic`).
- **Running in wrong directory**: Commands assume the current working directory is the unyo project root (where `pubspec.yaml` lives). The `--project` flag overrides this.
- **Class name collision**: If a class name already exists in another entity file, the generated `.g.dart` will have duplicate function names, causing compile errors. Check for existing names before running.
- **JSON with empty arrays**: fjson handles this but cannot infer the element type of an empty `List`. The field will be typed `List<dynamic>` and must be corrected manually.
- **After running `bean` in a non-Flutter directory**: `fjson gen` is skipped. You must manually run it from the project root.
## Cross-references
- **unyo project structure and entity patterns**: See `unyo-domain-data-layer` skill
- **DTO conventions and GraphQL response mapping**: See `unyo-domain-data-layer` skill
- **Rebuilding fjson-cli from source**: See `../fjson-cli/README.md`

Binary file not shown.

View File

@@ -27,8 +27,8 @@ import 'package:unyo/core/notification/reload/reload_type.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/core/notification/video_info_notifier.dart';
import 'package:unyo/core/services/api/http/http_exception.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/models/local/user/local_user_model.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/domain/repositories/episode_repository.dart';
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';

View File

@@ -21,8 +21,8 @@ import 'package:unyo/core/notification/reload/reload_notifier.dart';
import 'package:unyo/core/notification/reload/reload_type.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/application/effects/app_effects.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/models/local/user/local_user_model.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
import 'package:unyo/data/repositories/repositories.dart';
import 'package:unyo/domain/entities/media/anime.dart';

View File

@@ -17,8 +17,8 @@ import 'package:unyo/core/notification/manga_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/core/services/api/http/http_exception.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/models/local/user/local_user_model.dart';
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
import 'package:unyo/data/repositories/repositories.dart';

View File

@@ -11,8 +11,8 @@ import 'package:unyo/core/notification/anime_notifier.dart';
import 'package:unyo/core/notification/manga_notifier.dart';
import 'package:unyo/core/notification/media_list_notifier.dart';
import 'package:unyo/core/notification/user_notifier.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/models/local/user/local_user_model.dart';
import 'package:unyo/data/repositories/user_repository_anilist.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/manga.dart';

View File

@@ -18,8 +18,8 @@ import 'package:unyo/core/services/media/episode_service.dart';
import 'package:unyo/core/services/media/media_service.dart';
import 'package:unyo/core/theme/color_image_service.dart';
import 'package:unyo/core/theme/theme_service.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/models/local/user/local_user_model.dart';
import 'package:unyo/data/repositories/repositories.dart';
import 'package:unyo/domain/entities/user/settings.dart';
import 'package:unyo/domain/entities/user/user.dart';

View File

@@ -1,7 +1,7 @@
// Application configuration
const String version = 'v1.0.0';
// Locator configuration
const String applicationSupportDirectory = "applicationSupportDirectory";
const String anilistGraphQlService = 'anilistGraphQlService';
const String loggedUserNotifier = 'loggedUserNotifier';
const String newUserNotifier = 'newUserNotifier';
// Anilist API configuration
@@ -11,15 +11,19 @@ const String anilistAuthUrl =
const String anilistRedirectUri = 'http://localhost:9999/auth';
const String anilistClientId = '17550';
const String anilistClientSecret = 'xI8KTZlKm2F3kHXLko1ArQ21bKap4MojgDTk6Ukx';
const String anilistGraphQlService = 'anilistGraphQlService';
const String anilistGraphQLEndpoint = 'https://graphql.anilist.co';
// Shikimori API configuration
const String shikimoriGraphQlService = 'shikimoriGraphQlService';
const String shikimoriGraphQLEndpoint = 'https://shikimori.io/api/graphql';
// Anizip API configuration
const String anizipBaseEndpoint = 'https://api.ani.zip';
// Aniski API configuration
const String aniskiBaseEndpoint = 'https://api.aniskip.com';
// Torrent API configuration
const String torrentServiceEndpoint = 'http://127.0.0.1:8090';
const String localhost = '127.0.0.1';
// Cache configuration
const String localhost = '127.0.0.1';
const Set<String> cacheDisabledEndpoints = <String>{
anilistOAuthEndpoint,
};
@@ -30,7 +34,7 @@ const Set<String> cacheIgnoredHeaders = <String>{
'Authorization'
};
// Extensions condiguration
const String aniyomiExtensionsRepositoryUrl = 'https://gitea.k3vinb5.dev/Backups/kohi-den-extensions/raw/branch/main/index.min.json';
const String tachiyomiExtensionsRepositoryUrl = 'https://gitea.k3vinb5.dev/Backups/keiyoushi-extensions/raw/branch/repo/index.min.json';
const String aniyomiExtensionsDefaultRepositoryUrl = 'https://gitea.k3vinb5.dev/Backups/kohi-den-extensions/raw/branch/main/index.min.json';
const String tachiyomiExtensionsDefaultRepositoryUrl = 'https://gitea.k3vinb5.dev/Backups/keiyoushi-extensions/raw/branch/repo/index.min.json';
// TODO move to an asset
const plusImageUrl = "https://i.ibb.co/Kj8CQZH/cross.png";

View File

@@ -70,6 +70,10 @@ void setupLocator() async{
() => GraphQLService(httpService: sl<HttpService>(), endpoint: config.anilistGraphQLEndpoint),
instanceName: config.anilistGraphQlService,
);
sl.registerLazySingleton<GraphQLService>(
() => GraphQLService(httpService: sl<HttpService>(), endpoint: config.shikimoriGraphQLEndpoint),
instanceName: config.shikimoriGraphQlService,
);
sl.registerLazySingleton<AppEffectHandler>(() => AppEffectHandler());
sl.registerLazySingleton<ColorImageService>(() => ColorImageService());
sl.registerSingleton<AniyomiBridge>(AniyomiBridge());
@@ -104,6 +108,8 @@ void setupLocator() async{
));
sl.registerLazySingleton<AnimeRepositoryAnilist>(() => AnimeRepositoryAnilist());
sl.registerLazySingleton<MangaRepositoryAnilist>(() => MangaRepositoryAnilist());
sl.registerLazySingleton<AnimeRepositoryShikimori>(() => AnimeRepositoryShikimori());
sl.registerLazySingleton<MangaRepositoryShikimori>(() => MangaRepositoryShikimori());
sl.registerLazySingleton<AnimeRepository>(() => AnimeRepositoryFactory());
sl.registerLazySingleton<MangaRepository>(() => MangaRepositoryFactory());
sl.registerLazySingleton<EpisodeRepositoryAnizip>(() => EpisodeRepositoryAnizip());

View File

@@ -1,5 +1,6 @@
// { }
const viewerQuery = '''
const viewerQuery =
'''
query Viewer {
Viewer {
id
@@ -25,7 +26,8 @@ query Viewer {
// "userId": 0,
// "type" : "ANIME" or "MANGA"
// }
const mediaListCollectionQuery = '''
const mediaListCollectionQuery =
'''
query MediaListCollection(\$userName: String, \$userId: Int, \$type: MediaType) {
MediaListCollection(userName: \$userName, userId: \$userId, type: \$type) {
lists {
@@ -145,7 +147,8 @@ query Page(\$sort: [AiringSort], \$page: Int, \$perPage: Int, \$notYetAired: Boo
// "sort": "POPULARITY_DESC" or "TRENDING_DESC",
// "type": "ANIME" or "MANGA"
// }
const mediaTrendingOrPopularQuery = '''
const mediaTrendingOrPopularQuery =
'''
query Page(\$page: Int, \$perPage: Int, \$sort: [MediaSort], \$type: MediaType) {
Page(page: \$page, perPage: \$perPage) {
media(sort: \$sort, type: \$type) {
@@ -192,7 +195,6 @@ query Page(\$page: Int, \$perPage: Int, \$sort: [MediaSort], \$type: MediaType)
}
}
}
''';
// {
// "page" : 1,
@@ -202,7 +204,8 @@ query Page(\$page: Int, \$perPage: Int, \$sort: [MediaSort], \$type: MediaType)
// "endDateGreater": 20250612,
// "endDateLesser": 20250712
// }
const mediaRecentlyCompletedQuery = '''
const mediaRecentlyCompletedQuery =
'''
query Page(\$page: Int, \$perPage: Int, \$sort: [MediaSort], \$type: MediaType, \$endDateGreater: FuzzyDateInt, \$endDateLesser: FuzzyDateInt) {
Page(page: \$page, perPage: \$perPage) {
media(sort: \$sort, type: \$type, endDate_greater: \$endDateGreater, endDate_lesser: \$endDateLesser) {
@@ -257,7 +260,8 @@ query Page(\$page: Int, \$perPage: Int, \$sort: [MediaSort], \$type: MediaType,
// "sort": "POPULARITY_DESC" or "TRENDING_DESC",
// "startDateGreater": 20250724
// }
const mediaUpcomingQuery = '''
const mediaUpcomingQuery =
'''
query Page(\$page: Int, \$perPage: Int, \$sort: [MediaSort], \$type: MediaType, \$startDateGreater: FuzzyDateInt) {
Page(page: \$page, perPage: \$perPage) {
media(sort: \$sort, type: \$type, startDate_greater: \$startDateGreater) {
@@ -312,7 +316,8 @@ query Page(\$page: Int, \$perPage: Int, \$sort: [MediaSort], \$type: MediaType,
// "airingAtGreater": 1700000000, milliseconds since epoch
// "airingAtLesser": 1800000000, milliseconds since epoch
// }
const calendarQuery = '''
const calendarQuery =
'''
query Page(\$sort: [AiringSort], \$page: Int, \$perPage: Int, \$airingAtGreater: Int, \$airingAtLesser: Int) {
Page(page: \$page, perPage: \$perPage) {
airingSchedules(sort: \$sort, airingAt_greater: \$airingAtGreater, airingAt_lesser: \$airingAtLesser) {
@@ -368,7 +373,9 @@ query Page(\$sort: [AiringSort], \$page: Int, \$perPage: Int, \$airingAtGreater:
// "page" : 1,
// "perPage": 20,
// }
const mediaDetailsQuery = '''query Page(\$type: MediaType, \$mediaId: Int, \$page: Int, \$perPage: Int) {
const mediaDetailsQuery =
'''
query Page(\$type: MediaType, \$mediaId: Int, \$page: Int, \$perPage: Int) {
Media(id: \$mediaId, type: \$type) {
id
title {
@@ -463,7 +470,9 @@ const mediaDetailsQuery = '''query Page(\$type: MediaType, \$mediaId: Int, \$pag
// {
// "mediaId": selectedAnime.id
// }
const mediaListEntryQuery = '''query Page(\$mediaId: Int) {
const mediaListEntryQuery =
'''
query Page(\$mediaId: Int) {
Media(id: \$mediaId) {
mediaListEntry {
progress
@@ -495,7 +504,8 @@ const mediaListEntryQuery = '''query Page(\$mediaId: Int) {
// "genres": ["Action", "Fantasy"],
// "sort": "POPULARITY_DESC"
// }
const mediaAdvancedSearchQuery = '''
const mediaAdvancedSearchQuery =
'''
query (
\$page: Int, \$perPage: Int, \$type: MediaType, \$season: MediaSeason, \$seasonYear: Int, \$format: MediaFormat, \$status: MediaStatus, \$genres: [String], \$sort: [MediaSort], \$search: String, \$countryOfOrigin: CountryCode
) {
@@ -503,7 +513,7 @@ query (
media(
type: \$type, season: \$season, seasonYear: \$seasonYear, format: \$format, status: \$status, genre_in: \$genres, sort: \$sort, search: \$search, countryOfOrigin: \$countryOfOrigin
) {
id
id
idMal
title {
english
@@ -557,7 +567,8 @@ id
// "completedAt": {"day": 1, "month": 2, "year": 2023},
// "status": "COMPLETED"
// }
const updateMediaEntryQuery = '''
const updateMediaEntryQuery =
'''
mutation SaveMediaListEntry(\$mediaId: Int, \$progress: Int, \$progressVolumes: Int, \$repeat: Int, \$score: Float, \$startedAt: FuzzyDateInput, \$completedAt: FuzzyDateInput, \$status: MediaListStatus) {
SaveMediaListEntry(mediaId: \$mediaId, progress: \$progress, progressVolumes: \$progressVolumes, repeat: \$repeat, score: \$score, startedAt: \$startedAt, completedAt: \$completedAt, status: \$status) {
progress

View File

@@ -0,0 +1,410 @@
//sort and type are important
// {
// "page": 1,
// "limit": 50,
// "order": "aired_on" or "popularity",
// }
const animeUpcomingOrPopularQuery =
'''
query Animes(\$page: PositiveInt, \$limit: PositiveInt, \$order: OrderEnum) {
animes(page: \$page, limit: \$limit, order: \$order) {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
duration
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
episodes
nextEpisodeAt
genres {
kind
name
}
kind
isCensored
score
season
status
}
}
''';
// {
// "page": 1,
// "limit": 50,
// "season": "sprint_2026"
// "order": "popularity",
// }
const animeTrendingQuery =
'''
query Animes(\$page: PositiveInt, \$limit: PositiveInt, \$order: OrderEnum, \$season: SeasonString) {
animes(page: \$page, limit: \$limit, order: \$order, season: \$season) {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
duration
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
episodes
nextEpisodeAt
genres {
kind
name
}
kind
isCensored
score
season
status
}
}
''';
// {
// "page": 1,
// "limit": 50,
// "season": "sprint_2026"
// "order": "updated_at" or "popularity",
// "status": "ongoing" or "released"
// }
const animeRecentlyReleasedOrRecentlyCompletedQuery =
'''
query Animes(\$page: PositiveInt, \$limit: PositiveInt, \$order: OrderEnum, \$season: SeasonString, \$status: AnimeStatusString) {
animes(page: \$page, limit: \$limit, order: \$order, season: \$season, status: \$status) {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
duration
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
episodes
nextEpisodeAt
genres {
kind
name
}
kind
isCensored
score
season
status
}
}
''';
// {
// "ids": selectedAnime.id,
// "page" : 1,
// "limit": 20,
// }
const String shikimoriAnimeDetailsQuery = '''
query Animes(\$ids: String, \$page: PositiveInt, \$limit: PositiveInt) {
animes(ids: \$ids, page: \$page, limit: \$limit) {
userRate {
episodes
score
rewatches
status
createdAt
updatedAt
}
characterRoles {
character {
id
poster {
originalUrl
}
name
}
}
related {
relationKind
anime {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
duration
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
episodes
nextEpisodeAt
genres {
kind
name
}
kind
isCensored
score
season
status
}
}
}
}
''';
// {
// "page": 1,
// "limit": 50,
// "order": "aired_on" or "popularity",
// }
const mangaUpcomingOrPopularQuery =
'''
query Mangas(\$page: PositiveInt, \$limit: PositiveInt, \$order: OrderEnum) {
mangas(page: \$page, limit: \$limit, order: \$order) {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
chapters
genres {
kind
name
}
kind
isCensored
score
status
}
}
''';
// {
// "page": 1,
// "limit": 50,
// "season": "sprint_2026"
// "order": "popularity",
// }
const mangaTrendingQuery =
'''
query Mangas(\$page: PositiveInt, \$limit: PositiveInt, \$order: OrderEnum, \$season: SeasonString) {
mangas(page: \$page, limit: \$limit, order: \$order, season: \$season) {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
chapters
genres {
kind
name
}
kind
isCensored
score
status
}
}
''';
// {
// "page": 1,
// "limit": 50,
// "season": "sprint_2026"
// "order": "updated_at" or "popularity",
// "status": "ongoing" or "released"
// }
const mangaRecentlyReleasedOrRecentlyCompletedQuery =
'''
query Mangas(\$page: PositiveInt, \$limit: PositiveInt, \$order: OrderEnum, \$season: SeasonString, \$status: MangaStatusString) {
mangas(page: \$page, limit: \$limit, order: \$order, season: \$season, status: \$status) {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
chapters
genres {
kind
name
}
kind
isCensored
score
status
}
}
''';
// {
// "ids": selectedManga.id,
// "page" : 1,
// "limit": 20,
// }
const String shikimoriMangaDetailsQuery =
'''
query Mangas(\$ids: String, \$page: PositiveInt, \$limit: PositiveInt) {
mangas(ids: \$ids, page: \$page, limit: \$limit) {
userRate {
episodes
score
rewatches
status
createdAt
updatedAt
}
characterRoles {
character {
id
poster {
originalUrl
}
name
}
}
related {
relationKind
manga {
malId
id
english
name
russian
japanese
poster {
originalUrl
mainAlt2xUrl
}
description
airedOn {
day
month
year
}
releasedOn {
day
month
year
}
chapters
genres {
kind
name
}
kind
isCensored
score
status
}
}
}
}
''';
// Genres list query
const String shikimoriGenresListQuery =
r'''
query GenresList($entryType: GenreEntryTypeEnum!) {
genres(entryType: $entryType) {
id
name
russian
kind
}
}
''';

View File

@@ -73,7 +73,7 @@ class ShikimoriMediaService extends MediaService {
Service get service => Service.shikimori;
@override
List<String> get titleLanguages => [];
List<String> get titleLanguages => ['Romaji', 'English', 'Native', 'Russian'];
}
class SimklMediaService extends MediaService {

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/models/local/user/local_user_model.dart';
import 'package:unyo/data/repositories/user_repository_anilist.dart';
import 'package:unyo/data/repositories/user_repository_local.dart';
import 'package:unyo/domain/entities/user/settings.dart';

View File

@@ -4,8 +4,8 @@ import 'package:unyo/core/services/api/dto/anilist/media_details_graphql_entity.
import 'package:unyo/core/services/api/dto/anilist/media_details_media_list_entry_entity.dart';
import 'package:unyo/data/adapters/adapters_names.dart' as names;
import 'package:unyo/data/adapters/adapters_types.dart' as types;
import 'package:unyo/data/models/anilist/anilist_anime_model.dart';
import 'package:unyo/data/models/anilist/anilist_media_character.dart';
import 'package:unyo/data/models/anilist/media/anilist_anime_model.dart';
import 'package:unyo/data/models/anilist/media/anilist_media_character.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/anime_details.dart';
import 'package:unyo/domain/entities/media/media_character.dart';

View File

@@ -4,8 +4,8 @@ import 'package:unyo/core/services/api/dto/anilist/media_details_graphql_entity.
import 'package:unyo/core/services/api/dto/anilist/media_details_media_list_entry_entity.dart';
import 'package:unyo/data/adapters/adapters_names.dart' as names;
import 'package:unyo/data/adapters/adapters_types.dart' as types;
import 'package:unyo/data/models/anilist/anilist_manga_model.dart';
import 'package:unyo/data/models/anilist/anilist_media_character.dart';
import 'package:unyo/data/models/anilist/media/anilist_manga_model.dart';
import 'package:unyo/data/models/anilist/media/anilist_media_character.dart';
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/media/manga_details.dart';
import 'package:unyo/domain/entities/media/media_character.dart';

View File

@@ -1,7 +1,7 @@
export 'anilist/anilist_anime_details.dart';
export 'anilist/anilist_anime_model.dart';
export 'anilist/anilist_manga_details.dart';
export 'anilist/anilist_manga_model.dart';
export 'anilist/anilist_media_character.dart';
export 'anilist/anilist_user_model.dart';
export 'local/local_user_model.dart';
export 'anilist/media/anilist_anime_details.dart';
export 'anilist/media/anilist_anime_model.dart';
export 'anilist/media/anilist_manga_details.dart';
export 'anilist/media/anilist_manga_model.dart';
export 'anilist/media/anilist_media_character.dart';
export 'anilist/user/anilist_user_model.dart';
export 'local/user/local_user_model.dart';

View File

@@ -0,0 +1,55 @@
// External dependencies
import 'package:freezed_annotation/freezed_annotation.dart';
// Internal dependencies
import 'package:unyo/domain/entities/list/media_list_entry.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/anime_details.dart';
import 'package:unyo/domain/entities/media/media_character.dart';
part 'shikimori_anime_details.freezed.dart';
part 'shikimori_anime_details.g.dart';
@freezed
abstract class ShikimoriAnimeDetailsModel
with _$ShikimoriAnimeDetailsModel
implements AnimeDetails {
const factory ShikimoriAnimeDetailsModel({
@MediaListEntryConverter() required MediaListEntry mediaListEntry,
@AnimeConverter() required List<Anime> recommendedAnimes,
@MediaCharacterConverter() required List<MediaCharacter> characters,
}) = _ShikimoriAnimeDetailsModel;
factory ShikimoriAnimeDetailsModel.empty() => ShikimoriAnimeDetailsModel(
mediaListEntry: MediaListEntryModel.empty(),
recommendedAnimes: [],
characters: [],
);
factory ShikimoriAnimeDetailsModel.fromJson(Map<String, dynamic> json) =>
_$ShikimoriAnimeDetailsModelFromJson(json);
@override
Map<String, dynamic> toJson() =>
_$ShikimoriAnimeDetailsModelToJson(this as _ShikimoriAnimeDetailsModel);
// factory ShikimoriAnimeDetailsModel.fromDetailsEntry(
// ShikimoriAnimeDetailsGraphqlAnime entry,) {
// final recommendedAnimes = <Anime>[];
// for (final related in entry.related) {
// recommendedAnimes.add(
// ShikimoriAnimeModel.fromRelatedEntry(related.anime),
// );
// }
//
// final characters = entry.characterRoles
// .map((role) => ShikimoriMediaCharacterModel.fromCharacterRole(role))
// .toList();
//
// return ShikimoriAnimeDetailsModel(
// mediaListEntry: MediaListEntryModel.empty(),
// recommendedAnimes: recommendedAnimes,
// characters: characters,
// );
// }
}

View File

@@ -0,0 +1,295 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'shikimori_anime_details.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ShikimoriAnimeDetailsModel {
@MediaListEntryConverter() MediaListEntry get mediaListEntry;@AnimeConverter() List<Anime> get recommendedAnimes;@MediaCharacterConverter() List<MediaCharacter> get characters;
/// Create a copy of ShikimoriAnimeDetailsModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ShikimoriAnimeDetailsModelCopyWith<ShikimoriAnimeDetailsModel> get copyWith => _$ShikimoriAnimeDetailsModelCopyWithImpl<ShikimoriAnimeDetailsModel>(this as ShikimoriAnimeDetailsModel, _$identity);
/// Serializes this ShikimoriAnimeDetailsModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ShikimoriAnimeDetailsModel&&(identical(other.mediaListEntry, mediaListEntry) || other.mediaListEntry == mediaListEntry)&&const DeepCollectionEquality().equals(other.recommendedAnimes, recommendedAnimes)&&const DeepCollectionEquality().equals(other.characters, characters));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,mediaListEntry,const DeepCollectionEquality().hash(recommendedAnimes),const DeepCollectionEquality().hash(characters));
@override
String toString() {
return 'ShikimoriAnimeDetailsModel(mediaListEntry: $mediaListEntry, recommendedAnimes: $recommendedAnimes, characters: $characters)';
}
}
/// @nodoc
abstract mixin class $ShikimoriAnimeDetailsModelCopyWith<$Res> {
factory $ShikimoriAnimeDetailsModelCopyWith(ShikimoriAnimeDetailsModel value, $Res Function(ShikimoriAnimeDetailsModel) _then) = _$ShikimoriAnimeDetailsModelCopyWithImpl;
@useResult
$Res call({
@MediaListEntryConverter() MediaListEntry mediaListEntry,@AnimeConverter() List<Anime> recommendedAnimes,@MediaCharacterConverter() List<MediaCharacter> characters
});
}
/// @nodoc
class _$ShikimoriAnimeDetailsModelCopyWithImpl<$Res>
implements $ShikimoriAnimeDetailsModelCopyWith<$Res> {
_$ShikimoriAnimeDetailsModelCopyWithImpl(this._self, this._then);
final ShikimoriAnimeDetailsModel _self;
final $Res Function(ShikimoriAnimeDetailsModel) _then;
/// Create a copy of ShikimoriAnimeDetailsModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? mediaListEntry = null,Object? recommendedAnimes = null,Object? characters = null,}) {
return _then(_self.copyWith(
mediaListEntry: null == mediaListEntry ? _self.mediaListEntry : mediaListEntry // ignore: cast_nullable_to_non_nullable
as MediaListEntry,recommendedAnimes: null == recommendedAnimes ? _self.recommendedAnimes : recommendedAnimes // ignore: cast_nullable_to_non_nullable
as List<Anime>,characters: null == characters ? _self.characters : characters // ignore: cast_nullable_to_non_nullable
as List<MediaCharacter>,
));
}
}
/// Adds pattern-matching-related methods to [ShikimoriAnimeDetailsModel].
extension ShikimoriAnimeDetailsModelPatterns on ShikimoriAnimeDetailsModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ShikimoriAnimeDetailsModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ShikimoriAnimeDetailsModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ShikimoriAnimeDetailsModel value) $default,){
final _that = this;
switch (_that) {
case _ShikimoriAnimeDetailsModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ShikimoriAnimeDetailsModel value)? $default,){
final _that = this;
switch (_that) {
case _ShikimoriAnimeDetailsModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@MediaListEntryConverter() MediaListEntry mediaListEntry, @AnimeConverter() List<Anime> recommendedAnimes, @MediaCharacterConverter() List<MediaCharacter> characters)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ShikimoriAnimeDetailsModel() when $default != null:
return $default(_that.mediaListEntry,_that.recommendedAnimes,_that.characters);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@MediaListEntryConverter() MediaListEntry mediaListEntry, @AnimeConverter() List<Anime> recommendedAnimes, @MediaCharacterConverter() List<MediaCharacter> characters) $default,) {final _that = this;
switch (_that) {
case _ShikimoriAnimeDetailsModel():
return $default(_that.mediaListEntry,_that.recommendedAnimes,_that.characters);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@MediaListEntryConverter() MediaListEntry mediaListEntry, @AnimeConverter() List<Anime> recommendedAnimes, @MediaCharacterConverter() List<MediaCharacter> characters)? $default,) {final _that = this;
switch (_that) {
case _ShikimoriAnimeDetailsModel() when $default != null:
return $default(_that.mediaListEntry,_that.recommendedAnimes,_that.characters);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ShikimoriAnimeDetailsModel implements ShikimoriAnimeDetailsModel {
const _ShikimoriAnimeDetailsModel({@MediaListEntryConverter() required this.mediaListEntry, @AnimeConverter() required final List<Anime> recommendedAnimes, @MediaCharacterConverter() required final List<MediaCharacter> characters}): _recommendedAnimes = recommendedAnimes,_characters = characters;
factory _ShikimoriAnimeDetailsModel.fromJson(Map<String, dynamic> json) => _$ShikimoriAnimeDetailsModelFromJson(json);
@override@MediaListEntryConverter() final MediaListEntry mediaListEntry;
final List<Anime> _recommendedAnimes;
@override@AnimeConverter() List<Anime> get recommendedAnimes {
if (_recommendedAnimes is EqualUnmodifiableListView) return _recommendedAnimes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_recommendedAnimes);
}
final List<MediaCharacter> _characters;
@override@MediaCharacterConverter() List<MediaCharacter> get characters {
if (_characters is EqualUnmodifiableListView) return _characters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_characters);
}
/// Create a copy of ShikimoriAnimeDetailsModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ShikimoriAnimeDetailsModelCopyWith<_ShikimoriAnimeDetailsModel> get copyWith => __$ShikimoriAnimeDetailsModelCopyWithImpl<_ShikimoriAnimeDetailsModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ShikimoriAnimeDetailsModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ShikimoriAnimeDetailsModel&&(identical(other.mediaListEntry, mediaListEntry) || other.mediaListEntry == mediaListEntry)&&const DeepCollectionEquality().equals(other._recommendedAnimes, _recommendedAnimes)&&const DeepCollectionEquality().equals(other._characters, _characters));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,mediaListEntry,const DeepCollectionEquality().hash(_recommendedAnimes),const DeepCollectionEquality().hash(_characters));
@override
String toString() {
return 'ShikimoriAnimeDetailsModel(mediaListEntry: $mediaListEntry, recommendedAnimes: $recommendedAnimes, characters: $characters)';
}
}
/// @nodoc
abstract mixin class _$ShikimoriAnimeDetailsModelCopyWith<$Res> implements $ShikimoriAnimeDetailsModelCopyWith<$Res> {
factory _$ShikimoriAnimeDetailsModelCopyWith(_ShikimoriAnimeDetailsModel value, $Res Function(_ShikimoriAnimeDetailsModel) _then) = __$ShikimoriAnimeDetailsModelCopyWithImpl;
@override @useResult
$Res call({
@MediaListEntryConverter() MediaListEntry mediaListEntry,@AnimeConverter() List<Anime> recommendedAnimes,@MediaCharacterConverter() List<MediaCharacter> characters
});
}
/// @nodoc
class __$ShikimoriAnimeDetailsModelCopyWithImpl<$Res>
implements _$ShikimoriAnimeDetailsModelCopyWith<$Res> {
__$ShikimoriAnimeDetailsModelCopyWithImpl(this._self, this._then);
final _ShikimoriAnimeDetailsModel _self;
final $Res Function(_ShikimoriAnimeDetailsModel) _then;
/// Create a copy of ShikimoriAnimeDetailsModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? mediaListEntry = null,Object? recommendedAnimes = null,Object? characters = null,}) {
return _then(_ShikimoriAnimeDetailsModel(
mediaListEntry: null == mediaListEntry ? _self.mediaListEntry : mediaListEntry // ignore: cast_nullable_to_non_nullable
as MediaListEntry,recommendedAnimes: null == recommendedAnimes ? _self._recommendedAnimes : recommendedAnimes // ignore: cast_nullable_to_non_nullable
as List<Anime>,characters: null == characters ? _self._characters : characters // ignore: cast_nullable_to_non_nullable
as List<MediaCharacter>,
));
}
}
// dart format on

View File

@@ -0,0 +1,38 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shikimori_anime_details.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ShikimoriAnimeDetailsModel _$ShikimoriAnimeDetailsModelFromJson(
Map<String, dynamic> json,
) => _ShikimoriAnimeDetailsModel(
mediaListEntry: const MediaListEntryConverter().fromJson(
json['mediaListEntry'] as Map<String, dynamic>,
),
recommendedAnimes: (json['recommendedAnimes'] as List<dynamic>)
.map((e) => const AnimeConverter().fromJson(e as Map<String, dynamic>))
.toList(),
characters: (json['characters'] as List<dynamic>)
.map(
(e) =>
const MediaCharacterConverter().fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$ShikimoriAnimeDetailsModelToJson(
_ShikimoriAnimeDetailsModel instance,
) => <String, dynamic>{
'mediaListEntry': const MediaListEntryConverter().toJson(
instance.mediaListEntry,
),
'recommendedAnimes': instance.recommendedAnimes
.map(const AnimeConverter().toJson)
.toList(),
'characters': instance.characters
.map(const MediaCharacterConverter().toJson)
.toList(),
};

View File

@@ -0,0 +1,195 @@
// External dependencies
import 'package:freezed_annotation/freezed_annotation.dart';
// Internal dependencies
import 'package:unyo/domain/entities/media/airing_episode.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/title.dart';
part 'shikimori_anime_model.freezed.dart';
part 'shikimori_anime_model.g.dart';
@freezed
abstract class ShikimoriAnimeModel with _$ShikimoriAnimeModel implements Anime {
const ShikimoriAnimeModel._();
factory ShikimoriAnimeModel({
required int id,
required int idMal,
@TitleConverter() required Title title,
required int averageScore,
required String bannerImage,
required String countryOfOrigin,
required String coverImage,
required String description,
required int duration,
required String endDate,
required String startDate,
required int episodes,
required List<String> genres,
required String format,
required bool isAdult,
required int popularity,
required int meanScore,
required String season,
required String status,
required bool isFavourite,
@AiringEpisodeConverter() required AiringEpisode nextAiringEpisode,
}) = _ShikimoriAnimeModel;
factory ShikimoriAnimeModel.fromJson(Map<String, dynamic> json) =>
_$ShikimoriAnimeModelFromJson(json);
// factory ShikimoriAnimeModel.fromListEntry(
// ShikimoriAnimeListGraphqlAnimes entry,) {
// return ShikimoriAnimeModel(
// id: int.parse(entry.id),
// idMal: int.tryParse(entry.malId) ?? -1,
// title: TitleModel(
// romaji: entry.name,
// english: entry.english,
// userPreferred: entry.name,
// nativeTitle: entry.japanese,
// ),
// averageScore: (entry.score * 10).round(),
// bannerImage: entry.poster.originalUrl,
// countryOfOrigin: 'JP',
// coverImage: entry.poster.mainUrl,
// description: '',
// duration: entry.duration,
// endDate: _formatIncompleteDate(entry.releasedOn),
// startDate: _formatIncompleteDate(entry.airedOn),
// episodes: entry.episodes,
// genres: entry.genres.map((g) => g.name).toList(),
// format: _mapKindToFormat(entry.kind),
// isAdult: entry.rating == 'rx' || !entry.isCensored,
// popularity: 0,
// meanScore: (entry.score * 10).round(),
// season: entry.season,
// status: _mapStatusToAnilist(entry.status),
// isFavourite: false,
// nextAiringEpisode: AiringEpisodeModel(
// episode: entry.episodesAired,
// airingAt: entry.nextEpisodeAt is String ? entry.nextEpisodeAt as String : '',
// ),
// );
// }
// factory ShikimoriAnimeModel.fromDetailsEntry(
// ShikimoriAnimeDetailsGraphqlAnime entry,) {
// return ShikimoriAnimeModel(
// id: int.parse(entry.id),
// idMal: int.tryParse(entry.malId) ?? -1,
// title: TitleModel(
// romaji: entry.name,
// english: entry.english,
// userPreferred: entry.name,
// nativeTitle: entry.japanese,
// ),
// averageScore: (entry.score * 10).round(),
// bannerImage: entry.poster.originalUrl,
// countryOfOrigin: 'JP',
// coverImage: entry.poster.mainUrl,
// description: entry.description,
// duration: entry.duration,
// endDate: _formatIncompleteDate(entry.releasedOn),
// startDate: _formatIncompleteDate(entry.airedOn),
// episodes: entry.episodes,
// genres: entry.genres.map((g) => g.name).toList(),
// format: _mapKindToFormat(entry.kind),
// isAdult: entry.rating == 'rx' || !entry.isCensored,
// popularity: entry.scoresStats.fold(0, (sum, s) => sum + s.count),
// meanScore: (entry.score * 10).round(),
// season: entry.season,
// status: _mapStatusToAnilist(entry.status),
// isFavourite: false,
// nextAiringEpisode: AiringEpisodeModel(
// episode: entry.episodesAired,
// airingAt: entry.nextEpisodeAt is String ? entry.nextEpisodeAt as String : '',
// ),
// );
// }
// factory ShikimoriAnimeModel.fromRelatedEntry(
// ShikimoriAnimeDetailsGraphqlAnimeRelatedAnime entry,) {
// return ShikimoriAnimeModel(
// id: int.parse(entry.id),
// idMal: -1,
// title: TitleModel(
// romaji: entry.name,
// english: '',
// userPreferred: entry.name,
// nativeTitle: '',
// ),
// averageScore: 0,
// bannerImage: entry.poster.mainUrl,
// countryOfOrigin: 'JP',
// coverImage: entry.poster.mainUrl,
// description: '',
// duration: 0,
// endDate: '',
// startDate: '',
// episodes: entry.episodes,
// genres: [],
// format: _mapKindToFormat(entry.kind),
// isAdult: false,
// popularity: 0,
// meanScore: 0,
// season: '',
// status: 'FINISHED',
// isFavourite: false,
// nextAiringEpisode: AiringEpisodeModel.empty(),
// );
// }
}
String _formatIncompleteDate(dynamic dateObj) {
if (dateObj == null) return '';
final year = dateObj.year;
final month = dateObj.month;
final day = dateObj.day;
if (year == 0 && month == 0 && day == 0) return '';
final parts = <String>[];
if (day > 0) parts.add(day.toString().padLeft(2, '0'));
if (month > 0) parts.add(month.toString().padLeft(2, '0'));
if (year > 0) parts.add(year.toString());
return parts.join('/');
}
String _mapKindToFormat(String kind) {
switch (kind) {
case 'tv':
return 'TV';
case 'movie':
return 'MOVIE';
case 'ova':
return 'OVA';
case 'ona':
return 'ONA';
case 'special':
return 'SPECIAL';
case 'tv_special':
return 'TV_SHORT';
case 'music':
return 'MUSIC';
case 'pv':
return 'MUSIC';
case 'cm':
return 'SPECIAL';
default:
return kind.toUpperCase();
}
}
String _mapStatusToAnilist(String status) {
switch (status) {
case 'anons':
return 'NOT_YET_RELEASED';
case 'ongoing':
return 'RELEASING';
case 'released':
return 'FINISHED';
default:
return status.toUpperCase();
}
}

View File

@@ -0,0 +1,335 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'shikimori_anime_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ShikimoriAnimeModel {
int get id; int get idMal;@TitleConverter() Title get title; int get averageScore; String get bannerImage; String get countryOfOrigin; String get coverImage; String get description; int get duration; String get endDate; String get startDate; int get episodes; List<String> get genres; String get format; bool get isAdult; int get popularity; int get meanScore; String get season; String get status; bool get isFavourite;@AiringEpisodeConverter() AiringEpisode get nextAiringEpisode;
/// Create a copy of ShikimoriAnimeModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ShikimoriAnimeModelCopyWith<ShikimoriAnimeModel> get copyWith => _$ShikimoriAnimeModelCopyWithImpl<ShikimoriAnimeModel>(this as ShikimoriAnimeModel, _$identity);
/// Serializes this ShikimoriAnimeModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ShikimoriAnimeModel&&(identical(other.id, id) || other.id == id)&&(identical(other.idMal, idMal) || other.idMal == idMal)&&(identical(other.title, title) || other.title == title)&&(identical(other.averageScore, averageScore) || other.averageScore == averageScore)&&(identical(other.bannerImage, bannerImage) || other.bannerImage == bannerImage)&&(identical(other.countryOfOrigin, countryOfOrigin) || other.countryOfOrigin == countryOfOrigin)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.description, description) || other.description == description)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&const DeepCollectionEquality().equals(other.genres, genres)&&(identical(other.format, format) || other.format == format)&&(identical(other.isAdult, isAdult) || other.isAdult == isAdult)&&(identical(other.popularity, popularity) || other.popularity == popularity)&&(identical(other.meanScore, meanScore) || other.meanScore == meanScore)&&(identical(other.season, season) || other.season == season)&&(identical(other.status, status) || other.status == status)&&(identical(other.isFavourite, isFavourite) || other.isFavourite == isFavourite)&&(identical(other.nextAiringEpisode, nextAiringEpisode) || other.nextAiringEpisode == nextAiringEpisode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,idMal,title,averageScore,bannerImage,countryOfOrigin,coverImage,description,duration,endDate,startDate,episodes,const DeepCollectionEquality().hash(genres),format,isAdult,popularity,meanScore,season,status,isFavourite,nextAiringEpisode]);
}
/// @nodoc
abstract mixin class $ShikimoriAnimeModelCopyWith<$Res> {
factory $ShikimoriAnimeModelCopyWith(ShikimoriAnimeModel value, $Res Function(ShikimoriAnimeModel) _then) = _$ShikimoriAnimeModelCopyWithImpl;
@useResult
$Res call({
int id, int idMal,@TitleConverter() Title title, int averageScore, String bannerImage, String countryOfOrigin, String coverImage, String description, int duration, String endDate, String startDate, int episodes, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String season, String status, bool isFavourite,@AiringEpisodeConverter() AiringEpisode nextAiringEpisode
});
}
/// @nodoc
class _$ShikimoriAnimeModelCopyWithImpl<$Res>
implements $ShikimoriAnimeModelCopyWith<$Res> {
_$ShikimoriAnimeModelCopyWithImpl(this._self, this._then);
final ShikimoriAnimeModel _self;
final $Res Function(ShikimoriAnimeModel) _then;
/// Create a copy of ShikimoriAnimeModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? idMal = null,Object? title = null,Object? averageScore = null,Object? bannerImage = null,Object? countryOfOrigin = null,Object? coverImage = null,Object? description = null,Object? duration = null,Object? endDate = null,Object? startDate = null,Object? episodes = null,Object? genres = null,Object? format = null,Object? isAdult = null,Object? popularity = null,Object? meanScore = null,Object? season = null,Object? status = null,Object? isFavourite = null,Object? nextAiringEpisode = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,idMal: null == idMal ? _self.idMal : idMal // ignore: cast_nullable_to_non_nullable
as int,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as Title,averageScore: null == averageScore ? _self.averageScore : averageScore // ignore: cast_nullable_to_non_nullable
as int,bannerImage: null == bannerImage ? _self.bannerImage : bannerImage // ignore: cast_nullable_to_non_nullable
as String,countryOfOrigin: null == countryOfOrigin ? _self.countryOfOrigin : countryOfOrigin // ignore: cast_nullable_to_non_nullable
as String,coverImage: null == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable
as int,endDate: null == endDate ? _self.endDate : endDate // ignore: cast_nullable_to_non_nullable
as String,startDate: null == startDate ? _self.startDate : startDate // ignore: cast_nullable_to_non_nullable
as String,episodes: null == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int,genres: null == genres ? _self.genres : genres // ignore: cast_nullable_to_non_nullable
as List<String>,format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable
as String,isAdult: null == isAdult ? _self.isAdult : isAdult // ignore: cast_nullable_to_non_nullable
as bool,popularity: null == popularity ? _self.popularity : popularity // ignore: cast_nullable_to_non_nullable
as int,meanScore: null == meanScore ? _self.meanScore : meanScore // ignore: cast_nullable_to_non_nullable
as int,season: null == season ? _self.season : season // ignore: cast_nullable_to_non_nullable
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as String,isFavourite: null == isFavourite ? _self.isFavourite : isFavourite // ignore: cast_nullable_to_non_nullable
as bool,nextAiringEpisode: null == nextAiringEpisode ? _self.nextAiringEpisode : nextAiringEpisode // ignore: cast_nullable_to_non_nullable
as AiringEpisode,
));
}
}
/// Adds pattern-matching-related methods to [ShikimoriAnimeModel].
extension ShikimoriAnimeModelPatterns on ShikimoriAnimeModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ShikimoriAnimeModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ShikimoriAnimeModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ShikimoriAnimeModel value) $default,){
final _that = this;
switch (_that) {
case _ShikimoriAnimeModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ShikimoriAnimeModel value)? $default,){
final _that = this;
switch (_that) {
case _ShikimoriAnimeModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int id, int idMal, @TitleConverter() Title title, int averageScore, String bannerImage, String countryOfOrigin, String coverImage, String description, int duration, String endDate, String startDate, int episodes, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String season, String status, bool isFavourite, @AiringEpisodeConverter() AiringEpisode nextAiringEpisode)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ShikimoriAnimeModel() when $default != null:
return $default(_that.id,_that.idMal,_that.title,_that.averageScore,_that.bannerImage,_that.countryOfOrigin,_that.coverImage,_that.description,_that.duration,_that.endDate,_that.startDate,_that.episodes,_that.genres,_that.format,_that.isAdult,_that.popularity,_that.meanScore,_that.season,_that.status,_that.isFavourite,_that.nextAiringEpisode);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int id, int idMal, @TitleConverter() Title title, int averageScore, String bannerImage, String countryOfOrigin, String coverImage, String description, int duration, String endDate, String startDate, int episodes, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String season, String status, bool isFavourite, @AiringEpisodeConverter() AiringEpisode nextAiringEpisode) $default,) {final _that = this;
switch (_that) {
case _ShikimoriAnimeModel():
return $default(_that.id,_that.idMal,_that.title,_that.averageScore,_that.bannerImage,_that.countryOfOrigin,_that.coverImage,_that.description,_that.duration,_that.endDate,_that.startDate,_that.episodes,_that.genres,_that.format,_that.isAdult,_that.popularity,_that.meanScore,_that.season,_that.status,_that.isFavourite,_that.nextAiringEpisode);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int id, int idMal, @TitleConverter() Title title, int averageScore, String bannerImage, String countryOfOrigin, String coverImage, String description, int duration, String endDate, String startDate, int episodes, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String season, String status, bool isFavourite, @AiringEpisodeConverter() AiringEpisode nextAiringEpisode)? $default,) {final _that = this;
switch (_that) {
case _ShikimoriAnimeModel() when $default != null:
return $default(_that.id,_that.idMal,_that.title,_that.averageScore,_that.bannerImage,_that.countryOfOrigin,_that.coverImage,_that.description,_that.duration,_that.endDate,_that.startDate,_that.episodes,_that.genres,_that.format,_that.isAdult,_that.popularity,_that.meanScore,_that.season,_that.status,_that.isFavourite,_that.nextAiringEpisode);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ShikimoriAnimeModel extends ShikimoriAnimeModel {
_ShikimoriAnimeModel({required this.id, required this.idMal, @TitleConverter() required this.title, required this.averageScore, required this.bannerImage, required this.countryOfOrigin, required this.coverImage, required this.description, required this.duration, required this.endDate, required this.startDate, required this.episodes, required final List<String> genres, required this.format, required this.isAdult, required this.popularity, required this.meanScore, required this.season, required this.status, required this.isFavourite, @AiringEpisodeConverter() required this.nextAiringEpisode}): _genres = genres,super._();
factory _ShikimoriAnimeModel.fromJson(Map<String, dynamic> json) => _$ShikimoriAnimeModelFromJson(json);
@override final int id;
@override final int idMal;
@override@TitleConverter() final Title title;
@override final int averageScore;
@override final String bannerImage;
@override final String countryOfOrigin;
@override final String coverImage;
@override final String description;
@override final int duration;
@override final String endDate;
@override final String startDate;
@override final int episodes;
final List<String> _genres;
@override List<String> get genres {
if (_genres is EqualUnmodifiableListView) return _genres;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_genres);
}
@override final String format;
@override final bool isAdult;
@override final int popularity;
@override final int meanScore;
@override final String season;
@override final String status;
@override final bool isFavourite;
@override@AiringEpisodeConverter() final AiringEpisode nextAiringEpisode;
/// Create a copy of ShikimoriAnimeModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ShikimoriAnimeModelCopyWith<_ShikimoriAnimeModel> get copyWith => __$ShikimoriAnimeModelCopyWithImpl<_ShikimoriAnimeModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ShikimoriAnimeModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ShikimoriAnimeModel&&(identical(other.id, id) || other.id == id)&&(identical(other.idMal, idMal) || other.idMal == idMal)&&(identical(other.title, title) || other.title == title)&&(identical(other.averageScore, averageScore) || other.averageScore == averageScore)&&(identical(other.bannerImage, bannerImage) || other.bannerImage == bannerImage)&&(identical(other.countryOfOrigin, countryOfOrigin) || other.countryOfOrigin == countryOfOrigin)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.description, description) || other.description == description)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&(identical(other.episodes, episodes) || other.episodes == episodes)&&const DeepCollectionEquality().equals(other._genres, _genres)&&(identical(other.format, format) || other.format == format)&&(identical(other.isAdult, isAdult) || other.isAdult == isAdult)&&(identical(other.popularity, popularity) || other.popularity == popularity)&&(identical(other.meanScore, meanScore) || other.meanScore == meanScore)&&(identical(other.season, season) || other.season == season)&&(identical(other.status, status) || other.status == status)&&(identical(other.isFavourite, isFavourite) || other.isFavourite == isFavourite)&&(identical(other.nextAiringEpisode, nextAiringEpisode) || other.nextAiringEpisode == nextAiringEpisode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,idMal,title,averageScore,bannerImage,countryOfOrigin,coverImage,description,duration,endDate,startDate,episodes,const DeepCollectionEquality().hash(_genres),format,isAdult,popularity,meanScore,season,status,isFavourite,nextAiringEpisode]);
}
/// @nodoc
abstract mixin class _$ShikimoriAnimeModelCopyWith<$Res> implements $ShikimoriAnimeModelCopyWith<$Res> {
factory _$ShikimoriAnimeModelCopyWith(_ShikimoriAnimeModel value, $Res Function(_ShikimoriAnimeModel) _then) = __$ShikimoriAnimeModelCopyWithImpl;
@override @useResult
$Res call({
int id, int idMal,@TitleConverter() Title title, int averageScore, String bannerImage, String countryOfOrigin, String coverImage, String description, int duration, String endDate, String startDate, int episodes, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String season, String status, bool isFavourite,@AiringEpisodeConverter() AiringEpisode nextAiringEpisode
});
}
/// @nodoc
class __$ShikimoriAnimeModelCopyWithImpl<$Res>
implements _$ShikimoriAnimeModelCopyWith<$Res> {
__$ShikimoriAnimeModelCopyWithImpl(this._self, this._then);
final _ShikimoriAnimeModel _self;
final $Res Function(_ShikimoriAnimeModel) _then;
/// Create a copy of ShikimoriAnimeModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? idMal = null,Object? title = null,Object? averageScore = null,Object? bannerImage = null,Object? countryOfOrigin = null,Object? coverImage = null,Object? description = null,Object? duration = null,Object? endDate = null,Object? startDate = null,Object? episodes = null,Object? genres = null,Object? format = null,Object? isAdult = null,Object? popularity = null,Object? meanScore = null,Object? season = null,Object? status = null,Object? isFavourite = null,Object? nextAiringEpisode = null,}) {
return _then(_ShikimoriAnimeModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,idMal: null == idMal ? _self.idMal : idMal // ignore: cast_nullable_to_non_nullable
as int,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as Title,averageScore: null == averageScore ? _self.averageScore : averageScore // ignore: cast_nullable_to_non_nullable
as int,bannerImage: null == bannerImage ? _self.bannerImage : bannerImage // ignore: cast_nullable_to_non_nullable
as String,countryOfOrigin: null == countryOfOrigin ? _self.countryOfOrigin : countryOfOrigin // ignore: cast_nullable_to_non_nullable
as String,coverImage: null == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable
as int,endDate: null == endDate ? _self.endDate : endDate // ignore: cast_nullable_to_non_nullable
as String,startDate: null == startDate ? _self.startDate : startDate // ignore: cast_nullable_to_non_nullable
as String,episodes: null == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable
as int,genres: null == genres ? _self._genres : genres // ignore: cast_nullable_to_non_nullable
as List<String>,format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable
as String,isAdult: null == isAdult ? _self.isAdult : isAdult // ignore: cast_nullable_to_non_nullable
as bool,popularity: null == popularity ? _self.popularity : popularity // ignore: cast_nullable_to_non_nullable
as int,meanScore: null == meanScore ? _self.meanScore : meanScore // ignore: cast_nullable_to_non_nullable
as int,season: null == season ? _self.season : season // ignore: cast_nullable_to_non_nullable
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as String,isFavourite: null == isFavourite ? _self.isFavourite : isFavourite // ignore: cast_nullable_to_non_nullable
as bool,nextAiringEpisode: null == nextAiringEpisode ? _self.nextAiringEpisode : nextAiringEpisode // ignore: cast_nullable_to_non_nullable
as AiringEpisode,
));
}
}
// dart format on

View File

@@ -0,0 +1,63 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shikimori_anime_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ShikimoriAnimeModel _$ShikimoriAnimeModelFromJson(
Map<String, dynamic> json,
) => _ShikimoriAnimeModel(
id: (json['id'] as num).toInt(),
idMal: (json['idMal'] as num).toInt(),
title: const TitleConverter().fromJson(json['title'] as Map<String, dynamic>),
averageScore: (json['averageScore'] as num).toInt(),
bannerImage: json['bannerImage'] as String,
countryOfOrigin: json['countryOfOrigin'] as String,
coverImage: json['coverImage'] as String,
description: json['description'] as String,
duration: (json['duration'] as num).toInt(),
endDate: json['endDate'] as String,
startDate: json['startDate'] as String,
episodes: (json['episodes'] as num).toInt(),
genres: (json['genres'] as List<dynamic>).map((e) => e as String).toList(),
format: json['format'] as String,
isAdult: json['isAdult'] as bool,
popularity: (json['popularity'] as num).toInt(),
meanScore: (json['meanScore'] as num).toInt(),
season: json['season'] as String,
status: json['status'] as String,
isFavourite: json['isFavourite'] as bool,
nextAiringEpisode: const AiringEpisodeConverter().fromJson(
json['nextAiringEpisode'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$ShikimoriAnimeModelToJson(
_ShikimoriAnimeModel instance,
) => <String, dynamic>{
'id': instance.id,
'idMal': instance.idMal,
'title': const TitleConverter().toJson(instance.title),
'averageScore': instance.averageScore,
'bannerImage': instance.bannerImage,
'countryOfOrigin': instance.countryOfOrigin,
'coverImage': instance.coverImage,
'description': instance.description,
'duration': instance.duration,
'endDate': instance.endDate,
'startDate': instance.startDate,
'episodes': instance.episodes,
'genres': instance.genres,
'format': instance.format,
'isAdult': instance.isAdult,
'popularity': instance.popularity,
'meanScore': instance.meanScore,
'season': instance.season,
'status': instance.status,
'isFavourite': instance.isFavourite,
'nextAiringEpisode': const AiringEpisodeConverter().toJson(
instance.nextAiringEpisode,
),
};

View File

@@ -0,0 +1,55 @@
// External dependencies
import 'package:freezed_annotation/freezed_annotation.dart';
// Internal dependencies
import 'package:unyo/domain/entities/list/media_list_entry.dart';
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/media/manga_details.dart';
import 'package:unyo/domain/entities/media/media_character.dart';
part 'shikimori_manga_details.freezed.dart';
part 'shikimori_manga_details.g.dart';
@freezed
abstract class ShikimoriMangaDetailsModel
with _$ShikimoriMangaDetailsModel
implements MangaDetails {
const factory ShikimoriMangaDetailsModel({
@MediaListEntryConverter() required MediaListEntry mediaListEntry,
@MangaConverter() required List<Manga> recommendedMangas,
@MediaCharacterConverter() required List<MediaCharacter> characters,
}) = _ShikimoriMangaDetailsModel;
factory ShikimoriMangaDetailsModel.empty() => ShikimoriMangaDetailsModel(
mediaListEntry: MediaListEntryModel.empty(),
recommendedMangas: [],
characters: [],
);
factory ShikimoriMangaDetailsModel.fromJson(Map<String, dynamic> json) =>
_$ShikimoriMangaDetailsModelFromJson(json);
@override
Map<String, dynamic> toJson() =>
_$ShikimoriMangaDetailsModelToJson(this as _ShikimoriMangaDetailsModel);
// factory ShikimoriMangaDetailsModel.fromDetailsEntry(
// ShikimoriMangaDetailsGraphqlManga entry,) {
// final recommendedMangas = <Manga>[];
// for (final related in entry.related) {
// recommendedMangas.add(
// ShikimoriMangaModel.fromRelatedEntry(related.anime),
// );
// }
//
// final characters = entry.characterRoles
// .map((role) => ShikimoriMediaCharacterModel.fromMangaCharacterRole(role))
// .toList();
//
// return ShikimoriMangaDetailsModel(
// mediaListEntry: MediaListEntryModel.empty(),
// recommendedMangas: recommendedMangas,
// characters: characters,
// );
// }
}

View File

@@ -0,0 +1,295 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'shikimori_manga_details.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ShikimoriMangaDetailsModel {
@MediaListEntryConverter() MediaListEntry get mediaListEntry;@MangaConverter() List<Manga> get recommendedMangas;@MediaCharacterConverter() List<MediaCharacter> get characters;
/// Create a copy of ShikimoriMangaDetailsModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ShikimoriMangaDetailsModelCopyWith<ShikimoriMangaDetailsModel> get copyWith => _$ShikimoriMangaDetailsModelCopyWithImpl<ShikimoriMangaDetailsModel>(this as ShikimoriMangaDetailsModel, _$identity);
/// Serializes this ShikimoriMangaDetailsModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ShikimoriMangaDetailsModel&&(identical(other.mediaListEntry, mediaListEntry) || other.mediaListEntry == mediaListEntry)&&const DeepCollectionEquality().equals(other.recommendedMangas, recommendedMangas)&&const DeepCollectionEquality().equals(other.characters, characters));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,mediaListEntry,const DeepCollectionEquality().hash(recommendedMangas),const DeepCollectionEquality().hash(characters));
@override
String toString() {
return 'ShikimoriMangaDetailsModel(mediaListEntry: $mediaListEntry, recommendedMangas: $recommendedMangas, characters: $characters)';
}
}
/// @nodoc
abstract mixin class $ShikimoriMangaDetailsModelCopyWith<$Res> {
factory $ShikimoriMangaDetailsModelCopyWith(ShikimoriMangaDetailsModel value, $Res Function(ShikimoriMangaDetailsModel) _then) = _$ShikimoriMangaDetailsModelCopyWithImpl;
@useResult
$Res call({
@MediaListEntryConverter() MediaListEntry mediaListEntry,@MangaConverter() List<Manga> recommendedMangas,@MediaCharacterConverter() List<MediaCharacter> characters
});
}
/// @nodoc
class _$ShikimoriMangaDetailsModelCopyWithImpl<$Res>
implements $ShikimoriMangaDetailsModelCopyWith<$Res> {
_$ShikimoriMangaDetailsModelCopyWithImpl(this._self, this._then);
final ShikimoriMangaDetailsModel _self;
final $Res Function(ShikimoriMangaDetailsModel) _then;
/// Create a copy of ShikimoriMangaDetailsModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? mediaListEntry = null,Object? recommendedMangas = null,Object? characters = null,}) {
return _then(_self.copyWith(
mediaListEntry: null == mediaListEntry ? _self.mediaListEntry : mediaListEntry // ignore: cast_nullable_to_non_nullable
as MediaListEntry,recommendedMangas: null == recommendedMangas ? _self.recommendedMangas : recommendedMangas // ignore: cast_nullable_to_non_nullable
as List<Manga>,characters: null == characters ? _self.characters : characters // ignore: cast_nullable_to_non_nullable
as List<MediaCharacter>,
));
}
}
/// Adds pattern-matching-related methods to [ShikimoriMangaDetailsModel].
extension ShikimoriMangaDetailsModelPatterns on ShikimoriMangaDetailsModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ShikimoriMangaDetailsModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ShikimoriMangaDetailsModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ShikimoriMangaDetailsModel value) $default,){
final _that = this;
switch (_that) {
case _ShikimoriMangaDetailsModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ShikimoriMangaDetailsModel value)? $default,){
final _that = this;
switch (_that) {
case _ShikimoriMangaDetailsModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function(@MediaListEntryConverter() MediaListEntry mediaListEntry, @MangaConverter() List<Manga> recommendedMangas, @MediaCharacterConverter() List<MediaCharacter> characters)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ShikimoriMangaDetailsModel() when $default != null:
return $default(_that.mediaListEntry,_that.recommendedMangas,_that.characters);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function(@MediaListEntryConverter() MediaListEntry mediaListEntry, @MangaConverter() List<Manga> recommendedMangas, @MediaCharacterConverter() List<MediaCharacter> characters) $default,) {final _that = this;
switch (_that) {
case _ShikimoriMangaDetailsModel():
return $default(_that.mediaListEntry,_that.recommendedMangas,_that.characters);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function(@MediaListEntryConverter() MediaListEntry mediaListEntry, @MangaConverter() List<Manga> recommendedMangas, @MediaCharacterConverter() List<MediaCharacter> characters)? $default,) {final _that = this;
switch (_that) {
case _ShikimoriMangaDetailsModel() when $default != null:
return $default(_that.mediaListEntry,_that.recommendedMangas,_that.characters);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ShikimoriMangaDetailsModel implements ShikimoriMangaDetailsModel {
const _ShikimoriMangaDetailsModel({@MediaListEntryConverter() required this.mediaListEntry, @MangaConverter() required final List<Manga> recommendedMangas, @MediaCharacterConverter() required final List<MediaCharacter> characters}): _recommendedMangas = recommendedMangas,_characters = characters;
factory _ShikimoriMangaDetailsModel.fromJson(Map<String, dynamic> json) => _$ShikimoriMangaDetailsModelFromJson(json);
@override@MediaListEntryConverter() final MediaListEntry mediaListEntry;
final List<Manga> _recommendedMangas;
@override@MangaConverter() List<Manga> get recommendedMangas {
if (_recommendedMangas is EqualUnmodifiableListView) return _recommendedMangas;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_recommendedMangas);
}
final List<MediaCharacter> _characters;
@override@MediaCharacterConverter() List<MediaCharacter> get characters {
if (_characters is EqualUnmodifiableListView) return _characters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_characters);
}
/// Create a copy of ShikimoriMangaDetailsModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ShikimoriMangaDetailsModelCopyWith<_ShikimoriMangaDetailsModel> get copyWith => __$ShikimoriMangaDetailsModelCopyWithImpl<_ShikimoriMangaDetailsModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ShikimoriMangaDetailsModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ShikimoriMangaDetailsModel&&(identical(other.mediaListEntry, mediaListEntry) || other.mediaListEntry == mediaListEntry)&&const DeepCollectionEquality().equals(other._recommendedMangas, _recommendedMangas)&&const DeepCollectionEquality().equals(other._characters, _characters));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,mediaListEntry,const DeepCollectionEquality().hash(_recommendedMangas),const DeepCollectionEquality().hash(_characters));
@override
String toString() {
return 'ShikimoriMangaDetailsModel(mediaListEntry: $mediaListEntry, recommendedMangas: $recommendedMangas, characters: $characters)';
}
}
/// @nodoc
abstract mixin class _$ShikimoriMangaDetailsModelCopyWith<$Res> implements $ShikimoriMangaDetailsModelCopyWith<$Res> {
factory _$ShikimoriMangaDetailsModelCopyWith(_ShikimoriMangaDetailsModel value, $Res Function(_ShikimoriMangaDetailsModel) _then) = __$ShikimoriMangaDetailsModelCopyWithImpl;
@override @useResult
$Res call({
@MediaListEntryConverter() MediaListEntry mediaListEntry,@MangaConverter() List<Manga> recommendedMangas,@MediaCharacterConverter() List<MediaCharacter> characters
});
}
/// @nodoc
class __$ShikimoriMangaDetailsModelCopyWithImpl<$Res>
implements _$ShikimoriMangaDetailsModelCopyWith<$Res> {
__$ShikimoriMangaDetailsModelCopyWithImpl(this._self, this._then);
final _ShikimoriMangaDetailsModel _self;
final $Res Function(_ShikimoriMangaDetailsModel) _then;
/// Create a copy of ShikimoriMangaDetailsModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? mediaListEntry = null,Object? recommendedMangas = null,Object? characters = null,}) {
return _then(_ShikimoriMangaDetailsModel(
mediaListEntry: null == mediaListEntry ? _self.mediaListEntry : mediaListEntry // ignore: cast_nullable_to_non_nullable
as MediaListEntry,recommendedMangas: null == recommendedMangas ? _self._recommendedMangas : recommendedMangas // ignore: cast_nullable_to_non_nullable
as List<Manga>,characters: null == characters ? _self._characters : characters // ignore: cast_nullable_to_non_nullable
as List<MediaCharacter>,
));
}
}
// dart format on

View File

@@ -0,0 +1,38 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shikimori_manga_details.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ShikimoriMangaDetailsModel _$ShikimoriMangaDetailsModelFromJson(
Map<String, dynamic> json,
) => _ShikimoriMangaDetailsModel(
mediaListEntry: const MediaListEntryConverter().fromJson(
json['mediaListEntry'] as Map<String, dynamic>,
),
recommendedMangas: (json['recommendedMangas'] as List<dynamic>)
.map((e) => const MangaConverter().fromJson(e as Map<String, dynamic>))
.toList(),
characters: (json['characters'] as List<dynamic>)
.map(
(e) =>
const MediaCharacterConverter().fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$ShikimoriMangaDetailsModelToJson(
_ShikimoriMangaDetailsModel instance,
) => <String, dynamic>{
'mediaListEntry': const MediaListEntryConverter().toJson(
instance.mediaListEntry,
),
'recommendedMangas': instance.recommendedMangas
.map(const MangaConverter().toJson)
.toList(),
'characters': instance.characters
.map(const MediaCharacterConverter().toJson)
.toList(),
};

View File

@@ -0,0 +1,177 @@
// External dependencies
import 'package:freezed_annotation/freezed_annotation.dart';
// Internal dependencies
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/media/title.dart';
part 'shikimori_manga_model.freezed.dart';
part 'shikimori_manga_model.g.dart';
@freezed
abstract class ShikimoriMangaModel with _$ShikimoriMangaModel implements Manga {
const ShikimoriMangaModel._();
factory ShikimoriMangaModel({
required int id,
required int idMal,
@TitleConverter() required Title title,
required int averageScore,
required String bannerImage,
required int chapters,
required String countryOfOrigin,
required String coverImage,
required String description,
required String endDate,
required String startDate,
required List<String> genres,
required String format,
required bool isAdult,
required int popularity,
required int meanScore,
required String status,
required bool isFavourite,
}) = _ShikimoriMangaModel;
factory ShikimoriMangaModel.fromJson(Map<String, dynamic> json) =>
_$ShikimoriMangaModelFromJson(json);
// factory ShikimoriMangaModel.fromListEntry(
// ShikimoriMangaListGraphqlMangas entry,) {
// return ShikimoriMangaModel(
// id: int.parse(entry.id),
// idMal: int.tryParse(entry.malId) ?? -1,
// title: TitleModel(
// romaji: entry.name,
// english: entry.english,
// userPreferred: entry.name,
// nativeTitle: entry.japanese,
// ),
// averageScore: (entry.score * 10).round(),
// bannerImage: entry.poster.originalUrl,
// chapters: entry.chapters,
// countryOfOrigin: 'JP',
// coverImage: entry.poster.mainUrl,
// description: '',
// endDate: _formatIncompleteDate(entry.releasedOn),
// startDate: _formatIncompleteDate(entry.airedOn),
// genres: entry.genres.map((g) => g.name).toList(),
// format: _mapMangaKindToFormat(entry.kind),
// isAdult: !entry.isCensored,
// popularity: 0,
// meanScore: (entry.score * 10).round(),
// status: _mapMangaStatusToAnilist(entry.status),
// isFavourite: false,
// );
// }
// factory ShikimoriMangaModel.fromDetailsEntry(
// ShikimoriMangaDetailsGraphqlManga entry,) {
// return ShikimoriMangaModel(
// id: int.parse(entry.id),
// idMal: int.tryParse(entry.malId) ?? -1,
// title: TitleModel(
// romaji: entry.name,
// english: entry.english,
// userPreferred: entry.name,
// nativeTitle: entry.japanese,
// ),
// averageScore: (entry.score * 10).round(),
// bannerImage: entry.poster.originalUrl,
// chapters: entry.chapters,
// countryOfOrigin: 'JP',
// coverImage: entry.poster.mainUrl,
// description: entry.description,
// endDate: _formatIncompleteDate(entry.releasedOn),
// startDate: _formatIncompleteDate(entry.airedOn),
// genres: entry.genres.map((g) => g.name).toList(),
// format: _mapMangaKindToFormat(entry.kind),
// isAdult: !entry.isCensored,
// popularity: 0,
// meanScore: (entry.score * 10).round(),
// status: _mapMangaStatusToAnilist(entry.status),
// isFavourite: false,
// );
// }
// factory ShikimoriMangaModel.fromRelatedEntry(
// ShikimoriMangaDetailsGraphqlMangaRelatedAnime entry,) {
// return ShikimoriMangaModel(
// id: int.parse(entry.id),
// idMal: -1,
// title: TitleModel(
// romaji: entry.name,
// english: '',
// userPreferred: entry.name,
// nativeTitle: '',
// ),
// averageScore: 0,
// bannerImage: entry.poster.mainUrl,
// chapters: entry.episodes,
// countryOfOrigin: 'JP',
// coverImage: entry.poster.mainUrl,
// description: '',
// endDate: '',
// startDate: '',
// genres: [],
// format: _mapMangaKindToFormat(entry.kind),
// isAdult: false,
// popularity: 0,
// meanScore: 0,
// status: 'FINISHED',
// isFavourite: false,
// );
// }
}
String _formatIncompleteDate(dynamic dateObj) {
if (dateObj == null) return '';
final year = dateObj.year;
final month = dateObj.month;
final day = dateObj.day;
if (year == 0 && month == 0 && day == 0) return '';
final parts = <String>[];
if (day > 0) parts.add(day.toString().padLeft(2, '0'));
if (month > 0) parts.add(month.toString().padLeft(2, '0'));
if (year > 0) parts.add(year.toString());
return parts.join('/');
}
String _mapMangaKindToFormat(String kind) {
switch (kind) {
case 'manga':
return 'MANGA';
case 'manhwa':
return 'MANHWA';
case 'manhua':
return 'MANHUA';
case 'light_novel':
return 'NOVEL';
case 'novel':
return 'NOVEL';
case 'one_shot':
return 'ONE_SHOT';
case 'doujin':
return 'DOUJINSHI';
default:
return kind.toUpperCase();
}
}
String _mapMangaStatusToAnilist(String status) {
switch (status) {
case 'anons':
return 'NOT_YET_RELEASED';
case 'ongoing':
return 'RELEASING';
case 'released':
return 'FINISHED';
case 'paused':
return 'HIATUS';
case 'discontinued':
return 'CANCELLED';
default:
return status.toUpperCase();
}
}

View File

@@ -0,0 +1,326 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'shikimori_manga_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ShikimoriMangaModel {
int get id; int get idMal;@TitleConverter() Title get title; int get averageScore; String get bannerImage; int get chapters; String get countryOfOrigin; String get coverImage; String get description; String get endDate; String get startDate; List<String> get genres; String get format; bool get isAdult; int get popularity; int get meanScore; String get status; bool get isFavourite;
/// Create a copy of ShikimoriMangaModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ShikimoriMangaModelCopyWith<ShikimoriMangaModel> get copyWith => _$ShikimoriMangaModelCopyWithImpl<ShikimoriMangaModel>(this as ShikimoriMangaModel, _$identity);
/// Serializes this ShikimoriMangaModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ShikimoriMangaModel&&(identical(other.id, id) || other.id == id)&&(identical(other.idMal, idMal) || other.idMal == idMal)&&(identical(other.title, title) || other.title == title)&&(identical(other.averageScore, averageScore) || other.averageScore == averageScore)&&(identical(other.bannerImage, bannerImage) || other.bannerImage == bannerImage)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.countryOfOrigin, countryOfOrigin) || other.countryOfOrigin == countryOfOrigin)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.description, description) || other.description == description)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&const DeepCollectionEquality().equals(other.genres, genres)&&(identical(other.format, format) || other.format == format)&&(identical(other.isAdult, isAdult) || other.isAdult == isAdult)&&(identical(other.popularity, popularity) || other.popularity == popularity)&&(identical(other.meanScore, meanScore) || other.meanScore == meanScore)&&(identical(other.status, status) || other.status == status)&&(identical(other.isFavourite, isFavourite) || other.isFavourite == isFavourite));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,idMal,title,averageScore,bannerImage,chapters,countryOfOrigin,coverImage,description,endDate,startDate,const DeepCollectionEquality().hash(genres),format,isAdult,popularity,meanScore,status,isFavourite);
}
/// @nodoc
abstract mixin class $ShikimoriMangaModelCopyWith<$Res> {
factory $ShikimoriMangaModelCopyWith(ShikimoriMangaModel value, $Res Function(ShikimoriMangaModel) _then) = _$ShikimoriMangaModelCopyWithImpl;
@useResult
$Res call({
int id, int idMal,@TitleConverter() Title title, int averageScore, String bannerImage, int chapters, String countryOfOrigin, String coverImage, String description, String endDate, String startDate, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String status, bool isFavourite
});
}
/// @nodoc
class _$ShikimoriMangaModelCopyWithImpl<$Res>
implements $ShikimoriMangaModelCopyWith<$Res> {
_$ShikimoriMangaModelCopyWithImpl(this._self, this._then);
final ShikimoriMangaModel _self;
final $Res Function(ShikimoriMangaModel) _then;
/// Create a copy of ShikimoriMangaModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? idMal = null,Object? title = null,Object? averageScore = null,Object? bannerImage = null,Object? chapters = null,Object? countryOfOrigin = null,Object? coverImage = null,Object? description = null,Object? endDate = null,Object? startDate = null,Object? genres = null,Object? format = null,Object? isAdult = null,Object? popularity = null,Object? meanScore = null,Object? status = null,Object? isFavourite = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,idMal: null == idMal ? _self.idMal : idMal // ignore: cast_nullable_to_non_nullable
as int,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as Title,averageScore: null == averageScore ? _self.averageScore : averageScore // ignore: cast_nullable_to_non_nullable
as int,bannerImage: null == bannerImage ? _self.bannerImage : bannerImage // ignore: cast_nullable_to_non_nullable
as String,chapters: null == chapters ? _self.chapters : chapters // ignore: cast_nullable_to_non_nullable
as int,countryOfOrigin: null == countryOfOrigin ? _self.countryOfOrigin : countryOfOrigin // ignore: cast_nullable_to_non_nullable
as String,coverImage: null == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,endDate: null == endDate ? _self.endDate : endDate // ignore: cast_nullable_to_non_nullable
as String,startDate: null == startDate ? _self.startDate : startDate // ignore: cast_nullable_to_non_nullable
as String,genres: null == genres ? _self.genres : genres // ignore: cast_nullable_to_non_nullable
as List<String>,format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable
as String,isAdult: null == isAdult ? _self.isAdult : isAdult // ignore: cast_nullable_to_non_nullable
as bool,popularity: null == popularity ? _self.popularity : popularity // ignore: cast_nullable_to_non_nullable
as int,meanScore: null == meanScore ? _self.meanScore : meanScore // ignore: cast_nullable_to_non_nullable
as int,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as String,isFavourite: null == isFavourite ? _self.isFavourite : isFavourite // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [ShikimoriMangaModel].
extension ShikimoriMangaModelPatterns on ShikimoriMangaModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ShikimoriMangaModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ShikimoriMangaModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ShikimoriMangaModel value) $default,){
final _that = this;
switch (_that) {
case _ShikimoriMangaModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ShikimoriMangaModel value)? $default,){
final _that = this;
switch (_that) {
case _ShikimoriMangaModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int id, int idMal, @TitleConverter() Title title, int averageScore, String bannerImage, int chapters, String countryOfOrigin, String coverImage, String description, String endDate, String startDate, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String status, bool isFavourite)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ShikimoriMangaModel() when $default != null:
return $default(_that.id,_that.idMal,_that.title,_that.averageScore,_that.bannerImage,_that.chapters,_that.countryOfOrigin,_that.coverImage,_that.description,_that.endDate,_that.startDate,_that.genres,_that.format,_that.isAdult,_that.popularity,_that.meanScore,_that.status,_that.isFavourite);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int id, int idMal, @TitleConverter() Title title, int averageScore, String bannerImage, int chapters, String countryOfOrigin, String coverImage, String description, String endDate, String startDate, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String status, bool isFavourite) $default,) {final _that = this;
switch (_that) {
case _ShikimoriMangaModel():
return $default(_that.id,_that.idMal,_that.title,_that.averageScore,_that.bannerImage,_that.chapters,_that.countryOfOrigin,_that.coverImage,_that.description,_that.endDate,_that.startDate,_that.genres,_that.format,_that.isAdult,_that.popularity,_that.meanScore,_that.status,_that.isFavourite);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int id, int idMal, @TitleConverter() Title title, int averageScore, String bannerImage, int chapters, String countryOfOrigin, String coverImage, String description, String endDate, String startDate, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String status, bool isFavourite)? $default,) {final _that = this;
switch (_that) {
case _ShikimoriMangaModel() when $default != null:
return $default(_that.id,_that.idMal,_that.title,_that.averageScore,_that.bannerImage,_that.chapters,_that.countryOfOrigin,_that.coverImage,_that.description,_that.endDate,_that.startDate,_that.genres,_that.format,_that.isAdult,_that.popularity,_that.meanScore,_that.status,_that.isFavourite);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ShikimoriMangaModel extends ShikimoriMangaModel {
_ShikimoriMangaModel({required this.id, required this.idMal, @TitleConverter() required this.title, required this.averageScore, required this.bannerImage, required this.chapters, required this.countryOfOrigin, required this.coverImage, required this.description, required this.endDate, required this.startDate, required final List<String> genres, required this.format, required this.isAdult, required this.popularity, required this.meanScore, required this.status, required this.isFavourite}): _genres = genres,super._();
factory _ShikimoriMangaModel.fromJson(Map<String, dynamic> json) => _$ShikimoriMangaModelFromJson(json);
@override final int id;
@override final int idMal;
@override@TitleConverter() final Title title;
@override final int averageScore;
@override final String bannerImage;
@override final int chapters;
@override final String countryOfOrigin;
@override final String coverImage;
@override final String description;
@override final String endDate;
@override final String startDate;
final List<String> _genres;
@override List<String> get genres {
if (_genres is EqualUnmodifiableListView) return _genres;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_genres);
}
@override final String format;
@override final bool isAdult;
@override final int popularity;
@override final int meanScore;
@override final String status;
@override final bool isFavourite;
/// Create a copy of ShikimoriMangaModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ShikimoriMangaModelCopyWith<_ShikimoriMangaModel> get copyWith => __$ShikimoriMangaModelCopyWithImpl<_ShikimoriMangaModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ShikimoriMangaModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ShikimoriMangaModel&&(identical(other.id, id) || other.id == id)&&(identical(other.idMal, idMal) || other.idMal == idMal)&&(identical(other.title, title) || other.title == title)&&(identical(other.averageScore, averageScore) || other.averageScore == averageScore)&&(identical(other.bannerImage, bannerImage) || other.bannerImage == bannerImage)&&(identical(other.chapters, chapters) || other.chapters == chapters)&&(identical(other.countryOfOrigin, countryOfOrigin) || other.countryOfOrigin == countryOfOrigin)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.description, description) || other.description == description)&&(identical(other.endDate, endDate) || other.endDate == endDate)&&(identical(other.startDate, startDate) || other.startDate == startDate)&&const DeepCollectionEquality().equals(other._genres, _genres)&&(identical(other.format, format) || other.format == format)&&(identical(other.isAdult, isAdult) || other.isAdult == isAdult)&&(identical(other.popularity, popularity) || other.popularity == popularity)&&(identical(other.meanScore, meanScore) || other.meanScore == meanScore)&&(identical(other.status, status) || other.status == status)&&(identical(other.isFavourite, isFavourite) || other.isFavourite == isFavourite));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,idMal,title,averageScore,bannerImage,chapters,countryOfOrigin,coverImage,description,endDate,startDate,const DeepCollectionEquality().hash(_genres),format,isAdult,popularity,meanScore,status,isFavourite);
}
/// @nodoc
abstract mixin class _$ShikimoriMangaModelCopyWith<$Res> implements $ShikimoriMangaModelCopyWith<$Res> {
factory _$ShikimoriMangaModelCopyWith(_ShikimoriMangaModel value, $Res Function(_ShikimoriMangaModel) _then) = __$ShikimoriMangaModelCopyWithImpl;
@override @useResult
$Res call({
int id, int idMal,@TitleConverter() Title title, int averageScore, String bannerImage, int chapters, String countryOfOrigin, String coverImage, String description, String endDate, String startDate, List<String> genres, String format, bool isAdult, int popularity, int meanScore, String status, bool isFavourite
});
}
/// @nodoc
class __$ShikimoriMangaModelCopyWithImpl<$Res>
implements _$ShikimoriMangaModelCopyWith<$Res> {
__$ShikimoriMangaModelCopyWithImpl(this._self, this._then);
final _ShikimoriMangaModel _self;
final $Res Function(_ShikimoriMangaModel) _then;
/// Create a copy of ShikimoriMangaModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? idMal = null,Object? title = null,Object? averageScore = null,Object? bannerImage = null,Object? chapters = null,Object? countryOfOrigin = null,Object? coverImage = null,Object? description = null,Object? endDate = null,Object? startDate = null,Object? genres = null,Object? format = null,Object? isAdult = null,Object? popularity = null,Object? meanScore = null,Object? status = null,Object? isFavourite = null,}) {
return _then(_ShikimoriMangaModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,idMal: null == idMal ? _self.idMal : idMal // ignore: cast_nullable_to_non_nullable
as int,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as Title,averageScore: null == averageScore ? _self.averageScore : averageScore // ignore: cast_nullable_to_non_nullable
as int,bannerImage: null == bannerImage ? _self.bannerImage : bannerImage // ignore: cast_nullable_to_non_nullable
as String,chapters: null == chapters ? _self.chapters : chapters // ignore: cast_nullable_to_non_nullable
as int,countryOfOrigin: null == countryOfOrigin ? _self.countryOfOrigin : countryOfOrigin // ignore: cast_nullable_to_non_nullable
as String,coverImage: null == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,endDate: null == endDate ? _self.endDate : endDate // ignore: cast_nullable_to_non_nullable
as String,startDate: null == startDate ? _self.startDate : startDate // ignore: cast_nullable_to_non_nullable
as String,genres: null == genres ? _self._genres : genres // ignore: cast_nullable_to_non_nullable
as List<String>,format: null == format ? _self.format : format // ignore: cast_nullable_to_non_nullable
as String,isAdult: null == isAdult ? _self.isAdult : isAdult // ignore: cast_nullable_to_non_nullable
as bool,popularity: null == popularity ? _self.popularity : popularity // ignore: cast_nullable_to_non_nullable
as int,meanScore: null == meanScore ? _self.meanScore : meanScore // ignore: cast_nullable_to_non_nullable
as int,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as String,isFavourite: null == isFavourite ? _self.isFavourite : isFavourite // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -0,0 +1,53 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shikimori_manga_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ShikimoriMangaModel _$ShikimoriMangaModelFromJson(
Map<String, dynamic> json,
) => _ShikimoriMangaModel(
id: (json['id'] as num).toInt(),
idMal: (json['idMal'] as num).toInt(),
title: const TitleConverter().fromJson(json['title'] as Map<String, dynamic>),
averageScore: (json['averageScore'] as num).toInt(),
bannerImage: json['bannerImage'] as String,
chapters: (json['chapters'] as num).toInt(),
countryOfOrigin: json['countryOfOrigin'] as String,
coverImage: json['coverImage'] as String,
description: json['description'] as String,
endDate: json['endDate'] as String,
startDate: json['startDate'] as String,
genres: (json['genres'] as List<dynamic>).map((e) => e as String).toList(),
format: json['format'] as String,
isAdult: json['isAdult'] as bool,
popularity: (json['popularity'] as num).toInt(),
meanScore: (json['meanScore'] as num).toInt(),
status: json['status'] as String,
isFavourite: json['isFavourite'] as bool,
);
Map<String, dynamic> _$ShikimoriMangaModelToJson(
_ShikimoriMangaModel instance,
) => <String, dynamic>{
'id': instance.id,
'idMal': instance.idMal,
'title': const TitleConverter().toJson(instance.title),
'averageScore': instance.averageScore,
'bannerImage': instance.bannerImage,
'chapters': instance.chapters,
'countryOfOrigin': instance.countryOfOrigin,
'coverImage': instance.coverImage,
'description': instance.description,
'endDate': instance.endDate,
'startDate': instance.startDate,
'genres': instance.genres,
'format': instance.format,
'isAdult': instance.isAdult,
'popularity': instance.popularity,
'meanScore': instance.meanScore,
'status': instance.status,
'isFavourite': instance.isFavourite,
};

View File

@@ -0,0 +1,67 @@
// External dependencies
import 'package:freezed_annotation/freezed_annotation.dart';
// Internal dependencies
import 'package:unyo/domain/entities/media/media_character.dart';
part 'shikimori_media_character.freezed.dart';
part 'shikimori_media_character.g.dart';
@freezed
abstract class ShikimoriMediaCharacterModel
with _$ShikimoriMediaCharacterModel
implements MediaCharacter {
const factory ShikimoriMediaCharacterModel({
required int id,
required String image,
required String name,
required String gender,
required String description,
required String dateOfBirth,
required int age,
}) = _ShikimoriMediaCharacterModel;
factory ShikimoriMediaCharacterModel.empty() =>
const ShikimoriMediaCharacterModel(
id: -1,
image: '',
name: '',
gender: '',
description: '',
dateOfBirth: '',
age: 0,
);
factory ShikimoriMediaCharacterModel.fromJson(Map<String, dynamic> json) =>
_$ShikimoriMediaCharacterModelFromJson(json);
@override
Map<String, dynamic> toJson() =>
_$ShikimoriMediaCharacterModelToJson(this as _ShikimoriMediaCharacterModel);
// factory ShikimoriMediaCharacterModel.fromCharacterRole(
// ShikimoriAnimeDetailsGraphqlAnimeCharacterRoles role,) {
// return ShikimoriMediaCharacterModel(
// id: int.parse(role.character.id),
// image: role.character.poster.originalUrl,
// name: role.character.name,
// gender: '',
// description: '',
// dateOfBirth: '',
// age: 0,
// );
// }
// factory ShikimoriMediaCharacterModel.fromMangaCharacterRole(
// ShikimoriMangaDetailsGraphqlMangaCharacterRoles role,) {
// return ShikimoriMediaCharacterModel(
// id: int.parse(role.character.id),
// image: role.character.poster.originalUrl,
// name: role.character.name,
// gender: '',
// description: '',
// dateOfBirth: '',
// age: 0,
// );
// }
}

View File

@@ -0,0 +1,287 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'shikimori_media_character.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ShikimoriMediaCharacterModel {
int get id; String get image; String get name; String get gender; String get description; String get dateOfBirth; int get age;
/// Create a copy of ShikimoriMediaCharacterModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ShikimoriMediaCharacterModelCopyWith<ShikimoriMediaCharacterModel> get copyWith => _$ShikimoriMediaCharacterModelCopyWithImpl<ShikimoriMediaCharacterModel>(this as ShikimoriMediaCharacterModel, _$identity);
/// Serializes this ShikimoriMediaCharacterModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ShikimoriMediaCharacterModel&&(identical(other.id, id) || other.id == id)&&(identical(other.image, image) || other.image == image)&&(identical(other.name, name) || other.name == name)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.description, description) || other.description == description)&&(identical(other.dateOfBirth, dateOfBirth) || other.dateOfBirth == dateOfBirth)&&(identical(other.age, age) || other.age == age));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,image,name,gender,description,dateOfBirth,age);
}
/// @nodoc
abstract mixin class $ShikimoriMediaCharacterModelCopyWith<$Res> {
factory $ShikimoriMediaCharacterModelCopyWith(ShikimoriMediaCharacterModel value, $Res Function(ShikimoriMediaCharacterModel) _then) = _$ShikimoriMediaCharacterModelCopyWithImpl;
@useResult
$Res call({
int id, String image, String name, String gender, String description, String dateOfBirth, int age
});
}
/// @nodoc
class _$ShikimoriMediaCharacterModelCopyWithImpl<$Res>
implements $ShikimoriMediaCharacterModelCopyWith<$Res> {
_$ShikimoriMediaCharacterModelCopyWithImpl(this._self, this._then);
final ShikimoriMediaCharacterModel _self;
final $Res Function(ShikimoriMediaCharacterModel) _then;
/// Create a copy of ShikimoriMediaCharacterModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? image = null,Object? name = null,Object? gender = null,Object? description = null,Object? dateOfBirth = null,Object? age = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,image: null == image ? _self.image : image // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,gender: null == gender ? _self.gender : gender // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,dateOfBirth: null == dateOfBirth ? _self.dateOfBirth : dateOfBirth // ignore: cast_nullable_to_non_nullable
as String,age: null == age ? _self.age : age // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [ShikimoriMediaCharacterModel].
extension ShikimoriMediaCharacterModelPatterns on ShikimoriMediaCharacterModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ShikimoriMediaCharacterModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ShikimoriMediaCharacterModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ShikimoriMediaCharacterModel value) $default,){
final _that = this;
switch (_that) {
case _ShikimoriMediaCharacterModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ShikimoriMediaCharacterModel value)? $default,){
final _that = this;
switch (_that) {
case _ShikimoriMediaCharacterModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int id, String image, String name, String gender, String description, String dateOfBirth, int age)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ShikimoriMediaCharacterModel() when $default != null:
return $default(_that.id,_that.image,_that.name,_that.gender,_that.description,_that.dateOfBirth,_that.age);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int id, String image, String name, String gender, String description, String dateOfBirth, int age) $default,) {final _that = this;
switch (_that) {
case _ShikimoriMediaCharacterModel():
return $default(_that.id,_that.image,_that.name,_that.gender,_that.description,_that.dateOfBirth,_that.age);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int id, String image, String name, String gender, String description, String dateOfBirth, int age)? $default,) {final _that = this;
switch (_that) {
case _ShikimoriMediaCharacterModel() when $default != null:
return $default(_that.id,_that.image,_that.name,_that.gender,_that.description,_that.dateOfBirth,_that.age);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ShikimoriMediaCharacterModel implements ShikimoriMediaCharacterModel {
const _ShikimoriMediaCharacterModel({required this.id, required this.image, required this.name, required this.gender, required this.description, required this.dateOfBirth, required this.age});
factory _ShikimoriMediaCharacterModel.fromJson(Map<String, dynamic> json) => _$ShikimoriMediaCharacterModelFromJson(json);
@override final int id;
@override final String image;
@override final String name;
@override final String gender;
@override final String description;
@override final String dateOfBirth;
@override final int age;
/// Create a copy of ShikimoriMediaCharacterModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ShikimoriMediaCharacterModelCopyWith<_ShikimoriMediaCharacterModel> get copyWith => __$ShikimoriMediaCharacterModelCopyWithImpl<_ShikimoriMediaCharacterModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ShikimoriMediaCharacterModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ShikimoriMediaCharacterModel&&(identical(other.id, id) || other.id == id)&&(identical(other.image, image) || other.image == image)&&(identical(other.name, name) || other.name == name)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.description, description) || other.description == description)&&(identical(other.dateOfBirth, dateOfBirth) || other.dateOfBirth == dateOfBirth)&&(identical(other.age, age) || other.age == age));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,image,name,gender,description,dateOfBirth,age);
}
/// @nodoc
abstract mixin class _$ShikimoriMediaCharacterModelCopyWith<$Res> implements $ShikimoriMediaCharacterModelCopyWith<$Res> {
factory _$ShikimoriMediaCharacterModelCopyWith(_ShikimoriMediaCharacterModel value, $Res Function(_ShikimoriMediaCharacterModel) _then) = __$ShikimoriMediaCharacterModelCopyWithImpl;
@override @useResult
$Res call({
int id, String image, String name, String gender, String description, String dateOfBirth, int age
});
}
/// @nodoc
class __$ShikimoriMediaCharacterModelCopyWithImpl<$Res>
implements _$ShikimoriMediaCharacterModelCopyWith<$Res> {
__$ShikimoriMediaCharacterModelCopyWithImpl(this._self, this._then);
final _ShikimoriMediaCharacterModel _self;
final $Res Function(_ShikimoriMediaCharacterModel) _then;
/// Create a copy of ShikimoriMediaCharacterModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? image = null,Object? name = null,Object? gender = null,Object? description = null,Object? dateOfBirth = null,Object? age = null,}) {
return _then(_ShikimoriMediaCharacterModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as int,image: null == image ? _self.image : image // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,gender: null == gender ? _self.gender : gender // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,dateOfBirth: null == dateOfBirth ? _self.dateOfBirth : dateOfBirth // ignore: cast_nullable_to_non_nullable
as String,age: null == age ? _self.age : age // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

View File

@@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shikimori_media_character.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ShikimoriMediaCharacterModel _$ShikimoriMediaCharacterModelFromJson(
Map<String, dynamic> json,
) => _ShikimoriMediaCharacterModel(
id: (json['id'] as num).toInt(),
image: json['image'] as String,
name: json['name'] as String,
gender: json['gender'] as String,
description: json['description'] as String,
dateOfBirth: json['dateOfBirth'] as String,
age: (json['age'] as num).toInt(),
);
Map<String, dynamic> _$ShikimoriMediaCharacterModelToJson(
_ShikimoriMediaCharacterModel instance,
) => <String, dynamic>{
'id': instance.id,
'image': instance.image,
'name': instance.name,
'gender': instance.gender,
'description': instance.description,
'dateOfBirth': instance.dateOfBirth,
'age': instance.age,
};

View File

@@ -19,9 +19,9 @@ import 'package:unyo/core/services/api/graphql/queries/anilist_queries.dart' as
import 'package:unyo/core/services/api/dto/anilist/media_collection_recently_released_graphql_entity.dart';
import 'package:unyo/core/services/api/graphql/graphql_response.dart';
import 'package:unyo/core/services/api/graphql/graphql_service.dart';
import 'package:unyo/data/models/anilist/anilist_anime_details.dart';
import 'package:unyo/data/models/anilist/anilist_anime_model.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/anilist/media/anilist_anime_details.dart';
import 'package:unyo/data/models/anilist/media/anilist_anime_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/repositories/repository_mixin.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/anime_details.dart';

View File

@@ -0,0 +1,495 @@
// External dependencies
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:logger/logger.dart';
// Internal dependencies
import 'package:unyo/config/config.dart' as config;
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/services/api/graphql/graphql_service.dart';
import 'package:unyo/core/services/api/graphql/queries/shikimori_queries.dart' as shikimori_queries;
import 'package:unyo/data/repositories/repository_mixin.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/anime_details.dart';
import 'package:unyo/domain/entities/list/media_list_entry.dart';
import 'package:unyo/domain/entities/user/user.dart';
import 'package:unyo/domain/repositories/anime_repository.dart';
class AnimeRepositoryShikimori with RepositoryMixin implements AnimeRepository {
final GraphQLService _shikimoriGraphQLService = sl<GraphQLService>(
instanceName: config.shikimoriGraphQlService,
);
final Logger _logger = sl<Logger>();
@override
Future<(bool, AnimeDetails)> getAnimeDetails(Anime selectedAnime, User loggedUser) {
// TODO: implement getAnimeDetails
throw UnimplementedError();
}
@override
Future<Map<String, List<Anime>>> getCalendarReleases(int page, User loggedUser) {
// TODO: implement getCalendarReleases
throw UnimplementedError();
}
// Daqui
@override
Future<(bool, List<Anime>)> getPopularAnimes(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getPopularAnimes
throw UnimplementedError();
}
@override
Future<(bool, List<Anime>)> getRecentlyCompletedAnimes(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getRecentlyCompletedAnimes
throw UnimplementedError();
}
@override
Future<(bool, List<Anime>)> getRecentlyReleasedAnimes(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getRecentlyReleasedAnimes
throw UnimplementedError();
}
@override
Future<(bool, List<Anime>)> getTrendingAnimes(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getTrendingAnimes
throw UnimplementedError();
}
@override
Future<(bool, List<Anime>)> getUpcomingAnimes(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getUpcomingAnimes
throw UnimplementedError();
}
// Ate aqui
@override
Future<Map<String, (bool, List<String>)>> getUserAnimeAdvancedSearchFilters() {
// TODO: implement getUserAnimeAdvancedSearchFilters
throw UnimplementedError();
}
@override
Future<List<Anime>> performAnimeAdvancedSearch(String query, List<String> selectedGenres, String? selectedSeason, String? selectedFormat, int? selectedYear, String? selectedAiringStatus, String sort, int page, User loggedUser) {
// TODO: implement performAnimeAdvancedSearch
throw UnimplementedError();
}
// @override
// Future<(bool, List<Anime>)> getPopularAnimes(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori popular anime");
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeListGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeListQuery,
// fromJson: ShikimoriAnimeListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "popularity",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final animes = _processPaginatedResponse(response.data.animes, 30);
// return (animes.$1, animes.$2.map((e) => ShikimoriAnimeModel.fromListEntry(e)).toList());
// }
// @override
// Future<(bool, List<Anime>)> getTrendingAnimes(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori trending anime");
// // Shikimori doesn't have a native "trending" concept; use popularity as closest equivalent
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeListGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeListQuery,
// fromJson: ShikimoriAnimeListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "popularity",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final animes = _processPaginatedResponse(response.data.animes, 30);
// return (animes.$1, animes.$2.map((e) => ShikimoriAnimeModel.fromListEntry(e)).toList());
// }
// @override
// Future<(bool, List<Anime>)> getRecentlyReleasedAnimes(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori recently released anime");
// final now = DateTime.now();
// final currentSeason = _getCurrentSeason(now);
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeListGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeListQuery,
// fromJson: ShikimoriAnimeListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "aired_on",
// "status": "released",
// "season": currentSeason,
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final animes = _processPaginatedResponse(response.data.animes, 30);
// return (animes.$1, animes.$2.map((e) => ShikimoriAnimeModel.fromListEntry(e)).toList());
// }
// @override
// Future<(bool, List<Anime>)> getRecentlyCompletedAnimes(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori recently completed anime");
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeListGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeListQuery,
// fromJson: ShikimoriAnimeListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "aired_on",
// "status": "released",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final animes = _processPaginatedResponse(response.data.animes, 30);
// return (animes.$1, animes.$2.map((e) => ShikimoriAnimeModel.fromListEntry(e)).toList());
// }
// @override
// Future<(bool, List<Anime>)> getUpcomingAnimes(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori upcoming anime");
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeListGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeListQuery,
// fromJson: ShikimoriAnimeListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "popularity",
// "status": "anons",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final animes = _processPaginatedResponse(response.data.animes, 30);
// return (animes.$1, animes.$2.map((e) => ShikimoriAnimeModel.fromListEntry(e)).toList());
// }
// @override
// Future<Map<String, List<Anime>>> getCalendarReleases(int page, User loggedUser) async {
// _logger.i("Fetching Shikimori calendar releases");
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeListGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeListQuery,
// fromJson: ShikimoriAnimeListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 50,
// "order": "ranked",
// "status": "ongoing",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// );
// throwIfGraphQlError(response);
// final localeTag = loggedUser.settings.language;
// final calendarReleases = <String, List<Anime>>{};
// for (final entry in response.data.animes) {
// final anime = ShikimoriAnimeModel.fromListEntry(entry);
// if (anime.isAdult && !loggedUser.settings.enableNsfwContent) continue;
// if (entry.nextEpisodeAt != null) {
// final episodeRelease = DateTime.parse(entry.nextEpisodeAt.toString());
// final dateKey = DateFormat('EEEE, MMMM d, y', localeTag).format(episodeRelease);
// if (!calendarReleases.containsKey(dateKey)) {
// calendarReleases[dateKey] = [anime];
// } else {
// calendarReleases[dateKey]!.add(anime);
// }
// }
// }
// // Sort each list by airing time
// calendarReleases.forEach((date, animeList) {
// animeList.sort((a, b) => a.nextAiringEpisode.airingAt.compareTo(b.nextAiringEpisode.airingAt));
// });
// // Sort map entries by date
// final sortedEntries = calendarReleases.entries.toList()
// ..sort((a, b) {
// final dateA = DateFormat('EEEE, MMMM d, y', localeTag).parse(a.key);
// final dateB = DateFormat('EEEE, MMMM d, y', localeTag).parse(b.key);
// return dateA.millisecondsSinceEpoch.compareTo(dateB.millisecondsSinceEpoch);
// });
// return Map.fromEntries(sortedEntries);
// }
// @override
// Future<(bool, AnimeDetails)> getAnimeDetails(Anime selectedAnime, User loggedUser) async {
// _logger.i("Fetching Anime Details from Shikimori for ${selectedAnime.title.userPreferred}");
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeDetailsGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeDetailsQuery,
// fromJson: ShikimoriAnimeDetailsGraphqlEntity.fromJson,
// variables: {"ids": selectedAnime.id.toString()},
// );
// throwIfGraphQlError(response);
// if (response.data.anime.isEmpty) {
// return (false, AnimeDetailsModel.empty());
// }
// final animeDetails = ShikimoriAnimeDetailsModel.fromDetailsEntry(response.data.anime.first);
// return (true, animeDetails);
// }
// @override
// Future<Map<String, (bool, List<String>)>> getUserAnimeAdvancedSearchFilters() async {
// _logger.i("Fetching Shikimori anime advanced search filters");
// final filters = <String, (bool, List<String>)>{};
// // Fetch genres from API
// final genresResponse = await _shikimoriGraphQLService.query<ShikimoriGenresGraphqlEntity>(
// query: shikimori_queries.shikimoriGenresListQuery,
// fromJson: ShikimoriGenresGraphqlEntity.fromJson,
// variables: {"entryType": "Anime"},
// );
// throwIfGraphQlError(genresResponse);
// final genreNames = genresResponse.data.genres.map((g) => g.name).toList();
// filters.addAll({
// 'genres': (true, TextUtils.capitalizeList(genreNames)),
// });
// filters.addAll({
// 'seasons': (
// true,
// TextUtils.capitalizeList(
// ShikimoriSeasonFilters.values.map((e) => e.name).toList(),
// ),
// ),
// });
// filters.addAll({
// 'formats': (
// true,
// TextUtils.capitalizeList(
// ShikimoriFormatFilters.values.map((e) => e.name.replaceAll('_', ' ')).toList(),
// ),
// ),
// });
// filters.addAll({
// 'airingStatuses': (
// true,
// TextUtils.capitalizeList(
// ShikimoriAiringStatusFilters.values.map((e) => e.name.replaceAll('_', ' ')).toList(),
// ),
// ),
// });
// filters.addAll({
// 'years': (
// true,
// List<String>.generate(
// DateTime.now().year - 1939,
// (index) => (1940 + index).toString(),
// ).reversed.toList(),
// ),
// });
// filters.addAll({
// 'sortOptions': (
// true,
// TextUtils.capitalizeList(
// ShikimoriSortOptions.values.map((e) => e.name.replaceAll('_', ' ')).toList(),
// ),
// ),
// });
// filters.addAll({
// 'sortOrders': (
// true,
// TextUtils.capitalizeList(
// ShikimoriSortOrder.values.map((e) => e.name).toList(),
// ),
// ),
// });
// return filters;
// }
// @override
// Future<List<Anime>> performAnimeAdvancedSearch(
// String query,
// List<String> selectedGenres,
// String? selectedSeason,
// String? selectedFormat,
// int? selectedYear,
// String? selectedAiringStatus,
// String sort,
// int page,
// User loggedUser,
// ) async {
// _logger.i("Performing Shikimori anime advanced search");
// // Map genre names to IDs if needed - for now, Shikimori search uses genre IDs
// // But since we don't have ID mapping easily, we'll use genre names
// // Actually Shikimori genre filter accepts IDs, not names
// // For simplicity, we'll skip genre filtering in search or map common ones
// final genreIds = await _mapGenreNamesToIds(selectedGenres, "Anime");
// final response = await _shikimoriGraphQLService.query<ShikimoriAnimeListGraphqlEntity>(
// query: shikimori_queries.shikimoriAnimeListQuery,
// fromJson: ShikimoriAnimeListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 50,
// "order": _mapSortToShikimori(sort),
// if (query.isNotEmpty) "search": query,
// if (genreIds.isNotEmpty) "genre": genreIds.join(','),
// if (selectedSeason != null && selectedSeason.isNotEmpty) "season": selectedSeason.toLowerCase(),
// if (selectedYear != null) "season": "$selectedYear",
// if (selectedAiringStatus != null && selectedAiringStatus.isNotEmpty)
// "status": _mapAiringStatusToShikimori(selectedAiringStatus),
// if (selectedFormat != null && selectedFormat.isNotEmpty)
// "kind": _mapFormatToShikimoriKind(selectedFormat),
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// );
// throwIfGraphQlError(response);
// return response.data.animes.map((e) => ShikimoriAnimeModel.fromListEntry(e)).toList();
// }
@override
Future<List<String>> getMediaCoverImages(User loggedUser, {bool ignoreCache = false}) async {
_logger.i("Fetching Media Cover Images from Shikimori");
final (hasNext, animes) = await getPopularAnimes(1, loggedUser, ignoreCache: ignoreCache);
return animes.map((anime) => anime.coverImage).where((coverImage) => coverImage != "").shuffled(Random()).toList();
}
@override
Future<MediaListEntry> updateMediaListEntry(MediaListEntry newMediaListEntry, Anime selectedAnime, User loggedUser) async {
_logger.i("Updating Media List Entry to $newMediaListEntry on Shikimori");
// Metadata-only scope: return empty
return MediaListEntryModel.empty();
}
@override
Future<MediaListEntry> getMediaListEntry(Anime selectedAnime, User loggedUser, {bool ignoreCache = false}) async {
_logger.i("Fetching User Media List Entry from Shikimori for ${selectedAnime.title.userPreferred}");
// Metadata-only scope: return empty
return MediaListEntryModel.empty();
}
// Helper methods
// (bool, List<ShikimoriAnimeListGraphqlAnimes>) _processPaginatedResponse(
// List<ShikimoriAnimeListGraphqlAnimes> results,
// int limit,
// ) {
// final hasNextPage = results.length > limit;
// final items = hasNextPage ? results.take(limit).toList() : results;
// return (hasNextPage, items);
// }
String _getCurrentSeason(DateTime date) {
final month = date.month;
String season;
if (month >= 1 && month <= 3) {
season = 'winter';
} else if (month >= 4 && month <= 6) {
season = 'spring';
} else if (month >= 7 && month <= 9) {
season = 'summer';
} else {
season = 'fall';
}
return "${season}_${date.year}";
}
// Future<List<String>> _mapGenreNamesToIds(List<String> genreNames, String entryType) async {
// if (genreNames.isEmpty) return [];
// final genresResponse = await _shikimoriGraphQLService.query<ShikimoriGenresGraphqlEntity>(
// query: shikimori_queries.shikimoriGenresListQuery,
// fromJson: ShikimoriGenresGraphqlEntity.fromJson,
// variables: {"entryType": entryType},
// );
// throwIfGraphQlError(genresResponse);
// final genreMap = <String, String>{};
// for (final genre in genresResponse.data.genres) {
// genreMap[genre.name.toLowerCase()] = genre.id;
// }
// return genreNames
// .map((name) => genreMap[name.toLowerCase()])
// .whereType<String>()
// .toList();
// }
String _mapSortToShikimori(String sort) {
// sort comes as "popularity_desc" or "score_desc" etc.
final parts = sort.toLowerCase().split('_');
if (parts.length < 2) return 'popularity';
final field = parts.first;
switch (field) {
case 'popularity':
return 'popularity';
case 'score':
return 'ranked';
case 'startdate':
case 'start_date':
return 'aired_on';
case 'enddate':
case 'end_date':
return 'aired_on';
case 'title':
return 'name';
case 'episodes':
return 'episodes';
case 'status':
return 'status';
case 'trending':
return 'popularity';
default:
return 'popularity';
}
}
String _mapAiringStatusToShikimori(String status) {
switch (status.toLowerCase().replaceAll(' ', '_')) {
case 'releasing':
return 'ongoing';
case 'finished':
return 'released';
case 'not_yet_released':
return 'anons';
case 'cancelled':
return 'released'; // Shikimori doesn't have cancelled
default:
return status.toLowerCase();
}
}
String _mapFormatToShikimoriKind(String format) {
switch (format.toLowerCase().replaceAll(' ', '_')) {
case 'tv':
return 'tv';
case 'tv_short':
return 'tv_special';
case 'movie':
return 'movie';
case 'special':
return 'special';
case 'ova':
return 'ova';
case 'ona':
return 'ona';
case 'music':
return 'music';
default:
return format.toLowerCase().replaceAll(' ', '_');
}
}
}
enum ShikimoriSeasonFilters { winter, spring, summer, fall }
enum ShikimoriFormatFilters { tv, movie, tv_special, special, ova, ona, music }
enum ShikimoriAiringStatusFilters { ongoing, released, anons }
enum ShikimoriSortOptions {
popularity,
ranked,
name,
aired_on,
episodes,
status,
}
enum ShikimoriSortOrder { asc, desc }

View File

@@ -14,9 +14,9 @@ import 'package:unyo/core/services/api/dto/anilist/media_details_graphql_entity.
import 'package:unyo/core/services/api/dto/anilist/media_details_media_list_entry_entity.dart';
import 'package:unyo/core/services/api/graphql/graphql_response.dart';
import 'package:unyo/core/services/api/graphql/graphql_service.dart';
import 'package:unyo/data/models/anilist/anilist_manga_details.dart';
import 'package:unyo/data/models/anilist/anilist_manga_model.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/anilist/media/anilist_manga_details.dart';
import 'package:unyo/data/models/anilist/media/anilist_manga_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/repositories/repository_mixin.dart';
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/media/manga_details.dart';

View File

@@ -0,0 +1,349 @@
// External dependencies
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:logger/logger.dart';
// Internal dependencies
import 'package:unyo/config/config.dart' as config;
import 'package:unyo/core/di/locator.dart';
import 'package:unyo/core/services/api/graphql/graphql_service.dart';
import 'package:unyo/core/services/api/graphql/queries/shikimori_queries.dart' as shikimori_queries;
import 'package:unyo/data/repositories/repository_mixin.dart';
import 'package:unyo/domain/entities/media/manga.dart';
import 'package:unyo/domain/entities/media/manga_details.dart';
import 'package:unyo/domain/entities/user/user.dart';
import 'package:unyo/domain/repositories/manga_repository.dart';
class MangaRepositoryShikimori with RepositoryMixin implements MangaRepository {
final GraphQLService _shikimoriGraphQLService = sl<GraphQLService>(
instanceName: config.shikimoriGraphQlService,
);
final Logger _logger = sl<Logger>();
@override
Future<(bool, MangaDetails)> getMangaDetails(Manga selectedManga, User loggedUser) {
// TODO: implement getMangaDetails
throw UnimplementedError();
}
@override
Future<(bool, List<Manga>)> getPopularMangas(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getPopularMangas
throw UnimplementedError();
}
@override
Future<(bool, List<Manga>)> getRecentlyCompletedMangas(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getRecentlyCompletedMangas
throw UnimplementedError();
}
@override
Future<(bool, List<Manga>)> getTrendingMangas(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getTrendingMangas
throw UnimplementedError();
}
@override
Future<(bool, List<Manga>)> getUpcomingMangas(int page, User loggedUser, {bool ignoreCache = false}) {
// TODO: implement getUpcomingMangas
throw UnimplementedError();
}
@override
Future<Map<String, (bool, List<String>)>> getUserMangaAdvancedSearchFilters() {
// TODO: implement getUserMangaAdvancedSearchFilters
throw UnimplementedError();
}
@override
Future<List<Manga>> performMangaAdvancedSearch(String query, List<String> selectedGenres, String? selectedFormat, String? selectedCountry, String? selectedAiringStatus, String sort, int page, User loggedUser) {
// TODO: implement performMangaAdvancedSearch
throw UnimplementedError();
}
// @override
// Future<(bool, List<Manga>)> getPopularMangas(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori popular manga");
// final response = await _shikimoriGraphQLService.query<ShikimoriMangaListGraphqlEntity>(
// query: shikimori_queries.shikimoriMangaListQuery,
// fromJson: ShikimoriMangaListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "popularity",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final mangas = _processPaginatedResponse(response.data.mangas, 30);
// return (mangas.$1, mangas.$2.map((e) => ShikimoriMangaModel.fromListEntry(e)).toList());
// }
//
// @override
// Future<(bool, List<Manga>)> getTrendingMangas(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori trending manga");
// final response = await _shikimoriGraphQLService.query<ShikimoriMangaListGraphqlEntity>(
// query: shikimori_queries.shikimoriMangaListQuery,
// fromJson: ShikimoriMangaListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "popularity",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final mangas = _processPaginatedResponse(response.data.mangas, 30);
// return (mangas.$1, mangas.$2.map((e) => ShikimoriMangaModel.fromListEntry(e)).toList());
// }
//
// @override
// Future<(bool, List<Manga>)> getRecentlyCompletedMangas(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori recently completed manga");
// final response = await _shikimoriGraphQLService.query<ShikimoriMangaListGraphqlEntity>(
// query: shikimori_queries.shikimoriMangaListQuery,
// fromJson: ShikimoriMangaListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "aired_on",
// "status": "released",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final mangas = _processPaginatedResponse(response.data.mangas, 30);
// return (mangas.$1, mangas.$2.map((e) => ShikimoriMangaModel.fromListEntry(e)).toList());
// }
//
// @override
// Future<(bool, List<Manga>)> getUpcomingMangas(int page, User loggedUser, {bool ignoreCache = false}) async {
// _logger.i("Fetching Shikimori upcoming manga");
// final response = await _shikimoriGraphQLService.query<ShikimoriMangaListGraphqlEntity>(
// query: shikimori_queries.shikimoriMangaListQuery,
// fromJson: ShikimoriMangaListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 31,
// "order": "popularity",
// "status": "anons",
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// ignoreCache: ignoreCache,
// );
// throwIfGraphQlError(response);
// final mangas = _processPaginatedResponse(response.data.mangas, 30);
// return (mangas.$1, mangas.$2.map((e) => ShikimoriMangaModel.fromListEntry(e)).toList());
// }
//
// @override
// Future<(bool, MangaDetails)> getMangaDetails(Manga selectedManga, User loggedUser) async {
// _logger.i("Fetching Manga Details from Shikimori for ${selectedManga.title.userPreferred}");
// final response = await _shikimoriGraphQLService.query<ShikimoriMangaDetailsGraphqlEntity>(
// query: shikimori_queries.shikimoriMangaDetailsQuery,
// fromJson: ShikimoriMangaDetailsGraphqlEntity.fromJson,
// variables: {"ids": selectedManga.id.toString()},
// );
// throwIfGraphQlError(response);
// if (response.data.manga.isEmpty) {
// return (false, MangaDetailsModel.empty());
// }
// final mangaDetails = ShikimoriMangaDetailsModel.fromDetailsEntry(response.data.manga.first);
// return (true, mangaDetails);
// }
//
// @override
// Future<Map<String, (bool, List<String>)>> getUserMangaAdvancedSearchFilters() async {
// _logger.i("Fetching Shikimori manga advanced search filters");
// final filters = <String, (bool, List<String>)>{};
// final genresResponse = await _shikimoriGraphQLService.query<ShikimoriGenresGraphqlEntity>(
// query: shikimori_queries.shikimoriGenresListQuery,
// fromJson: ShikimoriGenresGraphqlEntity.fromJson,
// variables: {"entryType": "Manga"},
// );
// throwIfGraphQlError(genresResponse);
// final genreNames = genresResponse.data.genres.map((g) => g.name).toList();
// filters.addAll({
// 'genres': (true, TextUtils.capitalizeList(genreNames)),
// });
// filters.addAll({
// 'formats': (
// true,
// TextUtils.capitalizeList(
// ShikimoriMangaFormatFilters.values.map((e) => e.name.replaceAll('_', ' ')).toList(),
// ),
// ),
// });
// filters.addAll({
// 'airingStatuses': (
// true,
// TextUtils.capitalizeList(
// ShikimoriMangaStatusFilters.values.map((e) => e.name.replaceAll('_', ' ')).toList(),
// ),
// ),
// });
// filters.addAll({
// 'years': (
// true,
// List<String>.generate(
// DateTime.now().year - 1939,
// (index) => (1940 + index).toString(),
// ).reversed.toList(),
// ),
// });
// filters.addAll({
// 'sortOptions': (
// true,
// TextUtils.capitalizeList(
// ShikimoriMangaSortOptions.values.map((e) => e.name.replaceAll('_', ' ')).toList(),
// ),
// ),
// });
// filters.addAll({
// 'sortOrders': (
// true,
// TextUtils.capitalizeList(
// ShikimoriMangaSortOrder.values.map((e) => e.name).toList(),
// ),
// ),
// });
// return filters;
// }
//
// @override
// Future<List<Manga>> performMangaAdvancedSearch(
// String query,
// List<String> selectedGenres,
// String? selectedFormat,
// String? selectedCountry,
// String? selectedAiringStatus,
// String sort,
// int page,
// User loggedUser,
// ) async {
// _logger.i("Performing Shikimori manga advanced search");
// final genreIds = await _mapGenreNamesToIds(selectedGenres, "Manga");
// final response = await _shikimoriGraphQLService.query<ShikimoriMangaListGraphqlEntity>(
// query: shikimori_queries.shikimoriMangaListQuery,
// fromJson: ShikimoriMangaListGraphqlEntity.fromJson,
// variables: {
// "page": page,
// "limit": 50,
// "order": _mapSortToShikimori(sort),
// if (query.isNotEmpty) "search": query,
// if (genreIds.isNotEmpty) "genre": genreIds.join(','),
// if (selectedAiringStatus != null && selectedAiringStatus.isNotEmpty)
// "status": _mapAiringStatusToShikimori(selectedAiringStatus),
// if (selectedFormat != null && selectedFormat.isNotEmpty)
// "kind": _mapFormatToShikimoriKind(selectedFormat),
// "censored": loggedUser.settings.enableNsfwContent ? null : false,
// },
// );
// throwIfGraphQlError(response);
// return response.data.mangas.map((e) => ShikimoriMangaModel.fromListEntry(e)).toList();
// }
@override
Future<List<String>> getMediaCoverImages(User loggedUser, {bool ignoreCache = false}) async {
_logger.i("Fetching Media Cover Images from Shikimori");
final (hasNext, mangas) = await getPopularMangas(1, loggedUser, ignoreCache: ignoreCache);
return mangas.map((manga) => manga.coverImage).where((coverImage) => coverImage != "").shuffled(Random()).toList();
}
// Helper methods
// (bool, List<ShikimoriMangaListGraphqlMangas>) _processPaginatedResponse(
// List<ShikimoriMangaListGraphqlMangas> results,
// int limit,
// ) {
// final hasNextPage = results.length > limit;
// final items = hasNextPage ? results.take(limit).toList() : results;
// return (hasNextPage, items);
// }
//
// Future<List<String>> _mapGenreNamesToIds(List<String> genreNames, String entryType) async {
// if (genreNames.isEmpty) return [];
// final genresResponse = await _shikimoriGraphQLService.query<ShikimoriGenresGraphqlEntity>(
// query: shikimori_queries.shikimoriGenresListQuery,
// fromJson: ShikimoriGenresGraphqlEntity.fromJson,
// variables: {"entryType": entryType},
// );
// throwIfGraphQlError(genresResponse);
// final genreMap = <String, String>{};
// for (final genre in genresResponse.data.genres) {
// genreMap[genre.name.toLowerCase()] = genre.id;
// }
// return genreNames
// .map((name) => genreMap[name.toLowerCase()])
// .whereType<String>()
// .toList();
// }
String _mapSortToShikimori(String sort) {
final parts = sort.toLowerCase().split('_');
if (parts.length < 2) return 'popularity';
final field = parts.first;
switch (field) {
case 'popularity':
return 'popularity';
case 'score':
return 'ranked';
case 'startdate':
case 'start_date':
return 'aired_on';
case 'title':
return 'name';
default:
return 'popularity';
}
}
String _mapAiringStatusToShikimori(String status) {
switch (status.toLowerCase().replaceAll(' ', '_')) {
case 'releasing':
return 'ongoing';
case 'finished':
return 'released';
case 'not_yet_released':
return 'anons';
case 'hiatus':
return 'paused';
case 'cancelled':
return 'discontinued';
default:
return status.toLowerCase();
}
}
String _mapFormatToShikimoriKind(String format) {
switch (format.toLowerCase().replaceAll(' ', '_')) {
case 'manga':
return 'manga';
case 'manhwa':
return 'manhwa';
case 'manhua':
return 'manhua';
case 'novel':
return 'light_novel';
case 'one_shot':
return 'one_shot';
case 'doujinshi':
return 'doujin';
default:
return format.toLowerCase().replaceAll(' ', '_');
}
}
}
enum ShikimoriMangaFormatFilters { manga, manhwa, manhua, light_novel, one_shot, doujin }
enum ShikimoriMangaStatusFilters { ongoing, released, anons, paused, discontinued }
enum ShikimoriMangaSortOptions { popularity, ranked, name, aired_on }
enum ShikimoriMangaSortOrder { asc, desc }

View File

@@ -1,2 +1,4 @@
export 'user_repository_local.dart';
export 'user_repository_anilist.dart';
export 'user_repository_anilist.dart';
export 'anime_repository_shikimori.dart';
export 'manga_repository_shikimori.dart';

View File

@@ -7,8 +7,8 @@ import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelfio;
import 'package:unyo/core/services/api/dto/anilist/auth_token_dto.dart';
import 'package:unyo/core/services/api/dto/anilist/media_collection_graphql_entity.dart';
import 'package:unyo/data/models/anilist/anilist_anime_model.dart';
import 'package:unyo/data/models/anilist/anilist_manga_model.dart';
import 'package:unyo/data/models/anilist/media/anilist_anime_model.dart';
import 'package:unyo/data/models/anilist/media/anilist_manga_model.dart';
import 'package:unyo/data/repositories/repository_mixin.dart';
import 'package:unyo/domain/entities/media/anime.dart';
import 'package:unyo/domain/entities/media/manga.dart';
@@ -24,7 +24,7 @@ import 'package:unyo/core/services/api/graphql/graphql_response.dart';
import 'package:unyo/core/services/api/graphql/graphql_service.dart';
import 'package:unyo/core/services/api/http/api_response.dart';
import 'package:unyo/core/services/api/http/http_service.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/domain/repositories/repositories.dart';
import 'package:unyo/domain/entities/user/user.dart';

View File

@@ -81,8 +81,8 @@ abstract class SettingsModel with _$SettingsModel implements Settings {
@HiveField(16) @Default(false) bool enableNsfwContent,
@HiveField(17) @ColorConverter() @Default(Color(0xFF2196F3)) Color themeColor,
@HiveField(18) @Default(true) bool useWallpaperAsThemeColor,
@HiveField(19) @Default([config.aniyomiExtensionsRepositoryUrl]) List<String> aniyomiExtensionsRepositories,
@HiveField(20) @Default([config.tachiyomiExtensionsRepositoryUrl]) List<String> tachiyomiExtensionsRepositories,
@HiveField(19) @Default([config.aniyomiExtensionsDefaultRepositoryUrl]) List<String> aniyomiExtensionsRepositories,
@HiveField(20) @Default([config.tachiyomiExtensionsDefaultRepositoryUrl]) List<String> tachiyomiExtensionsRepositories,
}) = _SettingsModel;
factory SettingsModel.empty() =>

View File

@@ -227,7 +227,7 @@ return $default(_that.language,_that.service,_that.episodeService,_that.installe
@JsonSerializable()
class _SettingsModel implements SettingsModel {
const _SettingsModel({@HiveField(0) this.language = 'en', @HiveField(1) this.service = Service.anilist, @HiveField(2) this.episodeService = EpisodeService.anizip, @HiveField(3)@ExtensionConverter() final List<Extension> installedAnimeExtensions = const [], @HiveField(4)@ExtensionConverter() final List<Extension> installedMangaExtensions = const [], @HiveField(7)@ExtensionConverter() final Map<String, Extension> mediaExtensionConfigs = const {}, @HiveField(8) this.mediaTitleLanguage = 'userPreferred', @HiveField(9) this.episodeTitleLanguage = 'en', @HiveField(10) this.enableDiscordRichPresence = true, @HiveField(11) this.automaticallySkipOpening = false, @HiveField(12) this.automaticallySkipEnding = false, @HiveField(13) this.manualSkipTime = 85, @HiveField(14) this.autoPlayNextEpisode = false, @HiveField(15) this.enableOpenSubtitlesIntegration = false, @HiveField(16) this.enableNsfwContent = false, @HiveField(17)@ColorConverter() this.themeColor = const Color(0xFF2196F3), @HiveField(18) this.useWallpaperAsThemeColor = true, @HiveField(19) final List<String> aniyomiExtensionsRepositories = const [config.aniyomiExtensionsRepositoryUrl], @HiveField(20) final List<String> tachiyomiExtensionsRepositories = const [config.tachiyomiExtensionsRepositoryUrl]}): _installedAnimeExtensions = installedAnimeExtensions,_installedMangaExtensions = installedMangaExtensions,_mediaExtensionConfigs = mediaExtensionConfigs,_aniyomiExtensionsRepositories = aniyomiExtensionsRepositories,_tachiyomiExtensionsRepositories = tachiyomiExtensionsRepositories;
const _SettingsModel({@HiveField(0) this.language = 'en', @HiveField(1) this.service = Service.anilist, @HiveField(2) this.episodeService = EpisodeService.anizip, @HiveField(3)@ExtensionConverter() final List<Extension> installedAnimeExtensions = const [], @HiveField(4)@ExtensionConverter() final List<Extension> installedMangaExtensions = const [], @HiveField(7)@ExtensionConverter() final Map<String, Extension> mediaExtensionConfigs = const {}, @HiveField(8) this.mediaTitleLanguage = 'userPreferred', @HiveField(9) this.episodeTitleLanguage = 'en', @HiveField(10) this.enableDiscordRichPresence = true, @HiveField(11) this.automaticallySkipOpening = false, @HiveField(12) this.automaticallySkipEnding = false, @HiveField(13) this.manualSkipTime = 85, @HiveField(14) this.autoPlayNextEpisode = false, @HiveField(15) this.enableOpenSubtitlesIntegration = false, @HiveField(16) this.enableNsfwContent = false, @HiveField(17)@ColorConverter() this.themeColor = const Color(0xFF2196F3), @HiveField(18) this.useWallpaperAsThemeColor = true, @HiveField(19) final List<String> aniyomiExtensionsRepositories = const [config.aniyomiExtensionsDefaultRepositoryUrl], @HiveField(20) final List<String> tachiyomiExtensionsRepositories = const [config.tachiyomiExtensionsDefaultRepositoryUrl]}): _installedAnimeExtensions = installedAnimeExtensions,_installedMangaExtensions = installedMangaExtensions,_mediaExtensionConfigs = mediaExtensionConfigs,_aniyomiExtensionsRepositories = aniyomiExtensionsRepositories,_tachiyomiExtensionsRepositories = tachiyomiExtensionsRepositories;
factory _SettingsModel.fromJson(Map<String, dynamic> json) => _$SettingsModelFromJson(json);
@override@JsonKey()@HiveField(0) final String language;

View File

@@ -171,12 +171,12 @@ _SettingsModel _$SettingsModelFromJson(
(json['aniyomiExtensionsRepositories'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [config.aniyomiExtensionsRepositoryUrl],
const [config.aniyomiExtensionsDefaultRepositoryUrl],
tachiyomiExtensionsRepositories:
(json['tachiyomiExtensionsRepositories'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [config.tachiyomiExtensionsRepositoryUrl],
const [config.tachiyomiExtensionsDefaultRepositoryUrl],
);
Map<String, dynamic> _$SettingsModelToJson(

View File

@@ -5,10 +5,10 @@
import 'package:hive_ce/hive.dart';
import 'package:unyo/core/enums/episode_service.dart';
import 'package:unyo/core/enums/service.dart';
import 'package:unyo/data/models/anilist/anilist_anime_details.dart';
import 'package:unyo/data/models/anilist/anilist_manga_details.dart';
import 'package:unyo/data/models/anilist/anilist_user_model.dart';
import 'package:unyo/data/models/local/local_user_model.dart';
import 'package:unyo/data/models/anilist/media/anilist_anime_details.dart';
import 'package:unyo/data/models/anilist/media/anilist_manga_details.dart';
import 'package:unyo/data/models/anilist/user/anilist_user_model.dart';
import 'package:unyo/data/models/local/user/local_user_model.dart';
import 'package:unyo/domain/entities/extension/extension.dart';
import 'package:unyo/domain/entities/list/media_list.dart';
import 'package:unyo/domain/entities/list/media_list_entry.dart';

View File

@@ -40,7 +40,7 @@ class TextUtils {
case Service.kitsu:
throw UnimplementedError();
case Service.shikimori:
throw UnimplementedError();
return startDate.split("/").length > 1 ? startDate.split("/")[2] : "";
case Service.simkl:
throw UnimplementedError();
}