Compare commits

...

25 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
50 changed files with 2938 additions and 942 deletions

3
.gitignore vendored
View File

@@ -49,3 +49,6 @@ lib/i18n/
# NixOS
.direnv
.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 localPropertiesFile = rootProject.file('local.properties')
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')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
@@ -21,12 +22,8 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 33
compileSdkVersion 34
ndkVersion flutter.ndkVersion
compileOptions {
@@ -43,7 +40,6 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "me.polynom.anitrack"
// 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.
@@ -55,9 +51,7 @@ android {
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig null
}
}
}
@@ -65,7 +59,3 @@ android {
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@@ -30,7 +30,12 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="true" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
</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 {
repositories {
google()
@@ -26,6 +13,6 @@ subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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")
def properties = new Properties()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
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",
"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.",
"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": {
"title": "Über",
"source": "Quellcode"
"source": "Quellcode",
"license": "Lizenz",
"close": "Schließen"
},
"content": {
"anime": "Anime",
"manga": "Manga"
"manga": "Manga",
"list": "Liste"
},
"search": {
"anime": "Animesuche",
@@ -52,5 +63,18 @@
},
"tooltips": {
"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.",
"invalidMangaListTitle": "Invalid manga list",
"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": {
"title": "About",
"source": "Source code"
"source": "Source code",
"license": "License",
"close": "Close"
},
"tooltips": {
"addNewItem": "Add new item"
},
"content": {
"list": "List",
"anime": "Anime",
"manga": "Manga"
},
@@ -39,6 +50,19 @@
"chapters": "Chapters",
"volumesOwned": "Volumes owned"
},
"calendar": {
"calendar": "Calendar",
"days": {
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"unknown": "Unknown"
}
},
"data": {
"ongoing": {
"anime": "Watching",

View File

@@ -4,23 +4,34 @@
"importAnime": "Animelijst importeren",
"importAnimeDesc": "Importeer een animelijst van MyAnimeList.",
"invalidAnimeListTitle": "Ongeldige animelijst",
"invalidAnimeListBody": "Het gekozen bestand is geen MAL-lijst; het achtervoegsel .xml.gz ontbreekt.",
"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"
"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"
"source": "Broncode",
"close": "Sluiten",
"license": "Licentie"
},
"tooltips": {
"addNewItem": "Item toevoegen"
},
"content": {
"anime": "Anime",
"manga": "Manga"
"manga": "Manga",
"list": "Lijst"
},
"search": {
"anime": "Zoeken naar anime",
@@ -52,5 +63,18 @@
"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:
input_directory: assets/i18n
output_directory: lib/i18n
fallback_strategy: base_locale
base_locale: en

189
flake.lock generated
View File

@@ -1,6 +1,100 @@
{
"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": {
"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": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
@@ -17,24 +111,103 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1676076353,
"narHash": "sha256-mdUtE8Tp40cZETwcq5tCwwLqkJVV1ULJQ5GKRtbshag=",
"owner": "AtaraxiaSjel",
"lastModified": 1689679375,
"narHash": "sha256-LHUC52WvyVDi9PwyL1QCpaxYWBqp4ir4iL6zgOkmcb8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5deb99bdccbbb97e7562dee4ba8a3ee3021688e6",
"rev": "684c17c429c42515bafb3ad775d2a710947f3d67",
"type": "github"
},
"original": {
"owner": "AtaraxiaSjel",
"ref": "update/flutter",
"owner": "NixOS",
"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",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
"android-nixpkgs": "android-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";
inputs = {
nixpkgs.url = "github:AtaraxiaSjel/nixpkgs/update/flutter";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
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 {
inherit system;
config = {
@@ -13,36 +15,73 @@
allowUnfree = true;
};
};
android = pkgs.androidenv.composeAndroidPackages {
# TODO: Find a way to pin these
#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;
};
lib = pkgs.lib;
babPkgs = bab.packages."${system}";
pinnedJDK = pkgs.jdk17;
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
requests pyyaml # For the build scripts
# Everything to make Flutter happy
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 {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
flutter pinnedJDK android.platform-tools dart scrcpy # Flutter/Android
gitlint jq # Code hygiene
ripgrep # General utilities
# Android
sdk
# Flutter
flutter dart scrcpy pinnedJDK
# Code hygiene
gitlint
];
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.',
repository: 'https://github.com/brendan-duncan/archive',
authors: [],
version: '3.3.6',
version: '3.3.7',
license: '''The MIT License
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.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
isDirectDependency: true,
),
Package(
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.',
repository: 'https://github.com/dart-lang/async',
authors: [],
version: '2.9.0',
version: '2.10.0',
license: '''Copyright 2015, the Dart project authors.
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',
description:
'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: [],
version: '2.1.0',
license: '''Copyright 2016, the Dart project authors. All rights reserved.
version: '2.1.1',
license: '''Copyright 2016, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
@@ -240,7 +241,7 @@ met:
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
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
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.',
repository: 'https://github.com/dart-lang/collection',
authors: [],
version: '1.16.0',
version: '1.17.0',
license: '''Copyright 2015, the Dart project authors.
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.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
isDirectDependency: true,
),
Package(
name: 'convert',
@@ -1170,6 +1171,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: 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(
name: 'cupertino_icons',
description:
@@ -1561,6 +1595,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: 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(
name: 'fixnum',
description:
@@ -1604,7 +1671,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
description: 'A framework for writing Flutter applications',
homepage: 'https://flutter.dev',
authors: [],
version: '3.3.8',
version: '3.7.3',
license: '''Copyright 2014 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -1837,6 +1904,75 @@ SOFTWARE.''',
isSdk: 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(
name: 'freezed',
description:
@@ -2273,9 +2409,9 @@ SOFTWARE.''',
name: 'js',
description:
'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: [],
version: '0.6.4',
version: '0.6.5',
license: '''Copyright 2012, the Dart project authors.
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,
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(
name: 'json_annotation',
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.',
repository: 'https://github.com/dart-lang/matcher',
authors: [],
version: '0.12.12',
version: '0.12.13',
license: '''Copyright 2014, the Dart project authors.
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(
name: 'material_color_utilities',
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:
'https://github.com/material-foundation/material-color-utilities',
'https://github.com/material-foundation/material-color-utilities/tree/main/dart',
authors: [],
version: '0.1.5',
version: '0.2.0',
license: '''Apache License
Version 2.0, January 2004
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.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
isDirectDependency: true,
),
Package(
name: 'path_provider',
@@ -3181,6 +3350,167 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: 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(
name: 'petitparser',
description:
@@ -3604,6 +3934,102 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
isSdk: 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(
name: 'source_gen',
description:
@@ -3858,9 +4284,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
Package(
name: 'source_span',
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: [],
version: '1.9.0',
version: '1.9.1',
license: '''Copyright 2014, the Dart project authors.
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',
description:
'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: [],
version: '1.10.0',
license: '''Copyright 2014, the Dart project authors. All rights reserved.
version: '1.11.0',
license: '''Copyright 2014, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
@@ -3982,7 +4409,7 @@ met:
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
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
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',
description:
'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: [],
version: '2.1.0',
license: '''Copyright 2015, the Dart project authors. All rights reserved.
version: '2.1.1',
license: '''Copyright 2015, the Dart project authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
@@ -4019,7 +4447,7 @@ met:
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
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived
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.',
repository: 'https://github.com/dart-lang/string_scanner',
authors: [],
version: '1.1.1',
version: '1.2.0',
license: '''Copyright 2014, the Dart project authors.
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(
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',
authors: [],
version: '0.4.12',
version: '0.4.16',
license: '''Copyright 2018, the Dart project authors.
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.',
repository: 'https://github.com/google/vector_math.dart',
authors: [],
version: '2.1.2',
version: '2.1.4',
license: '''Copyright 2015, Google Inc. All rights reserved.
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.',
homepage: 'https://github.com/renggli/dart-xml',
authors: [],
version: '6.1.0',
version: '6.2.2',
license: '''The MIT License
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.''',
isMarkdown: false,
isSdk: false,
isDirectDependency: false,
isDirectDependency: true,
),
Package(
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/ui/bloc/anime_list_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/navigation_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/anime_list.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/settings.dart';
import 'package:flutter/material.dart';
@@ -32,6 +34,7 @@ void main() async {
GetIt.I.registerSingleton<DetailsBloc>(DetailsBloc());
GetIt.I.registerSingleton<NavigationBloc>(NavigationBloc(navKey));
GetIt.I.registerSingleton<SettingsBloc>(SettingsBloc());
GetIt.I.registerSingleton<CalendarBloc>(CalendarBloc());
// Load animes
GetIt.I.get<AnimeListBloc>().add(
@@ -59,6 +62,9 @@ void main() async {
BlocProvider<SettingsBloc>(
create: (_) => GetIt.I.get<SettingsBloc>(),
),
BlocProvider<CalendarBloc>(
create: (_) => GetIt.I.get<CalendarBloc>(),
),
],
child: MyApp(navKey),
),
@@ -95,6 +101,8 @@ class MyApp extends StatelessWidget {
return AnimeListPage.route;
case animeSearchRoute:
return AnimeSearchPage.route;
case calendarRoute:
return CalendarPage.route;
case detailsRoute:
return DetailsPage.route;
case aboutRoute:

View File

@@ -1,9 +1,20 @@
import 'package:anitrack/src/data/type.dart';
import 'package:anitrack/src/service/database.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'anime.freezed.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
@freezed
class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
@@ -25,6 +36,12 @@ class AnimeTrackingData with _$AnimeTrackingData, TrackingMedium {
/// URL to the thumbnail/cover art for the anime.
String thumbnailUrl,
/// Flag whether the anime is airing
@BoolConverter() bool airing,
/// The day of the week the anime is airing
String? broadcastDay,
) = _AnimeTrackingData;
/// JSON

View File

@@ -1,7 +1,7 @@
// 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
// 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';
@@ -12,7 +12,7 @@ part of 'anime.dart';
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#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) {
return _AnimeTrackingData.fromJson(json);
@@ -39,8 +39,19 @@ mixin _$AnimeTrackingData {
/// URL to the thumbnail/cover art for the anime.
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;
@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 =>
throw _privateConstructorUsedError;
}
@@ -49,136 +60,173 @@ mixin _$AnimeTrackingData {
abstract class $AnimeTrackingDataCopyWith<$Res> {
factory $AnimeTrackingDataCopyWith(
AnimeTrackingData value, $Res Function(AnimeTrackingData) then) =
_$AnimeTrackingDataCopyWithImpl<$Res>;
_$AnimeTrackingDataCopyWithImpl<$Res, AnimeTrackingData>;
@useResult
$Res call(
{String id,
@MediumTrackingStateConverter() MediumTrackingState state,
String title,
int episodesWatched,
int? episodesTotal,
String thumbnailUrl});
String thumbnailUrl,
@BoolConverter() bool airing,
String? broadcastDay});
}
/// @nodoc
class _$AnimeTrackingDataCopyWithImpl<$Res>
class _$AnimeTrackingDataCopyWithImpl<$Res, $Val extends AnimeTrackingData>
implements $AnimeTrackingDataCopyWith<$Res> {
_$AnimeTrackingDataCopyWithImpl(this._value, this._then);
final AnimeTrackingData _value;
// 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
$Res call({
Object? id = freezed,
Object? state = freezed,
Object? title = freezed,
Object? episodesWatched = freezed,
Object? id = null,
Object? state = null,
Object? title = null,
Object? episodesWatched = null,
Object? episodesTotal = freezed,
Object? thumbnailUrl = freezed,
Object? thumbnailUrl = null,
Object? airing = null,
Object? broadcastDay = freezed,
}) {
return _then(_value.copyWith(
id: id == freezed
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
state: state == freezed
state: null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
title: title == freezed
title: null == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
episodesWatched: episodesWatched == freezed
episodesWatched: null == episodesWatched
? _value.episodesWatched
: episodesWatched // ignore: cast_nullable_to_non_nullable
as int,
episodesTotal: episodesTotal == freezed
episodesTotal: freezed == episodesTotal
? _value.episodesTotal
: episodesTotal // ignore: cast_nullable_to_non_nullable
as int?,
thumbnailUrl: thumbnailUrl == freezed
thumbnailUrl: null == thumbnailUrl
? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable
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
abstract class _$$_AnimeTrackingDataCopyWith<$Res>
abstract class _$$AnimeTrackingDataImplCopyWith<$Res>
implements $AnimeTrackingDataCopyWith<$Res> {
factory _$$_AnimeTrackingDataCopyWith(_$_AnimeTrackingData value,
$Res Function(_$_AnimeTrackingData) then) =
__$$_AnimeTrackingDataCopyWithImpl<$Res>;
factory _$$AnimeTrackingDataImplCopyWith(_$AnimeTrackingDataImpl value,
$Res Function(_$AnimeTrackingDataImpl) then) =
__$$AnimeTrackingDataImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String id,
@MediumTrackingStateConverter() MediumTrackingState state,
String title,
int episodesWatched,
int? episodesTotal,
String thumbnailUrl});
String thumbnailUrl,
@BoolConverter() bool airing,
String? broadcastDay});
}
/// @nodoc
class __$$_AnimeTrackingDataCopyWithImpl<$Res>
extends _$AnimeTrackingDataCopyWithImpl<$Res>
implements _$$_AnimeTrackingDataCopyWith<$Res> {
__$$_AnimeTrackingDataCopyWithImpl(
_$_AnimeTrackingData _value, $Res Function(_$_AnimeTrackingData) _then)
: super(_value, (v) => _then(v as _$_AnimeTrackingData));
@override
_$_AnimeTrackingData get _value => super._value as _$_AnimeTrackingData;
class __$$AnimeTrackingDataImplCopyWithImpl<$Res>
extends _$AnimeTrackingDataCopyWithImpl<$Res, _$AnimeTrackingDataImpl>
implements _$$AnimeTrackingDataImplCopyWith<$Res> {
__$$AnimeTrackingDataImplCopyWithImpl(_$AnimeTrackingDataImpl _value,
$Res Function(_$AnimeTrackingDataImpl) _then)
: super(_value, _then);
/// Create a copy of AnimeTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? state = freezed,
Object? title = freezed,
Object? episodesWatched = freezed,
Object? id = null,
Object? state = null,
Object? title = null,
Object? episodesWatched = null,
Object? episodesTotal = freezed,
Object? thumbnailUrl = freezed,
Object? thumbnailUrl = null,
Object? airing = null,
Object? broadcastDay = freezed,
}) {
return _then(_$_AnimeTrackingData(
id == freezed
return _then(_$AnimeTrackingDataImpl(
null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
state == freezed
null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
title == freezed
null == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
episodesWatched == freezed
null == episodesWatched
? _value.episodesWatched
: episodesWatched // ignore: cast_nullable_to_non_nullable
as int,
episodesTotal == freezed
freezed == episodesTotal
? _value.episodesTotal
: episodesTotal // ignore: cast_nullable_to_non_nullable
as int?,
thumbnailUrl == freezed
null == thumbnailUrl
? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable
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
@JsonSerializable()
class _$_AnimeTrackingData implements _AnimeTrackingData {
_$_AnimeTrackingData(this.id, @MediumTrackingStateConverter() this.state,
this.title, this.episodesWatched, this.episodesTotal, this.thumbnailUrl);
class _$AnimeTrackingDataImpl implements _AnimeTrackingData {
_$AnimeTrackingDataImpl(
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) =>
_$$_AnimeTrackingDataFromJson(json);
factory _$AnimeTrackingDataImpl.fromJson(Map<String, dynamic> json) =>
_$$AnimeTrackingDataImplFromJson(json);
/// The ID of the anime
@override
@@ -205,47 +253,56 @@ class _$_AnimeTrackingData implements _AnimeTrackingData {
@override
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
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
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_AnimeTrackingData &&
const DeepCollectionEquality().equals(other.id, id) &&
const DeepCollectionEquality().equals(other.state, state) &&
const DeepCollectionEquality().equals(other.title, title) &&
const DeepCollectionEquality()
.equals(other.episodesWatched, episodesWatched) &&
const DeepCollectionEquality()
.equals(other.episodesTotal, episodesTotal) &&
const DeepCollectionEquality()
.equals(other.thumbnailUrl, thumbnailUrl));
other is _$AnimeTrackingDataImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.state, state) || other.state == state) &&
(identical(other.title, title) || other.title == title) &&
(identical(other.episodesWatched, episodesWatched) ||
other.episodesWatched == episodesWatched) &&
(identical(other.episodesTotal, episodesTotal) ||
other.episodesTotal == episodesTotal) &&
(identical(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
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(id),
const DeepCollectionEquality().hash(state),
const DeepCollectionEquality().hash(title),
const DeepCollectionEquality().hash(episodesWatched),
const DeepCollectionEquality().hash(episodesTotal),
const DeepCollectionEquality().hash(thumbnailUrl));
int get hashCode => Object.hash(runtimeType, id, state, title,
episodesWatched, episodesTotal, thumbnailUrl, airing, broadcastDay);
@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
_$$_AnimeTrackingDataCopyWith<_$_AnimeTrackingData> get copyWith =>
__$$_AnimeTrackingDataCopyWithImpl<_$_AnimeTrackingData>(
@pragma('vm:prefer-inline')
_$$AnimeTrackingDataImplCopyWith<_$AnimeTrackingDataImpl> get copyWith =>
__$$AnimeTrackingDataImplCopyWithImpl<_$AnimeTrackingDataImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_AnimeTrackingDataToJson(
return _$$AnimeTrackingDataImplToJson(
this,
);
}
@@ -258,38 +315,51 @@ abstract class _AnimeTrackingData implements AnimeTrackingData {
final String title,
final int episodesWatched,
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) =
_$_AnimeTrackingData.fromJson;
@override
_$AnimeTrackingDataImpl.fromJson;
/// The ID of the anime
String get id;
@override
String get id;
/// The state of the anime
@override
@MediumTrackingStateConverter()
MediumTrackingState get state;
@override
/// The title of the anime
String get title;
@override
String get title;
/// Episodes in total.
int get episodesWatched;
@override
int get episodesWatched;
/// Episodes watched.
int? get episodesTotal;
@override
int? get episodesTotal;
/// URL to the thumbnail/cover art for the anime.
String get thumbnailUrl;
@override
@JsonKey(ignore: true)
_$$_AnimeTrackingDataCopyWith<_$_AnimeTrackingData> get copyWith =>
String get thumbnailUrl;
/// 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;
}

View File

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

View File

@@ -1,7 +1,7 @@
// 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
// 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';
@@ -12,7 +12,7 @@ part of 'manga.dart';
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#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) {
return _MangaTrackingData.fromJson(json);
@@ -42,8 +42,12 @@ mixin _$MangaTrackingData {
/// URL to the thumbnail/cover art for the manga.
String get thumbnailUrl => throw _privateConstructorUsedError;
/// Serializes this MangaTrackingData to a JSON map.
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 =>
throw _privateConstructorUsedError;
}
@@ -52,7 +56,8 @@ mixin _$MangaTrackingData {
abstract class $MangaTrackingDataCopyWith<$Res> {
factory $MangaTrackingDataCopyWith(
MangaTrackingData value, $Res Function(MangaTrackingData) then) =
_$MangaTrackingDataCopyWithImpl<$Res>;
_$MangaTrackingDataCopyWithImpl<$Res, MangaTrackingData>;
@useResult
$Res call(
{String id,
@MediumTrackingStateConverter() MediumTrackingState state,
@@ -64,64 +69,69 @@ abstract class $MangaTrackingDataCopyWith<$Res> {
}
/// @nodoc
class _$MangaTrackingDataCopyWithImpl<$Res>
class _$MangaTrackingDataCopyWithImpl<$Res, $Val extends MangaTrackingData>
implements $MangaTrackingDataCopyWith<$Res> {
_$MangaTrackingDataCopyWithImpl(this._value, this._then);
final MangaTrackingData _value;
// 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
$Res call({
Object? id = freezed,
Object? state = freezed,
Object? title = freezed,
Object? chaptersRead = freezed,
Object? volumesOwned = freezed,
Object? id = null,
Object? state = null,
Object? title = null,
Object? chaptersRead = null,
Object? volumesOwned = null,
Object? chaptersTotal = freezed,
Object? thumbnailUrl = freezed,
Object? thumbnailUrl = null,
}) {
return _then(_value.copyWith(
id: id == freezed
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
state: state == freezed
state: null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
title: title == freezed
title: null == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
chaptersRead: chaptersRead == freezed
chaptersRead: null == chaptersRead
? _value.chaptersRead
: chaptersRead // ignore: cast_nullable_to_non_nullable
as int,
volumesOwned: volumesOwned == freezed
volumesOwned: null == volumesOwned
? _value.volumesOwned
: volumesOwned // ignore: cast_nullable_to_non_nullable
as int,
chaptersTotal: chaptersTotal == freezed
chaptersTotal: freezed == chaptersTotal
? _value.chaptersTotal
: chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?,
thumbnailUrl: thumbnailUrl == freezed
thumbnailUrl: null == thumbnailUrl
? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,
));
) as $Val);
}
}
/// @nodoc
abstract class _$$_MangaTrackingDataCopyWith<$Res>
abstract class _$$MangaTrackingDataImplCopyWith<$Res>
implements $MangaTrackingDataCopyWith<$Res> {
factory _$$_MangaTrackingDataCopyWith(_$_MangaTrackingData value,
$Res Function(_$_MangaTrackingData) then) =
__$$_MangaTrackingDataCopyWithImpl<$Res>;
factory _$$MangaTrackingDataImplCopyWith(_$MangaTrackingDataImpl value,
$Res Function(_$MangaTrackingDataImpl) then) =
__$$MangaTrackingDataImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String id,
@MediumTrackingStateConverter() MediumTrackingState state,
@@ -133,52 +143,52 @@ abstract class _$$_MangaTrackingDataCopyWith<$Res>
}
/// @nodoc
class __$$_MangaTrackingDataCopyWithImpl<$Res>
extends _$MangaTrackingDataCopyWithImpl<$Res>
implements _$$_MangaTrackingDataCopyWith<$Res> {
__$$_MangaTrackingDataCopyWithImpl(
_$_MangaTrackingData _value, $Res Function(_$_MangaTrackingData) _then)
: super(_value, (v) => _then(v as _$_MangaTrackingData));
@override
_$_MangaTrackingData get _value => super._value as _$_MangaTrackingData;
class __$$MangaTrackingDataImplCopyWithImpl<$Res>
extends _$MangaTrackingDataCopyWithImpl<$Res, _$MangaTrackingDataImpl>
implements _$$MangaTrackingDataImplCopyWith<$Res> {
__$$MangaTrackingDataImplCopyWithImpl(_$MangaTrackingDataImpl _value,
$Res Function(_$MangaTrackingDataImpl) _then)
: super(_value, _then);
/// Create a copy of MangaTrackingData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? state = freezed,
Object? title = freezed,
Object? chaptersRead = freezed,
Object? volumesOwned = freezed,
Object? id = null,
Object? state = null,
Object? title = null,
Object? chaptersRead = null,
Object? volumesOwned = null,
Object? chaptersTotal = freezed,
Object? thumbnailUrl = freezed,
Object? thumbnailUrl = null,
}) {
return _then(_$_MangaTrackingData(
id == freezed
return _then(_$MangaTrackingDataImpl(
null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
state == freezed
null == state
? _value.state
: state // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
title == freezed
null == title
? _value.title
: title // ignore: cast_nullable_to_non_nullable
as String,
chaptersRead == freezed
null == chaptersRead
? _value.chaptersRead
: chaptersRead // ignore: cast_nullable_to_non_nullable
as int,
volumesOwned == freezed
null == volumesOwned
? _value.volumesOwned
: volumesOwned // ignore: cast_nullable_to_non_nullable
as int,
chaptersTotal == freezed
freezed == chaptersTotal
? _value.chaptersTotal
: chaptersTotal // ignore: cast_nullable_to_non_nullable
as int?,
thumbnailUrl == freezed
null == thumbnailUrl
? _value.thumbnailUrl
: thumbnailUrl // ignore: cast_nullable_to_non_nullable
as String,
@@ -188,8 +198,8 @@ class __$$_MangaTrackingDataCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$_MangaTrackingData implements _MangaTrackingData {
_$_MangaTrackingData(
class _$MangaTrackingDataImpl implements _MangaTrackingData {
_$MangaTrackingDataImpl(
this.id,
@MediumTrackingStateConverter() this.state,
this.title,
@@ -198,8 +208,8 @@ class _$_MangaTrackingData implements _MangaTrackingData {
this.chaptersTotal,
this.thumbnailUrl);
factory _$_MangaTrackingData.fromJson(Map<String, dynamic> json) =>
_$$_MangaTrackingDataFromJson(json);
factory _$MangaTrackingDataImpl.fromJson(Map<String, dynamic> json) =>
_$$MangaTrackingDataImplFromJson(json);
/// The ID of the manga
@override
@@ -236,44 +246,40 @@ class _$_MangaTrackingData implements _MangaTrackingData {
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_MangaTrackingData &&
const DeepCollectionEquality().equals(other.id, id) &&
const DeepCollectionEquality().equals(other.state, state) &&
const DeepCollectionEquality().equals(other.title, title) &&
const DeepCollectionEquality()
.equals(other.chaptersRead, chaptersRead) &&
const DeepCollectionEquality()
.equals(other.volumesOwned, volumesOwned) &&
const DeepCollectionEquality()
.equals(other.chaptersTotal, chaptersTotal) &&
const DeepCollectionEquality()
.equals(other.thumbnailUrl, thumbnailUrl));
other is _$MangaTrackingDataImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.state, state) || other.state == state) &&
(identical(other.title, title) || other.title == title) &&
(identical(other.chaptersRead, chaptersRead) ||
other.chaptersRead == chaptersRead) &&
(identical(other.volumesOwned, volumesOwned) ||
other.volumesOwned == volumesOwned) &&
(identical(other.chaptersTotal, chaptersTotal) ||
other.chaptersTotal == chaptersTotal) &&
(identical(other.thumbnailUrl, thumbnailUrl) ||
other.thumbnailUrl == thumbnailUrl));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
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));
int get hashCode => Object.hash(runtimeType, id, state, title, chaptersRead,
volumesOwned, chaptersTotal, 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
_$$_MangaTrackingDataCopyWith<_$_MangaTrackingData> get copyWith =>
__$$_MangaTrackingDataCopyWithImpl<_$_MangaTrackingData>(
@pragma('vm:prefer-inline')
_$$MangaTrackingDataImplCopyWith<_$MangaTrackingDataImpl> get copyWith =>
__$$MangaTrackingDataImplCopyWithImpl<_$MangaTrackingDataImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_MangaTrackingDataToJson(
return _$$MangaTrackingDataImplToJson(
this,
);
}
@@ -287,42 +293,44 @@ abstract class _MangaTrackingData implements MangaTrackingData {
final int chaptersRead,
final int volumesOwned,
final int? chaptersTotal,
final String thumbnailUrl) = _$_MangaTrackingData;
final String thumbnailUrl) = _$MangaTrackingDataImpl;
factory _MangaTrackingData.fromJson(Map<String, dynamic> json) =
_$_MangaTrackingData.fromJson;
@override
_$MangaTrackingDataImpl.fromJson;
/// The ID of the manga
String get id;
@override
String get id;
/// The state of the manga
@override
@MediumTrackingStateConverter()
MediumTrackingState get state;
@override
/// The title of the manga
@override
String get title;
@override
/// Chapters read.
@override
int get chaptersRead;
@override
/// Chapters read.
int get volumesOwned;
@override
int get volumesOwned;
/// Episodes watched.
int? get chaptersTotal;
@override
int? get chaptersTotal;
/// URL to the thumbnail/cover art for the manga.
String get thumbnailUrl;
@override
@JsonKey(ignore: true)
_$$_MangaTrackingDataCopyWith<_$_MangaTrackingData> get copyWith =>
String get thumbnailUrl;
/// 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;
}

View File

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

View File

@@ -5,6 +5,8 @@ class SearchResult {
this.total,
this.thumbnailUrl,
this.description,
this.isAiring,
this.broadcastDay,
);
/// The title of the anime.
@@ -22,4 +24,10 @@ class SearchResult {
/// The description of the anime
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, ...
enum MediumTrackingState {
/// Currently watching or reading
ongoing,
ongoing(0),
/// Done
completed,
completed(1),
/// Plan to watch or read
planned,
planned(2),
/// Dropped
dropped,
dropped(3),
/// Paused
paused,
paused(4),
/// Meta state
all,
}
all(-1);
/// Interface for the Anime and Manga data classes
abstract class TrackingMedium {
/// The ID of the medium
final String id = '';
const MediumTrackingState(this.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;
}
extension MediumStateExtension on MediumTrackingState {
int toInteger() {
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;
}
factory MediumTrackingState.fromInt(int id) {
switch (id) {
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;
}
String toNameString(TrackingMediumType type) {
return MediumTrackingState.planned;
}
/// The id of the value.
final int id;
String getName(TrackingMediumType type) {
assert(
this != MediumTrackingState.all,
'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
implements JsonConverter<MediumTrackingState, int> {
const MediumTrackingStateConverter();
@override
MediumTrackingState fromJson(int 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;
}
MediumTrackingState fromJson(int json) => MediumTrackingState.fromInt(json);
@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/manga.dart';
import 'package:anitrack/src/service/migrations/0000_airing.dart';
import 'package:anitrack/src/service/migrations/0000_score.dart';
import 'package:sqflite/sqflite.dart';
const animeTable = 'Anime';
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 {
await db.execute(
'''
@@ -16,7 +29,9 @@ Future<void> _createDatabase(Database db, int version) async {
episodesWatched INTEGER NOT NULL,
thumbnailUrl TEXT NOT NULL,
title TEXT NOT NULL,
score INTEGER
score INTEGER,
airing INTEGER NOT NULL,
broadcastDay TEXT
)''',
);
await db.execute(
@@ -40,7 +55,7 @@ class DatabaseService {
Future<void> initialize() async {
_db = await openDatabase(
'anitrack.db',
version: 2,
version: 3,
onConfigure: (db) async {
// In order to do schema changes during database upgrades, we disable foreign
// keys in the onConfigure phase, but re-enable them here.
@@ -56,6 +71,9 @@ class DatabaseService {
if (oldVersion < 2) {
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/service/database.dart';
import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart';
@@ -35,6 +36,8 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
final List<MangaTrackingData> _mangas =
List<MangaTrackingData>.empty(growable: true);
List<AnimeTrackingData> get unfilteredAnime => _animes;
List<AnimeTrackingData> _getFilteredAnime({
MediumTrackingState? trackingState,
}) {
@@ -63,7 +66,16 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
await GetIt.I.get<DatabaseService>().addAnime(event.data);
// 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);
}
} else {
_animes.add(event.data);
}
emit(
state.copyWith(
@@ -80,7 +92,17 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
await GetIt.I.get<DatabaseService>().addManga(event.data);
// 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);
}
} else {
_mangas.add(event.data);
}
emit(
state.copyWith(
@@ -270,6 +292,8 @@ class AnimeListBloc extends Bloc<AnimeListEvent, AnimeListState> {
animes: _getFilteredAnime(),
),
);
await GetIt.I.get<DatabaseService>().updateAnime(event.anime);
}
Future<void> _onMangaUpdated(

View File

@@ -1,7 +1,7 @@
// 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
// 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';
@@ -12,7 +12,7 @@ part of 'anime_list_bloc.dart';
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#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
mixin _$AnimeListState {
@@ -25,7 +25,9 @@ mixin _$AnimeListState {
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 =>
throw _privateConstructorUsedError;
}
@@ -34,7 +36,8 @@ mixin _$AnimeListState {
abstract class $AnimeListStateCopyWith<$Res> {
factory $AnimeListStateCopyWith(
AnimeListState value, $Res Function(AnimeListState) then) =
_$AnimeListStateCopyWithImpl<$Res>;
_$AnimeListStateCopyWithImpl<$Res, AnimeListState>;
@useResult
$Res call(
{bool buttonVisibility,
List<AnimeTrackingData> animes,
@@ -45,59 +48,64 @@ abstract class $AnimeListStateCopyWith<$Res> {
}
/// @nodoc
class _$AnimeListStateCopyWithImpl<$Res>
class _$AnimeListStateCopyWithImpl<$Res, $Val extends AnimeListState>
implements $AnimeListStateCopyWith<$Res> {
_$AnimeListStateCopyWithImpl(this._value, this._then);
final AnimeListState _value;
// 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
$Res call({
Object? buttonVisibility = freezed,
Object? animes = freezed,
Object? mangas = freezed,
Object? animeFilterState = freezed,
Object? mangaFilterState = freezed,
Object? trackingType = freezed,
Object? buttonVisibility = null,
Object? animes = null,
Object? mangas = null,
Object? animeFilterState = null,
Object? mangaFilterState = null,
Object? trackingType = null,
}) {
return _then(_value.copyWith(
buttonVisibility: buttonVisibility == freezed
buttonVisibility: null == buttonVisibility
? _value.buttonVisibility
: buttonVisibility // ignore: cast_nullable_to_non_nullable
as bool,
animes: animes == freezed
animes: null == animes
? _value.animes
: animes // ignore: cast_nullable_to_non_nullable
as List<AnimeTrackingData>,
mangas: mangas == freezed
mangas: null == mangas
? _value.mangas
: mangas // ignore: cast_nullable_to_non_nullable
as List<MangaTrackingData>,
animeFilterState: animeFilterState == freezed
animeFilterState: null == animeFilterState
? _value.animeFilterState
: animeFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
mangaFilterState: mangaFilterState == freezed
mangaFilterState: null == mangaFilterState
? _value.mangaFilterState
: mangaFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
trackingType: trackingType == freezed
trackingType: null == trackingType
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
));
) as $Val);
}
}
/// @nodoc
abstract class _$$_AnimeListStateCopyWith<$Res>
abstract class _$$AnimeListStateImplCopyWith<$Res>
implements $AnimeListStateCopyWith<$Res> {
factory _$$_AnimeListStateCopyWith(
_$_AnimeListState value, $Res Function(_$_AnimeListState) then) =
__$$_AnimeListStateCopyWithImpl<$Res>;
factory _$$AnimeListStateImplCopyWith(_$AnimeListStateImpl value,
$Res Function(_$AnimeListStateImpl) then) =
__$$AnimeListStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool buttonVisibility,
List<AnimeTrackingData> animes,
@@ -108,47 +116,47 @@ abstract class _$$_AnimeListStateCopyWith<$Res>
}
/// @nodoc
class __$$_AnimeListStateCopyWithImpl<$Res>
extends _$AnimeListStateCopyWithImpl<$Res>
implements _$$_AnimeListStateCopyWith<$Res> {
__$$_AnimeListStateCopyWithImpl(
_$_AnimeListState _value, $Res Function(_$_AnimeListState) _then)
: super(_value, (v) => _then(v as _$_AnimeListState));
@override
_$_AnimeListState get _value => super._value as _$_AnimeListState;
class __$$AnimeListStateImplCopyWithImpl<$Res>
extends _$AnimeListStateCopyWithImpl<$Res, _$AnimeListStateImpl>
implements _$$AnimeListStateImplCopyWith<$Res> {
__$$AnimeListStateImplCopyWithImpl(
_$AnimeListStateImpl _value, $Res Function(_$AnimeListStateImpl) _then)
: super(_value, _then);
/// Create a copy of AnimeListState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? buttonVisibility = freezed,
Object? animes = freezed,
Object? mangas = freezed,
Object? animeFilterState = freezed,
Object? mangaFilterState = freezed,
Object? trackingType = freezed,
Object? buttonVisibility = null,
Object? animes = null,
Object? mangas = null,
Object? animeFilterState = null,
Object? mangaFilterState = null,
Object? trackingType = null,
}) {
return _then(_$_AnimeListState(
buttonVisibility: buttonVisibility == freezed
return _then(_$AnimeListStateImpl(
buttonVisibility: null == buttonVisibility
? _value.buttonVisibility
: buttonVisibility // ignore: cast_nullable_to_non_nullable
as bool,
animes: animes == freezed
animes: null == animes
? _value._animes
: animes // ignore: cast_nullable_to_non_nullable
as List<AnimeTrackingData>,
mangas: mangas == freezed
mangas: null == mangas
? _value._mangas
: mangas // ignore: cast_nullable_to_non_nullable
as List<MangaTrackingData>,
animeFilterState: animeFilterState == freezed
animeFilterState: null == animeFilterState
? _value.animeFilterState
: animeFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
mangaFilterState: mangaFilterState == freezed
mangaFilterState: null == mangaFilterState
? _value.mangaFilterState
: mangaFilterState // ignore: cast_nullable_to_non_nullable
as MediumTrackingState,
trackingType: trackingType == freezed
trackingType: null == trackingType
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
@@ -158,8 +166,8 @@ class __$$_AnimeListStateCopyWithImpl<$Res>
/// @nodoc
class _$_AnimeListState implements _AnimeListState {
_$_AnimeListState(
class _$AnimeListStateImpl implements _AnimeListState {
_$AnimeListStateImpl(
{this.buttonVisibility = true,
final List<AnimeTrackingData> animes = const [],
final List<MangaTrackingData> mangas = const [],
@@ -176,6 +184,7 @@ class _$_AnimeListState implements _AnimeListState {
@override
@JsonKey()
List<AnimeTrackingData> get animes {
if (_animes is EqualUnmodifiableListView) return _animes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_animes);
}
@@ -184,6 +193,7 @@ class _$_AnimeListState implements _AnimeListState {
@override
@JsonKey()
List<MangaTrackingData> get mangas {
if (_mangas is EqualUnmodifiableListView) return _mangas;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_mangas);
}
@@ -204,36 +214,40 @@ class _$_AnimeListState implements _AnimeListState {
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_AnimeListState &&
const DeepCollectionEquality()
.equals(other.buttonVisibility, buttonVisibility) &&
other is _$AnimeListStateImpl &&
(identical(other.buttonVisibility, buttonVisibility) ||
other.buttonVisibility == buttonVisibility) &&
const DeepCollectionEquality().equals(other._animes, _animes) &&
const DeepCollectionEquality().equals(other._mangas, _mangas) &&
const DeepCollectionEquality()
.equals(other.animeFilterState, animeFilterState) &&
const DeepCollectionEquality()
.equals(other.mangaFilterState, mangaFilterState) &&
const DeepCollectionEquality()
.equals(other.trackingType, trackingType));
(identical(other.animeFilterState, animeFilterState) ||
other.animeFilterState == animeFilterState) &&
(identical(other.mangaFilterState, mangaFilterState) ||
other.mangaFilterState == mangaFilterState) &&
(identical(other.trackingType, trackingType) ||
other.trackingType == trackingType));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(buttonVisibility),
buttonVisibility,
const DeepCollectionEquality().hash(_animes),
const DeepCollectionEquality().hash(_mangas),
const DeepCollectionEquality().hash(animeFilterState),
const DeepCollectionEquality().hash(mangaFilterState),
const DeepCollectionEquality().hash(trackingType));
animeFilterState,
mangaFilterState,
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
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith =>
__$$_AnimeListStateCopyWithImpl<_$_AnimeListState>(this, _$identity);
@pragma('vm:prefer-inline')
_$$AnimeListStateImplCopyWith<_$AnimeListStateImpl> get copyWith =>
__$$AnimeListStateImplCopyWithImpl<_$AnimeListStateImpl>(
this, _$identity);
}
abstract class _AnimeListState implements AnimeListState {
@@ -243,7 +257,7 @@ abstract class _AnimeListState implements AnimeListState {
final List<MangaTrackingData> mangas,
final MediumTrackingState animeFilterState,
final MediumTrackingState mangaFilterState,
final TrackingMediumType trackingType}) = _$_AnimeListState;
final TrackingMediumType trackingType}) = _$AnimeListStateImpl;
@override
bool get buttonVisibility;
@@ -257,8 +271,11 @@ abstract class _AnimeListState implements AnimeListState {
MediumTrackingState get mangaFilterState;
@override
TrackingMediumType get trackingType;
/// Create a copy of AnimeListState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
_$$_AnimeListStateCopyWith<_$_AnimeListState> get copyWith =>
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AnimeListStateImplCopyWith<_$AnimeListStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -17,10 +17,14 @@ class AnimeEpisodeDecrementedEvent extends AnimeListEvent {
}
class AnimeAddedEvent extends AnimeListEvent {
AnimeAddedEvent(this.data);
AnimeAddedEvent(this.data, {this.checkIfExists = false});
/// The anime to add.
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
@@ -43,9 +47,12 @@ class AnimeTrackingTypeChanged extends AnimeListEvent {
}
class AnimeUpdatedEvent extends AnimeListEvent {
AnimeUpdatedEvent(this.anime);
AnimeUpdatedEvent(this.anime, {this.commit = false});
final AnimeTrackingData anime;
/// Commit the new anime data to the database.
final bool commit;
}
class AnimeRemovedEvent extends AnimeListEvent {
@@ -56,10 +63,14 @@ class AnimeRemovedEvent extends AnimeListEvent {
}
class MangaAddedEvent extends AnimeListEvent {
MangaAddedEvent(this.data);
MangaAddedEvent(this.data, {this.checkIfExists = false});
/// The manga to add.
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

View File

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

View File

@@ -1,7 +1,7 @@
// 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
// 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';
@@ -12,7 +12,7 @@ part of 'anime_search_bloc.dart';
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#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
mixin _$AnimeSearchState {
@@ -21,7 +21,9 @@ mixin _$AnimeSearchState {
bool get working => 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 =>
throw _privateConstructorUsedError;
}
@@ -30,7 +32,8 @@ mixin _$AnimeSearchState {
abstract class $AnimeSearchStateCopyWith<$Res> {
factory $AnimeSearchStateCopyWith(
AnimeSearchState value, $Res Function(AnimeSearchState) then) =
_$AnimeSearchStateCopyWithImpl<$Res>;
_$AnimeSearchStateCopyWithImpl<$Res, AnimeSearchState>;
@useResult
$Res call(
{TrackingMediumType trackingType,
String searchQuery,
@@ -39,49 +42,54 @@ abstract class $AnimeSearchStateCopyWith<$Res> {
}
/// @nodoc
class _$AnimeSearchStateCopyWithImpl<$Res>
class _$AnimeSearchStateCopyWithImpl<$Res, $Val extends AnimeSearchState>
implements $AnimeSearchStateCopyWith<$Res> {
_$AnimeSearchStateCopyWithImpl(this._value, this._then);
final AnimeSearchState _value;
// 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
$Res call({
Object? trackingType = freezed,
Object? searchQuery = freezed,
Object? working = freezed,
Object? searchResults = freezed,
Object? trackingType = null,
Object? searchQuery = null,
Object? working = null,
Object? searchResults = null,
}) {
return _then(_value.copyWith(
trackingType: trackingType == freezed
trackingType: null == trackingType
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
searchQuery: searchQuery == freezed
searchQuery: null == searchQuery
? _value.searchQuery
: searchQuery // ignore: cast_nullable_to_non_nullable
as String,
working: working == freezed
working: null == working
? _value.working
: working // ignore: cast_nullable_to_non_nullable
as bool,
searchResults: searchResults == freezed
searchResults: null == searchResults
? _value.searchResults
: searchResults // ignore: cast_nullable_to_non_nullable
as List<SearchResult>,
));
) as $Val);
}
}
/// @nodoc
abstract class _$$_AnimeSearchStateCopyWith<$Res>
abstract class _$$AnimeSearchStateImplCopyWith<$Res>
implements $AnimeSearchStateCopyWith<$Res> {
factory _$$_AnimeSearchStateCopyWith(
_$_AnimeSearchState value, $Res Function(_$_AnimeSearchState) then) =
__$$_AnimeSearchStateCopyWithImpl<$Res>;
factory _$$AnimeSearchStateImplCopyWith(_$AnimeSearchStateImpl value,
$Res Function(_$AnimeSearchStateImpl) then) =
__$$AnimeSearchStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{TrackingMediumType trackingType,
String searchQuery,
@@ -90,37 +98,37 @@ abstract class _$$_AnimeSearchStateCopyWith<$Res>
}
/// @nodoc
class __$$_AnimeSearchStateCopyWithImpl<$Res>
extends _$AnimeSearchStateCopyWithImpl<$Res>
implements _$$_AnimeSearchStateCopyWith<$Res> {
__$$_AnimeSearchStateCopyWithImpl(
_$_AnimeSearchState _value, $Res Function(_$_AnimeSearchState) _then)
: super(_value, (v) => _then(v as _$_AnimeSearchState));
@override
_$_AnimeSearchState get _value => super._value as _$_AnimeSearchState;
class __$$AnimeSearchStateImplCopyWithImpl<$Res>
extends _$AnimeSearchStateCopyWithImpl<$Res, _$AnimeSearchStateImpl>
implements _$$AnimeSearchStateImplCopyWith<$Res> {
__$$AnimeSearchStateImplCopyWithImpl(_$AnimeSearchStateImpl _value,
$Res Function(_$AnimeSearchStateImpl) _then)
: super(_value, _then);
/// Create a copy of AnimeSearchState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? trackingType = freezed,
Object? searchQuery = freezed,
Object? working = freezed,
Object? searchResults = freezed,
Object? trackingType = null,
Object? searchQuery = null,
Object? working = null,
Object? searchResults = null,
}) {
return _then(_$_AnimeSearchState(
trackingType: trackingType == freezed
return _then(_$AnimeSearchStateImpl(
trackingType: null == trackingType
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
searchQuery: searchQuery == freezed
searchQuery: null == searchQuery
? _value.searchQuery
: searchQuery // ignore: cast_nullable_to_non_nullable
as String,
working: working == freezed
working: null == working
? _value.working
: working // ignore: cast_nullable_to_non_nullable
as bool,
searchResults: searchResults == freezed
searchResults: null == searchResults
? _value._searchResults
: searchResults // ignore: cast_nullable_to_non_nullable
as List<SearchResult>,
@@ -130,8 +138,8 @@ class __$$_AnimeSearchStateCopyWithImpl<$Res>
/// @nodoc
class _$_AnimeSearchState implements _AnimeSearchState {
_$_AnimeSearchState(
class _$AnimeSearchStateImpl implements _AnimeSearchState {
_$AnimeSearchStateImpl(
{this.trackingType = TrackingMediumType.anime,
this.searchQuery = '',
this.working = false,
@@ -151,6 +159,7 @@ class _$_AnimeSearchState implements _AnimeSearchState {
@override
@JsonKey()
List<SearchResult> get searchResults {
if (_searchResults is EqualUnmodifiableListView) return _searchResults;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_searchResults);
}
@@ -161,31 +170,31 @@ class _$_AnimeSearchState implements _AnimeSearchState {
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_AnimeSearchState &&
const DeepCollectionEquality()
.equals(other.trackingType, trackingType) &&
const DeepCollectionEquality()
.equals(other.searchQuery, searchQuery) &&
const DeepCollectionEquality().equals(other.working, working) &&
other is _$AnimeSearchStateImpl &&
(identical(other.trackingType, trackingType) ||
other.trackingType == trackingType) &&
(identical(other.searchQuery, searchQuery) ||
other.searchQuery == searchQuery) &&
(identical(other.working, working) || other.working == working) &&
const DeepCollectionEquality()
.equals(other._searchResults, _searchResults));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(trackingType),
const DeepCollectionEquality().hash(searchQuery),
const DeepCollectionEquality().hash(working),
const DeepCollectionEquality().hash(_searchResults));
int get hashCode => Object.hash(runtimeType, trackingType, searchQuery,
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
_$$_AnimeSearchStateCopyWith<_$_AnimeSearchState> get copyWith =>
__$$_AnimeSearchStateCopyWithImpl<_$_AnimeSearchState>(this, _$identity);
@pragma('vm:prefer-inline')
_$$AnimeSearchStateImplCopyWith<_$AnimeSearchStateImpl> get copyWith =>
__$$AnimeSearchStateImplCopyWithImpl<_$AnimeSearchStateImpl>(
this, _$identity);
}
abstract class _AnimeSearchState implements AnimeSearchState {
@@ -193,7 +202,7 @@ abstract class _AnimeSearchState implements AnimeSearchState {
{final TrackingMediumType trackingType,
final String searchQuery,
final bool working,
final List<SearchResult> searchResults}) = _$_AnimeSearchState;
final List<SearchResult> searchResults}) = _$AnimeSearchStateImpl;
@override
TrackingMediumType get trackingType;
@@ -203,8 +212,11 @@ abstract class _AnimeSearchState implements AnimeSearchState {
bool get working;
@override
List<SearchResult> get searchResults;
/// Create a copy of AnimeSearchState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
_$$_AnimeSearchStateCopyWith<_$_AnimeSearchState> get copyWith =>
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AnimeSearchStateImplCopyWith<_$AnimeSearchStateImpl> get copyWith =>
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(
state.copyWith(
trackingType: TrackingMediumType.anime,
heroImagePrefix: event.heroImagePrefix,
data: event.anime,
),
);

View File

@@ -1,7 +1,7 @@
// 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
// 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';
@@ -12,14 +12,17 @@ part of 'details_bloc.dart';
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#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
mixin _$DetailsState {
TrackingMedium? get data => throw _privateConstructorUsedError;
String? get heroImagePrefix => 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 =>
throw _privateConstructorUsedError;
}
@@ -28,68 +31,91 @@ mixin _$DetailsState {
abstract class $DetailsStateCopyWith<$Res> {
factory $DetailsStateCopyWith(
DetailsState value, $Res Function(DetailsState) then) =
_$DetailsStateCopyWithImpl<$Res>;
$Res call({TrackingMedium? data, TrackingMediumType trackingType});
_$DetailsStateCopyWithImpl<$Res, DetailsState>;
@useResult
$Res call(
{TrackingMedium? data,
String? heroImagePrefix,
TrackingMediumType trackingType});
}
/// @nodoc
class _$DetailsStateCopyWithImpl<$Res> implements $DetailsStateCopyWith<$Res> {
class _$DetailsStateCopyWithImpl<$Res, $Val extends DetailsState>
implements $DetailsStateCopyWith<$Res> {
_$DetailsStateCopyWithImpl(this._value, this._then);
final DetailsState _value;
// 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
$Res call({
Object? data = freezed,
Object? trackingType = freezed,
Object? heroImagePrefix = freezed,
Object? trackingType = null,
}) {
return _then(_value.copyWith(
data: data == freezed
data: freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as TrackingMedium?,
trackingType: trackingType == freezed
heroImagePrefix: freezed == heroImagePrefix
? _value.heroImagePrefix
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
as String?,
trackingType: null == trackingType
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
));
) as $Val);
}
}
/// @nodoc
abstract class _$$_DetailsStateCopyWith<$Res>
abstract class _$$DetailsStateImplCopyWith<$Res>
implements $DetailsStateCopyWith<$Res> {
factory _$$_DetailsStateCopyWith(
_$_DetailsState value, $Res Function(_$_DetailsState) then) =
__$$_DetailsStateCopyWithImpl<$Res>;
factory _$$DetailsStateImplCopyWith(
_$DetailsStateImpl value, $Res Function(_$DetailsStateImpl) then) =
__$$DetailsStateImplCopyWithImpl<$Res>;
@override
$Res call({TrackingMedium? data, TrackingMediumType trackingType});
@useResult
$Res call(
{TrackingMedium? data,
String? heroImagePrefix,
TrackingMediumType trackingType});
}
/// @nodoc
class __$$_DetailsStateCopyWithImpl<$Res>
extends _$DetailsStateCopyWithImpl<$Res>
implements _$$_DetailsStateCopyWith<$Res> {
__$$_DetailsStateCopyWithImpl(
_$_DetailsState _value, $Res Function(_$_DetailsState) _then)
: super(_value, (v) => _then(v as _$_DetailsState));
@override
_$_DetailsState get _value => super._value as _$_DetailsState;
class __$$DetailsStateImplCopyWithImpl<$Res>
extends _$DetailsStateCopyWithImpl<$Res, _$DetailsStateImpl>
implements _$$DetailsStateImplCopyWith<$Res> {
__$$DetailsStateImplCopyWithImpl(
_$DetailsStateImpl _value, $Res Function(_$DetailsStateImpl) _then)
: super(_value, _then);
/// Create a copy of DetailsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? data = freezed,
Object? trackingType = freezed,
Object? heroImagePrefix = freezed,
Object? trackingType = null,
}) {
return _then(_$_DetailsState(
data: data == freezed
return _then(_$DetailsStateImpl(
data: freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as TrackingMedium?,
trackingType: trackingType == freezed
heroImagePrefix: freezed == heroImagePrefix
? _value.heroImagePrefix
: heroImagePrefix // ignore: cast_nullable_to_non_nullable
as String?,
trackingType: null == trackingType
? _value.trackingType
: trackingType // ignore: cast_nullable_to_non_nullable
as TrackingMediumType,
@@ -99,53 +125,67 @@ class __$$_DetailsStateCopyWithImpl<$Res>
/// @nodoc
class _$_DetailsState implements _DetailsState {
_$_DetailsState({this.data, this.trackingType = TrackingMediumType.anime});
class _$DetailsStateImpl implements _DetailsState {
_$DetailsStateImpl(
{this.data,
this.heroImagePrefix,
this.trackingType = TrackingMediumType.anime});
@override
final TrackingMedium? data;
@override
final String? heroImagePrefix;
@override
@JsonKey()
final TrackingMediumType trackingType;
@override
String toString() {
return 'DetailsState(data: $data, trackingType: $trackingType)';
return 'DetailsState(data: $data, heroImagePrefix: $heroImagePrefix, trackingType: $trackingType)';
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_DetailsState &&
const DeepCollectionEquality().equals(other.data, data) &&
const DeepCollectionEquality()
.equals(other.trackingType, trackingType));
other is _$DetailsStateImpl &&
(identical(other.data, data) || other.data == data) &&
(identical(other.heroImagePrefix, heroImagePrefix) ||
other.heroImagePrefix == heroImagePrefix) &&
(identical(other.trackingType, trackingType) ||
other.trackingType == trackingType));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(data),
const DeepCollectionEquality().hash(trackingType));
int get hashCode =>
Object.hash(runtimeType, data, heroImagePrefix, 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
_$$_DetailsStateCopyWith<_$_DetailsState> get copyWith =>
__$$_DetailsStateCopyWithImpl<_$_DetailsState>(this, _$identity);
@pragma('vm:prefer-inline')
_$$DetailsStateImplCopyWith<_$DetailsStateImpl> get copyWith =>
__$$DetailsStateImplCopyWithImpl<_$DetailsStateImpl>(this, _$identity);
}
abstract class _DetailsState implements DetailsState {
factory _DetailsState(
{final TrackingMedium? data,
final TrackingMediumType trackingType}) = _$_DetailsState;
final String? heroImagePrefix,
final TrackingMediumType trackingType}) = _$DetailsStateImpl;
@override
TrackingMedium? get data;
@override
TrackingMediumType get trackingType;
String? get heroImagePrefix;
@override
@JsonKey(ignore: true)
_$$_DetailsStateCopyWith<_$_DetailsState> get copyWith =>
TrackingMediumType get trackingType;
/// 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;
}

View File

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

View File

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

View File

@@ -1,19 +1,25 @@
import 'dart:async';
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/manga.dart';
import 'package:anitrack/src/data/type.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:bloc/bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get_it/get_it.dart';
import 'package:jikan_api/jikan_api.dart';
import 'package:path/path.dart' as path;
import 'package:xml/xml.dart';
part 'settings_state.dart';
part 'settings_event.dart';
part 'settings_bloc.freezed.dart';
part 'settings_event.dart';
part 'settings_state.dart';
MediumTrackingState malStatusToTrackingState(String status) {
switch (status) {
@@ -39,6 +45,8 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
SettingsBloc() : super(SettingsState()) {
on<AnimeListImportedEvent>(_onAnimeListImported);
on<MangaListImportedEvent>(_onMangaListImported);
on<DataExportedEvent>(_onDataExported);
on<DataImportedEvent>(_onDataImported);
}
void _showLoadingSpinner(Emitter<SettingsState> emit) {
@@ -119,6 +127,9 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
// 0 means that MAL does not know
totalEpisodes == 0 ? null : totalEpisodes,
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
_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
// 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
// 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';
@@ -12,7 +12,7 @@ part of 'settings_bloc.dart';
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#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
mixin _$SettingsState {
@@ -20,7 +20,9 @@ mixin _$SettingsState {
int get importCurrent => 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 =>
throw _privateConstructorUsedError;
}
@@ -29,79 +31,85 @@ mixin _$SettingsState {
abstract class $SettingsStateCopyWith<$Res> {
factory $SettingsStateCopyWith(
SettingsState value, $Res Function(SettingsState) then) =
_$SettingsStateCopyWithImpl<$Res>;
_$SettingsStateCopyWithImpl<$Res, SettingsState>;
@useResult
$Res call({bool importSpinnerVisible, int importCurrent, int importTotal});
}
/// @nodoc
class _$SettingsStateCopyWithImpl<$Res>
class _$SettingsStateCopyWithImpl<$Res, $Val extends SettingsState>
implements $SettingsStateCopyWith<$Res> {
_$SettingsStateCopyWithImpl(this._value, this._then);
final SettingsState _value;
// 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
$Res call({
Object? importSpinnerVisible = freezed,
Object? importCurrent = freezed,
Object? importTotal = freezed,
Object? importSpinnerVisible = null,
Object? importCurrent = null,
Object? importTotal = null,
}) {
return _then(_value.copyWith(
importSpinnerVisible: importSpinnerVisible == freezed
importSpinnerVisible: null == importSpinnerVisible
? _value.importSpinnerVisible
: importSpinnerVisible // ignore: cast_nullable_to_non_nullable
as bool,
importCurrent: importCurrent == freezed
importCurrent: null == importCurrent
? _value.importCurrent
: importCurrent // ignore: cast_nullable_to_non_nullable
as int,
importTotal: importTotal == freezed
importTotal: null == importTotal
? _value.importTotal
: importTotal // ignore: cast_nullable_to_non_nullable
as int,
));
) as $Val);
}
}
/// @nodoc
abstract class _$$_SettingsStateCopyWith<$Res>
abstract class _$$SettingsStateImplCopyWith<$Res>
implements $SettingsStateCopyWith<$Res> {
factory _$$_SettingsStateCopyWith(
_$_SettingsState value, $Res Function(_$_SettingsState) then) =
__$$_SettingsStateCopyWithImpl<$Res>;
factory _$$SettingsStateImplCopyWith(
_$SettingsStateImpl value, $Res Function(_$SettingsStateImpl) then) =
__$$SettingsStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool importSpinnerVisible, int importCurrent, int importTotal});
}
/// @nodoc
class __$$_SettingsStateCopyWithImpl<$Res>
extends _$SettingsStateCopyWithImpl<$Res>
implements _$$_SettingsStateCopyWith<$Res> {
__$$_SettingsStateCopyWithImpl(
_$_SettingsState _value, $Res Function(_$_SettingsState) _then)
: super(_value, (v) => _then(v as _$_SettingsState));
@override
_$_SettingsState get _value => super._value as _$_SettingsState;
class __$$SettingsStateImplCopyWithImpl<$Res>
extends _$SettingsStateCopyWithImpl<$Res, _$SettingsStateImpl>
implements _$$SettingsStateImplCopyWith<$Res> {
__$$SettingsStateImplCopyWithImpl(
_$SettingsStateImpl _value, $Res Function(_$SettingsStateImpl) _then)
: super(_value, _then);
/// Create a copy of SettingsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? importSpinnerVisible = freezed,
Object? importCurrent = freezed,
Object? importTotal = freezed,
Object? importSpinnerVisible = null,
Object? importCurrent = null,
Object? importTotal = null,
}) {
return _then(_$_SettingsState(
importSpinnerVisible: importSpinnerVisible == freezed
return _then(_$SettingsStateImpl(
importSpinnerVisible: null == importSpinnerVisible
? _value.importSpinnerVisible
: importSpinnerVisible // ignore: cast_nullable_to_non_nullable
as bool,
importCurrent: importCurrent == freezed
importCurrent: null == importCurrent
? _value.importCurrent
: importCurrent // ignore: cast_nullable_to_non_nullable
as int,
importTotal: importTotal == freezed
importTotal: null == importTotal
? _value.importTotal
: importTotal // ignore: cast_nullable_to_non_nullable
as int,
@@ -111,8 +119,8 @@ class __$$_SettingsStateCopyWithImpl<$Res>
/// @nodoc
class _$_SettingsState implements _SettingsState {
_$_SettingsState(
class _$SettingsStateImpl implements _SettingsState {
_$SettingsStateImpl(
{this.importSpinnerVisible = false,
this.importCurrent = 0,
this.importTotal = 0});
@@ -133,36 +141,36 @@ class _$_SettingsState implements _SettingsState {
}
@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_SettingsState &&
const DeepCollectionEquality()
.equals(other.importSpinnerVisible, importSpinnerVisible) &&
const DeepCollectionEquality()
.equals(other.importCurrent, importCurrent) &&
const DeepCollectionEquality()
.equals(other.importTotal, importTotal));
other is _$SettingsStateImpl &&
(identical(other.importSpinnerVisible, importSpinnerVisible) ||
other.importSpinnerVisible == importSpinnerVisible) &&
(identical(other.importCurrent, importCurrent) ||
other.importCurrent == importCurrent) &&
(identical(other.importTotal, importTotal) ||
other.importTotal == importTotal));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(importSpinnerVisible),
const DeepCollectionEquality().hash(importCurrent),
const DeepCollectionEquality().hash(importTotal));
runtimeType, importSpinnerVisible, importCurrent, 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
_$$_SettingsStateCopyWith<_$_SettingsState> get copyWith =>
__$$_SettingsStateCopyWithImpl<_$_SettingsState>(this, _$identity);
@pragma('vm:prefer-inline')
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith =>
__$$SettingsStateImplCopyWithImpl<_$SettingsStateImpl>(this, _$identity);
}
abstract class _SettingsState implements SettingsState {
factory _SettingsState(
{final bool importSpinnerVisible,
final int importCurrent,
final int importTotal}) = _$_SettingsState;
final int importTotal}) = _$SettingsStateImpl;
@override
bool get importSpinnerVisible;
@@ -170,8 +178,11 @@ abstract class _SettingsState implements SettingsState {
int get importCurrent;
@override
int get importTotal;
/// Create a copy of SettingsState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
_$$_SettingsStateCopyWith<_$_SettingsState> get copyWith =>
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -34,3 +34,19 @@ class MangaListImportedEvent extends SettingsEvent {
/// The type of list we're importing
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 animeSearchRoute = '/anime/search';
const detailsRoute = '/anime/details';
const calendarRoute = '/calendar';
const aboutRoute = '/about';
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/src/ui/constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutPage extends StatelessWidget {
@@ -35,15 +36,55 @@ class AboutPage extends StatelessWidget {
'AniTrack',
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 {
await launchUrl(
Uri.parse('https://codeberg.org/PapaTutuWawa/anitrack'),
Uri.parse(
'https://codeberg.org/PapaTutuWawa/anitrack',
),
mode: LaunchMode.externalApplication,
);
},
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(
title: Text(dep.name),
onTap: () async {
if (dep.repository == null) return;
onTap: () {
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(
Uri.parse(dep.repository!),
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/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:bottom_bar/bottom_bar.dart';
@@ -71,23 +72,23 @@ class AnimeListPageState extends State<AnimeListPage> {
return [
PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.ongoing,
child: Text(MediumTrackingState.ongoing.toNameString(type)),
child: Text(MediumTrackingState.ongoing.getName(type)),
),
PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.completed,
child: Text(MediumTrackingState.completed.toNameString(type)),
child: Text(MediumTrackingState.completed.getName(type)),
),
PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.planned,
child: Text(MediumTrackingState.planned.toNameString(type)),
child: Text(MediumTrackingState.planned.getName(type)),
),
PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.dropped,
child: Text(MediumTrackingState.dropped.toNameString(type)),
child: Text(MediumTrackingState.dropped.getName(type)),
),
PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.paused,
child: Text(MediumTrackingState.paused.toNameString(type)),
child: Text(MediumTrackingState.paused.getName(type)),
),
const PopupMenuItem<MediumTrackingState>(
value: MediumTrackingState.all,
@@ -140,38 +141,7 @@ class AnimeListPageState extends State<AnimeListPage> {
_getPopupButton(context, state),
],
),
drawer: 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.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);
},
),
],
),
),
drawer: getDrawer(context),
body: PageView(
// Prevent swiping between pages
// (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: [
AnimeCoverImage(
url: state.data!.thumbnailUrl,
hero: state.data!.id,
hero: '${state.heroImagePrefix}${state.data!.id}',
),
Expanded(
child: Padding(
@@ -69,10 +69,14 @@ class DetailsPage extends StatelessWidget {
builder: (context) {
return AlertDialog(
title: Text(
t.details.removeTitle(title: state.data!.title),
t.details.removeTitle(
title: state.data!.title,
),
),
content: Text(
t.details.removeBody(title: state.data!.title),
t.details.removeBody(
title: state.data!.title,
),
),
actions: [
TextButton(
@@ -83,14 +87,18 @@ class DetailsPage extends StatelessWidget {
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
child: Text(t.details.removeButton),
child: Text(
t.details.removeButton,
),
),
TextButton(
onPressed: () {
Navigator.of(context)
.pop(false);
},
child: Text(t.details.cancelButton),
child: Text(
t.details.cancelButton,
),
),
],
);
@@ -150,27 +158,27 @@ class DetailsPage extends StatelessWidget {
SelectorItem(
MediumTrackingState.ongoing,
MediumTrackingState.ongoing
.toNameString(state.trackingType),
.getName(state.trackingType),
),
SelectorItem(
MediumTrackingState.completed,
MediumTrackingState.completed
.toNameString(state.trackingType),
.getName(state.trackingType),
),
SelectorItem(
MediumTrackingState.planned,
MediumTrackingState.planned
.toNameString(state.trackingType),
.getName(state.trackingType),
),
SelectorItem(
MediumTrackingState.dropped,
MediumTrackingState.dropped
.toNameString(state.trackingType),
.getName(state.trackingType),
),
SelectorItem(
MediumTrackingState.paused,
MediumTrackingState.paused
.toNameString(state.trackingType),
.getName(state.trackingType),
),
],
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_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:permission_handler/permission_handler.dart';
class SettingsPage extends StatelessWidget {
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 {
const GridItem({
required this.child,
required this.plusCallback,
required this.minusCallback,
this.plusCallback,
this.minusCallback,
this.enableDrag = true,
super.key,
});
final Widget child;
final void Function() plusCallback;
final void Function() minusCallback;
final bool enableDrag;
final void Function()? plusCallback;
final void Function()? minusCallback;
@override
GridItemState createState() => GridItemState();
@@ -26,16 +29,20 @@ class GridItemState extends State<GridItem> {
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragUpdate: (details) {
if (!widget.enableDrag) return;
setState(() {
_offset += details.delta.dx;
_translationX = 160 / (1 + exp(-1 * (1 / 30) * _offset)) - 80;
});
},
onHorizontalDragEnd: (_) {
if (_translationX <= -60) {
widget.plusCallback();
} else if (_translationX >= 60) {
widget.minusCallback();
if (!widget.enableDrag) return;
if (_translationX <= -40) {
widget.plusCallback!();
} else if (_translationX >= 40) {
widget.minusCallback!();
}
// Reset the view

File diff suppressed because it is too large Load Diff

View File

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