Compare commits

..

27 Commits

Author SHA1 Message Date
7c4e23e32c Merge remote-tracking branch 'weblate/master' 2024-09-29 15:50:12 +02:00
d569623dfe fix: Fix issue in the flake for building the release 2024-09-29 13:29:58 +02:00
eccdd418b7 feat: Enable Impeller 2024-09-29 13:29:44 +02:00
3a977688c8 fix: Fix build with newer versions of Flutter 2024-09-29 12:54:33 +02:00
Codeberg Translate
a837c38732 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (54 of 54 strings)

Added translation using Weblate (Chinese (Simplified))

Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: Outbreak2096 <Outbreak2096@users.noreply.translate.codeberg.org>
Translate-URL: https://translate.codeberg.org/projects/anitrack/anitrack/zh_Hans/
Translation: AniTrack/AniTrack
2024-08-26 06:18:24 +00:00
2d8862c512 chore: Update Flutter to 3.13 2023-09-04 17:35:52 +02:00
6375fb32a5 feat: Build and sign using my Yubikey 2023-07-24 18:14:07 +02:00
7dd53baf06 feat: Update flake to use newer Flutter 2023-07-20 20:34:19 +02:00
33fab699d4 feat: Sign with actual keys 2023-07-19 21:01:54 +02:00
ebacb533bc Merge pull request 'Translations update from Weblate' (#4) from translate/anitrack:weblate-anitrack-anitrack into master
Reviewed-on: https://codeberg.org/PapaTutuWawa/anitrack/pulls/4
2023-07-18 11:36:40 +00:00
Codeberg Translate
edbd229211 Translated using Weblate (Dutch)
Currently translated at 100.0% (54 of 54 strings)

Co-authored-by: Vistaus <vistausss@fastmail.com>
Translate-URL: https://translate.codeberg.org/projects/anitrack/anitrack/nl/
Translation: AniTrack/AniTrack
2023-07-17 18:38:06 +00:00
fc304edc31 fix: All license dialogs should look the same 2023-07-16 18:18:18 +02:00
415aa098af chore: Bump version code 2023-07-16 18:11:44 +02:00
95b56ecac6 Merge pull request 'Translations update from Weblate' (#3) from translate/anitrack:weblate-anitrack-anitrack into master
Reviewed-on: https://codeberg.org/PapaTutuWawa/anitrack/pulls/3
2023-07-16 16:11:15 +00:00
Codeberg Translate
1c49c2a299 Translated using Weblate (German)
Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (English)

Currently translated at 100.0% (54 of 54 strings)

Co-authored-by: PapaTutuWawa <papatutuwawa@polynom.me>
Translate-URL: https://translate.codeberg.org/projects/anitrack/anitrack/de/
Translate-URL: https://translate.codeberg.org/projects/anitrack/anitrack/en/
Translation: AniTrack/AniTrack
2023-07-16 16:10:33 +00:00
6d3367c1cc fix: Remove bad interpolation 2023-07-16 18:07:08 +02:00
037fab6409 feat: Make licenses work better 2023-07-16 18:06:31 +02:00
286f705c41 release: Release 0.1.3 2023-07-16 17:49:27 +02:00
2c0611cb9e feat: Update licenses 2023-07-16 17:48:59 +02:00
7b1ad49e4d fix: Less dragging is now required 2023-07-16 17:46:38 +02:00
a01e8602e5 fix: The modal should overlay the Scaffold as well 2023-07-16 17:43:16 +02:00
f081bd7c43 fix: Multiple fixes
- Improve performance when a lot of items are "unknown". Thanks to https://github.com/dattran-pt19/group_grid_view
- Fix some behaviour in the refresh code
2023-07-16 17:37:47 +02:00
1481841009 fix: Guard against Jikan errors when refreshing the calendar 2023-07-16 16:19:25 +02:00
918e42b424 feat: Implement importing/exporting data 2023-07-16 16:10:00 +02:00
9fed2116b1 feat: Implement a basic anime calendar 2023-07-16 15:13:35 +02:00
fbe72d1232 Merge pull request 'Translations update from Weblate' (#2) from translate/anitrack:weblate-anitrack-anitrack into master
Reviewed-on: https://codeberg.org/PapaTutuWawa/anitrack/pulls/2
2023-06-27 13:57:07 +00:00
Codeberg Translate
a6caeec383 Translated using Weblate (Dutch)
Currently translated at 100.0% (36 of 36 strings)

Added translation using Weblate (Dutch)

Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: Vistaus <vistausss@fastmail.com>
Translate-URL: https://translate.codeberg.org/projects/anitrack/anitrack/nl/
Translation: AniTrack/AniTrack
2023-06-27 13:51:27 +00:00
50 changed files with 2990 additions and 938 deletions

3
.gitignore vendored
View File

@@ -49,3 +49,6 @@ lib/i18n/
# NixOS # NixOS
.direnv .direnv
.envrc .envrc
# Build artifacts
release-*/

View File

@@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@@ -21,12 +22,8 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 33 compileSdkVersion 34
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@@ -43,7 +40,6 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "me.polynom.anitrack" applicationId "me.polynom.anitrack"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
@@ -55,9 +51,7 @@ android {
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. signingConfig null
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
} }
} }
} }
@@ -65,7 +59,3 @@ android {
flutter { flutter {
source '../..' source '../..'
} }
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@@ -30,7 +30,12 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="true" />
</application> </application>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
</manifest> </manifest>

View File

@@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects { allprojects {
repositories { repositories {
google() google()
@@ -26,6 +13,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip

View File

@@ -1,11 +1,25 @@
include ':app' pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
def properties = new Properties()
assert localPropertiesFile.exists() repositories {
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk") plugins {
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"

View File

@@ -9,15 +9,26 @@
"title": "Einstellungen", "title": "Einstellungen",
"invalidAnimeListBody": "Die ausgewählte Datei ist keine gültige MAL Animeliste, da die Dateiendung nicht \".xml.gz\" ist.", "invalidAnimeListBody": "Die ausgewählte Datei ist keine gültige MAL Animeliste, da die Dateiendung nicht \".xml.gz\" ist.",
"invalidMangaListBody": "Die ausgewählte Datei ist keine MAL Mangaliste, da die Dateiendung nicht \".xml.gz\" ist.", "invalidMangaListBody": "Die ausgewählte Datei ist keine MAL Mangaliste, da die Dateiendung nicht \".xml.gz\" ist.",
"importIndicator": "$current von $total" "importIndicator": "$current von $total",
"importData": "Daten importieren",
"dataExportSuccess": "Daten erfolgreich exportiert",
"dataImportSuccess": "Daten erfolgreich importiert",
"importInvalidData": {
"content": "Die ausgewählte Datei ist nicht ein AniTrack-Datenexport, da die Dateiendung \".json.gz\" fehlt.",
"title": "Ungültige AniTrack-Daten"
},
"exportData": "Daten exportieren"
}, },
"about": { "about": {
"title": "Über", "title": "Über",
"source": "Quellcode" "source": "Quellcode",
"license": "Lizenz",
"close": "Schließen"
}, },
"content": { "content": {
"anime": "Anime", "anime": "Anime",
"manga": "Manga" "manga": "Manga",
"list": "Liste"
}, },
"search": { "search": {
"anime": "Animesuche", "anime": "Animesuche",
@@ -52,5 +63,18 @@
}, },
"tooltips": { "tooltips": {
"addNewItem": "Neuen Eintrag erstellen" "addNewItem": "Neuen Eintrag erstellen"
},
"calendar": {
"calendar": "Kalender",
"days": {
"monday": "Montag",
"tuesday": "Dienstag",
"wednesday": "Mittwoch",
"thursday": "Donnerstag",
"friday": "Freitag",
"saturday": "Samstag",
"sunday": "Sonntag",
"unknown": "Unbekannt"
}
} }
} }

View File

@@ -9,16 +9,27 @@
"importMangaDesc": "Import manga list exported from MyAnimeList.", "importMangaDesc": "Import manga list exported from MyAnimeList.",
"invalidMangaListTitle": "Invalid manga list", "invalidMangaListTitle": "Invalid manga list",
"invalidMangaListBody": "The selected file is not a MAL manga list. It lacks the \".xml.gz\" suffix.", "invalidMangaListBody": "The selected file is not a MAL manga list. It lacks the \".xml.gz\" suffix.",
"importIndicator": "$current of $total" "importIndicator": "$current of $total",
"exportData": "Export data",
"importData": "Import data",
"dataExportSuccess": "Data successfully exported",
"dataImportSuccess": "Data successfully imported",
"importInvalidData": {
"title": "Invalid AniTrack Data",
"content": "The selected file is not an AniTrack data export. It lacks the \".json.gz\" suffix."
}
}, },
"about": { "about": {
"title": "About", "title": "About",
"source": "Source code" "source": "Source code",
"license": "License",
"close": "Close"
}, },
"tooltips": { "tooltips": {
"addNewItem": "Add new item" "addNewItem": "Add new item"
}, },
"content": { "content": {
"list": "List",
"anime": "Anime", "anime": "Anime",
"manga": "Manga" "manga": "Manga"
}, },
@@ -39,6 +50,19 @@
"chapters": "Chapters", "chapters": "Chapters",
"volumesOwned": "Volumes owned" "volumesOwned": "Volumes owned"
}, },
"calendar": {
"calendar": "Calendar",
"days": {
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"unknown": "Unknown"
}
},
"data": { "data": {
"ongoing": { "ongoing": {
"anime": "Watching", "anime": "Watching",

View File

@@ -0,0 +1,80 @@
{
"settings": {
"title": "Instellingen",
"importAnime": "Animelijst importeren",
"importAnimeDesc": "Importeer een animelijst van MyAnimeList.",
"invalidAnimeListTitle": "Ongeldige animelijst",
"invalidAnimeListBody": "Het gekozen bestand is geen MAL-lijst: de .xml.gz-extensie ontbreekt.",
"importManga": "Mangalijst importeren",
"importMangaDesc": "Importeer een mangalijst van MyAnimeList.",
"invalidMangaListTitle": "Ongeldige mangalijst",
"invalidMangaListBody": "Het gekozen bestand is geen MAL-lijst; het achtervoegsel .xml.gz ontbreekt.",
"importIndicator": "$current van $total",
"exportData": "Gegevens exporteren",
"importData": "Gegevens importeren",
"dataExportSuccess": "De gegevens zijn geëxporteerd",
"dataImportSuccess": "De gegevens zijn geïmporteerd",
"importInvalidData": {
"title": "De AniTrack-gegevens zijn ongeldig",
"content": "Het gekozen bestand is geen AniTrack-gegevensexport: de .json.gz-extensie ontbreekt."
}
},
"about": {
"title": "Over",
"source": "Broncode",
"close": "Sluiten",
"license": "Licentie"
},
"tooltips": {
"addNewItem": "Item toevoegen"
},
"content": {
"anime": "Anime",
"manga": "Manga",
"list": "Lijst"
},
"search": {
"anime": "Zoeken naar anime",
"manga": "Zoeken naar manga",
"query": "Zoekopdracht"
},
"details": {
"title": "Details",
"removeBody": "Weet je zeker dat je $title van de lijst wilt verwijderen?",
"removeButton": "Verwijderen",
"cancelButton": "Annuleren",
"removeTitle": "$title verwijderen?",
"watchState": "Kijkstatus",
"readState": "Leesstatus",
"episodes": "Afleveringen",
"chapters": "Hoofdstukken",
"volumesOwned": "Aantal edities in bezit"
},
"data": {
"ongoing": {
"anime": "Aan het kijken",
"manga": "Aan het lezen"
},
"completed": "Afgerond",
"planned": {
"anime": "Wil ik kijken",
"manga": "Wil ik lezen"
},
"dropped": "Gestopt",
"paused": "Onderbroken",
"all": "Alles"
},
"calendar": {
"calendar": "Kalender",
"days": {
"monday": "maandag",
"tuesday": "dinsdag",
"wednesday": "woensdag",
"thursday": "donderdag",
"friday": "vrijdag",
"saturday": "zaterdag",
"sunday": "zondag",
"unknown": "Onbekend"
}
}
}

View File

@@ -0,0 +1,80 @@
{
"settings": {
"importAnime": "导入动画列表",
"title": "设置",
"importManga": "导入漫画列表",
"importMangaDesc": "导入从 MyAnimeList 导出的漫画列表。",
"invalidMangaListTitle": "漫画列表无效",
"importIndicator": "$current / $total",
"exportData": "导出数据",
"importData": "导入数据",
"dataExportSuccess": "数据已成功导出",
"dataImportSuccess": "数据已成功导入",
"importInvalidData": {
"title": "AniTrack 数据无效",
"content": "所选文件不是 AniTrack 导出的数据。它缺少“.json.gz”后缀。"
},
"importAnimeDesc": "导入从 MyAnimeList 导出的动画列表。",
"invalidAnimeListTitle": "动画列表无效",
"invalidAnimeListBody": "所选文件不是 MyAnimeList 动画列表。它缺少“.xml.gz”后缀。",
"invalidMangaListBody": "所选文件不是 MyAnimeList 漫画列表。它缺少“.xml.gz”后缀。"
},
"details": {
"removeTitle": "移除 $title",
"title": "详情",
"removeBody": "是否确定要从列表中移除“$title”",
"removeButton": "移除",
"cancelButton": "取消",
"watchState": "观看状态",
"episodes": "剧集",
"chapters": "章节",
"volumesOwned": "拥有的卷",
"readState": "阅读状态"
},
"calendar": {
"days": {
"monday": "星期一",
"tuesday": "星期二",
"wednesday": "星期三",
"thursday": "星期四",
"friday": "星期五",
"saturday": "星期六",
"sunday": "星期日",
"unknown": "未知"
},
"calendar": "日历"
},
"data": {
"planned": {
"manga": "计划阅读",
"anime": "计划观看"
},
"completed": "已完成",
"paused": "已暂停",
"all": "全部",
"ongoing": {
"anime": "正在观看",
"manga": "正在阅读"
},
"dropped": "已丢弃"
},
"about": {
"title": "关于",
"source": "源代码",
"license": "许可证",
"close": "关闭"
},
"tooltips": {
"addNewItem": "添加新项目"
},
"content": {
"list": "列表",
"anime": "动画",
"manga": "漫画"
},
"search": {
"anime": "动画搜索",
"manga": "漫画搜索",
"query": "搜索查询"
}
}

View File

@@ -5,3 +5,5 @@ targets:
options: options:
input_directory: assets/i18n input_directory: assets/i18n
output_directory: lib/i18n output_directory: lib/i18n
fallback_strategy: base_locale
base_locale: en

189
flake.lock generated
View File

@@ -1,6 +1,100 @@
{ {
"nodes": { "nodes": {
"android-nixpkgs": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1689798050,
"narHash": "sha256-ZyFPra7N0MF803o55dYQQyX9b/BmXr6QTCyN7slRThY=",
"owner": "tadfisher",
"repo": "android-nixpkgs",
"rev": "9aa0e2990da86de8ca203af313668851dcb9ea6e",
"type": "github"
},
"original": {
"owner": "tadfisher",
"repo": "android-nixpkgs",
"type": "github"
}
},
"bab": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1689978337,
"narHash": "sha256-d4Rn+YtBrs6NpQobODZYUeVqsTS+WCiGih+WOt+gazA=",
"ref": "refs/heads/master",
"rev": "92687b6513492c6fdc839f313d14da632c9d2767",
"revCount": 1,
"type": "git",
"url": "https://codeberg.org/PapaTutuWawa/bits-and-bytes.git"
},
"original": {
"type": "git",
"url": "https://codeberg.org/PapaTutuWawa/bits-and-bytes.git"
}
},
"devshell": {
"inputs": {
"nixpkgs": ["android-nixpkgs", "nixpkgs"],
"systems": "systems"
},
"locked": {
"lastModified": 1688380630,
"narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=",
"owner": "numtide",
"repo": "devshell",
"rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
@@ -17,24 +111,103 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1676076353, "lastModified": 1689679375,
"narHash": "sha256-mdUtE8Tp40cZETwcq5tCwwLqkJVV1ULJQ5GKRtbshag=", "narHash": "sha256-LHUC52WvyVDi9PwyL1QCpaxYWBqp4ir4iL6zgOkmcb8=",
"owner": "AtaraxiaSjel", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5deb99bdccbbb97e7562dee4ba8a3ee3021688e6", "rev": "684c17c429c42515bafb3ad775d2a710947f3d67",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "AtaraxiaSjel", "owner": "NixOS",
"ref": "update/flutter", "ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1689935543,
"narHash": "sha256-6GQ9ib4dA/r1leC5VUpsBo0BmDvNxLjKrX1iyL+h8mc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e43e2448161c0a2c4928abec4e16eae1516571bc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1727586919,
"narHash": "sha256-e/YXG0tO5GWHDS8QQauj8aj4HhXEm602q9swrrlTlKQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2dcd9c55e8914017226f5948ac22c53872a13ee2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "android-nixpkgs": "android-nixpkgs",
"nixpkgs": "nixpkgs" "bab": "bab",
"flake-utils": "flake-utils_3",
"nixpkgs": "nixpkgs_3"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
} }
} }
}, },

View File

@@ -1,11 +1,13 @@
{ {
description = "AniTrack"; description = "AniTrack";
inputs = { inputs = {
nixpkgs.url = "github:AtaraxiaSjel/nixpkgs/update/flutter"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
android-nixpkgs.url = "github:tadfisher/android-nixpkgs";
bab.url = "git+https://codeberg.org/PapaTutuWawa/bits-and-bytes.git";
}; };
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let outputs = { self, nixpkgs, android-nixpkgs, flake-utils, bab }: flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
config = { config = {
@@ -13,36 +15,73 @@
allowUnfree = true; allowUnfree = true;
}; };
}; };
android = pkgs.androidenv.composeAndroidPackages { lib = pkgs.lib;
# TODO: Find a way to pin these babPkgs = bab.packages."${system}";
#toolsVersion = "26.1.1";
#platformToolsVersion = "31.0.3";
#buildToolsVersions = [ "31.0.0" ];
#includeEmulator = true;
#emulatorVersion = "30.6.3";
platformVersions = [ "28" ];
includeSources = false;
includeSystemImages = true;
systemImageTypes = [ "default" ];
abiVersions = [ "x86_64" ];
includeNDK = false;
useGoogleAPIs = false;
useGoogleTVAddOns = false;
};
pinnedJDK = pkgs.jdk17; pinnedJDK = pkgs.jdk17;
pythonEnv = pkgs.python3.withPackages (ps: with ps; [ # Everything to make Flutter happy
requests pyyaml # For the build scripts sdk = android-nixpkgs.sdk.${system} (sdkPkgs: with sdkPkgs; [
cmdline-tools-latest
build-tools-30-0-3
build-tools-33-0-2
build-tools-34-0-0
platform-tools
emulator
patcher-v4
#platforms-android-30
platforms-android-31
platforms-android-33
platforms-android-34
]); ]);
in { in {
devShell = pkgs.mkShell { devShell = pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
flutter pinnedJDK android.platform-tools dart scrcpy # Flutter/Android # Android
gitlint jq # Code hygiene sdk
ripgrep # General utilities
# Flutter
flutter dart scrcpy pinnedJDK
# Code hygiene
gitlint
]; ];
JAVA_HOME = pinnedJDK; JAVA_HOME = pinnedJDK;
ANDROID_HOME = "${sdk}/share/android-sdk";
ANDROID_SDK_ROOT = "${sdk}/share/android-sdk";
# Fix an issue with Flutter using an older version of aapt2, which does not know
# an used parameter.
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${sdk}/share/android-sdk/build-tools/34.0.0/aapt2";
};
apps = let
providerArg = pkgs.writeText "provider-arg.cfg" ''
name = OpenSC-PKCS11
description = SunPKCS11 via OpenSC
library = ${pkgs.opensc}/lib/opensc-pkcs11.so
slotListIndex = 0
'';
mkBuildScript = skipBuild: pkgs.writeShellScript "build-anitrack.sh" ''
${babPkgs.flutter-build}/bin/flutter-build \
--name AniTrack \
--not-signed \
--zipalign ${sdk}/share/android-sdk/build-tools/34.0.0/zipalign \
--apksigner ${sdk}/share/android-sdk/build-tools/34.0.0/apksigner \
--provider-config ${providerArg} ${lib.optionalString skipBuild "--skip-build"}
'';
in {
# Skip the build and just sign
onlySign = {
type = "app";
program = "${mkBuildScript true}";
};
# Build everything and sign
build = {
type = "app";
program = "${mkBuildScript false}";
};
}; };
}); });
} }

View File

@@ -86,7 +86,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
'Provides encoders and decoders for various archive and compression formats such as zip, tar, bzip2, gzip, and zlib.', 'Provides encoders and decoders for various archive and compression formats such as zip, tar, bzip2, gzip, and zlib.',
repository: 'https://github.com/brendan-duncan/archive', repository: 'https://github.com/brendan-duncan/archive',
authors: [], authors: [],
version: '3.3.6', version: '3.3.7',
license: '''The MIT License license: '''The MIT License
Copyright (c) 2013-2021 Brendan Duncan. Copyright (c) 2013-2021 Brendan Duncan.
@@ -111,7 +111,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.''', THE SOFTWARE.''',
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: true,
), ),
Package( Package(
name: 'args', name: 'args',
@@ -157,7 +157,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
'Utility functions and classes related to the \'dart:async\' library.', 'Utility functions and classes related to the \'dart:async\' library.',
repository: 'https://github.com/dart-lang/async', repository: 'https://github.com/dart-lang/async',
authors: [], authors: [],
version: '2.9.0', version: '2.10.0',
license: '''Copyright 2015, the Dart project authors. license: '''Copyright 2015, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -226,10 +226,11 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.''',
name: 'boolean_selector', name: 'boolean_selector',
description: description:
'A flexible syntax for boolean expressions, based on a simplified version of Dart\'s expression syntax.', 'A flexible syntax for boolean expressions, based on a simplified version of Dart\'s expression syntax.',
homepage: 'https://github.com/dart-lang/boolean_selector', repository: 'https://github.com/dart-lang/boolean_selector',
authors: [], authors: [],
version: '2.1.0', version: '2.1.1',
license: '''Copyright 2016, the Dart project authors. All rights reserved. license: '''Copyright 2016, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
met: met:
@@ -240,7 +241,7 @@ met:
copyright notice, this list of conditions and the following copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided disclaimer in the documentation and/or other materials provided
with the distribution. with the distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived contributors may be used to endorse or promote products derived
from this software without specific prior written permission. from this software without specific prior written permission.
@@ -1062,7 +1063,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
'Collections and utilities functions and classes related to collections.', 'Collections and utilities functions and classes related to collections.',
repository: 'https://github.com/dart-lang/collection', repository: 'https://github.com/dart-lang/collection',
authors: [], authors: [],
version: '1.16.0', version: '1.17.0',
license: '''Copyright 2015, the Dart project authors. license: '''Copyright 2015, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -1092,7 +1093,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: true,
), ),
Package( Package(
name: 'convert', name: 'convert',
@@ -1170,6 +1171,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: false,
), ),
Package(
name: 'csv',
description: '''A codec to transform between a string and a list of values.
The string must be comma (configurable) separated values.''',
homepage: 'https://github.com/close2/csv',
authors: [],
version: '5.0.2',
license: '''The MIT License (MIT)
Copyright (c) 2014 Christian Loitsch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package( Package(
name: 'cupertino_icons', name: 'cupertino_icons',
description: description:
@@ -1561,6 +1595,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: false,
), ),
Package(
name: 'file_picker',
description:
'A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.',
homepage: 'https://github.com/miguelpruivo/plugins_flutter_file_picker',
repository: 'https://github.com/miguelpruivo/flutter_file_picker',
authors: [],
version: '5.2.8',
license: '''MIT License
Copyright (c) 2018 Miguel Ruivo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: true,
),
Package( Package(
name: 'fixnum', name: 'fixnum',
description: description:
@@ -1604,7 +1671,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
description: 'A framework for writing Flutter applications', description: 'A framework for writing Flutter applications',
homepage: 'https://flutter.dev', homepage: 'https://flutter.dev',
authors: [], authors: [],
version: '3.3.8', version: '3.7.3',
license: '''Copyright 2014 The Flutter Authors. All rights reserved. license: '''Copyright 2014 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
@@ -1837,6 +1904,75 @@ SOFTWARE.''',
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: false,
), ),
Package(
name: 'flutter_plugin_android_lifecycle',
description:
'Flutter plugin for accessing an Android Lifecycle within other plugins.',
repository:
'https://github.com/flutter/packages/tree/main/packages/flutter_plugin_android_lifecycle',
authors: [],
version: '2.0.9',
license: '''Copyright 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package(
name: 'fluttertoast',
description:
'Toast Library for Flutter, Easily create toast messages in single line of code',
homepage: 'https://github.com/PonnamKarthik/FlutterToast',
authors: [],
version: '8.2.2',
license: '''MIT License
Copyright (c) 2020 Karthik Ponnam
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: true,
),
Package( Package(
name: 'freezed', name: 'freezed',
description: description:
@@ -2273,9 +2409,9 @@ SOFTWARE.''',
name: 'js', name: 'js',
description: description:
'Annotations to create static Dart interfaces for JavaScript APIs.', 'Annotations to create static Dart interfaces for JavaScript APIs.',
homepage: 'https://github.com/dart-lang/sdk/tree/master/pkg/js', repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/js',
authors: [], authors: [],
version: '0.6.4', version: '0.6.5',
license: '''Copyright 2012, the Dart project authors. license: '''Copyright 2012, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -2307,6 +2443,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: false,
), ),
Package(
name: 'json2yaml',
description:
'''Dart package to render JSON data to YAML with built-in automatic beautifier and
support for Dart pubspec.yaml and pubspec.lock conventions''',
homepage: 'https://github.com/alexei-sintotski/json2yaml',
authors: [],
version: '3.0.1',
license: '''MIT License
Copyright (c) 2019 Alexei Sintotski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package( Package(
name: 'json_annotation', name: 'json_annotation',
description: description:
@@ -2466,7 +2635,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
'Support for specifying test expectations via an extensible Matcher class. Also includes a number of built-in Matcher implementations for common cases.', 'Support for specifying test expectations via an extensible Matcher class. Also includes a number of built-in Matcher implementations for common cases.',
repository: 'https://github.com/dart-lang/matcher', repository: 'https://github.com/dart-lang/matcher',
authors: [], authors: [],
version: '0.12.12', version: '0.12.13',
license: '''Copyright 2014, the Dart project authors. license: '''Copyright 2014, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -2501,11 +2670,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
Package( Package(
name: 'material_color_utilities', name: 'material_color_utilities',
description: description:
'Algorithms and utilities that power the Material Design 3 (M3) color system, including choosing theme colors from images and creating tones of colors; all in a new color space.', 'Algorithms and utilities that power the Material Design 3 color system, including choosing theme colors from images and creating tones of colors; all in a new color space.',
repository: repository:
'https://github.com/material-foundation/material-color-utilities', 'https://github.com/material-foundation/material-color-utilities/tree/main/dart',
authors: [], authors: [],
version: '0.1.5', version: '0.2.0',
license: '''Apache License license: '''Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/
@@ -2925,7 +3094,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: true,
), ),
Package( Package(
name: 'path_provider', name: 'path_provider',
@@ -3181,6 +3350,167 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: false,
), ),
Package(
name: 'permission_handler',
description:
'Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.',
repository: 'https://github.com/baseflow/flutter-permission-handler',
authors: [],
version: '10.4.3',
license: '''MIT License
Copyright (c) 2018 Baseflow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: true,
),
Package(
name: 'permission_handler_android',
description:
'Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.',
homepage: 'https://github.com/baseflow/flutter-permission-handler',
authors: [],
version: '10.3.2',
license: '''MIT License
Copyright (c) 2018 Baseflow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package(
name: 'permission_handler_apple',
description:
'Permission plugin for Flutter. This plugin provides the iOS API to request and check permissions.',
repository: 'https://github.com/baseflow/flutter-permission-handler',
authors: [],
version: '9.1.4',
license: '''MIT License
Copyright (c) 2018 Baseflow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package(
name: 'permission_handler_platform_interface',
description:
'A common platform interface for the permission_handler plugin.',
homepage:
'https://github.com/baseflow/flutter-permission-handler/tree/master/permission_handler_platform_interface',
authors: [],
version: '3.11.3',
license: '''MIT License
Copyright (c) 2018 Baseflow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package(
name: 'permission_handler_windows',
description:
'Permission plugin for Flutter. This plugin provides the Windows API to request and check permissions.',
homepage: 'https://github.com/baseflow/flutter-permission-handler',
authors: [],
version: '0.1.3',
license: '''MIT License
Copyright (c) 2018 Baseflow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package( Package(
name: 'petitparser', name: 'petitparser',
description: description:
@@ -3604,6 +3934,102 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: false,
), ),
Package(
name: 'slang',
description:
'Localization / Internationalization (i18n) solution. Use JSON, YAML or CSV files to create typesafe translations via source generation.',
repository: 'https://github.com/Tienisto/slang',
authors: [],
version: '3.19.0',
license: '''MIT License
Copyright (c) 2020-2023 Tien Do Nam
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: true,
),
Package(
name: 'slang_build_runner',
description:
'build_runner integration for slang. This library ensures that slang is recognized by build_runner.',
repository: 'https://github.com/Tienisto/slang',
authors: [],
version: '3.19.0',
license: '''MIT License
Copyright (c) 2020-2023 Tien Do Nam
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
),
Package(
name: 'slang_flutter',
description:
'Flutter support for slang. This library provides helpful Flutter API.',
repository: 'https://github.com/Tienisto/slang',
authors: [],
version: '3.19.0',
license: '''MIT License
Copyright (c) 2020-2023 Tien Do Nam
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: true,
),
Package( Package(
name: 'source_gen', name: 'source_gen',
description: description:
@@ -3858,9 +4284,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
Package( Package(
name: 'source_span', name: 'source_span',
description: 'A library for identifying source spans and locations.', description: 'A library for identifying source spans and locations.',
homepage: 'https://github.com/dart-lang/source_span', repository: 'https://github.com/dart-lang/source_span',
authors: [], authors: [],
version: '1.9.0', version: '1.9.1',
license: '''Copyright 2014, the Dart project authors. license: '''Copyright 2014, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -3968,10 +4394,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
name: 'stack_trace', name: 'stack_trace',
description: description:
'A package for manipulating stack traces and printing them readably.', 'A package for manipulating stack traces and printing them readably.',
homepage: 'https://github.com/dart-lang/stack_trace', repository: 'https://github.com/dart-lang/stack_trace',
authors: [], authors: [],
version: '1.10.0', version: '1.11.0',
license: '''Copyright 2014, the Dart project authors. All rights reserved. license: '''Copyright 2014, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
met: met:
@@ -3982,7 +4409,7 @@ met:
copyright notice, this list of conditions and the following copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided disclaimer in the documentation and/or other materials provided
with the distribution. with the distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived contributors may be used to endorse or promote products derived
from this software without specific prior written permission. from this software without specific prior written permission.
@@ -4005,10 +4432,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
name: 'stream_channel', name: 'stream_channel',
description: description:
'An abstraction for two-way communication channels based on the Dart Stream class.', 'An abstraction for two-way communication channels based on the Dart Stream class.',
homepage: 'https://github.com/dart-lang/stream_channel', repository: 'https://github.com/dart-lang/stream_channel',
authors: [], authors: [],
version: '2.1.0', version: '2.1.1',
license: '''Copyright 2015, the Dart project authors. All rights reserved. license: '''Copyright 2015, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
met: met:
@@ -4019,7 +4447,7 @@ met:
copyright notice, this list of conditions and the following copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided disclaimer in the documentation and/or other materials provided
with the distribution. with the distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived contributors may be used to endorse or promote products derived
from this software without specific prior written permission. from this software without specific prior written permission.
@@ -4081,7 +4509,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
description: 'A class for parsing strings using a sequence of patterns.', description: 'A class for parsing strings using a sequence of patterns.',
repository: 'https://github.com/dart-lang/string_scanner', repository: 'https://github.com/dart-lang/string_scanner',
authors: [], authors: [],
version: '1.1.1', version: '1.2.0',
license: '''Copyright 2014, the Dart project authors. license: '''Copyright 2014, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -4225,10 +4653,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
), ),
Package( Package(
name: 'test_api', name: 'test_api',
description: 'A library for writing Dart tests.', description:
'The user facing API for structuring Dart tests and checking expectations.',
repository: 'https://github.com/dart-lang/test/tree/master/pkgs/test_api', repository: 'https://github.com/dart-lang/test/tree/master/pkgs/test_api',
authors: [], authors: [],
version: '0.4.12', version: '0.4.16',
license: '''Copyright 2018, the Dart project authors. license: '''Copyright 2018, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -4855,7 +5284,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
description: 'A Vector Math library for 2D and 3D applications.', description: 'A Vector Math library for 2D and 3D applications.',
repository: 'https://github.com/google/vector_math.dart', repository: 'https://github.com/google/vector_math.dart',
authors: [], authors: [],
version: '2.1.2', version: '2.1.4',
license: '''Copyright 2015, Google Inc. All rights reserved. license: '''Copyright 2015, Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -5096,7 +5525,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
'A lightweight library for parsing, traversing, querying, transforming and building XML documents.', 'A lightweight library for parsing, traversing, querying, transforming and building XML documents.',
homepage: 'https://github.com/renggli/dart-xml', homepage: 'https://github.com/renggli/dart-xml',
authors: [], authors: [],
version: '6.1.0', version: '6.2.2',
license: '''The MIT License license: '''The MIT License
Copyright (c) 2006-2022 Lukas Renggli. Copyright (c) 2006-2022 Lukas Renggli.
@@ -5121,7 +5550,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.''', THE SOFTWARE.''',
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
isDirectDependency: false, isDirectDependency: true,
), ),
Package( Package(
name: 'yaml', name: 'yaml',

View File

@@ -2,6 +2,7 @@ import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/service/database.dart'; import 'package:anitrack/src/service/database.dart';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart'; import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
import 'package:anitrack/src/ui/bloc/calendar_bloc.dart';
import 'package:anitrack/src/ui/bloc/details_bloc.dart'; import 'package:anitrack/src/ui/bloc/details_bloc.dart';
import 'package:anitrack/src/ui/bloc/navigation_bloc.dart'; import 'package:anitrack/src/ui/bloc/navigation_bloc.dart';
import 'package:anitrack/src/ui/bloc/settings_bloc.dart'; import 'package:anitrack/src/ui/bloc/settings_bloc.dart';
@@ -9,6 +10,7 @@ import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/pages/about.dart'; import 'package:anitrack/src/ui/pages/about.dart';
import 'package:anitrack/src/ui/pages/anime_list.dart'; import 'package:anitrack/src/ui/pages/anime_list.dart';
import 'package:anitrack/src/ui/pages/anime_search.dart'; import 'package:anitrack/src/ui/pages/anime_search.dart';
import 'package:anitrack/src/ui/pages/calendar.dart';
import 'package:anitrack/src/ui/pages/details.dart'; import 'package:anitrack/src/ui/pages/details.dart';
import 'package:anitrack/src/ui/pages/settings.dart'; import 'package:anitrack/src/ui/pages/settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -32,6 +34,7 @@ void main() async {
GetIt.I.registerSingleton<DetailsBloc>(DetailsBloc()); GetIt.I.registerSingleton<DetailsBloc>(DetailsBloc());
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey)); GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey));
GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc()); GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc());
GetIt.I.registerSingleton<CalendarBloc>(CalendarBloc());
// Load animes // Load animes
GetIt.I.get<AnimeListBloc>().add( GetIt.I.get<AnimeListBloc>().add(
@@ -59,6 +62,9 @@ void main() async {
BlocProvider<SettingsBloc>( BlocProvider<SettingsBloc>(
create: (_) => GetIt.I.get<SettingsBloc>(), create: (_) => GetIt.I.get<SettingsBloc>(),
), ),
BlocProvider<CalendarBloc>(
create: (_) => GetIt.I.get<CalendarBloc>(),
),
], ],
child: MyApp(navKey), child: MyApp(navKey),
), ),
@@ -95,6 +101,8 @@ class MyApp extends StatelessWidget {
return AnimeListPage.route; return AnimeListPage.route;
case animeSearchRoute: case animeSearchRoute:
return AnimeSearchPage.route; return AnimeSearchPage.route;
case calendarRoute:
return CalendarPage.route;
case detailsRoute: case detailsRoute:
return DetailsPage.route; return DetailsPage.route;
case aboutRoute: case aboutRoute:

View File

@@ -1,9 +1,20 @@
import 'package:anitrack/src/data/type.dart'; import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'anime.freezed.dart'; part 'anime.freezed.dart';
part 'anime.g.dart'; part 'anime.g.dart';
class BoolConverter implements JsonConverter<bool, int> {
const BoolConverter();
@override
bool fromJson(int json) => json.toBool();
@override
int toJson(bool object) => object.toInt();
}
/// Data about a tracked anime /// Data about a tracked anime
@freezed @freezed
class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium { class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
@@ -25,6 +36,12 @@ class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
/// URL to the thumbnail/cover art for the anime. /// URL to the thumbnail/cover art for the anime.
String thumbnailUrl, String thumbnailUrl,
/// Flag whether the anime is airing
@BoolConverter() bool airing,
/// The day of the week the anime is airing
String? broadcastDay,
) = _AnimeTrackingData; ) = _AnimeTrackingData;
/// JSON /// JSON

View File

@@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // 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 // 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 'anime.dart'; part of 'anime.dart';
@@ -12,7 +12,7 @@ part of 'anime.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError( final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
AnimeTrackingData _$AnimeTrackingDataFromJson(Map<String, dynamic> json) { AnimeTrackingData _$AnimeTrackingDataFromJson(Map<String, dynamic> json) {
return _AnimeTrackingData.fromJson(json); return _AnimeTrackingData.fromJson(json);
@@ -39,8 +39,19 @@ mixin _$AnimeTrackingData {
/// URL to the thumbnail/cover art for the anime. /// URL to the thumbnail/cover art for the anime.
String get thumbnailUrl => throw _privateConstructorUsedError; String get thumbnailUrl => throw _privateConstructorUsedError;
/// Flag whether the anime is airing
@BoolConverter()
bool get airing => throw _privateConstructorUsedError;
/// The day of the week the anime is airing
String? get broadcastDay => throw _privateConstructorUsedError;
/// Serializes this AnimeTrackingData to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AnimeTrackingDataCopyWith<AnimeTrackingData> get copyWith => $AnimeTrackingDataCopyWith<AnimeTrackingData> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -49,136 +60,173 @@ mixin _$AnimeTrackingData {
abstract class $AnimeTrackingDataCopyWith<$Res> { abstract class $AnimeTrackingDataCopyWith<$Res> {
factory $AnimeTrackingDataCopyWith( factory $AnimeTrackingDataCopyWith(
AnimeTrackingData value, $Res Function(AnimeTrackingData) then) = AnimeTrackingData value, $Res Function(AnimeTrackingData) then) =
_$AnimeTrackingDataCopyWithImpl<$Res>; _$AnimeTrackingDataCopyWithImpl<$Res, AnimeTrackingData>;
@useResult
$Res call( $Res call(
{String id, {String id,
@MediumTrackingStateConverter() MediumTrackingState state, @MediumTrackingStateConverter() MediumTrackingState state,
String title, String title,
int episodesWatched, int episodesWatched,
int? episodesTotal, int? episodesTotal,
String thumbnailUrl}); String thumbnailUrl,
@BoolConverter() bool airing,
String? broadcastDay});
} }
/// @nodoc /// @nodoc
class _$AnimeTrackingDataCopyWithImpl<$Res> class _$AnimeTrackingDataCopyWithImpl<$Res, $Val extends AnimeTrackingData>
implements $AnimeTrackingDataCopyWith<$Res> { implements $AnimeTrackingDataCopyWith<$Res> {
_$AnimeTrackingDataCopyWithImpl(this._value, this._then); _$AnimeTrackingDataCopyWithImpl(this._value, this._then);
final AnimeTrackingData _value;
// ignore: unused_field // ignore: unused_field
final $Res Function(AnimeTrackingData) _then; final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? id = freezed, Object? id = null,
Object? state = freezed, Object? state = null,
Object? title = freezed, Object? title = null,
Object? episodesWatched = freezed, Object? episodesWatched = null,
Object? episodesTotal = freezed, Object? episodesTotal = freezed,
Object? thumbnailUrl = freezed, Object? thumbnailUrl = null,
Object? airing = null,
Object? broadcastDay = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
id: id == freezed id: null == id
? _value.id ? _value.id
: id // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as String, as String,
state: state == freezed state: null == state
? _value.state ? _value.state
: state // ignore: cast_nullable_to_non_nullable : state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
title: title == freezed title: null == title
? _value.title ? _value.title
: title // ignore: cast_nullable_to_non_nullable : title // ignore: cast_nullable_to_non_nullable
as String, as String,
episodesWatched: episodesWatched == freezed episodesWatched: null == episodesWatched
? _value.episodesWatched ? _value.episodesWatched
: episodesWatched // ignore: cast_nullable_to_non_nullable : episodesWatched // ignore: cast_nullable_to_non_nullable
as int, as int,
episodesTotal: episodesTotal == freezed episodesTotal: freezed == episodesTotal
? _value.episodesTotal ? _value.episodesTotal
: episodesTotal // ignore: cast_nullable_to_non_nullable : episodesTotal // ignore: cast_nullable_to_non_nullable
as int?, as int?,
thumbnailUrl: thumbnailUrl == freezed thumbnailUrl: null == thumbnailUrl
? _value.thumbnailUrl ? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String, as String,
)); airing: null == airing
? _value.airing
: airing // ignore: cast_nullable_to_non_nullable
as bool,
broadcastDay: freezed == broadcastDay
? _value.broadcastDay
: broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$_AnimeTrackingDataCopyWith<$Res> abstract class _$$AnimeTrackingDataImplCopyWith<$Res>
implements $AnimeTrackingDataCopyWith<$Res> { implements $AnimeTrackingDataCopyWith<$Res> {
factory _$$_AnimeTrackingDataCopyWith(_$_AnimeTrackingData value, factory _$$AnimeTrackingDataImplCopyWith(_$AnimeTrackingDataImpl value,
$Res Function(_$_AnimeTrackingData) then) = $Res Function(_$AnimeTrackingDataImpl) then) =
__$$_AnimeTrackingDataCopyWithImpl<$Res>; __$$AnimeTrackingDataImplCopyWithImpl<$Res>;
@override @override
@useResult
$Res call( $Res call(
{String id, {String id,
@MediumTrackingStateConverter() MediumTrackingState state, @MediumTrackingStateConverter() MediumTrackingState state,
String title, String title,
int episodesWatched, int episodesWatched,
int? episodesTotal, int? episodesTotal,
String thumbnailUrl}); String thumbnailUrl,
@BoolConverter() bool airing,
String? broadcastDay});
} }
/// @nodoc /// @nodoc
class __$$_AnimeTrackingDataCopyWithImpl<$Res> class __$$AnimeTrackingDataImplCopyWithImpl<$Res>
extends _$AnimeTrackingDataCopyWithImpl<$Res> extends _$AnimeTrackingDataCopyWithImpl<$Res, _$AnimeTrackingDataImpl>
implements _$$_AnimeTrackingDataCopyWith<$Res> { implements _$$AnimeTrackingDataImplCopyWith<$Res> {
__$$_AnimeTrackingDataCopyWithImpl( __$$AnimeTrackingDataImplCopyWithImpl(_$AnimeTrackingDataImpl _value,
_$_AnimeTrackingData _value, $Res Function(_$_AnimeTrackingData) _then) $Res Function(_$AnimeTrackingDataImpl) _then)
: super(_value, (v) => _then(v as _$_AnimeTrackingData)); : super(_value, _then);
@override
_$_AnimeTrackingData get _value => super._value as _$_AnimeTrackingData;
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? id = freezed, Object? id = null,
Object? state = freezed, Object? state = null,
Object? title = freezed, Object? title = null,
Object? episodesWatched = freezed, Object? episodesWatched = null,
Object? episodesTotal = freezed, Object? episodesTotal = freezed,
Object? thumbnailUrl = freezed, Object? thumbnailUrl = null,
Object? airing = null,
Object? broadcastDay = freezed,
}) { }) {
return _then(_$_AnimeTrackingData( return _then(_$AnimeTrackingDataImpl(
id == freezed null == id
? _value.id ? _value.id
: id // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as String, as String,
state == freezed null == state
? _value.state ? _value.state
: state // ignore: cast_nullable_to_non_nullable : state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
title == freezed null == title
? _value.title ? _value.title
: title // ignore: cast_nullable_to_non_nullable : title // ignore: cast_nullable_to_non_nullable
as String, as String,
episodesWatched == freezed null == episodesWatched
? _value.episodesWatched ? _value.episodesWatched
: episodesWatched // ignore: cast_nullable_to_non_nullable : episodesWatched // ignore: cast_nullable_to_non_nullable
as int, as int,
episodesTotal == freezed freezed == episodesTotal
? _value.episodesTotal ? _value.episodesTotal
: episodesTotal // ignore: cast_nullable_to_non_nullable : episodesTotal // ignore: cast_nullable_to_non_nullable
as int?, as int?,
thumbnailUrl == freezed null == thumbnailUrl
? _value.thumbnailUrl ? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String, as String,
null == airing
? _value.airing
: airing // ignore: cast_nullable_to_non_nullable
as bool,
freezed == broadcastDay
? _value.broadcastDay
: broadcastDay // ignore: cast_nullable_to_non_nullable
as String?,
)); ));
} }
} }
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$_AnimeTrackingData implements _AnimeTrackingData { class _$AnimeTrackingDataImpl implements _AnimeTrackingData {
_$_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state, _$AnimeTrackingDataImpl(
this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl); this.id,
@MediumTrackingStateConverter() this.state,
this.title,
this.episodesWatched,
this.episodesTotal,
this.thumbnailUrl,
@BoolConverter() this.airing,
this.broadcastDay);
factory _$_AnimeTrackingData.fromJson(Map<String, dynamic> json) => factory _$AnimeTrackingDataImpl.fromJson(Map<String, dynamic> json) =>
_$$_AnimeTrackingDataFromJson(json); _$$AnimeTrackingDataImplFromJson(json);
/// The ID of the anime /// The ID of the anime
@override @override
@@ -205,47 +253,56 @@ class _$_AnimeTrackingData implements _AnimeTrackingData {
@override @override
final String thumbnailUrl; final String thumbnailUrl;
/// Flag whether the anime is airing
@override
@BoolConverter()
final bool airing;
/// The day of the week the anime is airing
@override
final String? broadcastDay;
@override @override
String toString() { String toString() {
return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl)'; return 'AnimeTrackingData(id: $id, state: $state, title: $title, episodesWatched: $episodesWatched, episodesTotal: $episodesTotal, thumbnailUrl: $thumbnailUrl, airing: $airing, broadcastDay: $broadcastDay)';
} }
@override @override
bool operator ==(dynamic other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_AnimeTrackingData && other is _$AnimeTrackingDataImpl &&
const DeepCollectionEquality().equals(other.id, id) && (identical(other.id, id) || other.id == id) &&
const DeepCollectionEquality().equals(other.state, state) && (identical(other.state, state) || other.state == state) &&
const DeepCollectionEquality().equals(other.title, title) && (identical(other.title, title) || other.title == title) &&
const DeepCollectionEquality() (identical(other.episodesWatched, episodesWatched) ||
.equals(other.episodesWatched, episodesWatched) && other.episodesWatched == episodesWatched) &&
const DeepCollectionEquality() (identical(other.episodesTotal, episodesTotal) ||
.equals(other.episodesTotal, episodesTotal) && other.episodesTotal == episodesTotal) &&
const DeepCollectionEquality() (identical(other.thumbnailUrl, thumbnailUrl) ||
.equals(other.thumbnailUrl, thumbnailUrl)); other.thumbnailUrl == thumbnailUrl) &&
(identical(other.airing, airing) || other.airing == airing) &&
(identical(other.broadcastDay, broadcastDay) ||
other.broadcastDay == broadcastDay));
} }
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(runtimeType, id, state, title,
runtimeType, episodesWatched, episodesTotal, thumbnailUrl, airing, broadcastDay);
const DeepCollectionEquality().hash(id),
const DeepCollectionEquality().hash(state),
const DeepCollectionEquality().hash(title),
const DeepCollectionEquality().hash(episodesWatched),
const DeepCollectionEquality().hash(episodesTotal),
const DeepCollectionEquality().hash(thumbnailUrl));
@JsonKey(ignore: true) /// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
_$$_AnimeTrackingDataCopyWith<_$_AnimeTrackingData> get copyWith => @pragma('vm:prefer-inline')
__$$_AnimeTrackingDataCopyWithImpl<_$_AnimeTrackingData>( _$$AnimeTrackingDataImplCopyWith<_$AnimeTrackingDataImpl> get copyWith =>
__$$AnimeTrackingDataImplCopyWithImpl<_$AnimeTrackingDataImpl>(
this, _$identity); this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$$_AnimeTrackingDataToJson( return _$$AnimeTrackingDataImplToJson(
this, this,
); );
} }
@@ -258,38 +315,51 @@ abstract class _AnimeTrackingData implements AnimeTrackingData {
final String title, final String title,
final int episodesWatched, final int episodesWatched,
final int? episodesTotal, final int? episodesTotal,
final String thumbnailUrl) = _$_AnimeTrackingData; final String thumbnailUrl,
@BoolConverter() final bool airing,
final String? broadcastDay) = _$AnimeTrackingDataImpl;
factory _AnimeTrackingData.fromJson(Map<String, dynamic> json) = factory _AnimeTrackingData.fromJson(Map<String, dynamic> json) =
_$_AnimeTrackingData.fromJson; _$AnimeTrackingDataImpl.fromJson;
@override
/// The ID of the anime /// The ID of the anime
String get id;
@override @override
String get id;
/// The state of the anime /// The state of the anime
@override
@MediumTrackingStateConverter() @MediumTrackingStateConverter()
MediumTrackingState get state; MediumTrackingState get state;
@override
/// The title of the anime /// The title of the anime
String get title;
@override @override
String get title;
/// Episodes in total. /// Episodes in total.
int get episodesWatched;
@override @override
int get episodesWatched;
/// Episodes watched. /// Episodes watched.
int? get episodesTotal;
@override @override
int? get episodesTotal;
/// URL to the thumbnail/cover art for the anime. /// URL to the thumbnail/cover art for the anime.
String get thumbnailUrl;
@override @override
@JsonKey(ignore: true) String get thumbnailUrl;
_$$_AnimeTrackingDataCopyWith<_$_AnimeTrackingData> get copyWith =>
/// Flag whether the anime is airing
@override
@BoolConverter()
bool get airing;
/// The day of the week the anime is airing
@override
String? get broadcastDay;
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AnimeTrackingDataImplCopyWith<_$AnimeTrackingDataImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -6,18 +6,22 @@ part of 'anime.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$_AnimeTrackingData _$$_AnimeTrackingDataFromJson(Map<String, dynamic> json) => _$AnimeTrackingDataImpl _$$AnimeTrackingDataImplFromJson(
_$_AnimeTrackingData( Map<String, dynamic> json) =>
_$AnimeTrackingDataImpl(
json['id'] as String, json['id'] as String,
const MediumTrackingStateConverter().fromJson(json['state'] as int), const MediumTrackingStateConverter()
.fromJson((json['state'] as num).toInt()),
json['title'] as String, json['title'] as String,
json['episodesWatched'] as int, (json['episodesWatched'] as num).toInt(),
json['episodesTotal'] as int?, (json['episodesTotal'] as num?)?.toInt(),
json['thumbnailUrl'] as String, json['thumbnailUrl'] as String,
const BoolConverter().fromJson((json['airing'] as num).toInt()),
json['broadcastDay'] as String?,
); );
Map<String, dynamic> _$$_AnimeTrackingDataToJson( Map<String, dynamic> _$$AnimeTrackingDataImplToJson(
_$_AnimeTrackingData instance) => _$AnimeTrackingDataImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'state': const MediumTrackingStateConverter().toJson(instance.state), 'state': const MediumTrackingStateConverter().toJson(instance.state),
@@ -25,4 +29,6 @@ Map<String, dynamic> _$$_AnimeTrackingDataToJson(
'episodesWatched': instance.episodesWatched, 'episodesWatched': instance.episodesWatched,
'episodesTotal': instance.episodesTotal, 'episodesTotal': instance.episodesTotal,
'thumbnailUrl': instance.thumbnailUrl, 'thumbnailUrl': instance.thumbnailUrl,
'airing': const BoolConverter().toJson(instance.airing),
'broadcastDay': instance.broadcastDay,
}; };

View File

@@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // 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 // 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 'manga.dart'; part of 'manga.dart';
@@ -12,7 +12,7 @@ part of 'manga.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError( final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
MangaTrackingData _$MangaTrackingDataFromJson(Map<String, dynamic> json) { MangaTrackingData _$MangaTrackingDataFromJson(Map<String, dynamic> json) {
return _MangaTrackingData.fromJson(json); return _MangaTrackingData.fromJson(json);
@@ -42,8 +42,12 @@ mixin _$MangaTrackingData {
/// URL to the thumbnail/cover art for the manga. /// URL to the thumbnail/cover art for the manga.
String get thumbnailUrl => throw _privateConstructorUsedError; String get thumbnailUrl => throw _privateConstructorUsedError;
/// Serializes this MangaTrackingData to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MangaTrackingDataCopyWith<MangaTrackingData> get copyWith => $MangaTrackingDataCopyWith<MangaTrackingData> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -52,7 +56,8 @@ mixin _$MangaTrackingData {
abstract class $MangaTrackingDataCopyWith<$Res> { abstract class $MangaTrackingDataCopyWith<$Res> {
factory $MangaTrackingDataCopyWith( factory $MangaTrackingDataCopyWith(
MangaTrackingData value, $Res Function(MangaTrackingData) then) = MangaTrackingData value, $Res Function(MangaTrackingData) then) =
_$MangaTrackingDataCopyWithImpl<$Res>; _$MangaTrackingDataCopyWithImpl<$Res, MangaTrackingData>;
@useResult
$Res call( $Res call(
{String id, {String id,
@MediumTrackingStateConverter() MediumTrackingState state, @MediumTrackingStateConverter() MediumTrackingState state,
@@ -64,64 +69,69 @@ abstract class $MangaTrackingDataCopyWith<$Res> {
} }
/// @nodoc /// @nodoc
class _$MangaTrackingDataCopyWithImpl<$Res> class _$MangaTrackingDataCopyWithImpl<$Res, $Val extends MangaTrackingData>
implements $MangaTrackingDataCopyWith<$Res> { implements $MangaTrackingDataCopyWith<$Res> {
_$MangaTrackingDataCopyWithImpl(this._value, this._then); _$MangaTrackingDataCopyWithImpl(this._value, this._then);
final MangaTrackingData _value;
// ignore: unused_field // ignore: unused_field
final $Res Function(MangaTrackingData) _then; final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? id = freezed, Object? id = null,
Object? state = freezed, Object? state = null,
Object? title = freezed, Object? title = null,
Object? chaptersRead = freezed, Object? chaptersRead = null,
Object? volumesOwned = freezed, Object? volumesOwned = null,
Object? chaptersTotal = freezed, Object? chaptersTotal = freezed,
Object? thumbnailUrl = freezed, Object? thumbnailUrl = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
id: id == freezed id: null == id
? _value.id ? _value.id
: id // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as String, as String,
state: state == freezed state: null == state
? _value.state ? _value.state
: state // ignore: cast_nullable_to_non_nullable : state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
title: title == freezed title: null == title
? _value.title ? _value.title
: title // ignore: cast_nullable_to_non_nullable : title // ignore: cast_nullable_to_non_nullable
as String, as String,
chaptersRead: chaptersRead == freezed chaptersRead: null == chaptersRead
? _value.chaptersRead ? _value.chaptersRead
: chaptersRead // ignore: cast_nullable_to_non_nullable : chaptersRead // ignore: cast_nullable_to_non_nullable
as int, as int,
volumesOwned: volumesOwned == freezed volumesOwned: null == volumesOwned
? _value.volumesOwned ? _value.volumesOwned
: volumesOwned // ignore: cast_nullable_to_non_nullable : volumesOwned // ignore: cast_nullable_to_non_nullable
as int, as int,
chaptersTotal: chaptersTotal == freezed chaptersTotal: freezed == chaptersTotal
? _value.chaptersTotal ? _value.chaptersTotal
: chaptersTotal // ignore: cast_nullable_to_non_nullable : chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?, as int?,
thumbnailUrl: thumbnailUrl == freezed thumbnailUrl: null == thumbnailUrl
? _value.thumbnailUrl ? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String, as String,
)); ) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$_MangaTrackingDataCopyWith<$Res> abstract class _$$MangaTrackingDataImplCopyWith<$Res>
implements $MangaTrackingDataCopyWith<$Res> { implements $MangaTrackingDataCopyWith<$Res> {
factory _$$_MangaTrackingDataCopyWith(_$_MangaTrackingData value, factory _$$MangaTrackingDataImplCopyWith(_$MangaTrackingDataImpl value,
$Res Function(_$_MangaTrackingData) then) = $Res Function(_$MangaTrackingDataImpl) then) =
__$$_MangaTrackingDataCopyWithImpl<$Res>; __$$MangaTrackingDataImplCopyWithImpl<$Res>;
@override @override
@useResult
$Res call( $Res call(
{String id, {String id,
@MediumTrackingStateConverter() MediumTrackingState state, @MediumTrackingStateConverter() MediumTrackingState state,
@@ -133,52 +143,52 @@ abstract class _$$_MangaTrackingDataCopyWith<$Res>
} }
/// @nodoc /// @nodoc
class __$$_MangaTrackingDataCopyWithImpl<$Res> class __$$MangaTrackingDataImplCopyWithImpl<$Res>
extends _$MangaTrackingDataCopyWithImpl<$Res> extends _$MangaTrackingDataCopyWithImpl<$Res, _$MangaTrackingDataImpl>
implements _$$_MangaTrackingDataCopyWith<$Res> { implements _$$MangaTrackingDataImplCopyWith<$Res> {
__$$_MangaTrackingDataCopyWithImpl( __$$MangaTrackingDataImplCopyWithImpl(_$MangaTrackingDataImpl _value,
_$_MangaTrackingData _value, $Res Function(_$_MangaTrackingData) _then) $Res Function(_$MangaTrackingDataImpl) _then)
: super(_value, (v) => _then(v as _$_MangaTrackingData)); : super(_value, _then);
@override
_$_MangaTrackingData get _value => super._value as _$_MangaTrackingData;
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? id = freezed, Object? id = null,
Object? state = freezed, Object? state = null,
Object? title = freezed, Object? title = null,
Object? chaptersRead = freezed, Object? chaptersRead = null,
Object? volumesOwned = freezed, Object? volumesOwned = null,
Object? chaptersTotal = freezed, Object? chaptersTotal = freezed,
Object? thumbnailUrl = freezed, Object? thumbnailUrl = null,
}) { }) {
return _then(_$_MangaTrackingData( return _then(_$MangaTrackingDataImpl(
id == freezed null == id
? _value.id ? _value.id
: id // ignore: cast_nullable_to_non_nullable : id // ignore: cast_nullable_to_non_nullable
as String, as String,
state == freezed null == state
? _value.state ? _value.state
: state // ignore: cast_nullable_to_non_nullable : state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
title == freezed null == title
? _value.title ? _value.title
: title // ignore: cast_nullable_to_non_nullable : title // ignore: cast_nullable_to_non_nullable
as String, as String,
chaptersRead == freezed null == chaptersRead
? _value.chaptersRead ? _value.chaptersRead
: chaptersRead // ignore: cast_nullable_to_non_nullable : chaptersRead // ignore: cast_nullable_to_non_nullable
as int, as int,
volumesOwned == freezed null == volumesOwned
? _value.volumesOwned ? _value.volumesOwned
: volumesOwned // ignore: cast_nullable_to_non_nullable : volumesOwned // ignore: cast_nullable_to_non_nullable
as int, as int,
chaptersTotal == freezed freezed == chaptersTotal
? _value.chaptersTotal ? _value.chaptersTotal
: chaptersTotal // ignore: cast_nullable_to_non_nullable : chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?, as int?,
thumbnailUrl == freezed null == thumbnailUrl
? _value.thumbnailUrl ? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable : thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String, as String,
@@ -188,8 +198,8 @@ class __$$_MangaTrackingDataCopyWithImpl<$Res>
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _$_MangaTrackingData implements _MangaTrackingData { class _$MangaTrackingDataImpl implements _MangaTrackingData {
_$_MangaTrackingData( _$MangaTrackingDataImpl(
this.id, this.id,
@MediumTrackingStateConverter() this.state, @MediumTrackingStateConverter() this.state,
this.title, this.title,
@@ -198,8 +208,8 @@ class _$_MangaTrackingData implements _MangaTrackingData {
this.chaptersTotal, this.chaptersTotal,
this.thumbnailUrl); this.thumbnailUrl);
factory _$_MangaTrackingData.fromJson(Map<String, dynamic> json) => factory _$MangaTrackingDataImpl.fromJson(Map<String, dynamic> json) =>
_$$_MangaTrackingDataFromJson(json); _$$MangaTrackingDataImplFromJson(json);
/// The ID of the manga /// The ID of the manga
@override @override
@@ -236,44 +246,40 @@ class _$_MangaTrackingData implements _MangaTrackingData {
} }
@override @override
bool operator ==(dynamic other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_MangaTrackingData && other is _$MangaTrackingDataImpl &&
const DeepCollectionEquality().equals(other.id, id) && (identical(other.id, id) || other.id == id) &&
const DeepCollectionEquality().equals(other.state, state) && (identical(other.state, state) || other.state == state) &&
const DeepCollectionEquality().equals(other.title, title) && (identical(other.title, title) || other.title == title) &&
const DeepCollectionEquality() (identical(other.chaptersRead, chaptersRead) ||
.equals(other.chaptersRead, chaptersRead) && other.chaptersRead == chaptersRead) &&
const DeepCollectionEquality() (identical(other.volumesOwned, volumesOwned) ||
.equals(other.volumesOwned, volumesOwned) && other.volumesOwned == volumesOwned) &&
const DeepCollectionEquality() (identical(other.chaptersTotal, chaptersTotal) ||
.equals(other.chaptersTotal, chaptersTotal) && other.chaptersTotal == chaptersTotal) &&
const DeepCollectionEquality() (identical(other.thumbnailUrl, thumbnailUrl) ||
.equals(other.thumbnailUrl, thumbnailUrl)); other.thumbnailUrl == thumbnailUrl));
} }
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(runtimeType, id, state, title, chaptersRead,
runtimeType, volumesOwned, chaptersTotal, thumbnailUrl);
const DeepCollectionEquality().hash(id),
const DeepCollectionEquality().hash(state),
const DeepCollectionEquality().hash(title),
const DeepCollectionEquality().hash(chaptersRead),
const DeepCollectionEquality().hash(volumesOwned),
const DeepCollectionEquality().hash(chaptersTotal),
const DeepCollectionEquality().hash(thumbnailUrl));
@JsonKey(ignore: true) /// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
_$$_MangaTrackingDataCopyWith<_$_MangaTrackingData> get copyWith => @pragma('vm:prefer-inline')
__$$_MangaTrackingDataCopyWithImpl<_$_MangaTrackingData>( _$$MangaTrackingDataImplCopyWith<_$MangaTrackingDataImpl> get copyWith =>
__$$MangaTrackingDataImplCopyWithImpl<_$MangaTrackingDataImpl>(
this, _$identity); this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$$_MangaTrackingDataToJson( return _$$MangaTrackingDataImplToJson(
this, this,
); );
} }
@@ -287,42 +293,44 @@ abstract class _MangaTrackingData implements MangaTrackingData {
final int chaptersRead, final int chaptersRead,
final int volumesOwned, final int volumesOwned,
final int? chaptersTotal, final int? chaptersTotal,
final String thumbnailUrl) = _$_MangaTrackingData; final String thumbnailUrl) = _$MangaTrackingDataImpl;
factory _MangaTrackingData.fromJson(Map<String, dynamic> json) = factory _MangaTrackingData.fromJson(Map<String, dynamic> json) =
_$_MangaTrackingData.fromJson; _$MangaTrackingDataImpl.fromJson;
@override
/// The ID of the manga /// The ID of the manga
String get id;
@override @override
String get id;
/// The state of the manga /// The state of the manga
@override
@MediumTrackingStateConverter() @MediumTrackingStateConverter()
MediumTrackingState get state; MediumTrackingState get state;
@override
/// The title of the manga /// The title of the manga
@override
String get title; String get title;
@override
/// Chapters read. /// Chapters read.
@override
int get chaptersRead; int get chaptersRead;
@override
/// Chapters read. /// Chapters read.
int get volumesOwned;
@override @override
int get volumesOwned;
/// Episodes watched. /// Episodes watched.
int? get chaptersTotal;
@override @override
int? get chaptersTotal;
/// URL to the thumbnail/cover art for the manga. /// URL to the thumbnail/cover art for the manga.
String get thumbnailUrl;
@override @override
@JsonKey(ignore: true) String get thumbnailUrl;
_$$_MangaTrackingDataCopyWith<_$_MangaTrackingData> get copyWith =>
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$MangaTrackingDataImplCopyWith<_$MangaTrackingDataImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -6,19 +6,21 @@ part of 'manga.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
_$_MangaTrackingData _$$_MangaTrackingDataFromJson(Map<String, dynamic> json) => _$MangaTrackingDataImpl _$$MangaTrackingDataImplFromJson(
_$_MangaTrackingData( Map<String, dynamic> json) =>
_$MangaTrackingDataImpl(
json['id'] as String, json['id'] as String,
const MediumTrackingStateConverter().fromJson(json['state'] as int), const MediumTrackingStateConverter()
.fromJson((json['state'] as num).toInt()),
json['title'] as String, json['title'] as String,
json['chaptersRead'] as int, (json['chaptersRead'] as num).toInt(),
json['volumesOwned'] as int, (json['volumesOwned'] as num).toInt(),
json['chaptersTotal'] as int?, (json['chaptersTotal'] as num?)?.toInt(),
json['thumbnailUrl'] as String, json['thumbnailUrl'] as String,
); );
Map<String, dynamic> _$$_MangaTrackingDataToJson( Map<String, dynamic> _$$MangaTrackingDataImplToJson(
_$_MangaTrackingData instance) => _$MangaTrackingDataImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'state': const MediumTrackingStateConverter().toJson(instance.state), 'state': const MediumTrackingStateConverter().toJson(instance.state),

View File

@@ -5,6 +5,8 @@ class SearchResult {
this.total, this.total,
this.thumbnailUrl, this.thumbnailUrl,
this.description, this.description,
this.isAiring,
this.broadcastDay,
); );
/// The title of the anime. /// The title of the anime.
@@ -22,4 +24,10 @@ class SearchResult {
/// The description of the anime /// The description of the anime
final String description; final String description;
/// Flag whether the anime is airing.
final bool isAiring;
/// The day of the week the anime is airing.
final String? broadcastDay;
} }

View File

@@ -10,62 +10,46 @@ enum TrackingMediumType {
/// The state of the medium we're tracking, i.e. reading/watching, dropped, ... /// The state of the medium we're tracking, i.e. reading/watching, dropped, ...
enum MediumTrackingState { enum MediumTrackingState {
/// Currently watching or reading /// Currently watching or reading
ongoing, ongoing(0),
/// Done /// Done
completed, completed(1),
/// Plan to watch or read /// Plan to watch or read
planned, planned(2),
/// Dropped /// Dropped
dropped, dropped(3),
/// Paused /// Paused
paused, paused(4),
/// Meta state /// Meta state
all, all(-1);
}
/// Interface for the Anime and Manga data classes const MediumTrackingState(this.id);
abstract class TrackingMedium {
/// The ID of the medium
final String id = '';
/// The title of the medium factory MediumTrackingState.fromInt(int id) {
final String title = ''; switch (id) {
case 0:
/// The URL of the cover image. return MediumTrackingState.ongoing;
final String thumbnailUrl = ''; case 1:
return MediumTrackingState.completed;
/// The tracking state case 2:
final MediumTrackingState state = MediumTrackingState.planned; return MediumTrackingState.planned;
} case 3:
return MediumTrackingState.dropped;
extension MediumStateExtension on MediumTrackingState { case 4:
int toInteger() { return MediumTrackingState.paused;
assert(
this != MediumTrackingState.all,
'MediumTrackingState.all must not be serialized',
);
switch (this) {
case MediumTrackingState.ongoing:
return 0;
case MediumTrackingState.completed:
return 1;
case MediumTrackingState.planned:
return 2;
case MediumTrackingState.dropped:
return 3;
case MediumTrackingState.paused:
return 4;
case MediumTrackingState.all:
return -1;
}
} }
String toNameString(TrackingMediumType type) { return MediumTrackingState.planned;
}
/// The id of the value.
final int id;
String getName(TrackingMediumType type) {
assert( assert(
this != MediumTrackingState.all, this != MediumTrackingState.all,
'MediumTrackingState.all must not be stringified', 'MediumTrackingState.all must not be stringified',
@@ -98,28 +82,28 @@ extension MediumStateExtension on MediumTrackingState {
} }
} }
/// Interface for the Anime and Manga data classes
mixin TrackingMedium {
/// The ID of the medium
final String id = '';
/// The title of the medium
final String title = '';
/// The URL of the cover image.
final String thumbnailUrl = '';
/// The tracking state
final MediumTrackingState state = MediumTrackingState.planned;
}
class MediumTrackingStateConverter class MediumTrackingStateConverter
implements JsonConverter<MediumTrackingState, int> { implements JsonConverter<MediumTrackingState, int> {
const MediumTrackingStateConverter(); const MediumTrackingStateConverter();
@override @override
MediumTrackingState fromJson(int json) { MediumTrackingState fromJson(int json) => MediumTrackingState.fromInt(json);
switch (json) {
case 0:
return MediumTrackingState.ongoing;
case 1:
return MediumTrackingState.completed;
case 2:
return MediumTrackingState.planned;
case 3:
return MediumTrackingState.dropped;
case 4:
return MediumTrackingState.paused;
}
return MediumTrackingState.planned;
}
@override @override
int toJson(MediumTrackingState state) => state.toInteger(); int toJson(MediumTrackingState state) => state.id;
} }

View File

@@ -1,11 +1,24 @@
import 'package:anitrack/src/data/anime.dart'; import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.dart'; import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/service/migrations/0000_airing.dart';
import 'package:anitrack/src/service/migrations/0000_score.dart'; import 'package:anitrack/src/service/migrations/0000_score.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
const animeTable = 'Anime'; const animeTable = 'Anime';
const mangaTable = 'Manga'; const mangaTable = 'Manga';
extension BoolToInt on bool {
int toInt() {
return this ? 1 : 0;
}
}
extension IntToBool on int {
bool toBool() {
return this == 1;
}
}
Future<void> _createDatabase(Database db, int version) async { Future<void> _createDatabase(Database db, int version) async {
await db.execute( await db.execute(
''' '''
@@ -16,7 +29,9 @@ Future<void> _createDatabase(Database db, int version) async {
episodesWatched INTEGER NOT NULL, episodesWatched INTEGER NOT NULL,
thumbnailUrl TEXT NOT NULL, thumbnailUrl TEXT NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
score INTEGER score INTEGER,
airing INTEGER NOT NULL,
broadcastDay TEXT
)''', )''',
); );
await db.execute( await db.execute(
@@ -40,7 +55,7 @@ class DatabaseService {
Future<void> initialize() async { Future<void> initialize() async {
_db = await openDatabase( _db = await openDatabase(
'anitrack.db', 'anitrack.db',
version: 2, version: 3,
onConfigure: (db) async { onConfigure: (db) async {
// In order to do schema changes during database upgrades, we disable foreign // In order to do schema changes during database upgrades, we disable foreign
// keys in the onConfigure phase, but re-enable them here. // keys in the onConfigure phase, but re-enable them here.
@@ -56,6 +71,9 @@ class DatabaseService {
if (oldVersion < 2) { if (oldVersion < 2) {
await migrateFromV1ToV2(db); await migrateFromV1ToV2(db);
} }
if (oldVersion < 3) {
await migrateFromV2ToV3(db);
}
}, },
); );
} }

View File

@@ -0,0 +1,11 @@
import 'package:anitrack/src/service/database.dart';
import 'package:sqflite/sqflite.dart';
Future<void> migrateFromV2ToV3(Database db) async {
await db.execute(
'ALTER TABLE $animeTable ADD COLUMN airing INTEGER NOT NULL DEFAULT ${true.toInt()};',
);
await db.execute(
'ALTER TABLE $animeTable ADD COLUMN broadcastDay TEXT;',
);
}

View File

@@ -3,6 +3,7 @@ import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/data/type.dart'; import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart'; import 'package:anitrack/src/service/database.dart';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
@@ -35,6 +36,8 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
final List<MangaTrackingData> _mangas = final List<MangaTrackingData> _mangas =
List<MangaTrackingData>.empty(growable: true); List<MangaTrackingData>.empty(growable: true);
List<AnimeTrackingData> get unfilteredAnime => _animes;
List<AnimeTrackingData> _getFilteredAnime({ List<AnimeTrackingData> _getFilteredAnime({
MediumTrackingState? trackingState, MediumTrackingState? trackingState,
}) { }) {
@@ -63,7 +66,16 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
await GetIt.I.get<DatabaseService>().addAnime(event.data); await GetIt.I.get<DatabaseService>().addAnime(event.data);
// Add it to the cache // Add it to the cache
if (event.checkIfExists) {
final shouldAdd =
_animes.firstWhereOrNull((element) => element.id == event.data.id) ==
null;
if (shouldAdd) {
_animes.add(event.data); _animes.add(event.data);
}
} else {
_animes.add(event.data);
}
emit( emit(
state.copyWith( state.copyWith(
@@ -80,7 +92,17 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
await GetIt.I.get<DatabaseService>().addManga(event.data); await GetIt.I.get<DatabaseService>().addManga(event.data);
// Add it to the cache // Add it to the cache
// Add it to the cache
if (event.checkIfExists) {
final shouldAdd =
_mangas.firstWhereOrNull((element) => element.id == event.data.id) ==
null;
if (shouldAdd) {
_mangas.add(event.data); _mangas.add(event.data);
}
} else {
_mangas.add(event.data);
}
emit( emit(
state.copyWith( state.copyWith(
@@ -270,6 +292,8 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
animes: _getFilteredAnime(), animes: _getFilteredAnime(),
), ),
); );
await GetIt.I.get<DatabaseService>().updateAnime(event.anime);
} }
Future<void> _onMangaUpdated( Future<void> _onMangaUpdated(

View File

@@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // 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 // 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 'anime_list_bloc.dart'; part of 'anime_list_bloc.dart';
@@ -12,7 +12,7 @@ part of 'anime_list_bloc.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError( final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc /// @nodoc
mixin _$AnimeListState { mixin _$AnimeListState {
@@ -25,7 +25,9 @@ mixin _$AnimeListState {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
TrackingMediumType get trackingType => throw _privateConstructorUsedError; TrackingMediumType get trackingType => throw _privateConstructorUsedError;
@JsonKey(ignore: true) /// Create a copy of AnimeListState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AnimeListStateCopyWith<AnimeListState> get copyWith => $AnimeListStateCopyWith<AnimeListState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -34,7 +36,8 @@ mixin _$AnimeListState {
abstract class $AnimeListStateCopyWith<$Res> { abstract class $AnimeListStateCopyWith<$Res> {
factory $AnimeListStateCopyWith( factory $AnimeListStateCopyWith(
AnimeListState value, $Res Function(AnimeListState) then) = AnimeListState value, $Res Function(AnimeListState) then) =
_$AnimeListStateCopyWithImpl<$Res>; _$AnimeListStateCopyWithImpl<$Res, AnimeListState>;
@useResult
$Res call( $Res call(
{bool buttonVisibility, {bool buttonVisibility,
List<AnimeTrackingData> animes, List<AnimeTrackingData> animes,
@@ -45,59 +48,64 @@ abstract class $AnimeListStateCopyWith<$Res> {
} }
/// @nodoc /// @nodoc
class _$AnimeListStateCopyWithImpl<$Res> class _$AnimeListStateCopyWithImpl<$Res, $Val extends AnimeListState>
implements $AnimeListStateCopyWith<$Res> { implements $AnimeListStateCopyWith<$Res> {
_$AnimeListStateCopyWithImpl(this._value, this._then); _$AnimeListStateCopyWithImpl(this._value, this._then);
final AnimeListState _value;
// ignore: unused_field // ignore: unused_field
final $Res Function(AnimeListState) _then; final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AnimeListState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? buttonVisibility = freezed, Object? buttonVisibility = null,
Object? animes = freezed, Object? animes = null,
Object? mangas = freezed, Object? mangas = null,
Object? animeFilterState = freezed, Object? animeFilterState = null,
Object? mangaFilterState = freezed, Object? mangaFilterState = null,
Object? trackingType = freezed, Object? trackingType = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
buttonVisibility: buttonVisibility == freezed buttonVisibility: null == buttonVisibility
? _value.buttonVisibility ? _value.buttonVisibility
: buttonVisibility // ignore: cast_nullable_to_non_nullable : buttonVisibility // ignore: cast_nullable_to_non_nullable
as bool, as bool,
animes: animes == freezed animes: null == animes
? _value.animes ? _value.animes
: animes // ignore: cast_nullable_to_non_nullable : animes // ignore: cast_nullable_to_non_nullable
as List<AnimeTrackingData>, as List<AnimeTrackingData>,
mangas: mangas == freezed mangas: null == mangas
? _value.mangas ? _value.mangas
: mangas // ignore: cast_nullable_to_non_nullable : mangas // ignore: cast_nullable_to_non_nullable
as List<MangaTrackingData>, as List<MangaTrackingData>,
animeFilterState: animeFilterState == freezed animeFilterState: null == animeFilterState
? _value.animeFilterState ? _value.animeFilterState
: animeFilterState // ignore: cast_nullable_to_non_nullable : animeFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
mangaFilterState: mangaFilterState == freezed mangaFilterState: null == mangaFilterState
? _value.mangaFilterState ? _value.mangaFilterState
: mangaFilterState // ignore: cast_nullable_to_non_nullable : mangaFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
trackingType: trackingType == freezed trackingType: null == trackingType
? _value.trackingType ? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable : trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType, as TrackingMediumType,
)); ) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$_AnimeListStateCopyWith<$Res> abstract class _$$AnimeListStateImplCopyWith<$Res>
implements $AnimeListStateCopyWith<$Res> { implements $AnimeListStateCopyWith<$Res> {
factory _$$_AnimeListStateCopyWith( factory _$$AnimeListStateImplCopyWith(_$AnimeListStateImpl value,
_$_AnimeListState value, $Res Function(_$_AnimeListState) then) = $Res Function(_$AnimeListStateImpl) then) =
__$$_AnimeListStateCopyWithImpl<$Res>; __$$AnimeListStateImplCopyWithImpl<$Res>;
@override @override
@useResult
$Res call( $Res call(
{bool buttonVisibility, {bool buttonVisibility,
List<AnimeTrackingData> animes, List<AnimeTrackingData> animes,
@@ -108,47 +116,47 @@ abstract class _$$_AnimeListStateCopyWith<$Res>
} }
/// @nodoc /// @nodoc
class __$$_AnimeListStateCopyWithImpl<$Res> class __$$AnimeListStateImplCopyWithImpl<$Res>
extends _$AnimeListStateCopyWithImpl<$Res> extends _$AnimeListStateCopyWithImpl<$Res, _$AnimeListStateImpl>
implements _$$_AnimeListStateCopyWith<$Res> { implements _$$AnimeListStateImplCopyWith<$Res> {
__$$_AnimeListStateCopyWithImpl( __$$AnimeListStateImplCopyWithImpl(
_$_AnimeListState _value, $Res Function(_$_AnimeListState) _then) _$AnimeListStateImpl _value, $Res Function(_$AnimeListStateImpl) _then)
: super(_value, (v) => _then(v as _$_AnimeListState)); : super(_value, _then);
@override
_$_AnimeListState get _value => super._value as _$_AnimeListState;
/// Create a copy of AnimeListState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? buttonVisibility = freezed, Object? buttonVisibility = null,
Object? animes = freezed, Object? animes = null,
Object? mangas = freezed, Object? mangas = null,
Object? animeFilterState = freezed, Object? animeFilterState = null,
Object? mangaFilterState = freezed, Object? mangaFilterState = null,
Object? trackingType = freezed, Object? trackingType = null,
}) { }) {
return _then(_$_AnimeListState( return _then(_$AnimeListStateImpl(
buttonVisibility: buttonVisibility == freezed buttonVisibility: null == buttonVisibility
? _value.buttonVisibility ? _value.buttonVisibility
: buttonVisibility // ignore: cast_nullable_to_non_nullable : buttonVisibility // ignore: cast_nullable_to_non_nullable
as bool, as bool,
animes: animes == freezed animes: null == animes
? _value._animes ? _value._animes
: animes // ignore: cast_nullable_to_non_nullable : animes // ignore: cast_nullable_to_non_nullable
as List<AnimeTrackingData>, as List<AnimeTrackingData>,
mangas: mangas == freezed mangas: null == mangas
? _value._mangas ? _value._mangas
: mangas // ignore: cast_nullable_to_non_nullable : mangas // ignore: cast_nullable_to_non_nullable
as List<MangaTrackingData>, as List<MangaTrackingData>,
animeFilterState: animeFilterState == freezed animeFilterState: null == animeFilterState
? _value.animeFilterState ? _value.animeFilterState
: animeFilterState // ignore: cast_nullable_to_non_nullable : animeFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
mangaFilterState: mangaFilterState == freezed mangaFilterState: null == mangaFilterState
? _value.mangaFilterState ? _value.mangaFilterState
: mangaFilterState // ignore: cast_nullable_to_non_nullable : mangaFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState, as MediumTrackingState,
trackingType: trackingType == freezed trackingType: null == trackingType
? _value.trackingType ? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable : trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType, as TrackingMediumType,
@@ -158,8 +166,8 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$_AnimeListState implements _AnimeListState { class _$AnimeListStateImpl implements _AnimeListState {
_$_AnimeListState( _$AnimeListStateImpl(
{this.buttonVisibility = true, {this.buttonVisibility = true,
final List<AnimeTrackingData> animes = const [], final List<AnimeTrackingData> animes = const [],
final List<MangaTrackingData> mangas = const [], final List<MangaTrackingData> mangas = const [],
@@ -176,6 +184,7 @@ class _$_AnimeListState implements _AnimeListState {
@override @override
@JsonKey() @JsonKey()
List<AnimeTrackingData> get animes { List<AnimeTrackingData> get animes {
if (_animes is EqualUnmodifiableListView) return _animes;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_animes); return EqualUnmodifiableListView(_animes);
} }
@@ -184,6 +193,7 @@ class _$_AnimeListState implements _AnimeListState {
@override @override
@JsonKey() @JsonKey()
List<MangaTrackingData> get mangas { List<MangaTrackingData> get mangas {
if (_mangas is EqualUnmodifiableListView) return _mangas;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_mangas); return EqualUnmodifiableListView(_mangas);
} }
@@ -204,36 +214,40 @@ class _$_AnimeListState implements _AnimeListState {
} }
@override @override
bool operator ==(dynamic other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_AnimeListState && other is _$AnimeListStateImpl &&
const DeepCollectionEquality() (identical(other.buttonVisibility, buttonVisibility) ||
.equals(other.buttonVisibility, buttonVisibility) && other.buttonVisibility == buttonVisibility) &&
const DeepCollectionEquality().equals(other._animes, _animes) && const DeepCollectionEquality().equals(other._animes, _animes) &&
const DeepCollectionEquality().equals(other._mangas, _mangas) && const DeepCollectionEquality().equals(other._mangas, _mangas) &&
const DeepCollectionEquality() (identical(other.animeFilterState, animeFilterState) ||
.equals(other.animeFilterState, animeFilterState) && other.animeFilterState == animeFilterState) &&
const DeepCollectionEquality() (identical(other.mangaFilterState, mangaFilterState) ||
.equals(other.mangaFilterState, mangaFilterState) && other.mangaFilterState == mangaFilterState) &&
const DeepCollectionEquality() (identical(other.trackingType, trackingType) ||
.equals(other.trackingType, trackingType)); other.trackingType == trackingType));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType,
const DeepCollectionEquality().hash(buttonVisibility), buttonVisibility,
const DeepCollectionEquality().hash(_animes), const DeepCollectionEquality().hash(_animes),
const DeepCollectionEquality().hash(_mangas), const DeepCollectionEquality().hash(_mangas),
const DeepCollectionEquality().hash(animeFilterState), animeFilterState,
const DeepCollectionEquality().hash(mangaFilterState), mangaFilterState,
const DeepCollectionEquality().hash(trackingType)); trackingType);
@JsonKey(ignore: true) /// Create a copy of AnimeListState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith => @pragma('vm:prefer-inline')
__$$_AnimeListStateCopyWithImpl<_$_AnimeListState>(this, _$identity); _$$AnimeListStateImplCopyWith<_$AnimeListStateImpl> get copyWith =>
__$$AnimeListStateImplCopyWithImpl<_$AnimeListStateImpl>(
this, _$identity);
} }
abstract class _AnimeListState implements AnimeListState { abstract class _AnimeListState implements AnimeListState {
@@ -243,7 +257,7 @@ abstract class _AnimeListState implements AnimeListState {
final List<MangaTrackingData> mangas, final List<MangaTrackingData> mangas,
final MediumTrackingState animeFilterState, final MediumTrackingState animeFilterState,
final MediumTrackingState mangaFilterState, final MediumTrackingState mangaFilterState,
final TrackingMediumType trackingType}) = _$_AnimeListState; final TrackingMediumType trackingType}) = _$AnimeListStateImpl;
@override @override
bool get buttonVisibility; bool get buttonVisibility;
@@ -257,8 +271,11 @@ abstract class _AnimeListState implements AnimeListState {
MediumTrackingState get mangaFilterState; MediumTrackingState get mangaFilterState;
@override @override
TrackingMediumType get trackingType; TrackingMediumType get trackingType;
/// Create a copy of AnimeListState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith => _$$AnimeListStateImplCopyWith<_$AnimeListStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -17,10 +17,14 @@ class AnimeEpisodeDecrementedEvent extends AnimeListEvent {
} }
class AnimeAddedEvent extends AnimeListEvent { class AnimeAddedEvent extends AnimeListEvent {
AnimeAddedEvent(this.data); AnimeAddedEvent(this.data, {this.checkIfExists = false});
/// The anime to add. /// The anime to add.
final AnimeTrackingData data; final AnimeTrackingData data;
/// If true, checks if the anime with the id is already in the list.
/// If it is, does nothing.
final bool checkIfExists;
} }
/// Triggered when animes are to be loaded from the database /// Triggered when animes are to be loaded from the database
@@ -43,9 +47,12 @@ class AnimeTrackingTypeChanged extends AnimeListEvent {
} }
class AnimeUpdatedEvent extends AnimeListEvent { class AnimeUpdatedEvent extends AnimeListEvent {
AnimeUpdatedEvent(this.anime); AnimeUpdatedEvent(this.anime, {this.commit = false});
final AnimeTrackingData anime; final AnimeTrackingData anime;
/// Commit the new anime data to the database.
final bool commit;
} }
class AnimeRemovedEvent extends AnimeListEvent { class AnimeRemovedEvent extends AnimeListEvent {
@@ -56,10 +63,14 @@ class AnimeRemovedEvent extends AnimeListEvent {
} }
class MangaAddedEvent extends AnimeListEvent { class MangaAddedEvent extends AnimeListEvent {
MangaAddedEvent(this.data); MangaAddedEvent(this.data, {this.checkIfExists = false});
/// The manga to add. /// The manga to add.
final MangaTrackingData data; final MangaTrackingData data;
/// If true, checks if the manga with the id is already in the list.
/// If it is, does nothing.
final bool checkIfExists;
} }
/// Triggered when the manga filter is changed /// Triggered when the manga filter is changed

View File

@@ -82,6 +82,8 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
anime.episodes, anime.episodes,
anime.imageUrl, anime.imageUrl,
anime.synopsis ?? '', anime.synopsis ?? '',
anime.airing,
anime.broadcast?.split(' ').first,
), ),
) )
.toList(), .toList(),
@@ -104,6 +106,9 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
manga.chapters, manga.chapters,
manga.imageUrl, manga.imageUrl,
manga.synopsis ?? '', manga.synopsis ?? '',
// TODO(Unknown): Implement for Manga
false,
null,
), ),
) )
.toList(), .toList(),
@@ -126,6 +131,8 @@ class AnimeSearchBloc extends Bloc<AnimeSearchEvent, AnimeSearchState> {
0, 0,
event.result.total, event.result.total,
event.result.thumbnailUrl, event.result.thumbnailUrl,
event.result.isAiring,
event.result.broadcastDay,
), ),
) )
: list.MangaAddedEvent( : list.MangaAddedEvent(

View File

@@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // 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 // 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 'anime_search_bloc.dart'; part of 'anime_search_bloc.dart';
@@ -12,7 +12,7 @@ part of 'anime_search_bloc.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError( final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc /// @nodoc
mixin _$AnimeSearchState { mixin _$AnimeSearchState {
@@ -21,7 +21,9 @@ mixin _$AnimeSearchState {
bool get working => throw _privateConstructorUsedError; bool get working => throw _privateConstructorUsedError;
List<SearchResult> get searchResults => throw _privateConstructorUsedError; List<SearchResult> get searchResults => throw _privateConstructorUsedError;
@JsonKey(ignore: true) /// Create a copy of AnimeSearchState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AnimeSearchStateCopyWith<AnimeSearchState> get copyWith => $AnimeSearchStateCopyWith<AnimeSearchState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -30,7 +32,8 @@ mixin _$AnimeSearchState {
abstract class $AnimeSearchStateCopyWith<$Res> { abstract class $AnimeSearchStateCopyWith<$Res> {
factory $AnimeSearchStateCopyWith( factory $AnimeSearchStateCopyWith(
AnimeSearchState value, $Res Function(AnimeSearchState) then) = AnimeSearchState value, $Res Function(AnimeSearchState) then) =
_$AnimeSearchStateCopyWithImpl<$Res>; _$AnimeSearchStateCopyWithImpl<$Res, AnimeSearchState>;
@useResult
$Res call( $Res call(
{TrackingMediumType trackingType, {TrackingMediumType trackingType,
String searchQuery, String searchQuery,
@@ -39,49 +42,54 @@ abstract class $AnimeSearchStateCopyWith<$Res> {
} }
/// @nodoc /// @nodoc
class _$AnimeSearchStateCopyWithImpl<$Res> class _$AnimeSearchStateCopyWithImpl<$Res, $Val extends AnimeSearchState>
implements $AnimeSearchStateCopyWith<$Res> { implements $AnimeSearchStateCopyWith<$Res> {
_$AnimeSearchStateCopyWithImpl(this._value, this._then); _$AnimeSearchStateCopyWithImpl(this._value, this._then);
final AnimeSearchState _value;
// ignore: unused_field // ignore: unused_field
final $Res Function(AnimeSearchState) _then; final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AnimeSearchState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? trackingType = freezed, Object? trackingType = null,
Object? searchQuery = freezed, Object? searchQuery = null,
Object? working = freezed, Object? working = null,
Object? searchResults = freezed, Object? searchResults = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
trackingType: trackingType == freezed trackingType: null == trackingType
? _value.trackingType ? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable : trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType, as TrackingMediumType,
searchQuery: searchQuery == freezed searchQuery: null == searchQuery
? _value.searchQuery ? _value.searchQuery
: searchQuery // ignore: cast_nullable_to_non_nullable : searchQuery // ignore: cast_nullable_to_non_nullable
as String, as String,
working: working == freezed working: null == working
? _value.working ? _value.working
: working // ignore: cast_nullable_to_non_nullable : working // ignore: cast_nullable_to_non_nullable
as bool, as bool,
searchResults: searchResults == freezed searchResults: null == searchResults
? _value.searchResults ? _value.searchResults
: searchResults // ignore: cast_nullable_to_non_nullable : searchResults // ignore: cast_nullable_to_non_nullable
as List<SearchResult>, as List<SearchResult>,
)); ) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$_AnimeSearchStateCopyWith<$Res> abstract class _$$AnimeSearchStateImplCopyWith<$Res>
implements $AnimeSearchStateCopyWith<$Res> { implements $AnimeSearchStateCopyWith<$Res> {
factory _$$_AnimeSearchStateCopyWith( factory _$$AnimeSearchStateImplCopyWith(_$AnimeSearchStateImpl value,
_$_AnimeSearchState value, $Res Function(_$_AnimeSearchState) then) = $Res Function(_$AnimeSearchStateImpl) then) =
__$$_AnimeSearchStateCopyWithImpl<$Res>; __$$AnimeSearchStateImplCopyWithImpl<$Res>;
@override @override
@useResult
$Res call( $Res call(
{TrackingMediumType trackingType, {TrackingMediumType trackingType,
String searchQuery, String searchQuery,
@@ -90,37 +98,37 @@ abstract class _$$_AnimeSearchStateCopyWith<$Res>
} }
/// @nodoc /// @nodoc
class __$$_AnimeSearchStateCopyWithImpl<$Res> class __$$AnimeSearchStateImplCopyWithImpl<$Res>
extends _$AnimeSearchStateCopyWithImpl<$Res> extends _$AnimeSearchStateCopyWithImpl<$Res, _$AnimeSearchStateImpl>
implements _$$_AnimeSearchStateCopyWith<$Res> { implements _$$AnimeSearchStateImplCopyWith<$Res> {
__$$_AnimeSearchStateCopyWithImpl( __$$AnimeSearchStateImplCopyWithImpl(_$AnimeSearchStateImpl _value,
_$_AnimeSearchState _value, $Res Function(_$_AnimeSearchState) _then) $Res Function(_$AnimeSearchStateImpl) _then)
: super(_value, (v) => _then(v as _$_AnimeSearchState)); : super(_value, _then);
@override
_$_AnimeSearchState get _value => super._value as _$_AnimeSearchState;
/// Create a copy of AnimeSearchState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? trackingType = freezed, Object? trackingType = null,
Object? searchQuery = freezed, Object? searchQuery = null,
Object? working = freezed, Object? working = null,
Object? searchResults = freezed, Object? searchResults = null,
}) { }) {
return _then(_$_AnimeSearchState( return _then(_$AnimeSearchStateImpl(
trackingType: trackingType == freezed trackingType: null == trackingType
? _value.trackingType ? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable : trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType, as TrackingMediumType,
searchQuery: searchQuery == freezed searchQuery: null == searchQuery
? _value.searchQuery ? _value.searchQuery
: searchQuery // ignore: cast_nullable_to_non_nullable : searchQuery // ignore: cast_nullable_to_non_nullable
as String, as String,
working: working == freezed working: null == working
? _value.working ? _value.working
: working // ignore: cast_nullable_to_non_nullable : working // ignore: cast_nullable_to_non_nullable
as bool, as bool,
searchResults: searchResults == freezed searchResults: null == searchResults
? _value._searchResults ? _value._searchResults
: searchResults // ignore: cast_nullable_to_non_nullable : searchResults // ignore: cast_nullable_to_non_nullable
as List<SearchResult>, as List<SearchResult>,
@@ -130,8 +138,8 @@ class __$$_AnimeSearchStateCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$_AnimeSearchState implements _AnimeSearchState { class _$AnimeSearchStateImpl implements _AnimeSearchState {
_$_AnimeSearchState( _$AnimeSearchStateImpl(
{this.trackingType = TrackingMediumType.anime, {this.trackingType = TrackingMediumType.anime,
this.searchQuery = '', this.searchQuery = '',
this.working = false, this.working = false,
@@ -151,6 +159,7 @@ class _$_AnimeSearchState implements _AnimeSearchState {
@override @override
@JsonKey() @JsonKey()
List<SearchResult> get searchResults { List<SearchResult> get searchResults {
if (_searchResults is EqualUnmodifiableListView) return _searchResults;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_searchResults); return EqualUnmodifiableListView(_searchResults);
} }
@@ -161,31 +170,31 @@ class _$_AnimeSearchState implements _AnimeSearchState {
} }
@override @override
bool operator ==(dynamic other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_AnimeSearchState && other is _$AnimeSearchStateImpl &&
const DeepCollectionEquality() (identical(other.trackingType, trackingType) ||
.equals(other.trackingType, trackingType) && other.trackingType == trackingType) &&
const DeepCollectionEquality() (identical(other.searchQuery, searchQuery) ||
.equals(other.searchQuery, searchQuery) && other.searchQuery == searchQuery) &&
const DeepCollectionEquality().equals(other.working, working) && (identical(other.working, working) || other.working == working) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._searchResults, _searchResults)); .equals(other._searchResults, _searchResults));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(runtimeType, trackingType, searchQuery,
runtimeType, working, const DeepCollectionEquality().hash(_searchResults));
const DeepCollectionEquality().hash(trackingType),
const DeepCollectionEquality().hash(searchQuery),
const DeepCollectionEquality().hash(working),
const DeepCollectionEquality().hash(_searchResults));
@JsonKey(ignore: true) /// Create a copy of AnimeSearchState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
_$$_AnimeSearchStateCopyWith<_$_AnimeSearchState> get copyWith => @pragma('vm:prefer-inline')
__$$_AnimeSearchStateCopyWithImpl<_$_AnimeSearchState>(this, _$identity); _$$AnimeSearchStateImplCopyWith<_$AnimeSearchStateImpl> get copyWith =>
__$$AnimeSearchStateImplCopyWithImpl<_$AnimeSearchStateImpl>(
this, _$identity);
} }
abstract class _AnimeSearchState implements AnimeSearchState { abstract class _AnimeSearchState implements AnimeSearchState {
@@ -193,7 +202,7 @@ abstract class _AnimeSearchState implements AnimeSearchState {
{final TrackingMediumType trackingType, {final TrackingMediumType trackingType,
final String searchQuery, final String searchQuery,
final bool working, final bool working,
final List<SearchResult> searchResults}) = _$_AnimeSearchState; final List<SearchResult> searchResults}) = _$AnimeSearchStateImpl;
@override @override
TrackingMediumType get trackingType; TrackingMediumType get trackingType;
@@ -203,8 +212,11 @@ abstract class _AnimeSearchState implements AnimeSearchState {
bool get working; bool get working;
@override @override
List<SearchResult> get searchResults; List<SearchResult> get searchResults;
/// Create a copy of AnimeSearchState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$_AnimeSearchStateCopyWith<_$_AnimeSearchState> get copyWith => _$$AnimeSearchStateImplCopyWith<_$AnimeSearchStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -0,0 +1,78 @@
import 'dart:async';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart';
import 'package:jikan_api/jikan_api.dart';
part 'calendar_state.dart';
part 'calendar_bloc.freezed.dart';
part 'calendar_event.dart';
class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
CalendarBloc() : super(CalendarState(false, 0, 0)) {
on<RefreshPerformedEvent>(_onRefreshPerformed);
}
Future<void> _onRefreshPerformed(
RefreshPerformedEvent event,
Emitter<CalendarState> emit,
) async {
emit(
state.copyWith(
refreshing: true,
refreshingCount: 0,
refreshingTotal: 0,
),
);
final al = GetIt.I.get<AnimeListBloc>();
final animes = al.unfilteredAnime.where((anime) => anime.airing);
emit(
state.copyWith(
refreshing: true,
refreshingCount: 0,
refreshingTotal: animes.length,
),
);
for (final anime in animes) {
emit(state.copyWith(refreshingCount: state.refreshingCount + 1));
String? broadcastDay;
bool airing;
try {
final apiData = await Jikan().getAnime(int.parse(anime.id));
airing = apiData.airing;
broadcastDay = apiData.broadcast?.split(' ').first;
} catch (ex) {
print('API request for anime ${anime.id} failed: $ex');
airing = false;
}
print('Anime "${anime.title}": airing=$airing');
if (!airing) {
al.add(
AnimeUpdatedEvent(
anime.copyWith(airing: false, broadcastDay: null),
commit: true,
),
);
} else if (anime.broadcastDay != broadcastDay) {
print('Updating Anime "${anime.title}": broadcastDay=$broadcastDay');
al.add(
AnimeUpdatedEvent(
anime.copyWith(airing: true, broadcastDay: broadcastDay),
commit: true,
),
);
}
// Prevent hammering Jikan
await Future<void>.delayed(const Duration(milliseconds: 500));
}
emit(state.copyWith(refreshing: false));
}
}

View File

@@ -0,0 +1,181 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'calendar_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$CalendarState {
bool get refreshing => throw _privateConstructorUsedError;
int get refreshingCount => throw _privateConstructorUsedError;
int get refreshingTotal => throw _privateConstructorUsedError;
/// Create a copy of CalendarState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$CalendarStateCopyWith<CalendarState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CalendarStateCopyWith<$Res> {
factory $CalendarStateCopyWith(
CalendarState value, $Res Function(CalendarState) then) =
_$CalendarStateCopyWithImpl<$Res, CalendarState>;
@useResult
$Res call({bool refreshing, int refreshingCount, int refreshingTotal});
}
/// @nodoc
class _$CalendarStateCopyWithImpl<$Res, $Val extends CalendarState>
implements $CalendarStateCopyWith<$Res> {
_$CalendarStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of CalendarState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? refreshing = null,
Object? refreshingCount = null,
Object? refreshingTotal = null,
}) {
return _then(_value.copyWith(
refreshing: null == refreshing
? _value.refreshing
: refreshing // ignore: cast_nullable_to_non_nullable
as bool,
refreshingCount: null == refreshingCount
? _value.refreshingCount
: refreshingCount // ignore: cast_nullable_to_non_nullable
as int,
refreshingTotal: null == refreshingTotal
? _value.refreshingTotal
: refreshingTotal // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$CalendarStateImplCopyWith<$Res>
implements $CalendarStateCopyWith<$Res> {
factory _$$CalendarStateImplCopyWith(
_$CalendarStateImpl value, $Res Function(_$CalendarStateImpl) then) =
__$$CalendarStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool refreshing, int refreshingCount, int refreshingTotal});
}
/// @nodoc
class __$$CalendarStateImplCopyWithImpl<$Res>
extends _$CalendarStateCopyWithImpl<$Res, _$CalendarStateImpl>
implements _$$CalendarStateImplCopyWith<$Res> {
__$$CalendarStateImplCopyWithImpl(
_$CalendarStateImpl _value, $Res Function(_$CalendarStateImpl) _then)
: super(_value, _then);
/// Create a copy of CalendarState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? refreshing = null,
Object? refreshingCount = null,
Object? refreshingTotal = null,
}) {
return _then(_$CalendarStateImpl(
null == refreshing
? _value.refreshing
: refreshing // ignore: cast_nullable_to_non_nullable
as bool,
null == refreshingCount
? _value.refreshingCount
: refreshingCount // ignore: cast_nullable_to_non_nullable
as int,
null == refreshingTotal
? _value.refreshingTotal
: refreshingTotal // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$CalendarStateImpl implements _CalendarState {
_$CalendarStateImpl(
this.refreshing, this.refreshingCount, this.refreshingTotal);
@override
final bool refreshing;
@override
final int refreshingCount;
@override
final int refreshingTotal;
@override
String toString() {
return 'CalendarState(refreshing: $refreshing, refreshingCount: $refreshingCount, refreshingTotal: $refreshingTotal)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CalendarStateImpl &&
(identical(other.refreshing, refreshing) ||
other.refreshing == refreshing) &&
(identical(other.refreshingCount, refreshingCount) ||
other.refreshingCount == refreshingCount) &&
(identical(other.refreshingTotal, refreshingTotal) ||
other.refreshingTotal == refreshingTotal));
}
@override
int get hashCode =>
Object.hash(runtimeType, refreshing, refreshingCount, refreshingTotal);
/// Create a copy of CalendarState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CalendarStateImplCopyWith<_$CalendarStateImpl> get copyWith =>
__$$CalendarStateImplCopyWithImpl<_$CalendarStateImpl>(this, _$identity);
}
abstract class _CalendarState implements CalendarState {
factory _CalendarState(final bool refreshing, final int refreshingCount,
final int refreshingTotal) = _$CalendarStateImpl;
@override
bool get refreshing;
@override
int get refreshingCount;
@override
int get refreshingTotal;
/// Create a copy of CalendarState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CalendarStateImplCopyWith<_$CalendarStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,6 @@
part of 'calendar_bloc.dart';
abstract class CalendarEvent {}
/// Triggered by the UI when the user wants to refresh the airing anime list.
class RefreshPerformedEvent extends CalendarEvent {}

View File

@@ -0,0 +1,10 @@
part of 'calendar_bloc.dart';
@freezed
class CalendarState with _$CalendarState {
factory CalendarState(
bool refreshing,
int refreshingCount,
int refreshingTotal,
) = _CalendarState;
}

View File

@@ -28,6 +28,7 @@ class DetailsBloc extends Bloc<DetailsEvent, DetailsState> {
emit( emit(
state.copyWith( state.copyWith(
trackingType: TrackingMediumType.anime, trackingType: TrackingMediumType.anime,
heroImagePrefix: event.heroImagePrefix,
data: event.anime, data: event.anime,
), ),
); );

View File

@@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // 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 // 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 'details_bloc.dart'; part of 'details_bloc.dart';
@@ -12,14 +12,17 @@ part of 'details_bloc.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError( final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc /// @nodoc
mixin _$DetailsState { mixin _$DetailsState {
TrackingMedium? get data => throw _privateConstructorUsedError; TrackingMedium? get data => throw _privateConstructorUsedError;
String? get heroImagePrefix => throw _privateConstructorUsedError;
TrackingMediumType get trackingType => throw _privateConstructorUsedError; TrackingMediumType get trackingType => throw _privateConstructorUsedError;
@JsonKey(ignore: true) /// Create a copy of DetailsState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$DetailsStateCopyWith<DetailsState> get copyWith => $DetailsStateCopyWith<DetailsState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -28,68 +31,91 @@ mixin _$DetailsState {
abstract class $DetailsStateCopyWith<$Res> { abstract class $DetailsStateCopyWith<$Res> {
factory $DetailsStateCopyWith( factory $DetailsStateCopyWith(
DetailsState value, $Res Function(DetailsState) then) = DetailsState value, $Res Function(DetailsState) then) =
_$DetailsStateCopyWithImpl<$Res>; _$DetailsStateCopyWithImpl<$Res, DetailsState>;
$Res call({TrackingMedium? data, TrackingMediumType trackingType}); @useResult
$Res call(
{TrackingMedium? data,
String? heroImagePrefix,
TrackingMediumType trackingType});
} }
/// @nodoc /// @nodoc
class _$DetailsStateCopyWithImpl<$Res> implements $DetailsStateCopyWith<$Res> { class _$DetailsStateCopyWithImpl<$Res, $Val extends DetailsState>
implements $DetailsStateCopyWith<$Res> {
_$DetailsStateCopyWithImpl(this._value, this._then); _$DetailsStateCopyWithImpl(this._value, this._then);
final DetailsState _value;
// ignore: unused_field // ignore: unused_field
final $Res Function(DetailsState) _then; final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of DetailsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? data = freezed, Object? data = freezed,
Object? trackingType = freezed, Object? heroImagePrefix = freezed,
Object? trackingType = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
data: data == freezed data: freezed == data
? _value.data ? _value.data
: data // ignore: cast_nullable_to_non_nullable : data // ignore: cast_nullable_to_non_nullable
as TrackingMedium?, as TrackingMedium?,
trackingType: trackingType == freezed heroImagePrefix: freezed == heroImagePrefix
? _value.heroImagePrefix
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
as String?,
trackingType: null == trackingType
? _value.trackingType ? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable : trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType, as TrackingMediumType,
)); ) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$_DetailsStateCopyWith<$Res> abstract class _$$DetailsStateImplCopyWith<$Res>
implements $DetailsStateCopyWith<$Res> { implements $DetailsStateCopyWith<$Res> {
factory _$$_DetailsStateCopyWith( factory _$$DetailsStateImplCopyWith(
_$_DetailsState value, $Res Function(_$_DetailsState) then) = _$DetailsStateImpl value, $Res Function(_$DetailsStateImpl) then) =
__$$_DetailsStateCopyWithImpl<$Res>; __$$DetailsStateImplCopyWithImpl<$Res>;
@override @override
$Res call({TrackingMedium? data, TrackingMediumType trackingType}); @useResult
$Res call(
{TrackingMedium? data,
String? heroImagePrefix,
TrackingMediumType trackingType});
} }
/// @nodoc /// @nodoc
class __$$_DetailsStateCopyWithImpl<$Res> class __$$DetailsStateImplCopyWithImpl<$Res>
extends _$DetailsStateCopyWithImpl<$Res> extends _$DetailsStateCopyWithImpl<$Res, _$DetailsStateImpl>
implements _$$_DetailsStateCopyWith<$Res> { implements _$$DetailsStateImplCopyWith<$Res> {
__$$_DetailsStateCopyWithImpl( __$$DetailsStateImplCopyWithImpl(
_$_DetailsState _value, $Res Function(_$_DetailsState) _then) _$DetailsStateImpl _value, $Res Function(_$DetailsStateImpl) _then)
: super(_value, (v) => _then(v as _$_DetailsState)); : super(_value, _then);
@override
_$_DetailsState get _value => super._value as _$_DetailsState;
/// Create a copy of DetailsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? data = freezed, Object? data = freezed,
Object? trackingType = freezed, Object? heroImagePrefix = freezed,
Object? trackingType = null,
}) { }) {
return _then(_$_DetailsState( return _then(_$DetailsStateImpl(
data: data == freezed data: freezed == data
? _value.data ? _value.data
: data // ignore: cast_nullable_to_non_nullable : data // ignore: cast_nullable_to_non_nullable
as TrackingMedium?, as TrackingMedium?,
trackingType: trackingType == freezed heroImagePrefix: freezed == heroImagePrefix
? _value.heroImagePrefix
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
as String?,
trackingType: null == trackingType
? _value.trackingType ? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable : trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType, as TrackingMediumType,
@@ -99,53 +125,67 @@ class __$$_DetailsStateCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$_DetailsState implements _DetailsState { class _$DetailsStateImpl implements _DetailsState {
_$_DetailsState({this.data, this.trackingType = TrackingMediumType.anime}); _$DetailsStateImpl(
{this.data,
this.heroImagePrefix,
this.trackingType = TrackingMediumType.anime});
@override @override
final TrackingMedium? data; final TrackingMedium? data;
@override @override
final String? heroImagePrefix;
@override
@JsonKey() @JsonKey()
final TrackingMediumType trackingType; final TrackingMediumType trackingType;
@override @override
String toString() { String toString() {
return 'DetailsState(data: $data, trackingType: $trackingType)'; return 'DetailsState(data: $data, heroImagePrefix: $heroImagePrefix, trackingType: $trackingType)';
} }
@override @override
bool operator ==(dynamic other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_DetailsState && other is _$DetailsStateImpl &&
const DeepCollectionEquality().equals(other.data, data) && (identical(other.data, data) || other.data == data) &&
const DeepCollectionEquality() (identical(other.heroImagePrefix, heroImagePrefix) ||
.equals(other.trackingType, trackingType)); other.heroImagePrefix == heroImagePrefix) &&
(identical(other.trackingType, trackingType) ||
other.trackingType == trackingType));
} }
@override @override
int get hashCode => Object.hash( int get hashCode =>
runtimeType, Object.hash(runtimeType, data, heroImagePrefix, trackingType);
const DeepCollectionEquality().hash(data),
const DeepCollectionEquality().hash(trackingType));
@JsonKey(ignore: true) /// Create a copy of DetailsState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
_$$_DetailsStateCopyWith<_$_DetailsState> get copyWith => @pragma('vm:prefer-inline')
__$$_DetailsStateCopyWithImpl<_$_DetailsState>(this, _$identity); _$$DetailsStateImplCopyWith<_$DetailsStateImpl> get copyWith =>
__$$DetailsStateImplCopyWithImpl<_$DetailsStateImpl>(this, _$identity);
} }
abstract class _DetailsState implements DetailsState { abstract class _DetailsState implements DetailsState {
factory _DetailsState( factory _DetailsState(
{final TrackingMedium? data, {final TrackingMedium? data,
final TrackingMediumType trackingType}) = _$_DetailsState; final String? heroImagePrefix,
final TrackingMediumType trackingType}) = _$DetailsStateImpl;
@override @override
TrackingMedium? get data; TrackingMedium? get data;
@override @override
TrackingMediumType get trackingType; String? get heroImagePrefix;
@override @override
@JsonKey(ignore: true) TrackingMediumType get trackingType;
_$$_DetailsStateCopyWith<_$_DetailsState> get copyWith =>
/// Create a copy of DetailsState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$DetailsStateImplCopyWith<_$DetailsStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -3,10 +3,15 @@ part of 'details_bloc.dart';
abstract class DetailsEvent {} abstract class DetailsEvent {}
class AnimeDetailsRequestedEvent extends DetailsEvent { class AnimeDetailsRequestedEvent extends DetailsEvent {
AnimeDetailsRequestedEvent(this.anime); AnimeDetailsRequestedEvent(
this.anime, {
this.heroImagePrefix,
});
/// The anime to show details about /// The anime to show details about
final AnimeTrackingData anime; final AnimeTrackingData anime;
final String? heroImagePrefix;
} }
class MangaDetailsRequestedEvent extends DetailsEvent { class MangaDetailsRequestedEvent extends DetailsEvent {

View File

@@ -4,6 +4,7 @@ part of 'details_bloc.dart';
class DetailsState with _$DetailsState { class DetailsState with _$DetailsState {
factory DetailsState({ factory DetailsState({
TrackingMedium? data, TrackingMedium? data,
String? heroImagePrefix,
@Default(TrackingMediumType.anime) TrackingMediumType trackingType, @Default(TrackingMediumType.anime) TrackingMediumType trackingType,
}) = _DetailsState; }) = _DetailsState;
} }

View File

@@ -1,19 +1,25 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/data/anime.dart'; import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/data/manga.dart'; import 'package:anitrack/src/data/manga.dart';
import 'package:anitrack/src/data/type.dart'; import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart'; import 'package:anitrack/src/service/database.dart';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:archive/archive.dart' as archive;
import 'package:archive/archive_io.dart'; import 'package:archive/archive_io.dart';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:jikan_api/jikan_api.dart'; import 'package:jikan_api/jikan_api.dart';
import 'package:path/path.dart' as path;
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
part 'settings_state.dart';
part 'settings_event.dart';
part 'settings_bloc.freezed.dart'; part 'settings_bloc.freezed.dart';
part 'settings_event.dart';
part 'settings_state.dart';
MediumTrackingState malStatusToTrackingState(String status) { MediumTrackingState malStatusToTrackingState(String status) {
switch (status) { switch (status) {
@@ -39,6 +45,8 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
SettingsBloc() : super(SettingsState()) { SettingsBloc() : super(SettingsState()) {
on<AnimeListImportedEvent>(_onAnimeListImported); on<AnimeListImportedEvent>(_onAnimeListImported);
on<MangaListImportedEvent>(_onMangaListImported); on<MangaListImportedEvent>(_onMangaListImported);
on<DataExportedEvent>(_onDataExported);
on<DataImportedEvent>(_onDataImported);
} }
void _showLoadingSpinner(Emitter<SettingsState> emit) { void _showLoadingSpinner(Emitter<SettingsState> emit) {
@@ -119,6 +127,9 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
// 0 means that MAL does not know // 0 means that MAL does not know
totalEpisodes == 0 ? null : totalEpisodes, totalEpisodes == 0 ? null : totalEpisodes,
data.imageUrl, data.imageUrl,
// NOTE: When the calendar gets refreshed, this should also get cleared
true,
null,
), ),
); );
} }
@@ -202,4 +213,69 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
// Hide the spinner again // Hide the spinner again
_hideLoadingSpinner(emit); _hideLoadingSpinner(emit);
} }
Future<void> _onDataExported(
DataExportedEvent event,
Emitter<SettingsState> emit,
) async {
final al = GetIt.I.get<AnimeListBloc>();
final data = {
// TODO(Unknown): Track the version here to (maybe) to migrations
'animes': al.state.animes.map((anime) => anime.toJson()).toList(),
'mangas': al.state.mangas.map((manga) => manga.toJson()).toList(),
};
final exportData = jsonEncode(data);
final date = DateTime.now();
final outputPath = path.join(
event.path,
'anitrack_${date.year}${date.month}${date.day}.json.gz',
);
archive.GZipEncoder().encode(
InputStream(utf8.encode(exportData)),
output: OutputFileStream(outputPath),
);
await Fluttertoast.showToast(
msg: t.settings.dataExportSuccess,
);
}
Future<void> _onDataImported(
DataImportedEvent event,
Emitter<SettingsState> emit,
) async {
final al = GetIt.I.get<AnimeListBloc>();
final exportArchive = archive.GZipDecoder().decodeBytes(
await File(event.path).readAsBytes(),
);
final json = jsonDecode(utf8.decode(exportArchive)) as Map<String, dynamic>;
// Process anime
for (final animeRaw
in (json['animes']! as List<dynamic>).cast<Map<dynamic, dynamic>>()) {
final anime = AnimeTrackingData.fromJson(
animeRaw.cast<String, dynamic>(),
);
al.add(
AnimeAddedEvent(anime, checkIfExists: true),
);
}
// Process manga
for (final mangaRaw
in (json['mangas']! as List<dynamic>).cast<Map<dynamic, dynamic>>()) {
final manga = MangaTrackingData.fromJson(
mangaRaw.cast<String, dynamic>(),
);
al.add(
MangaAddedEvent(manga, checkIfExists: true),
);
}
await Fluttertoast.showToast(
msg: t.settings.dataImportSuccess,
);
}
} }

View File

@@ -1,7 +1,7 @@
// coverage:ignore-file // coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint // 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 // 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 'settings_bloc.dart'; part of 'settings_bloc.dart';
@@ -12,7 +12,7 @@ part of 'settings_bloc.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError( final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc /// @nodoc
mixin _$SettingsState { mixin _$SettingsState {
@@ -20,7 +20,9 @@ mixin _$SettingsState {
int get importCurrent => throw _privateConstructorUsedError; int get importCurrent => throw _privateConstructorUsedError;
int get importTotal => throw _privateConstructorUsedError; int get importTotal => throw _privateConstructorUsedError;
@JsonKey(ignore: true) /// Create a copy of SettingsState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SettingsStateCopyWith<SettingsState> get copyWith => $SettingsStateCopyWith<SettingsState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -29,79 +31,85 @@ mixin _$SettingsState {
abstract class $SettingsStateCopyWith<$Res> { abstract class $SettingsStateCopyWith<$Res> {
factory $SettingsStateCopyWith( factory $SettingsStateCopyWith(
SettingsState value, $Res Function(SettingsState) then) = SettingsState value, $Res Function(SettingsState) then) =
_$SettingsStateCopyWithImpl<$Res>; _$SettingsStateCopyWithImpl<$Res, SettingsState>;
@useResult
$Res call({bool importSpinnerVisible, int importCurrent, int importTotal}); $Res call({bool importSpinnerVisible, int importCurrent, int importTotal});
} }
/// @nodoc /// @nodoc
class _$SettingsStateCopyWithImpl<$Res> class _$SettingsStateCopyWithImpl<$Res, $Val extends SettingsState>
implements $SettingsStateCopyWith<$Res> { implements $SettingsStateCopyWith<$Res> {
_$SettingsStateCopyWithImpl(this._value, this._then); _$SettingsStateCopyWithImpl(this._value, this._then);
final SettingsState _value;
// ignore: unused_field // ignore: unused_field
final $Res Function(SettingsState) _then; final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SettingsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? importSpinnerVisible = freezed, Object? importSpinnerVisible = null,
Object? importCurrent = freezed, Object? importCurrent = null,
Object? importTotal = freezed, Object? importTotal = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
importSpinnerVisible: importSpinnerVisible == freezed importSpinnerVisible: null == importSpinnerVisible
? _value.importSpinnerVisible ? _value.importSpinnerVisible
: importSpinnerVisible // ignore: cast_nullable_to_non_nullable : importSpinnerVisible // ignore: cast_nullable_to_non_nullable
as bool, as bool,
importCurrent: importCurrent == freezed importCurrent: null == importCurrent
? _value.importCurrent ? _value.importCurrent
: importCurrent // ignore: cast_nullable_to_non_nullable : importCurrent // ignore: cast_nullable_to_non_nullable
as int, as int,
importTotal: importTotal == freezed importTotal: null == importTotal
? _value.importTotal ? _value.importTotal
: importTotal // ignore: cast_nullable_to_non_nullable : importTotal // ignore: cast_nullable_to_non_nullable
as int, as int,
)); ) as $Val);
} }
} }
/// @nodoc /// @nodoc
abstract class _$$_SettingsStateCopyWith<$Res> abstract class _$$SettingsStateImplCopyWith<$Res>
implements $SettingsStateCopyWith<$Res> { implements $SettingsStateCopyWith<$Res> {
factory _$$_SettingsStateCopyWith( factory _$$SettingsStateImplCopyWith(
_$_SettingsState value, $Res Function(_$_SettingsState) then) = _$SettingsStateImpl value, $Res Function(_$SettingsStateImpl) then) =
__$$_SettingsStateCopyWithImpl<$Res>; __$$SettingsStateImplCopyWithImpl<$Res>;
@override @override
@useResult
$Res call({bool importSpinnerVisible, int importCurrent, int importTotal}); $Res call({bool importSpinnerVisible, int importCurrent, int importTotal});
} }
/// @nodoc /// @nodoc
class __$$_SettingsStateCopyWithImpl<$Res> class __$$SettingsStateImplCopyWithImpl<$Res>
extends _$SettingsStateCopyWithImpl<$Res> extends _$SettingsStateCopyWithImpl<$Res, _$SettingsStateImpl>
implements _$$_SettingsStateCopyWith<$Res> { implements _$$SettingsStateImplCopyWith<$Res> {
__$$_SettingsStateCopyWithImpl( __$$SettingsStateImplCopyWithImpl(
_$_SettingsState _value, $Res Function(_$_SettingsState) _then) _$SettingsStateImpl _value, $Res Function(_$SettingsStateImpl) _then)
: super(_value, (v) => _then(v as _$_SettingsState)); : super(_value, _then);
@override
_$_SettingsState get _value => super._value as _$_SettingsState;
/// Create a copy of SettingsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? importSpinnerVisible = freezed, Object? importSpinnerVisible = null,
Object? importCurrent = freezed, Object? importCurrent = null,
Object? importTotal = freezed, Object? importTotal = null,
}) { }) {
return _then(_$_SettingsState( return _then(_$SettingsStateImpl(
importSpinnerVisible: importSpinnerVisible == freezed importSpinnerVisible: null == importSpinnerVisible
? _value.importSpinnerVisible ? _value.importSpinnerVisible
: importSpinnerVisible // ignore: cast_nullable_to_non_nullable : importSpinnerVisible // ignore: cast_nullable_to_non_nullable
as bool, as bool,
importCurrent: importCurrent == freezed importCurrent: null == importCurrent
? _value.importCurrent ? _value.importCurrent
: importCurrent // ignore: cast_nullable_to_non_nullable : importCurrent // ignore: cast_nullable_to_non_nullable
as int, as int,
importTotal: importTotal == freezed importTotal: null == importTotal
? _value.importTotal ? _value.importTotal
: importTotal // ignore: cast_nullable_to_non_nullable : importTotal // ignore: cast_nullable_to_non_nullable
as int, as int,
@@ -111,8 +119,8 @@ class __$$_SettingsStateCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$_SettingsState implements _SettingsState { class _$SettingsStateImpl implements _SettingsState {
_$_SettingsState( _$SettingsStateImpl(
{this.importSpinnerVisible = false, {this.importSpinnerVisible = false,
this.importCurrent = 0, this.importCurrent = 0,
this.importTotal = 0}); this.importTotal = 0});
@@ -133,36 +141,36 @@ class _$_SettingsState implements _SettingsState {
} }
@override @override
bool operator ==(dynamic other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_SettingsState && other is _$SettingsStateImpl &&
const DeepCollectionEquality() (identical(other.importSpinnerVisible, importSpinnerVisible) ||
.equals(other.importSpinnerVisible, importSpinnerVisible) && other.importSpinnerVisible == importSpinnerVisible) &&
const DeepCollectionEquality() (identical(other.importCurrent, importCurrent) ||
.equals(other.importCurrent, importCurrent) && other.importCurrent == importCurrent) &&
const DeepCollectionEquality() (identical(other.importTotal, importTotal) ||
.equals(other.importTotal, importTotal)); other.importTotal == importTotal));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType, importSpinnerVisible, importCurrent, importTotal);
const DeepCollectionEquality().hash(importSpinnerVisible),
const DeepCollectionEquality().hash(importCurrent),
const DeepCollectionEquality().hash(importTotal));
@JsonKey(ignore: true) /// Create a copy of SettingsState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
_$$_SettingsStateCopyWith<_$_SettingsState> get copyWith => @pragma('vm:prefer-inline')
__$$_SettingsStateCopyWithImpl<_$_SettingsState>(this, _$identity); _$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith =>
__$$SettingsStateImplCopyWithImpl<_$SettingsStateImpl>(this, _$identity);
} }
abstract class _SettingsState implements SettingsState { abstract class _SettingsState implements SettingsState {
factory _SettingsState( factory _SettingsState(
{final bool importSpinnerVisible, {final bool importSpinnerVisible,
final int importCurrent, final int importCurrent,
final int importTotal}) = _$_SettingsState; final int importTotal}) = _$SettingsStateImpl;
@override @override
bool get importSpinnerVisible; bool get importSpinnerVisible;
@@ -170,8 +178,11 @@ abstract class _SettingsState implements SettingsState {
int get importCurrent; int get importCurrent;
@override @override
int get importTotal; int get importTotal;
/// Create a copy of SettingsState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$_SettingsStateCopyWith<_$_SettingsState> get copyWith => _$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -34,3 +34,19 @@ class MangaListImportedEvent extends SettingsEvent {
/// The type of list we're importing /// The type of list we're importing
final ImportListType type; final ImportListType type;
} }
/// Triggered when a data export should be produced.
class DataExportedEvent extends SettingsEvent {
DataExportedEvent(this.path);
/// The path where the export should be stored.
final String path;
}
/// Triggered when a data export has been picked for import.
class DataImportedEvent extends SettingsEvent {
DataImportedEvent(this.path);
/// The path of the data export to import.
final String path;
}

View File

@@ -1,5 +1,6 @@
const animeListRoute = '/anime/list'; const animeListRoute = '/anime/list';
const animeSearchRoute = '/anime/search'; const animeSearchRoute = '/anime/search';
const detailsRoute = '/anime/details'; const detailsRoute = '/anime/details';
const calendarRoute = '/calendar';
const aboutRoute = '/about'; const aboutRoute = '/about';
const settingsRoute = '/settings'; const settingsRoute = '/settings';

58
lib/src/ui/helpers.dart Normal file
View File

@@ -0,0 +1,58 @@
import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:flutter/material.dart';
Widget getDrawer(BuildContext context) {
return Drawer(
child: ListView(
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Color(0xffcf4aff),
),
child: Text(
'AniTrack',
style: TextStyle(
color: Color(0xff232323),
fontSize: 24,
),
),
),
ListTile(
leading: const Icon(Icons.list),
title: Text(t.content.list),
onTap: () {
Navigator.of(context).pushNamedAndRemoveUntil(
animeListRoute,
(_) => false,
);
},
),
ListTile(
leading: const Icon(Icons.calendar_today),
title: Text(t.calendar.calendar),
onTap: () {
Navigator.of(context).pushNamedAndRemoveUntil(
calendarRoute,
(_) => false,
);
},
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(t.settings.title),
onTap: () {
Navigator.of(context).pushNamed(settingsRoute);
},
),
ListTile(
leading: const Icon(Icons.info),
title: Text(t.about.title),
onTap: () {
Navigator.of(context).pushNamed(aboutRoute);
},
),
],
),
);
}

View File

@@ -2,6 +2,7 @@ import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/licenses.g.dart'; import 'package:anitrack/licenses.g.dart';
import 'package:anitrack/src/ui/constants.dart'; import 'package:anitrack/src/ui/constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class AboutPage extends StatelessWidget { class AboutPage extends StatelessWidget {
@@ -35,15 +36,55 @@ class AboutPage extends StatelessWidget {
'AniTrack', 'AniTrack',
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
ElevatedButton( Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ElevatedButton(
onPressed: () async { onPressed: () async {
await launchUrl( await launchUrl(
Uri.parse('https://codeberg.org/PapaTutuWawa/anitrack'), Uri.parse(
'https://codeberg.org/PapaTutuWawa/anitrack',
),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
); );
}, },
child: Text(t.about.source), child: Text(t.about.source),
), ),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ElevatedButton(
onPressed: () async {
final licenseText = await rootBundle.loadString(
'LICENSE',
);
await showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
content: SingleChildScrollView(
child: Text(licenseText),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(t.about.close),
),
],
);
},
);
},
child: Text(t.about.license),
),
),
],
),
], ],
), ),
); );
@@ -54,14 +95,34 @@ class AboutPage extends StatelessWidget {
return ListTile( return ListTile(
title: Text(dep.name), title: Text(dep.name),
onTap: () async { onTap: () {
if (dep.repository == null) return; showDialog<void>(
context: context,
builder: (context) => AlertDialog(
content: SingleChildScrollView(
child: Text(dep.license ?? ''),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(t.about.close),
),
if (dep.repository != null)
TextButton(
onPressed: () async {
await launchUrl( await launchUrl(
Uri.parse(dep.repository!), Uri.parse(dep.repository!),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
); );
}, },
child: Text(t.about.source),
),
],
),
);
},
); );
}, },
), ),

View File

@@ -4,6 +4,7 @@ import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart'; import 'package:anitrack/src/ui/bloc/anime_search_bloc.dart';
import 'package:anitrack/src/ui/bloc/details_bloc.dart'; import 'package:anitrack/src/ui/bloc/details_bloc.dart';
import 'package:anitrack/src/ui/constants.dart'; import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/helpers.dart';
import 'package:anitrack/src/ui/widgets/grid_item.dart'; import 'package:anitrack/src/ui/widgets/grid_item.dart';
import 'package:anitrack/src/ui/widgets/image.dart'; import 'package:anitrack/src/ui/widgets/image.dart';
import 'package:bottom_bar/bottom_bar.dart'; import 'package:bottom_bar/bottom_bar.dart';
@@ -71,23 +72,23 @@ class AnimeListPageState extends State<AnimeListPage> {
return [ return [
PopupMenuItem<MediumTrackingState>( PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.ongoing, value: MediumTrackingState.ongoing,
child: Text(MediumTrackingState.ongoing.toNameString(type)), child: Text(MediumTrackingState.ongoing.getName(type)),
), ),
PopupMenuItem<MediumTrackingState>( PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.completed, value: MediumTrackingState.completed,
child: Text(MediumTrackingState.completed.toNameString(type)), child: Text(MediumTrackingState.completed.getName(type)),
), ),
PopupMenuItem<MediumTrackingState>( PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.planned, value: MediumTrackingState.planned,
child: Text(MediumTrackingState.planned.toNameString(type)), child: Text(MediumTrackingState.planned.getName(type)),
), ),
PopupMenuItem<MediumTrackingState>( PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.dropped, value: MediumTrackingState.dropped,
child: Text(MediumTrackingState.dropped.toNameString(type)), child: Text(MediumTrackingState.dropped.getName(type)),
), ),
PopupMenuItem<MediumTrackingState>( PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.paused, value: MediumTrackingState.paused,
child: Text(MediumTrackingState.paused.toNameString(type)), child: Text(MediumTrackingState.paused.getName(type)),
), ),
const PopupMenuItem<MediumTrackingState>( const PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.all, value: MediumTrackingState.all,
@@ -140,38 +141,7 @@ class AnimeListPageState extends State<AnimeListPage> {
_getPopupButton(context, state), _getPopupButton(context, state),
], ],
), ),
drawer: Drawer( drawer: getDrawer(context),
child: ListView(
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Color(0xffcf4aff),
),
child: Text(
'AniTrack',
style: TextStyle(
color: Color(0xff232323),
fontSize: 24,
),
),
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(t.settings.title),
onTap: () {
Navigator.of(context).pushNamed(settingsRoute);
},
),
ListTile(
leading: const Icon(Icons.info),
title: Text(t.about.title),
onTap: () {
Navigator.of(context).pushNamed(aboutRoute);
},
),
],
),
),
body: PageView( body: PageView(
// Prevent swiping between pages // Prevent swiping between pages
// (https://github.com/flutter/flutter/issues/37510#issuecomment-612663656) // (https://github.com/flutter/flutter/issues/37510#issuecomment-612663656)

View File

@@ -0,0 +1,317 @@
import 'package:anitrack/i18n/strings.g.dart';
import 'package:anitrack/src/data/anime.dart';
import 'package:anitrack/src/ui/bloc/anime_list_bloc.dart';
import 'package:anitrack/src/ui/bloc/calendar_bloc.dart';
import 'package:anitrack/src/ui/bloc/details_bloc.dart';
import 'package:anitrack/src/ui/constants.dart';
import 'package:anitrack/src/ui/helpers.dart';
import 'package:anitrack/src/ui/widgets/grid_item.dart';
import 'package:anitrack/src/ui/widgets/image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
enum Weekday {
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday,
unknown;
String toName() {
switch (this) {
case Weekday.monday:
return t.calendar.days.monday;
case Weekday.tuesday:
return t.calendar.days.tuesday;
case Weekday.wednesday:
return t.calendar.days.wednesday;
case Weekday.thursday:
return t.calendar.days.thursday;
case Weekday.friday:
return t.calendar.days.friday;
case Weekday.saturday:
return t.calendar.days.saturday;
case Weekday.sunday:
return t.calendar.days.sunday;
case Weekday.unknown:
return t.calendar.days.unknown;
}
}
}
extension AddIfExists<K, V> on Map<K, List<V>> {
void addOrSet(K key, V value) {
if (containsKey(key)) {
this[key]!.add(value);
} else {
this[key] = List<V>.from([value]);
}
}
}
class CalendarPage extends StatefulWidget {
const CalendarPage({super.key});
static MaterialPageRoute<dynamic> get route => MaterialPageRoute<dynamic>(
builder: (_) => const CalendarPage(),
settings: const RouteSettings(
name: calendarRoute,
),
);
@override
CalendarPageState createState() => CalendarPageState();
}
class CalendarPageState extends State<CalendarPage> {
List<Widget> _renderWeekdayList(
BuildContext context,
Weekday day,
Map<Weekday, List<AnimeTrackingData>> data,
) {
if (!data.containsKey(day)) {
return const [];
}
assert(data[day]!.isNotEmpty, 'There should be at least one anime');
return [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
right: 16,
top: 20,
bottom: 4,
),
child: Text(
day.toName(),
style: Theme.of(context).textTheme.titleLarge,
),
),
),
SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 120 / (100 * (16 / 9)),
),
itemCount: data[day]!.length,
itemBuilder: (context, index) {
final anime = data[day]![index];
return GridItem(
child: AnimeCoverImage(
url: anime.thumbnailUrl,
hero: 'calendar_${anime.id}',
onTap: () {
context.read<DetailsBloc>().add(
AnimeDetailsRequestedEvent(
anime,
heroImagePrefix: 'calendar_',
),
);
},
),
);
},
),
];
}
@override
Widget build(BuildContext context) {
final airingAnimeMap = <Weekday, List<AnimeTrackingData>>{};
for (final anime in GetIt.I.get<AnimeListBloc>().unfilteredAnime) {
if (!anime.airing) continue;
final Weekday day;
switch (anime.broadcastDay) {
case 'Mondays':
day = Weekday.monday;
break;
case 'Tuesdays':
day = Weekday.tuesday;
break;
case 'Wednesdays':
day = Weekday.wednesday;
break;
case 'Thursdays':
day = Weekday.thursday;
break;
case 'Fridays':
day = Weekday.friday;
break;
case 'Saturdays':
day = Weekday.saturday;
break;
case 'Sundays':
day = Weekday.sunday;
break;
default:
day = Weekday.unknown;
break;
}
airingAnimeMap.addOrSet(day, anime);
}
return BlocListener<CalendarBloc, CalendarState>(
listenWhen: (previous, current) =>
previous.refreshing != current.refreshing,
listener: (context, state) {
// Force an update
if (!state.refreshing) {
setState(() {});
}
},
child: WillPopScope(
onWillPop: () async => !context.read<CalendarBloc>().state.refreshing,
child: Stack(
children: [
Positioned(
left: 8,
right: 8,
top: 0,
bottom: 0,
child: Scaffold(
appBar: AppBar(
title: Text(t.calendar.calendar),
actions: [
IconButton(
onPressed: () {
context
.read<CalendarBloc>()
.add(RefreshPerformedEvent());
},
icon: const Icon(Icons.refresh),
),
],
),
drawer: getDrawer(context),
body: CustomScrollView(
slivers: [
// Render all available weekdays
..._renderWeekdayList(
context,
Weekday.unknown,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.monday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.tuesday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.wednesday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.thursday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.friday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.saturday,
airingAnimeMap,
),
..._renderWeekdayList(
context,
Weekday.sunday,
airingAnimeMap,
),
// Provide a nice bottom padding, while keeping the elastic effect attached
// to the bottom-most edge.
const SliverToBoxAdapter(
child: SizedBox(
height: 16,
),
),
],
),
),
),
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: BlocBuilder<CalendarBloc, CalendarState>(
buildWhen: (previous, current) =>
previous.refreshing != current.refreshing,
builder: (context, state) {
if (!state.refreshing) {
return const SizedBox();
}
return const ModalBarrier(
dismissible: false,
color: Colors.black54,
);
},
),
),
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
if (!state.refreshing) {
return const SizedBox();
}
return Center(
child: SizedBox(
width: 150,
height: 150,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.grey.shade800,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Padding(
padding: EdgeInsets.all(25),
child: CircularProgressIndicator(),
),
Text(
t.settings.importIndicator(
current: state.refreshingCount,
total: state.refreshingTotal,
),
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
);
},
),
),
],
),
),
);
}
}

View File

@@ -42,7 +42,7 @@ class DetailsPage extends StatelessWidget {
children: [ children: [
AnimeCoverImage( AnimeCoverImage(
url: state.data!.thumbnailUrl, url: state.data!.thumbnailUrl,
hero: state.data!.id, hero: '${state.heroImagePrefix}${state.data!.id}',
), ),
Expanded( Expanded(
child: Padding( child: Padding(
@@ -69,10 +69,14 @@ class DetailsPage extends StatelessWidget {
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: Text( title: Text(
t.details.removeTitle(title: state.data!.title), t.details.removeTitle(
title: state.data!.title,
),
), ),
content: Text( content: Text(
t.details.removeBody(title: state.data!.title), t.details.removeBody(
title: state.data!.title,
),
), ),
actions: [ actions: [
TextButton( TextButton(
@@ -83,14 +87,18 @@ class DetailsPage extends StatelessWidget {
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: Colors.red, foregroundColor: Colors.red,
), ),
child: Text(t.details.removeButton), child: Text(
t.details.removeButton,
),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context) Navigator.of(context)
.pop(false); .pop(false);
}, },
child: Text(t.details.cancelButton), child: Text(
t.details.cancelButton,
),
), ),
], ],
); );
@@ -150,27 +158,27 @@ class DetailsPage extends StatelessWidget {
SelectorItem( SelectorItem(
MediumTrackingState.ongoing, MediumTrackingState.ongoing,
MediumTrackingState.ongoing MediumTrackingState.ongoing
.toNameString(state.trackingType), .getName(state.trackingType),
), ),
SelectorItem( SelectorItem(
MediumTrackingState.completed, MediumTrackingState.completed,
MediumTrackingState.completed MediumTrackingState.completed
.toNameString(state.trackingType), .getName(state.trackingType),
), ),
SelectorItem( SelectorItem(
MediumTrackingState.planned, MediumTrackingState.planned,
MediumTrackingState.planned MediumTrackingState.planned
.toNameString(state.trackingType), .getName(state.trackingType),
), ),
SelectorItem( SelectorItem(
MediumTrackingState.dropped, MediumTrackingState.dropped,
MediumTrackingState.dropped MediumTrackingState.dropped
.toNameString(state.trackingType), .getName(state.trackingType),
), ),
SelectorItem( SelectorItem(
MediumTrackingState.paused, MediumTrackingState.paused,
MediumTrackingState.paused MediumTrackingState.paused
.toNameString(state.trackingType), .getName(state.trackingType),
), ),
], ],
initialValue: state.data!.state, initialValue: state.data!.state,

View File

@@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:permission_handler/permission_handler.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
const SettingsPage({super.key}); const SettingsPage({super.key});
@@ -91,6 +92,51 @@ class SettingsPage extends StatelessWidget {
); );
}, },
), ),
ListTile(
title: Text(t.settings.exportData),
onTap: () async {
// Pick the file
final result =
await FilePicker.platform.getDirectoryPath();
if (result == null) return;
if (!(await Permission.manageExternalStorage
.request())
.isGranted) return;
GetIt.I.get<SettingsBloc>().add(
DataExportedEvent(
result,
),
);
},
),
ListTile(
title: Text(t.settings.importData),
onTap: () async {
// Pick the file
final result = await FilePicker.platform.pickFiles();
if (result == null) return;
if (!result.files.first.path!.endsWith('.json.gz')) {
await showDialog<void>(
context: context,
builder: (_) => AlertDialog(
title: Text(t.settings.importInvalidData.title),
content:
Text(t.settings.importInvalidData.content),
),
);
return;
}
GetIt.I.get<SettingsBloc>().add(
DataImportedEvent(
result.files.first.path!,
),
);
},
),
], ],
), ),
), ),

View File

@@ -4,15 +4,18 @@ import 'package:flutter/material.dart';
class GridItem extends StatefulWidget { class GridItem extends StatefulWidget {
const GridItem({ const GridItem({
required this.child, required this.child,
required this.plusCallback, this.plusCallback,
required this.minusCallback, this.minusCallback,
this.enableDrag = true,
super.key, super.key,
}); });
final Widget child; final Widget child;
final void Function() plusCallback; final bool enableDrag;
final void Function() minusCallback;
final void Function()? plusCallback;
final void Function()? minusCallback;
@override @override
GridItemState createState() => GridItemState(); GridItemState createState() => GridItemState();
@@ -26,16 +29,20 @@ class GridItemState extends State<GridItem> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onHorizontalDragUpdate: (details) { onHorizontalDragUpdate: (details) {
if (!widget.enableDrag) return;
setState(() { setState(() {
_offset += details.delta.dx; _offset += details.delta.dx;
_translationX = 160 / (1 + exp(-1 * (1 / 30) * _offset)) - 80; _translationX = 160 / (1 + exp(-1 * (1 / 30) * _offset)) - 80;
}); });
}, },
onHorizontalDragEnd: (_) { onHorizontalDragEnd: (_) {
if (_translationX <= -60) { if (!widget.enableDrag) return;
widget.plusCallback();
} else if (_translationX >= 60) { if (_translationX <= -40) {
widget.minusCallback(); widget.plusCallback!();
} else if (_translationX >= 40) {
widget.minusCallback!();
} }
// Reset the view // Reset the view

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,53 @@
name: anitrack name: anitrack
description: An anime and manga tracker description: An anime and manga tracker
publish_to: 'none' publish_to: "none"
version: 0.1.2+8 version: 0.1.3+2011
environment: environment:
sdk: '>=2.18.4 <3.0.0' sdk: ">=3.0.0 <4.0.0"
dependencies: dependencies:
archive: ^3.3.7 archive: ^3.6.1
bloc: ^8.1.0 bloc: ^8.1.4
bottom_bar: ^2.0.3 bottom_bar: ^2.0.3
cached_network_image: ^3.2.3 cached_network_image: ^3.4.1
collection: ^1.17.0 collection: ^1.18.0
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.8
file_picker: ^5.2.8 file_picker: ^8.1.2
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^8.1.1 flutter_bloc: ^8.1.6
freezed_annotation: 2.1.0 fluttertoast: ^8.2.8
get_it: ^7.2.0 freezed_annotation: ^2.4.4
jikan_api: ^2.0.0 get_it: ^8.0.0
json_annotation: 4.6.0 jikan_api: ^2.2.1
slang: 3.19.0 json_annotation: ^4.9.0
slang_flutter: 3.19.0 path: ^1.9.0
sqflite: ^2.2.4+1 permission_handler: ^11.3.1
swipeable_tile: ^2.0.0+3 slang: ^3.31.2
url_launcher: ^6.1.8 slang_flutter: ^3.31.0
xml: ^6.2.2 sqflite: ^2.3.3+2
swipeable_tile: ^2.0.1
url_launcher: ^6.3.0
xml: ^6.5.0
dev_dependencies: dev_dependencies:
build_runner: ^2.1.11 build_runner: ^2.4.12
flutter_launcher_icons: ^0.11.0 flutter_launcher_icons: ^0.14.1
flutter_lints: ^2.0.0 flutter_lints: ^5.0.0
flutter_oss_licenses: ^2.0.1 flutter_oss_licenses: ^3.0.2
flutter_test: flutter_test:
sdk: flutter sdk: flutter
freezed: ^2.1.0+1 freezed: ^2.5.7
json_serializable: ^6.3.1 json_serializable: ^6.8.0
slang_build_runner: 3.19.0 slang_build_runner: ^3.31.0
very_good_analysis: ^3.0.1 very_good_analysis: ^6.0.0
flutter: flutter:
uses-material-design: true uses-material-design: true
assets:
- LICENSE
flutter_icons: flutter_icons:
android: "launcher_icon" android: "launcher_icon"