mirror of
https://github.com/K3vinb5/Unyo.git
synced 2026-06-13 05:49:42 +00:00
rewrite: Initial Setup
This commit is contained in:
19
.metadata
19
.metadata
@@ -4,7 +4,7 @@
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "ea121f8859e4b13e47a8f845e4586164519588bc"
|
||||
revision: "077b4a4ce10a07b82caa6897f0c626f9c0a3ac90"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
@@ -13,11 +13,20 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
|
||||
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
|
||||
create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
- platform: linux
|
||||
create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
- platform: macos
|
||||
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
|
||||
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
|
||||
create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
- platform: web
|
||||
create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
- platform: windows
|
||||
create_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
base_revision: 077b4a4ce10a07b82caa6897f0c626f9c0a3ac90
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,425 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:unyo/util/constants.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const String anilistEndPointGetToken =
|
||||
"https://anilist.co/api/v2/oauth/authorize?client_id=17550&response_type=token";
|
||||
const int maxAttempts = 5;
|
||||
|
||||
Future<List<AnimeModel>> getAnimeModelListTrending(
|
||||
int page, int n, int attempt) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": page,
|
||||
"type": "ANIME",
|
||||
"sort": ["TRENDING_DESC", "POPULARITY_DESC"]
|
||||
}
|
||||
};
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("Trending list: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return await getAnimeModelListRecentlyReleased(page, n, newAttempt);
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
List<dynamic> media = jsonDecode(response.body)["data"]["Page"]["media"];
|
||||
List<AnimeModel> list = [];
|
||||
for (int i = 0; i < n; i++) {
|
||||
Map<String, dynamic> json = media[i];
|
||||
list.add(AnimeModel.fromJson(json));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<AnimeModel>> getAnimeModelListRecentlyReleased(
|
||||
int page, int n, int attempt) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query{ Page(page: $page, perPage: $n) { airingSchedules (sort: TIME_DESC, notYetAired: false) {episode media { id idMal title { userPreferred romaji english} coverImage { large } bannerImage format startDate { year month day } endDate { year month day } type description status averageScore episodes duration}}}}"
|
||||
};
|
||||
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("Recently released: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return await getAnimeModelListRecentlyReleased(page, n, newAttempt);
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
List<dynamic> media =
|
||||
jsonDecode(response.body)["data"]["Page"]["airingSchedules"];
|
||||
List<AnimeModel> list = [];
|
||||
for (int i = 0; i < media.length; i++) {
|
||||
var currentMedia = media[i]["media"];
|
||||
|
||||
list.add(AnimeModel.fromJson(currentMedia));
|
||||
}
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
for (int j = i + 1; j < list.length - 1; j++) {
|
||||
if (list[i].id == list[j].id) {
|
||||
list.removeAt(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<AnimeModel>> getAnimeModelListSeasonPopular(
|
||||
int page, int n, int year, String season, int attempt) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": page,
|
||||
"type": "ANIME",
|
||||
"seasonYear": year,
|
||||
"season": season,
|
||||
}
|
||||
};
|
||||
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("Season Popular: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return await getAnimeModelListRecentlyReleased(page, n, newAttempt);
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
List<dynamic> media = jsonDecode(response.body)["data"]["Page"]["media"];
|
||||
List<AnimeModel> list = [];
|
||||
for (int i = 0; i < n; i++) {
|
||||
Map<String, dynamic> json = media[i];
|
||||
|
||||
list.add(AnimeModel.fromJson(json));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<AnimeModel>> getAnimeModelListSearch(
|
||||
String search,
|
||||
String genre,
|
||||
String sort,
|
||||
String season,
|
||||
String status,
|
||||
String format,
|
||||
String year,
|
||||
int n,
|
||||
) async {
|
||||
String finalSearch = search.isNotEmpty ? "search:\"$search\"" : "";
|
||||
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source $finalSearch onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": 1,
|
||||
"type": "ANIME",
|
||||
if (sort == "Select Sorting")
|
||||
"sort": "SEARCH_MATCH"
|
||||
else
|
||||
"sort": "${sort.toUpperCase()}_DESC",
|
||||
if (format != "Select Format") "format": [format.toUpperCase().replaceAll(' ', '_')],
|
||||
if (season != "Select Season") "season": season.toUpperCase(),
|
||||
if (year != "Select Year") "seasonYear": int.parse(year),
|
||||
if (genre != "Select Genre") "genres": [genre],
|
||||
if (status != "Select Status") "status": status.toUpperCase().replaceAll(' ', '_')
|
||||
}
|
||||
};
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
print("ERROR:\n${response.statusCode}\n${response.body}");
|
||||
return [];
|
||||
} else {
|
||||
List<dynamic> media = jsonDecode(response.body)["data"]["Page"]["media"];
|
||||
List<AnimeModel> list = [];
|
||||
for (int i = 0; i < media.length; i++) {
|
||||
Map<String, dynamic> json = media[i];
|
||||
list.add(AnimeModel.fromJson(json));
|
||||
}
|
||||
print(list.length);
|
||||
// if ((sort != "Select Sorting")) {
|
||||
// return list.reversed.toList();
|
||||
// }
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getRandomAnimeBanner(int attempt) async {
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int, \$perPage:Int){ Page(page: \$page, perPage: \$perPage) { media(type: ANIME) { bannerImage } } }",
|
||||
"variables": {
|
||||
"perPage": 50,
|
||||
"page": Random().nextInt(395),
|
||||
}
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
if (attempt < maxAttempts) {
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("Random anime banner: $attempt - failure");
|
||||
int newAttempt = attempt + 1;
|
||||
return getRandomAnimeBanner(newAttempt);
|
||||
}
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (jsonResponse["data"]["Page"]["media"][i]["bannerImage"] == null) {
|
||||
continue;
|
||||
} else {
|
||||
return jsonResponse["data"]["Page"]["media"][i]["bannerImage"];
|
||||
}
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("Random anime banner: $attempt - failure");
|
||||
int newAttempt = attempt + 1;
|
||||
return getRandomAnimeBanner(newAttempt);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
getUserToken() async {
|
||||
var url = Uri.parse(anilistEndPointGetToken);
|
||||
launchUrl(url, mode: LaunchMode.platformDefault);
|
||||
}
|
||||
|
||||
String capitalize(String s) {
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
Future<Map<String, Map<String, double>>> getUserStatsMaps({int? newAttempt}) async {
|
||||
Map<String, Map<String, double>> userStatsMaps = {};
|
||||
int attempt = newAttempt ?? 0;
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$name:String){User(name:\$name){statistics{anime{episodesWatched minutesWatched formats{format\n\tcount\n\tmeanScore\n\tminutesWatched\n\tchaptersRead\n\tmediaIds\n}statuses{status\n\tcount\n\tmeanScore\n\tminutesWatched\n\tchaptersRead\n\tmediaIds\n}releaseYears{releaseYear\n\tcount\n\tmeanScore\n\tminutesWatched\n\tchaptersRead\n\tmediaIds\n}}}}}",
|
||||
"variables": {
|
||||
"name": userName,
|
||||
}
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("User stats: $attempt - failure");
|
||||
print(response.body);
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return await getUserStatsMaps(newAttempt: newAttempt);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
var animeStatistics =
|
||||
json.decode(response.body)["data"]["User"]["statistics"]["anime"];
|
||||
|
||||
List<dynamic> formats = animeStatistics["formats"];
|
||||
List<dynamic> statuses = animeStatistics["statuses"];
|
||||
List<dynamic> releaseYears = animeStatistics["releaseYears"];
|
||||
|
||||
Map<String, double> formatsMap = {};
|
||||
for (int i = 0; i < formats.length; i++) {
|
||||
formatsMap.addAll(
|
||||
{capitalize(formats[i]["format"]): formats[i]["count"].toDouble()});
|
||||
}
|
||||
userStatsMaps.addAll({"formats": formatsMap});
|
||||
|
||||
Map<String, double> statusesMap = {};
|
||||
for (int i = 0; i < statuses.length; i++) {
|
||||
statusesMap.addAll(
|
||||
{capitalize(statuses[i]["status"]): statuses[i]["count"].toDouble()});
|
||||
}
|
||||
userStatsMaps.addAll({"statuses": statusesMap});
|
||||
|
||||
Map<String, double> releaseYearsMap = {};
|
||||
for (int i = 0; i < releaseYears.length; i++) {
|
||||
releaseYearsMap.addAll({
|
||||
releaseYears[i]["releaseYear"].toString():
|
||||
releaseYears[i]["count"].toDouble()
|
||||
});
|
||||
}
|
||||
// userStatsMaps.addAll({"releaseYears": releaseYearsMap});
|
||||
Map<String, double> watchedStatisticsMap = {};
|
||||
watchedStatisticsMap.addAll(
|
||||
{"episodesWatched": animeStatistics["episodesWatched"].toDouble()});
|
||||
watchedStatisticsMap
|
||||
.addAll({"minutesWatched": animeStatistics["minutesWatched"].toDouble()});
|
||||
userStatsMaps.addAll({"watchedStatistics": watchedStatisticsMap});
|
||||
|
||||
return userStatsMaps;
|
||||
}
|
||||
|
||||
Future<List<String>> getUserAccessToken(String code, int attempt) async {
|
||||
var url = Uri.parse("https://anilist.co/api/v2/oauth/token");
|
||||
Map<String, dynamic> query = {
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": 17550,
|
||||
"client_secret": "xI8KTZlKm2F3kHXLko1ArQ21bKap4MojgDTk6Ukx",
|
||||
"redirect_uri": "http://localhost:9999/auth", // http://example.com/callback
|
||||
"code": code,
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("User access token : $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return getUserAccessToken(code, newAttempt);
|
||||
}
|
||||
}
|
||||
print("Response: ${response.body}");
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
|
||||
return [jsonResponse["access_token"], jsonResponse["refresh_token"]];
|
||||
}
|
||||
|
||||
Future<int> getAnimeCurrentEpisode(int mediaId, int attempt) async {
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query{ AiringSchedule(mediaId: $mediaId, sort: TIME_DESC, notYetAired: false){ episode } }",
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("anime current episode: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return getAnimeCurrentEpisode(mediaId, newAttempt);
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
return jsonResponse["data"]["AiringSchedule"]["episode"];
|
||||
}
|
||||
|
||||
Future<Map<String, List<AnimeModel>>> getCalendar(
|
||||
String localeTag,
|
||||
Map<String, List<AnimeModel>> calendarListMap,
|
||||
int page,
|
||||
int airingAtGreater,
|
||||
int airingAtLesser,
|
||||
int attempt,
|
||||
) async {
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query{Page(page: $page, perPage: 50) { pageInfo { hasNextPage total } airingSchedules(airingAt_greater: $airingAtGreater, airingAt_lesser: $airingAtLesser, sort: TIME_DESC) { episode airingAt media { id idMal status chapters episodes nextAiringEpisode { episode } isAdult type meanScore isFavourite format bannerImage startDate {day month year} endDate {day month year} countryOfOrigin coverImage { large } title { english romaji userPreferred } mediaListEntry { progress private score(format: POINT_100) status } } } }}",
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("Calendar lists: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return await getCalendar(localeTag, calendarListMap, page,
|
||||
airingAtGreater, airingAtLesser, newAttempt);
|
||||
}
|
||||
//NOTE empry Map
|
||||
return {};
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
List<dynamic> mediaList = jsonResponse["data"]["Page"]["airingSchedules"];
|
||||
List<AnimeModel> animeModelList = [];
|
||||
|
||||
for (int j = 0; j < mediaList.length; j++) {
|
||||
Map<String, dynamic> json = mediaList[j]["media"];
|
||||
animeModelList.add(AnimeModel.fromJson(json));
|
||||
}
|
||||
|
||||
calendarListMap = formatCalendarListMap(
|
||||
localeTag, calendarListMap, animeModelList, mediaList);
|
||||
if (jsonResponse["data"]["Page"]["pageInfo"]["hasNextPage"]) {
|
||||
int newPage = page + 1;
|
||||
return await getCalendar(localeTag, calendarListMap, newPage,
|
||||
airingAtGreater, airingAtLesser, attempt);
|
||||
} else {
|
||||
return Map.fromEntries(calendarListMap.entries.toList().reversed);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<AnimeModel>> formatCalendarListMap(
|
||||
String locale,
|
||||
Map<String, List<AnimeModel>> calendarListMap,
|
||||
List<AnimeModel> animeModelList,
|
||||
List<dynamic> mediaList) {
|
||||
for (int i = 0; i < animeModelList.length; i++) {
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
|
||||
mediaList[i]["airingAt"] * 1000,
|
||||
isUtc: true);
|
||||
DateFormat dateFormat = DateFormat('EEEE, MMMM d y', locale);
|
||||
String listKey = dateFormat.format(dateTime);
|
||||
String formattedListKey =
|
||||
"${listKey[0].toUpperCase()}${listKey.substring(1)}";
|
||||
if (!calendarListMap.containsKey(formattedListKey)) {
|
||||
calendarListMap.addAll({
|
||||
formattedListKey: [animeModelList[i]]
|
||||
});
|
||||
} else {
|
||||
calendarListMap[formattedListKey]!.add(animeModelList[i]);
|
||||
}
|
||||
}
|
||||
return calendarListMap;
|
||||
}
|
||||
@@ -1,512 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
const String anilistEndPointGetToken =
|
||||
"https://anilist.co/api/v2/oauth/authorize?client_id=17550&response_type=token";
|
||||
|
||||
Future<List<MangaModel>> getMangaModelListTrending(
|
||||
int page, int n, int attempt) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)chapters duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": page,
|
||||
"type": "MANGA",
|
||||
"sort": ["TRENDING_DESC", "POPULARITY_DESC"]
|
||||
}
|
||||
};
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode == 500) {
|
||||
if (attempt < 5) {
|
||||
print("mangaModelListTrending : $attempt - failure");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
int newAttempt = attempt + 1;
|
||||
return await getMangaModelListTrending(page, n, newAttempt);
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
List<dynamic> media = jsonDecode(response.body)["data"]["Page"]["media"];
|
||||
List<MangaModel> list = [];
|
||||
for (int i = 0; i < n; i++) {
|
||||
Map<String, dynamic> json = media[i];
|
||||
// list.add(MangaModel(
|
||||
// id: json["id"],
|
||||
// title: json["title"]["userPreferred"],
|
||||
// coverImage: json["coverImage"]["large"],
|
||||
// bannerImage: json["bannerImage"],
|
||||
// startDate:
|
||||
// "${json["startDate"]["day"]}/${json["startDate"]["month"]}/${json["startDate"]["year"]}",
|
||||
// endDate:
|
||||
// "${json["endDate"]["day"]}/${json["endDate"]["month"]}/${json["endDate"]["year"]}",
|
||||
// type: json["type"],
|
||||
// description: json["description"],
|
||||
// status: json["status"],
|
||||
// averageScore: json["averageScore"],
|
||||
// chapters: json["chapters"],
|
||||
// duration: json["duration"],
|
||||
// format: json["format"],
|
||||
// ));
|
||||
list.add(MangaModel.fromJson(json));
|
||||
}
|
||||
// print(list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<MangaModel>> getMangaModelListYearlyPopular(
|
||||
int page, int year, int attempt, int n) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int \$id:Int \$type:MediaType \$isAdult:Boolean = false \$search:String \$format:[MediaFormat]\$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source search:\$search onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": page,
|
||||
"type": "MANGA",
|
||||
"year": "${year.toString()}%",
|
||||
}
|
||||
};
|
||||
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode == 500) {
|
||||
if (attempt < 5) {
|
||||
print("mangaModelListYearlyPopular : $attempt - failure");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
int newAttempt = attempt + 1;
|
||||
return await getMangaModelListRecentlyReleased(page, year, newAttempt);
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
print(response.body);
|
||||
List<dynamic> media = jsonDecode(response.body)["data"]["Page"]["media"];
|
||||
List<MangaModel> list = [];
|
||||
for (int i = 0; i < media.length; i++) {
|
||||
Map<String, dynamic> json = media[i];
|
||||
list.add(MangaModel.fromJson(json));
|
||||
// list.add(MangaModel(
|
||||
// id: json["id"],
|
||||
// title: json["title"]["userPreferred"],
|
||||
// coverImage: json["coverImage"]["large"],
|
||||
// bannerImage: json["bannerImage"],
|
||||
// startDate:
|
||||
// "${json["startDate"]["day"]}/${json["startDate"]["month"]}/${json["startDate"]["year"]}",
|
||||
// endDate:
|
||||
// "${json["endDate"]["day"]}/${json["endDate"]["month"]}/${json["endDate"]["year"]}",
|
||||
// type: json["type"],
|
||||
// description: json["description"],
|
||||
// status: json["status"],
|
||||
// averageScore: json["averageScore"],
|
||||
// chapters: json["chapters"],
|
||||
// duration: json["duration"],
|
||||
// format: json["format"],
|
||||
// ));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO fix for mangas
|
||||
Future<List<MangaModel>> getMangaModelListRecentlyReleased(
|
||||
int page, int n, int attempt) async {
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query{ Page(page: $page, perPage: $n) { airingSchedules (sort: TIME_DESC, notYetAired: false) {episode media { id idMal title { userPreferred english romaji} coverImage { large } bannerImage format startDate { year month day } endDate { year month day } type description status averageScore episodes duration}}}}"
|
||||
};
|
||||
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode == 500) {
|
||||
if (attempt < 5) {
|
||||
print("mangaModelListRecentlyReleased : $attempt - failure");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
int newAttempt = attempt + 1;
|
||||
return await getMangaModelListRecentlyReleased(page, n, newAttempt);
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
List<dynamic> media =
|
||||
jsonDecode(response.body)["data"]["Page"]["airingSchedules"];
|
||||
List<MangaModel> list = [];
|
||||
for (int i = 0; i < media.length; i++) {
|
||||
var json = media[i]["media"];
|
||||
list.add(MangaModel.fromJson(json));
|
||||
// list.add(MangaModel(
|
||||
// id: json["id"],
|
||||
// title: json["title"]["userPreferred"],
|
||||
// coverImage: json["coverImage"]["large"],
|
||||
// bannerImage: json["bannerImage"],
|
||||
// startDate:
|
||||
// "${json["startDate"]["day"]}/${json["startDate"]["month"]}/${json["startDate"]["year"]}",
|
||||
// endDate:
|
||||
// "${json["endDate"]["day"]}/${json["endDate"]["month"]}/${json["endDate"]["year"]}",
|
||||
// type: json["type"],
|
||||
// status: json["status"],
|
||||
// averageScore: json["averageScore"],
|
||||
// chapters: json["chapters"],
|
||||
// currentEpisode: media[i]["episode"],
|
||||
// duration: json["duration"],
|
||||
// description: json["description"],
|
||||
// format: json["format"],
|
||||
// ));
|
||||
}
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
for (int j = i + 1; j < list.length - 1; j++) {
|
||||
if (list[i].id == list[j].id) {
|
||||
list.removeAt(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
// Future<List<MangaModel>> getUserMangaLists(
|
||||
// int userId, String listName, int attempt) async {
|
||||
// var url = Uri.parse(anilistEndpoint);
|
||||
// Map<String, dynamic> query = {
|
||||
// "query":
|
||||
// "query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type,sort:UPDATED_TIME_DESC){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id title{userPreferred romaji english native}coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore description popularity isAdult countryOfOrigin genres bannerImage startDate{year month day}}}",
|
||||
// "variables": {
|
||||
// "userId": userId,
|
||||
// "type": "MANGA",
|
||||
// }
|
||||
// };
|
||||
// var response = await http.post(
|
||||
// url,
|
||||
// headers: {"Content-Type": "application/json"},
|
||||
// body: json.encode(query),
|
||||
// );
|
||||
// if (response.statusCode != 200) {
|
||||
// print(response.body);
|
||||
// if (attempt < 5) {
|
||||
// print("userMangaLists : $attempt - failure");
|
||||
// await Future.delayed(const Duration(milliseconds: 200));
|
||||
// int newAttempt = attempt + 1;
|
||||
// return await getUserMangaLists(userId, listName, newAttempt);
|
||||
// }
|
||||
// return [];
|
||||
// }
|
||||
// List<MangaModel> animeModelList = [];
|
||||
// Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
// List<dynamic> animeLists =
|
||||
// jsonResponse["data"]["MediaListCollection"]["lists"];
|
||||
// for (int i = 0; i < animeLists.length; i++) {
|
||||
// if (animeLists[i]["name"] == listName) {
|
||||
// List<dynamic> wantedList = animeLists[i]["entries"];
|
||||
// for (int i = 0; i < wantedList.length; i++) {
|
||||
// animeModelList.add(
|
||||
// MangaModel(
|
||||
// id: wantedList[i]["media"]["id"],
|
||||
// title: wantedList[i]["media"]["title"]["userPreferred"],
|
||||
// coverImage: wantedList[i]["media"]["coverImage"]["large"],
|
||||
// bannerImage: wantedList[i]["media"]["bannerImage"],
|
||||
// startDate:
|
||||
// "${wantedList[i]["media"]["startDate"]["day"]}/${wantedList[i]["media"]["startDate"]["month"]}/${wantedList[i]["media"]["startDate"]["year"]}",
|
||||
// endDate: "",
|
||||
// //"${wantedList[i]["media"]["endDate"]["day"]}/${wantedList[i]["media"]["endDate"]["month"]}/${wantedList[i]["media"]["endDate"]["year"]}",
|
||||
// type: wantedList[i]["media"]["type"],
|
||||
// description: wantedList[i]["media"]["description"],
|
||||
// status: wantedList[i]["media"]["status"],
|
||||
// averageScore: wantedList[i]["media"]["averageScore"],
|
||||
// chapters: wantedList[i]["media"]["chapters"],
|
||||
// duration: wantedList[i]["media"]["episodes"],
|
||||
// format: wantedList[i]["media"]["format"],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// return animeModelList;
|
||||
// }
|
||||
|
||||
// Future<Map<String, List<MangaModel>>> getAllUserMangaLists(
|
||||
// int userId, int attempt) async {
|
||||
// var url = Uri.parse(anilistEndpoint);
|
||||
// Map<String, dynamic> query = {
|
||||
// "query":
|
||||
// "query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id title{userPreferred romaji english native}coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore description popularity isAdult countryOfOrigin genres bannerImage startDate{year month day}}}",
|
||||
// "variables": {
|
||||
// "userId": /*859862*/ userId,
|
||||
// "type": "MANGA",
|
||||
// }
|
||||
// };
|
||||
// var response = await http.post(
|
||||
// url,
|
||||
// headers: {"Content-Type": "application/json"},
|
||||
// body: json.encode(query),
|
||||
// );
|
||||
// if (response.statusCode != 200) {
|
||||
// print(response.body);
|
||||
// if (attempt < 5) {
|
||||
// print("allUserMangaLists : $attempt - failure");
|
||||
// await Future.delayed(const Duration(milliseconds: 200));
|
||||
// int newAttempt = attempt + 1;
|
||||
// return await getAllUserMangaLists(userId, newAttempt);
|
||||
// }
|
||||
// //NOTE empry Map
|
||||
// return {};
|
||||
// }
|
||||
// Map<String, List<MangaModel>> userMangaListsMap = {};
|
||||
// Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
// List<dynamic> userMangaLists =
|
||||
// jsonResponse["data"]["MediaListCollection"]["lists"];
|
||||
// for (int i = 0; i < userMangaLists.length; i++) {
|
||||
// List<dynamic> currentList = userMangaLists[i]["entries"];
|
||||
//
|
||||
// List<MangaModel> mangaModelList = [];
|
||||
//
|
||||
// for (int j = 0; j < currentList.length; j++) {
|
||||
// mangaModelList.add(
|
||||
// MangaModel(
|
||||
// id: currentList[j]["media"]["id"],
|
||||
// title: currentList[j]["media"]["title"]["userPreferred"],
|
||||
// coverImage: currentList[j]["media"]["coverImage"]["large"],
|
||||
// bannerImage: currentList[j]["media"]["bannerImage"],
|
||||
// startDate:
|
||||
// "${currentList[j]["media"]["startDate"]["day"]}/${currentList[j]["media"]["startDate"]["month"]}/${currentList[j]["media"]["startDate"]["year"]}",
|
||||
// endDate: "",
|
||||
// //"${wantedList[i]["media"]["endDate"]["day"]}/${wantedList[i]["media"]["endDate"]["month"]}/${wantedList[i]["media"]["endDate"]["year"]}",
|
||||
// type: currentList[j]["media"]["type"],
|
||||
// description: currentList[j]["media"]["description"],
|
||||
// status: currentList[j]["media"]["status"],
|
||||
// averageScore: currentList[j]["media"]["averageScore"],
|
||||
// chapters: currentList[j]["media"]["chapters"],
|
||||
// duration: currentList[j]["media"]["episodes"],
|
||||
// format: currentList[j]["media"]["format"],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// userMangaListsMap.addAll({userMangaLists[i]["name"]: mangaModelList});
|
||||
// }
|
||||
// print(userMangaListsMap);
|
||||
// return userMangaListsMap;
|
||||
// }
|
||||
|
||||
// Future<UserMediaModel> getUserMangaInfo(int mediaId, int attempt) async {
|
||||
// var url = Uri.parse(anilistEndpoint);
|
||||
// Map<String, dynamic> query = {
|
||||
// "query":
|
||||
// "query{ Media(id: $mediaId){ mediaListEntry { score progress repeat priority status startedAt{day month year} completedAt{day month year} } } }",
|
||||
// };
|
||||
// var response = await http.post(
|
||||
// url,
|
||||
// headers: {
|
||||
// "Authorization": "Bearer $accessToken",
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: json.encode(query),
|
||||
// );
|
||||
// if (response.statusCode != 200) {
|
||||
// if (attempt < 5) {
|
||||
// print("userMangaInfo : $attempt - failure");
|
||||
// await Future.delayed(const Duration(milliseconds: 200));
|
||||
// int newAttempt = attempt + 1;
|
||||
// return getUserMangaInfo(mediaId, newAttempt);
|
||||
// }
|
||||
// }
|
||||
// Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
// if (jsonResponse["data"]["Media"]["mediaListEntry"] == null) {
|
||||
// return UserMediaModel(
|
||||
// score: 0,
|
||||
// progress: 0,
|
||||
// repeat: 0,
|
||||
// priority: 0,
|
||||
// status: "",
|
||||
// startDate: "~/~/~",
|
||||
// endDate: "~/~/~",
|
||||
// );
|
||||
// }
|
||||
// Map<String, dynamic> mediaListEntry =
|
||||
// jsonResponse["data"]["Media"]["mediaListEntry"];
|
||||
// return UserMediaModel(
|
||||
// score: mediaListEntry["score"],
|
||||
// progress: mediaListEntry["progress"],
|
||||
// repeat: mediaListEntry["repeat"],
|
||||
// priority: mediaListEntry["priority"],
|
||||
// status: mediaListEntry["status"],
|
||||
// startDate:
|
||||
// "${mediaListEntry["startedAt"]["day"]}/${mediaListEntry["startedAt"]["month"]}/${mediaListEntry["startedAt"]["year"]}",
|
||||
// endDate:
|
||||
// "${mediaListEntry["completedAt"]["day"]}/${mediaListEntry["completedAt"]["month"]}/${mediaListEntry["completedAt"]["year"]}",
|
||||
// );
|
||||
// }
|
||||
|
||||
// void deleteUserManga(int mediaId) async {
|
||||
// var url = Uri.parse(anilistEndpoint);
|
||||
//
|
||||
// Map<String, dynamic> query1 = {
|
||||
// "query": "query(\$mediaId:Int){ MediaList(mediaId:\$mediaId){ id } }",
|
||||
// "variables": {
|
||||
// "mediaId": mediaId,
|
||||
// },
|
||||
// };
|
||||
// var response1 = await http.post(
|
||||
// url,
|
||||
// headers: {
|
||||
// "Authorization": "Bearer $accessToken",
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: json.encode(query1),
|
||||
// );
|
||||
//
|
||||
// int entryId = jsonDecode(response1.body)["data"]["MediaList"]["id"];
|
||||
//
|
||||
// Map<String, dynamic> query = {
|
||||
// "query":
|
||||
// "mutation (\$entryId: Int) {DeleteMediaListEntry(id: \$entryId){ deleted }}",
|
||||
// "variables": {
|
||||
// "entryId": entryId,
|
||||
// },
|
||||
// };
|
||||
// var response = await http.post(
|
||||
// url,
|
||||
// headers: {
|
||||
// "Authorization": "Bearer $accessToken",
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: json.encode(query),
|
||||
// );
|
||||
// print(response.body);
|
||||
// }
|
||||
//
|
||||
// void setUserMangaInfo(int mediaId, Map<String, String> receivedQuery) async {
|
||||
// var url = Uri.parse(anilistEndpoint);
|
||||
// Map<String, dynamic> query = {
|
||||
// "query":
|
||||
// "mutation (\$mediaId: Int, \$status: MediaListStatus, \$score: Float, \$progress: Int, \$startedAt: FuzzyDateInput, \$completedAt: FuzzyDateInput) { SaveMediaListEntry(mediaId: \$mediaId, status: \$status, score: \$score, progress: \$progress, startedAt: \$startedAt, completedAt: \$completedAt) { mediaId status score progress startedAt { year month day } completedAt { year month day } } } ",
|
||||
// "variables": {
|
||||
// "mediaId": mediaId,
|
||||
// "status": receivedQuery["status"],
|
||||
// "score": double.parse(receivedQuery["score"]!),
|
||||
// "progress": int.parse(receivedQuery["progress"]!),
|
||||
// "startedAt": {
|
||||
// "day": receivedQuery["startDateDay"],
|
||||
// "month": receivedQuery["startDateMonth"],
|
||||
// "year": receivedQuery["startDateYear"]
|
||||
// },
|
||||
// "completedAt": {
|
||||
// "day": receivedQuery["endDateDay"],
|
||||
// "month": receivedQuery["endDateMonth"],
|
||||
// "year": receivedQuery["endDateYear"]
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// var response = await http.post(
|
||||
// url,
|
||||
// headers: {
|
||||
// "Authorization": "Bearer $accessToken",
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: json.encode(query),
|
||||
// );
|
||||
// print(response.body);
|
||||
// }
|
||||
|
||||
Future<List<MangaModel>> getMangaModelListSearch(
|
||||
String search,
|
||||
String sort,
|
||||
String format,
|
||||
String status,
|
||||
String country,
|
||||
String genre,
|
||||
int n)
|
||||
async {
|
||||
String finalSearch = search.isNotEmpty ? "search:\"$search\"" : "";
|
||||
|
||||
String? countryFilter;
|
||||
if (country != "Select Country") {
|
||||
switch (country) {
|
||||
case 'Japan':
|
||||
countryFilter = 'JP';
|
||||
break;
|
||||
case 'Taiwan':
|
||||
countryFilter = 'TW';
|
||||
break;
|
||||
case 'South Korea':
|
||||
countryFilter = 'KR';
|
||||
break;
|
||||
case 'China':
|
||||
countryFilter = 'CN';
|
||||
break;
|
||||
default:
|
||||
countryFilter = country.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$page:Int = 1 \$id:Int \$type:MediaType \$isAdult:Boolean = false \$format:[MediaFormat] \$status:MediaStatus \$countryOfOrigin:CountryCode \$source:MediaSource \$season:MediaSeason \$seasonYear:Int \$year:String \$onList:Boolean \$yearLesser:FuzzyDateInt \$yearGreater:FuzzyDateInt \$episodeLesser:Int \$episodeGreater:Int \$durationLesser:Int \$durationGreater:Int \$chapterLesser:Int \$chapterGreater:Int \$volumeLesser:Int \$volumeGreater:Int \$licensedBy:[Int]\$isLicensed:Boolean \$genres:[String]\$excludedGenres:[String]\$tags:[String]\$excludedTags:[String]\$minimumTagRank:Int \$sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:\$page,perPage:$n){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:\$id type:\$type season:\$season format_in:\$format status:\$status countryOfOrigin:\$countryOfOrigin source:\$source $finalSearch onList:\$onList seasonYear:\$seasonYear startDate_like:\$year startDate_lesser:\$yearLesser startDate_greater:\$yearGreater episodes_lesser:\$episodeLesser episodes_greater:\$episodeGreater duration_lesser:\$durationLesser duration_greater:\$durationGreater chapters_lesser:\$chapterLesser chapters_greater:\$chapterGreater volumes_lesser:\$volumeLesser volumes_greater:\$volumeGreater licensedById_in:\$licensedBy isLicensed:\$isLicensed genre_in:\$genres genre_not_in:\$excludedGenres tag_in:\$tags tag_not_in:\$excludedTags minimumTagRank:\$minimumTagRank sort:\$sort isAdult:\$isAdult){id idMal title{userPreferred english romaji}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season seasonYear description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}",
|
||||
"variables": {
|
||||
"page": 1,
|
||||
"type": "MANGA",
|
||||
if (sort == "Select Sorting")
|
||||
"sort": "SEARCH_MATCH"
|
||||
else if (sort == "A-Z")
|
||||
"sort": "TITLE_ENGLISH"
|
||||
else if (sort == "Z-A")
|
||||
"sort": "TITLE_ENGLISH_DESC"
|
||||
else
|
||||
"sort": sort.toUpperCase(),
|
||||
if (format != "Select Format") "format": [format.toUpperCase().replaceAll(' ', '_')],
|
||||
if (genre != "Select Genre") "genres": [genre],
|
||||
if (status != "Select Status") "status": status.toUpperCase().replaceAll(' ', '_'),
|
||||
if (countryFilter != null) "countryOfOrigin": countryFilter,
|
||||
}
|
||||
};
|
||||
|
||||
var url = Uri.parse(anilistEndpoint);
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
print("ERROR:\n${response.statusCode}\n${response.body}");
|
||||
return [];
|
||||
} else {
|
||||
List<dynamic> media = jsonDecode(response.body)["data"]["Page"]["media"];
|
||||
List<MangaModel> list = [];
|
||||
for (int i = 0; i < media.length; i++) {
|
||||
Map<String, dynamic> json = media[i];
|
||||
list.add(MangaModel.fromJson(json));
|
||||
// list.add(MangaModel(
|
||||
// id: json["id"],
|
||||
// title: json["title"]["userPreferred"],
|
||||
// coverImage: json["coverImage"]["large"],
|
||||
// bannerImage: json["bannerImage"],
|
||||
// startDate:
|
||||
// "${json["startDate"]["day"]}/${json["startDate"]["month"]}/${json["startDate"]["year"]}",
|
||||
// endDate:
|
||||
// "${json["endDate"]["day"]}/${json["endDate"]["month"]}/${json["endDate"]["year"]}",
|
||||
// type: json["type"],
|
||||
// description: json["description"],
|
||||
// status: json["status"],
|
||||
// averageScore: json["averageScore"],
|
||||
// chapters: json["chapters"],
|
||||
// duration: json["duration"],
|
||||
// format: json["format"],
|
||||
// ));
|
||||
}
|
||||
print(list.length);
|
||||
if ((sort != "Select Sorting")) {
|
||||
return list.reversed.toList();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
const aniskipEndpoint = "https://api.aniskip.com/v1/skip-times";
|
||||
|
||||
Future<Map<String, double>> getOpeningSkipTimeStamps(
|
||||
String? malId, String episode) async {
|
||||
var url = Uri.parse("$aniskipEndpoint/$malId/$episode?types=op");
|
||||
var response = await http.get(url);
|
||||
Map<String, dynamic> json = jsonDecode(response.body);
|
||||
if (json["found"]!= null && json['found'] && malId != null && malId != "-1") {
|
||||
return {
|
||||
"start": json["results"][0]["interval"]["start_time"].toDouble(),
|
||||
"end": json["results"][0]["interval"]["end_time"].toDouble(),
|
||||
};
|
||||
} else {
|
||||
return {"start": -1, "end": -1};
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:html/parser.dart' as parser;
|
||||
|
||||
const searchEndPoint =
|
||||
"https://www.opensubtitles.org/libs/suggest.php?format=json3&MovieName=";
|
||||
const animeEpisodesEndpoint =
|
||||
"https://www.opensubtitles.org/en/ssearch/idmovie-";
|
||||
|
||||
class OpenSubtitlesApi {
|
||||
Future<Map<String, String>> getSubtitlesUrl(
|
||||
String query, int season, int episode) async {
|
||||
String animeId = await getAnimeId(query);
|
||||
if (animeId == "-1") {
|
||||
return {};
|
||||
}
|
||||
List<List<String>> seasons = await getAnimeEpisodes(animeId);
|
||||
print(seasons);
|
||||
if (seasons.isEmpty ||
|
||||
seasons.length < season ||
|
||||
seasons[season - 1].length < episode) {
|
||||
return {};
|
||||
}
|
||||
String episodeSubtitlesUrl = seasons[season - 1][episode - 1];
|
||||
Map<String, String> subtitles =
|
||||
await getSubtitlesUrlAndLanguage(episodeSubtitlesUrl);
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
Future<String> getAnimeId(String query) async {
|
||||
var url =
|
||||
Uri.parse("$searchEndPoint${query.replaceAll(RegExp(r'[:!.,]'), '')}");
|
||||
var response = await http.get(url);
|
||||
print(response.body);
|
||||
List<dynamic> jsonResponse = [];
|
||||
try {
|
||||
jsonResponse = json.decode(response.body);
|
||||
} catch (e) {
|
||||
print("OpenSubtitles is Down");
|
||||
return "-1";
|
||||
}
|
||||
|
||||
if (jsonResponse.isEmpty) {
|
||||
return "-1";
|
||||
}
|
||||
return jsonResponse[0]["id"].toString();
|
||||
}
|
||||
|
||||
Future<List<List<String>>> getAnimeEpisodes(String animeId) async {
|
||||
var url = Uri.parse("$animeEpisodesEndpoint$animeId");
|
||||
var response = await http.get(url);
|
||||
String html = response.body;
|
||||
print(url);
|
||||
var document = parser.parse(html);
|
||||
List<Element> elements = document.querySelectorAll('tr');
|
||||
List<String> idsAttribute = elements
|
||||
.map((element) =>
|
||||
element.querySelector('td span[id]')?.attributes['id'] ?? "-1")
|
||||
.toList();
|
||||
List<int> ids = idsAttribute
|
||||
.asMap()
|
||||
.entries
|
||||
.where((element) => element.value.contains("season"))
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
List<String> urls = elements
|
||||
.map((element) =>
|
||||
element.querySelector('td a[href]')?.attributes['href'] ?? "-1")
|
||||
.toList();
|
||||
List<List<String>> seasons = urls
|
||||
.asMap()
|
||||
.entries
|
||||
.splitBefore((element) => ids.contains(element.key))
|
||||
.toList()
|
||||
.map((list) => list.map((map) => map.value).toList())
|
||||
.toList();
|
||||
if (seasons.isNotEmpty) {
|
||||
seasons.removeAt(0);
|
||||
}
|
||||
for (List<String> list in seasons) {
|
||||
list.removeAt(0);
|
||||
list.removeWhere((element) => !element.contains("imdbid"));
|
||||
}
|
||||
return seasons;
|
||||
}
|
||||
|
||||
Future<Map<String, String>> getSubtitlesUrlAndLanguage(
|
||||
String episodeUrl) async {
|
||||
var url =
|
||||
Uri.parse("https://www.opensubtitles.org$episodeUrl/subformat-srt");
|
||||
var response = await http.get(url);
|
||||
var document = parser.parse(response.body);
|
||||
List<String> hrefsWithSrt = document
|
||||
.querySelectorAll('tr td')
|
||||
.where((tdElement) =>
|
||||
tdElement.querySelector('span')?.text.contains("srt") ?? false)
|
||||
.map((tdElement) => tdElement.querySelector('a')?.attributes['href'])
|
||||
.where((href) => href != null)
|
||||
.cast<String>()
|
||||
.toList();
|
||||
List<String> titlesWithSublanguageId = document
|
||||
.querySelectorAll('tr td a[href*="sublanguageid"]')
|
||||
.map((aElement) => aElement.attributes['title'])
|
||||
.where((title) => title != null)
|
||||
.cast<String>()
|
||||
.toList();
|
||||
titlesWithSublanguageId.removeLast();
|
||||
print(titlesWithSublanguageId.length);
|
||||
print(hrefsWithSrt.length);
|
||||
if (hrefsWithSrt.length != titlesWithSublanguageId.length) {
|
||||
return {};
|
||||
}
|
||||
return Map.fromIterables(titlesWithSublanguageId, hrefsWithSrt);
|
||||
}
|
||||
}
|
||||
47
lib/application/cubits/effect_mixin.dart
Normal file
47
lib/application/cubits/effect_mixin.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
// External dependencies
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
// Internal dependencies
|
||||
import 'package:unyo/application/effects/app_effects.dart';
|
||||
|
||||
mixin EffectMixin<State> on Cubit<State> {
|
||||
State copyStateWithEffects(State state, List<AppEffect> effects);
|
||||
Logger get logger;
|
||||
|
||||
void addEffect(AppEffect effect) {
|
||||
logger.d("Adding AppEffect: $effect");
|
||||
final current = state;
|
||||
final newEffects = [..._currentEffects, effect];
|
||||
emit(copyStateWithEffects(current, newEffects));
|
||||
}
|
||||
|
||||
void clearEffects() {
|
||||
emit(copyStateWithEffects(state, []));
|
||||
}
|
||||
|
||||
List<AppEffect> get _currentEffects {
|
||||
if (state is HasEffects) {
|
||||
return (state as HasEffects).stateEffects;
|
||||
}
|
||||
throw StateError('Cubit/Bloc State must implement HasEffects');
|
||||
}
|
||||
|
||||
void showSnackBarEffect({required String message}) {
|
||||
logger.i("ShowSnackbar with message: $message");
|
||||
addEffect(ShowSnackbarEffect(message));
|
||||
}
|
||||
|
||||
void replaceRouteEffect({required String path}) {
|
||||
logger.i("ReplaceRoute with path: $path");
|
||||
addEffect(ReplaceRouteEffect(path));
|
||||
}
|
||||
|
||||
void pushRouteEffect({required String path}) {
|
||||
logger.i("PushRoute with path: $path");
|
||||
addEffect(PushRouteEffect(path));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class HasEffects {
|
||||
List<AppEffect> get stateEffects;
|
||||
}
|
||||
54
lib/application/cubits/home_cubit.dart
Normal file
54
lib/application/cubits/home_cubit.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
// Dart dependencies
|
||||
import 'dart:async';
|
||||
|
||||
// External dependencies
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
// Internal dependencies
|
||||
import 'package:unyo/application/cubits/effect_mixin.dart';
|
||||
import 'package:unyo/application/states/home_state.dart';
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
import 'package:unyo/core/notifier/user_notifier.dart';
|
||||
import 'package:unyo/data/models/models.dart';
|
||||
import 'package:unyo/data/repositories/repositories.dart';
|
||||
import 'package:unyo/application/effects/app_effects.dart';
|
||||
|
||||
class HomeCubit extends Cubit<HomeState> with EffectMixin<HomeState> {
|
||||
//Repositories
|
||||
final UserRepositoryHive _userRepository;
|
||||
|
||||
// Notifiers / Subscriptions
|
||||
final UserNotifier _userNotifier;
|
||||
late StreamSubscription<UserModel> _userSubscription;
|
||||
|
||||
final Logger _logger = sl<Logger>();
|
||||
|
||||
HomeCubit(
|
||||
this._userRepository,
|
||||
this._userNotifier,
|
||||
) : super(HomeState(user: UserModel.empty())) {
|
||||
_init();
|
||||
}
|
||||
|
||||
@override
|
||||
HomeState copyStateWithEffects(HomeState state, List<AppEffect> effects) {
|
||||
return state.copyWith(effects: effects);
|
||||
}
|
||||
|
||||
@override
|
||||
Logger get logger => _logger;
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_userSubscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _init() {
|
||||
_userSubscription = _userNotifier.userStream.listen((user) {
|
||||
emit(state.copyWith(user: user)); // Update state on new data
|
||||
});
|
||||
}
|
||||
}
|
||||
48
lib/application/cubits/login_cubit.dart
Normal file
48
lib/application/cubits/login_cubit.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
// External dependencies
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
// Internal dependencies
|
||||
import 'package:unyo/application/cubits/effect_mixin.dart';
|
||||
import 'package:unyo/application/states/login_state.dart';
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
import 'package:unyo/core/notifier/user_notifier.dart';
|
||||
import 'package:unyo/data/models/models.dart';
|
||||
import 'package:unyo/data/repositories/repositories.dart';
|
||||
import 'package:unyo/application/effects/app_effects.dart';
|
||||
import 'package:unyo/domain/entities/user.dart';
|
||||
|
||||
class LoginCubit extends Cubit<LoginState> with EffectMixin<LoginState> {
|
||||
// Repositories
|
||||
final UserRepositoryHive _userRepository;
|
||||
final Logger _logger = sl<Logger>();
|
||||
|
||||
// Notifiers / Subscriptions
|
||||
final UserNotifier _userNotifier;
|
||||
|
||||
LoginCubit(this._userRepository, this._userNotifier) : super(LoginState(user: UserModel.empty(), availableUsers: []));
|
||||
|
||||
@override
|
||||
LoginState copyStateWithEffects(LoginState state, List<AppEffect> effects) {
|
||||
return state.copyWith(effects: effects);
|
||||
}
|
||||
|
||||
@override
|
||||
Logger get logger => _logger;
|
||||
|
||||
|
||||
// Future<void> initiateAccountCreation() {
|
||||
// // Effect that creates dialog
|
||||
// }
|
||||
|
||||
Future<void> fetchAllUsers() async{
|
||||
List<UserModel> usersAvailable = (await _userRepository.fetchAllUsers()).cast<UserModel>();
|
||||
updateAvailableUsers(usersAvailable);
|
||||
}
|
||||
|
||||
void updateAvailableUsers(List<UserModel> users) {
|
||||
emit(state.copyWith(availableUsers: users));
|
||||
}
|
||||
|
||||
}
|
||||
31
lib/application/effects/app_effects.dart
Normal file
31
lib/application/effects/app_effects.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AppEffect {}
|
||||
|
||||
// Navigation Effects
|
||||
class ReplaceRouteEffect extends AppEffect {
|
||||
final String routeName;
|
||||
final Object? arguments;
|
||||
|
||||
ReplaceRouteEffect(this.routeName, {this.arguments});
|
||||
}
|
||||
|
||||
class PushRouteEffect extends AppEffect {
|
||||
final String routeName;
|
||||
final Object? arguments;
|
||||
|
||||
PushRouteEffect(this.routeName, {this.arguments});
|
||||
}
|
||||
|
||||
// Feedback Effects
|
||||
class ShowSnackbarEffect extends AppEffect {
|
||||
final String message;
|
||||
|
||||
ShowSnackbarEffect(this.message);
|
||||
}
|
||||
|
||||
class ShowDialogEffect extends AppEffect {
|
||||
final Dialog dialog;
|
||||
|
||||
ShowDialogEffect(this.dialog);
|
||||
}
|
||||
22
lib/application/states/home_state.dart
Normal file
22
lib/application/states/home_state.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
//External dependencies
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
//Internal dependencies
|
||||
import 'package:unyo/application/cubits/effect_mixin.dart';
|
||||
import 'package:unyo/application/effects/app_effects.dart';
|
||||
import 'package:unyo/data/models/models.dart';
|
||||
|
||||
part 'home_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class HomeState with _$HomeState implements HasEffects{
|
||||
const factory HomeState({
|
||||
required UserModel user,
|
||||
@Default(<AppEffect>[]) List<AppEffect> effects,
|
||||
}) = _HomeState;
|
||||
|
||||
const HomeState._();
|
||||
|
||||
@override
|
||||
List<AppEffect> get stateEffects => effects;
|
||||
}
|
||||
298
lib/application/states/home_state.freezed.dart
Normal file
298
lib/application/states/home_state.freezed.dart
Normal file
@@ -0,0 +1,298 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'home_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$HomeState {
|
||||
|
||||
UserModel get user; List<AppEffect> get effects;
|
||||
/// Create a copy of HomeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$HomeStateCopyWith<HomeState> get copyWith => _$HomeStateCopyWithImpl<HomeState>(this as HomeState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is HomeState&&(identical(other.user, user) || other.user == user)&&const DeepCollectionEquality().equals(other.effects, effects));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,user,const DeepCollectionEquality().hash(effects));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HomeState(user: $user, effects: $effects)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $HomeStateCopyWith<$Res> {
|
||||
factory $HomeStateCopyWith(HomeState value, $Res Function(HomeState) _then) = _$HomeStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
UserModel user, List<AppEffect> effects
|
||||
});
|
||||
|
||||
|
||||
$UserModelCopyWith<$Res> get user;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$HomeStateCopyWithImpl<$Res>
|
||||
implements $HomeStateCopyWith<$Res> {
|
||||
_$HomeStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final HomeState _self;
|
||||
final $Res Function(HomeState) _then;
|
||||
|
||||
/// Create a copy of HomeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? user = null,Object? effects = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
||||
as UserModel,effects: null == effects ? _self.effects : effects // ignore: cast_nullable_to_non_nullable
|
||||
as List<AppEffect>,
|
||||
));
|
||||
}
|
||||
/// Create a copy of HomeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserModelCopyWith<$Res> get user {
|
||||
|
||||
return $UserModelCopyWith<$Res>(_self.user, (value) {
|
||||
return _then(_self.copyWith(user: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [HomeState].
|
||||
extension HomeStatePatterns on HomeState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _HomeState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _HomeState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _HomeState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _HomeState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _HomeState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _HomeState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( UserModel user, List<AppEffect> effects)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _HomeState() when $default != null:
|
||||
return $default(_that.user,_that.effects);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( UserModel user, List<AppEffect> effects) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _HomeState():
|
||||
return $default(_that.user,_that.effects);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( UserModel user, List<AppEffect> effects)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _HomeState() when $default != null:
|
||||
return $default(_that.user,_that.effects);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _HomeState extends HomeState {
|
||||
const _HomeState({required this.user, final List<AppEffect> effects = const <AppEffect>[]}): _effects = effects,super._();
|
||||
|
||||
|
||||
@override final UserModel user;
|
||||
final List<AppEffect> _effects;
|
||||
@override@JsonKey() List<AppEffect> get effects {
|
||||
if (_effects is EqualUnmodifiableListView) return _effects;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_effects);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of HomeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$HomeStateCopyWith<_HomeState> get copyWith => __$HomeStateCopyWithImpl<_HomeState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomeState&&(identical(other.user, user) || other.user == user)&&const DeepCollectionEquality().equals(other._effects, _effects));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,user,const DeepCollectionEquality().hash(_effects));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HomeState(user: $user, effects: $effects)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$HomeStateCopyWith<$Res> implements $HomeStateCopyWith<$Res> {
|
||||
factory _$HomeStateCopyWith(_HomeState value, $Res Function(_HomeState) _then) = __$HomeStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
UserModel user, List<AppEffect> effects
|
||||
});
|
||||
|
||||
|
||||
@override $UserModelCopyWith<$Res> get user;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$HomeStateCopyWithImpl<$Res>
|
||||
implements _$HomeStateCopyWith<$Res> {
|
||||
__$HomeStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _HomeState _self;
|
||||
final $Res Function(_HomeState) _then;
|
||||
|
||||
/// Create a copy of HomeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? user = null,Object? effects = null,}) {
|
||||
return _then(_HomeState(
|
||||
user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
||||
as UserModel,effects: null == effects ? _self._effects : effects // ignore: cast_nullable_to_non_nullable
|
||||
as List<AppEffect>,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of HomeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserModelCopyWith<$Res> get user {
|
||||
|
||||
return $UserModelCopyWith<$Res>(_self.user, (value) {
|
||||
return _then(_self.copyWith(user: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
23
lib/application/states/login_state.dart
Normal file
23
lib/application/states/login_state.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
//External dependencies
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:unyo/application/cubits/effect_mixin.dart';
|
||||
import 'package:unyo/application/effects/app_effects.dart';
|
||||
|
||||
//Internal dependencies
|
||||
import 'package:unyo/data/models/models.dart';
|
||||
|
||||
part 'login_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class LoginState with _$LoginState implements HasEffects{
|
||||
const factory LoginState({
|
||||
required UserModel user,
|
||||
required List<UserModel> availableUsers,
|
||||
@Default(<AppEffect>[]) List<AppEffect> effects,
|
||||
}) = _LoginState;
|
||||
|
||||
const LoginState._();
|
||||
|
||||
@override
|
||||
List<AppEffect> get stateEffects => effects;
|
||||
}
|
||||
307
lib/application/states/login_state.freezed.dart
Normal file
307
lib/application/states/login_state.freezed.dart
Normal file
@@ -0,0 +1,307 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'login_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$LoginState {
|
||||
|
||||
UserModel get user; List<UserModel> get availableUsers; List<AppEffect> get effects;
|
||||
/// Create a copy of LoginState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$LoginStateCopyWith<LoginState> get copyWith => _$LoginStateCopyWithImpl<LoginState>(this as LoginState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is LoginState&&(identical(other.user, user) || other.user == user)&&const DeepCollectionEquality().equals(other.availableUsers, availableUsers)&&const DeepCollectionEquality().equals(other.effects, effects));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,user,const DeepCollectionEquality().hash(availableUsers),const DeepCollectionEquality().hash(effects));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoginState(user: $user, availableUsers: $availableUsers, effects: $effects)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $LoginStateCopyWith<$Res> {
|
||||
factory $LoginStateCopyWith(LoginState value, $Res Function(LoginState) _then) = _$LoginStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
UserModel user, List<UserModel> availableUsers, List<AppEffect> effects
|
||||
});
|
||||
|
||||
|
||||
$UserModelCopyWith<$Res> get user;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$LoginStateCopyWithImpl<$Res>
|
||||
implements $LoginStateCopyWith<$Res> {
|
||||
_$LoginStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final LoginState _self;
|
||||
final $Res Function(LoginState) _then;
|
||||
|
||||
/// Create a copy of LoginState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? user = null,Object? availableUsers = null,Object? effects = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
||||
as UserModel,availableUsers: null == availableUsers ? _self.availableUsers : availableUsers // ignore: cast_nullable_to_non_nullable
|
||||
as List<UserModel>,effects: null == effects ? _self.effects : effects // ignore: cast_nullable_to_non_nullable
|
||||
as List<AppEffect>,
|
||||
));
|
||||
}
|
||||
/// Create a copy of LoginState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserModelCopyWith<$Res> get user {
|
||||
|
||||
return $UserModelCopyWith<$Res>(_self.user, (value) {
|
||||
return _then(_self.copyWith(user: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [LoginState].
|
||||
extension LoginStatePatterns on LoginState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LoginState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LoginState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LoginState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LoginState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LoginState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LoginState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( UserModel user, List<UserModel> availableUsers, List<AppEffect> effects)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LoginState() when $default != null:
|
||||
return $default(_that.user,_that.availableUsers,_that.effects);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( UserModel user, List<UserModel> availableUsers, List<AppEffect> effects) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LoginState():
|
||||
return $default(_that.user,_that.availableUsers,_that.effects);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( UserModel user, List<UserModel> availableUsers, List<AppEffect> effects)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LoginState() when $default != null:
|
||||
return $default(_that.user,_that.availableUsers,_that.effects);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _LoginState extends LoginState {
|
||||
const _LoginState({required this.user, required final List<UserModel> availableUsers, final List<AppEffect> effects = const <AppEffect>[]}): _availableUsers = availableUsers,_effects = effects,super._();
|
||||
|
||||
|
||||
@override final UserModel user;
|
||||
final List<UserModel> _availableUsers;
|
||||
@override List<UserModel> get availableUsers {
|
||||
if (_availableUsers is EqualUnmodifiableListView) return _availableUsers;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_availableUsers);
|
||||
}
|
||||
|
||||
final List<AppEffect> _effects;
|
||||
@override@JsonKey() List<AppEffect> get effects {
|
||||
if (_effects is EqualUnmodifiableListView) return _effects;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_effects);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of LoginState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$LoginStateCopyWith<_LoginState> get copyWith => __$LoginStateCopyWithImpl<_LoginState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoginState&&(identical(other.user, user) || other.user == user)&&const DeepCollectionEquality().equals(other._availableUsers, _availableUsers)&&const DeepCollectionEquality().equals(other._effects, _effects));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,user,const DeepCollectionEquality().hash(_availableUsers),const DeepCollectionEquality().hash(_effects));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoginState(user: $user, availableUsers: $availableUsers, effects: $effects)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$LoginStateCopyWith<$Res> implements $LoginStateCopyWith<$Res> {
|
||||
factory _$LoginStateCopyWith(_LoginState value, $Res Function(_LoginState) _then) = __$LoginStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
UserModel user, List<UserModel> availableUsers, List<AppEffect> effects
|
||||
});
|
||||
|
||||
|
||||
@override $UserModelCopyWith<$Res> get user;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$LoginStateCopyWithImpl<$Res>
|
||||
implements _$LoginStateCopyWith<$Res> {
|
||||
__$LoginStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _LoginState _self;
|
||||
final $Res Function(_LoginState) _then;
|
||||
|
||||
/// Create a copy of LoginState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? user = null,Object? availableUsers = null,Object? effects = null,}) {
|
||||
return _then(_LoginState(
|
||||
user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
|
||||
as UserModel,availableUsers: null == availableUsers ? _self._availableUsers : availableUsers // ignore: cast_nullable_to_non_nullable
|
||||
as List<UserModel>,effects: null == effects ? _self._effects : effects // ignore: cast_nullable_to_non_nullable
|
||||
as List<AppEffect>,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of LoginState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserModelCopyWith<$Res> get user {
|
||||
|
||||
return $UserModelCopyWith<$Res>(_self.user, (value) {
|
||||
return _then(_self.copyWith(user: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
4
lib/config/config.dart
Normal file
4
lib/config/config.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
const String baseUrl = 'https://api.example.com';
|
||||
const String version = 'v1.0.0';
|
||||
|
||||
const plusImageUrl = "https://i.ibb.co/Kj8CQZH/cross.png";
|
||||
33
lib/core/di/locator.dart
Normal file
33
lib/core/di/locator.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
// External dependencies
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
// Internal dependencies
|
||||
import 'package:unyo/application/cubits/login_cubit.dart';
|
||||
import 'package:unyo/core/log/logger.dart';
|
||||
import 'package:unyo/core/notifier/user_notifier.dart';
|
||||
import 'package:unyo/core/services/api/http/http_service.dart';
|
||||
import 'package:unyo/core/services/effects/app_effect_handler.dart';
|
||||
import 'package:unyo/data/repositories/repositories.dart';
|
||||
import 'package:unyo/application/cubits/home_cubit.dart';
|
||||
|
||||
final sl = GetIt.instance;
|
||||
|
||||
void setupLocator() {
|
||||
// Singletons
|
||||
sl.registerLazySingleton<Logger>(() => getLogger());
|
||||
|
||||
// Services
|
||||
sl.registerLazySingleton<HttpService>(() => HttpService());
|
||||
sl.registerLazySingleton<AppEffectHandler>(() => AppEffectHandler());
|
||||
|
||||
// Notifiers
|
||||
sl.registerLazySingleton<UserNotifier>(() => UserNotifier());
|
||||
|
||||
// Repositories
|
||||
sl.registerLazySingleton<UserRepositoryHive>(() => UserRepositoryHive());
|
||||
|
||||
// Cubits / Blocs
|
||||
sl.registerFactory<LoginCubit>(() => LoginCubit(sl(), sl()));
|
||||
sl.registerFactory<HomeCubit>(() => HomeCubit(sl(), sl()));
|
||||
}
|
||||
8
lib/core/log/logger.dart
Normal file
8
lib/core/log/logger.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
Logger getLogger() {
|
||||
return Logger(
|
||||
printer: PrettyPrinter(),
|
||||
filter: ProductionFilter(),
|
||||
);
|
||||
}
|
||||
25
lib/core/notifier/user_notifier.dart
Normal file
25
lib/core/notifier/user_notifier.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
// External dependencies
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
// Internal dependencies
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
import 'package:unyo/data/models/user_model.dart';
|
||||
|
||||
class UserNotifier {
|
||||
final BehaviorSubject<UserModel> _userSubject;
|
||||
final _logger = sl<Logger>();
|
||||
|
||||
UserNotifier() : _userSubject = BehaviorSubject.seeded(UserModel.empty());
|
||||
|
||||
// Public stream for Cubits to subscribe
|
||||
Stream<UserModel> get userStream => _userSubject.stream;
|
||||
|
||||
void updateUser(UserModel user) {
|
||||
_logger.d("User notifier updated with: ${user.name}");
|
||||
_userSubject.add(user);
|
||||
}
|
||||
|
||||
UserModel get currentUser => _userSubject.value;
|
||||
|
||||
void dispose() => _userSubject.close();
|
||||
}
|
||||
25
lib/core/router/app_router.dart
Normal file
25
lib/core/router/app_router.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:unyo/core/router/app_router.gr.dart';
|
||||
|
||||
@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route')
|
||||
class AppRouter extends RootStackRouter {
|
||||
// @override
|
||||
// RouteType get defaultRouteType => RouteType.custom();
|
||||
|
||||
@override
|
||||
List<AutoRoute> get routes => [
|
||||
AutoRoute(
|
||||
page: RootScaffoldRoute.page,
|
||||
path: '/',
|
||||
children: [
|
||||
AutoRoute(page: LoginRoute.page, path: 'login', initial: true),
|
||||
AutoRoute(page: HomeRoute.page, path: 'home')
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
List<AutoRouteGuard> get guards => [
|
||||
// guards can be added here
|
||||
];
|
||||
}
|
||||
63
lib/core/router/app_router.gr.dart
Normal file
63
lib/core/router/app_router.gr.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
// dart format width=80
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
// **************************************************************************
|
||||
// AutoRouterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// coverage:ignore-file
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:auto_route/auto_route.dart' as _i4;
|
||||
import 'package:unyo/presentation/screens/home_screen.dart' as _i1;
|
||||
import 'package:unyo/presentation/screens/login_screen.dart' as _i2;
|
||||
import 'package:unyo/presentation/screens/root_scaffold_screen.dart' as _i3;
|
||||
|
||||
/// generated route for
|
||||
/// [_i1.HomeScreen]
|
||||
class HomeRoute extends _i4.PageRouteInfo<void> {
|
||||
const HomeRoute({List<_i4.PageRouteInfo>? children})
|
||||
: super(HomeRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'HomeRoute';
|
||||
|
||||
static _i4.PageInfo page = _i4.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i1.HomeScreen();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i2.LoginScreen]
|
||||
class LoginRoute extends _i4.PageRouteInfo<void> {
|
||||
const LoginRoute({List<_i4.PageRouteInfo>? children})
|
||||
: super(LoginRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'LoginRoute';
|
||||
|
||||
static _i4.PageInfo page = _i4.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i2.LoginScreen();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i3.RootScaffoldScreen]
|
||||
class RootScaffoldRoute extends _i4.PageRouteInfo<void> {
|
||||
const RootScaffoldRoute({List<_i4.PageRouteInfo>? children})
|
||||
: super(RootScaffoldRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'RootScaffoldRoute';
|
||||
|
||||
static _i4.PageInfo page = _i4.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i3.RootScaffoldScreen();
|
||||
},
|
||||
);
|
||||
}
|
||||
17
lib/core/services/api/http/README.md
Normal file
17
lib/core/services/api/http/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Http
|
||||
|
||||
A composable, Future-based library for making HTTP requests that contains a set of high-level functions and classes that make it easy to consume HTTP resources.
|
||||
|
||||
## Folders
|
||||
|
||||
- http_helpers (Contains all the information about base urls, api calls and exception handling)
|
||||
|
||||
### http_helpers
|
||||
|
||||
- [http_client.dart](api_sdk/http/http_helpers/http_client.dart) - HttpClient class is responsible for handling all the network call methods.
|
||||
- [http_exception.dart](api_sdk/http/http_helpers/http_exception.dart) - HttpExceptions handles all the exceptions for different error codes.
|
||||
- [http_interceptor.dart](api_sdk/http/http_helpers/http_interceptor.dart) - HttpInterceptor lets you intercept the different requests and responses.
|
||||
|
||||
### http_api_sdk
|
||||
|
||||
Contains two classes HttpApi and HttpService that will eventually call the network methods. **HttpApi** class will create a client instance which will have interceptors added to it. **HttpService** will eventually call the network methods present in [http_service.dart](api_sdk/http/http_service.dart)
|
||||
98
lib/core/services/api/http/helpers/http_client.dart
Normal file
98
lib/core/services/api/http/helpers/http_client.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
// Dart dependencies
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
// External dependencies
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'http_exception.dart';
|
||||
|
||||
class HttpClient {
|
||||
final http.Client client;
|
||||
HttpClient({required this.client});
|
||||
Future<dynamic> get(
|
||||
String url, {
|
||||
Map<String, String>? headers,
|
||||
bool isTokenRequired = false,
|
||||
}) async {
|
||||
http.Response? response;
|
||||
dynamic responseJson;
|
||||
|
||||
try {
|
||||
response =
|
||||
await http.get(Uri.parse(url), headers: headers ?? {});
|
||||
|
||||
responseJson = _returnResponse(response);
|
||||
} on SocketException {
|
||||
throw FetchDataException('No Internet connection');
|
||||
}
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
Future<dynamic> post(String url,
|
||||
{Map<String, String>? headers, Object? body}) async {
|
||||
http.Response? response;
|
||||
dynamic responseJson;
|
||||
try {
|
||||
response = await http.post(Uri.parse(url),
|
||||
headers: headers ?? {}, body: body);
|
||||
responseJson = _returnResponse(response);
|
||||
} on SocketException {
|
||||
throw FetchDataException('No Internet connection');
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
Future<dynamic> put(String url,
|
||||
{Map<String, String>? headers, Object? body}) async {
|
||||
http.Response? response;
|
||||
dynamic responseJson;
|
||||
try {
|
||||
response = await http.put(Uri.parse(url),
|
||||
headers: headers ?? {}, body: body);
|
||||
responseJson = _returnResponse(response);
|
||||
} on SocketException {
|
||||
throw FetchDataException('No Internet connection');
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
Future<dynamic> delete(String url, {Map<String, String>? headers}) async {
|
||||
http.Response? response;
|
||||
dynamic responseJson;
|
||||
try {
|
||||
response =
|
||||
await http.delete(Uri.parse(url), headers: headers ?? {});
|
||||
responseJson = _returnResponse(response);
|
||||
} on SocketException {
|
||||
throw FetchDataException('No Internet connection');
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _returnResponse(http.Response response) {
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
var responseJson = json.decode(response.body.toString());
|
||||
return responseJson;
|
||||
case 400:
|
||||
throw BadRequestException(response.body.toString());
|
||||
case 401:
|
||||
throw UnauthorisedException(response.body.toString());
|
||||
case 403:
|
||||
throw UnauthorisedException(response.body.toString());
|
||||
case 404:
|
||||
throw FileNotFoundException(response.body.toString());
|
||||
case 500:
|
||||
throw InternalServerException(response.body.toString());
|
||||
case 502:
|
||||
throw BadGateWayException(response.body.toString());
|
||||
case 503:
|
||||
throw BadGateWayException(response.body.toString());
|
||||
default:
|
||||
return FetchDataException(
|
||||
'Error occured while Communication with Server with StatusCode : ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
42
lib/core/services/api/http/helpers/http_exception.dart
Normal file
42
lib/core/services/api/http/helpers/http_exception.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
class HttpException implements Exception {
|
||||
final dynamic _message;
|
||||
final dynamic _prefix;
|
||||
|
||||
HttpException([this._message, this._prefix]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "$_prefix$_message";
|
||||
}
|
||||
}
|
||||
|
||||
class FetchDataException extends HttpException {
|
||||
FetchDataException([String? message])
|
||||
: super(message, "Error During Communication: ");
|
||||
}
|
||||
|
||||
class BadRequestException extends HttpException {
|
||||
BadRequestException([message]) : super(message, "Invalid Request: ");
|
||||
}
|
||||
|
||||
class UnauthorisedException extends HttpException {
|
||||
UnauthorisedException([message]) : super(message, "Unauthorised: ");
|
||||
}
|
||||
|
||||
class InvalidInputException extends HttpException {
|
||||
InvalidInputException([String? message]) : super(message, "Invalid Input: ");
|
||||
}
|
||||
|
||||
class FileNotFoundException extends HttpException {
|
||||
FileNotFoundException([String? message]) : super(message, "File not found: ");
|
||||
}
|
||||
|
||||
class InternalServerException extends HttpException {
|
||||
InternalServerException([String? message])
|
||||
: super(message, "Internal Server Exception: ");
|
||||
}
|
||||
|
||||
class BadGateWayException extends HttpException {
|
||||
BadGateWayException([String? message])
|
||||
: super(message, "Bad Gateway Exception: ");
|
||||
}
|
||||
33
lib/core/services/api/http/http_service.dart
Normal file
33
lib/core/services/api/http/http_service.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
//External dependencies
|
||||
import 'package:http/http.dart';
|
||||
//Internal dependencies
|
||||
import 'package:unyo/core/services/api/http/helpers/http_client.dart';
|
||||
|
||||
class HttpService {
|
||||
|
||||
//Empty constructor
|
||||
const HttpService();
|
||||
|
||||
Client createHttp() {
|
||||
return Client();
|
||||
}
|
||||
|
||||
getData(String url) async {
|
||||
final client = HttpClient(client: createHttp());
|
||||
final response = await client.get(url);
|
||||
return response;
|
||||
}
|
||||
|
||||
postData(String url, dynamic body) async {
|
||||
final client = HttpClient(client: createHttp());
|
||||
final response = await client.post(url, body: body);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
putData(String url, dynamic body) async {
|
||||
final client = HttpClient(client: createHttp());
|
||||
final response = await client.put(url, body: body);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
61
lib/core/services/effects/app_effect_handler.dart
Normal file
61
lib/core/services/effects/app_effect_handler.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
// External dependencies
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
// Internal dependencies
|
||||
import 'package:unyo/application/effects/app_effects.dart';
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
|
||||
class AppEffectHandler {
|
||||
final _logger = sl<Logger>();
|
||||
|
||||
AppEffectHandler();
|
||||
|
||||
void handleEffects(
|
||||
BuildContext context,
|
||||
List<AppEffect> effects,
|
||||
void Function() clearAppEffects,
|
||||
) {
|
||||
for (var effect in effects) {
|
||||
handle(effect, context);
|
||||
}
|
||||
clearAppEffects();
|
||||
}
|
||||
|
||||
void handle(AppEffect effect, BuildContext context) {
|
||||
switch (effect) {
|
||||
case ShowSnackbarEffect showSnackBarEffect:
|
||||
_handleShowSnackbarEffect(showSnackBarEffect);
|
||||
case ReplaceRouteEffect replaceRouteEffect:
|
||||
_handleReplaceRouteEffect(replaceRouteEffect, context);
|
||||
case PushRouteEffect pushRouteEffect:
|
||||
_handlePushRouteEffect(pushRouteEffect, context);
|
||||
default:
|
||||
_handleUnkownEffect(effect);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleShowSnackbarEffect(ShowSnackbarEffect effect) {
|
||||
_logger.d("Handling ShowSnackbarEffect");
|
||||
}
|
||||
|
||||
void _handleUnkownEffect(AppEffect effect) {
|
||||
_logger.e("Unimplemented Effect Handler for effect: $effect");
|
||||
}
|
||||
|
||||
void _handleReplaceRouteEffect(ReplaceRouteEffect effect, BuildContext context,) {
|
||||
_logger.d("Handling ReplaceRouteEffect");
|
||||
context.router.root.replacePath(effect.routeName.replaceFirst("/", ""), onFailure: _handleRouteFailure);
|
||||
}
|
||||
|
||||
void _handlePushRouteEffect(PushRouteEffect effect, BuildContext context) {
|
||||
_logger.d("Handling PushRouteEffect");
|
||||
context.router.root.pushPath(effect.routeName.replaceFirst("/", ""), onFailure: _handleRouteFailure);
|
||||
}
|
||||
|
||||
void _handleRouteFailure(NavigationFailure failure) {
|
||||
_logger.e("Navigation failure: ${failure.toString()}");
|
||||
}
|
||||
}
|
||||
1
lib/data/models/models.dart
Normal file
1
lib/data/models/models.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'user_model.dart';
|
||||
34
lib/data/models/user_model.dart
Normal file
34
lib/data/models/user_model.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
//External dependencies
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
//Internal dependencies
|
||||
import 'package:unyo/domain/entities/user.dart';
|
||||
|
||||
part 'user_model.freezed.dart';
|
||||
|
||||
part 'user_model.g.dart'; // For JSON serialization
|
||||
|
||||
@freezed
|
||||
abstract class UserModel with _$UserModel implements User {
|
||||
const UserModel._();
|
||||
|
||||
const factory UserModel({required String name, required String avatarImage}) = _UserModel;
|
||||
|
||||
factory UserModel.empty() => const UserModel(name: '', avatarImage: 'https://i.imgur.com/EKtChtm.png');
|
||||
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserModelFromJson(json);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$UserModelToJson(this as _UserModel);
|
||||
}
|
||||
|
||||
class UserConverter implements JsonConverter<User, Map<String, dynamic>> {
|
||||
const UserConverter();
|
||||
|
||||
@override
|
||||
User fromJson(Map<String, dynamic> json) => UserModel.fromJson(json);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson(User object) => (object as UserModel).toJson();
|
||||
}
|
||||
280
lib/data/models/user_model.freezed.dart
Normal file
280
lib/data/models/user_model.freezed.dart
Normal file
@@ -0,0 +1,280 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'user_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UserModel {
|
||||
|
||||
String get name; String get avatarImage;
|
||||
/// Create a copy of UserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$UserModelCopyWith<UserModel> get copyWith => _$UserModelCopyWithImpl<UserModel>(this as UserModel, _$identity);
|
||||
|
||||
/// Serializes this UserModel to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is UserModel&&(identical(other.name, name) || other.name == name)&&(identical(other.avatarImage, avatarImage) || other.avatarImage == avatarImage));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,avatarImage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserModel(name: $name, avatarImage: $avatarImage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $UserModelCopyWith<$Res> {
|
||||
factory $UserModelCopyWith(UserModel value, $Res Function(UserModel) _then) = _$UserModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String name, String avatarImage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$UserModelCopyWithImpl<$Res>
|
||||
implements $UserModelCopyWith<$Res> {
|
||||
_$UserModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final UserModel _self;
|
||||
final $Res Function(UserModel) _then;
|
||||
|
||||
/// Create a copy of UserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? avatarImage = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,avatarImage: null == avatarImage ? _self.avatarImage : avatarImage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [UserModel].
|
||||
extension UserModelPatterns on UserModel {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _UserModel value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UserModel() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _UserModel value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UserModel():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _UserModel value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _UserModel() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String avatarImage)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UserModel() when $default != null:
|
||||
return $default(_that.name,_that.avatarImage);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String avatarImage) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UserModel():
|
||||
return $default(_that.name,_that.avatarImage);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String avatarImage)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _UserModel() when $default != null:
|
||||
return $default(_that.name,_that.avatarImage);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _UserModel extends UserModel {
|
||||
const _UserModel({required this.name, required this.avatarImage}): super._();
|
||||
factory _UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
|
||||
|
||||
@override final String name;
|
||||
@override final String avatarImage;
|
||||
|
||||
/// Create a copy of UserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$UserModelCopyWith<_UserModel> get copyWith => __$UserModelCopyWithImpl<_UserModel>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$UserModelToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserModel&&(identical(other.name, name) || other.name == name)&&(identical(other.avatarImage, avatarImage) || other.avatarImage == avatarImage));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,avatarImage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserModel(name: $name, avatarImage: $avatarImage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$UserModelCopyWith<$Res> implements $UserModelCopyWith<$Res> {
|
||||
factory _$UserModelCopyWith(_UserModel value, $Res Function(_UserModel) _then) = __$UserModelCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String name, String avatarImage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$UserModelCopyWithImpl<$Res>
|
||||
implements _$UserModelCopyWith<$Res> {
|
||||
__$UserModelCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _UserModel _self;
|
||||
final $Res Function(_UserModel) _then;
|
||||
|
||||
/// Create a copy of UserModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? avatarImage = null,}) {
|
||||
return _then(_UserModel(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,avatarImage: null == avatarImage ? _self.avatarImage : avatarImage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
18
lib/data/models/user_model.g.dart
Normal file
18
lib/data/models/user_model.g.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_UserModel _$UserModelFromJson(Map<String, dynamic> json) => _UserModel(
|
||||
name: json['name'] as String,
|
||||
avatarImage: json['avatarImage'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UserModelToJson(_UserModel instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'avatarImage': instance.avatarImage,
|
||||
};
|
||||
1
lib/data/repositories/repositories.dart
Normal file
1
lib/data/repositories/repositories.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'user_repository_hive.dart';
|
||||
24
lib/data/repositories/user_repository_hive.dart
Normal file
24
lib/data/repositories/user_repository_hive.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
// External Dependencies
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
// Internal Dependencies
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
import 'package:unyo/data/models/models.dart';
|
||||
import 'package:unyo/domain/entities/entities.dart';
|
||||
import 'package:unyo/domain/repositories/user_repository.dart';
|
||||
|
||||
class UserRepositoryHive implements UserRepository {
|
||||
final Logger _logger = sl<Logger>();
|
||||
|
||||
@override
|
||||
Future<List<User>> fetchAllUsers() async {
|
||||
return [UserModel.empty()];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> registerUser(User user) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
import 'package:unyo/widgets/widgets.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
void showChangeRepoDialog(
|
||||
BuildContext context, TextEditingController controller) {
|
||||
logger.i("Opened changeRepo dialog");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ChangeRepoDialog(
|
||||
controller: controller,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class ChangeRepoDialog extends StatelessWidget {
|
||||
const ChangeRepoDialog({super.key, required this.controller});
|
||||
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(context.tr("change_repo"),
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(context.tr("change_repo_message"),
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
StyledTextField(
|
||||
width: 500,
|
||||
controller: controller,
|
||||
color: Colors.white,
|
||||
hintColor: Colors.grey,
|
||||
hint: prefs.getString("extensions_json_url") ??
|
||||
"https://raw.githubusercontent.com/K3vinb5/Unyo-Extensions/main/index.json",
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
StyledButton(
|
||||
onPressed: () {
|
||||
logger.i("Restored extensions repository to default");
|
||||
prefs.setString("extensions_json_url",
|
||||
"https://raw.githubusercontent.com/K3vinb5/Unyo-Extensions/main/index.json");
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
text: context.tr("restore_default"),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
StyledButton(
|
||||
onPressed: () {
|
||||
logger.i("Changed extensions repository");
|
||||
if (controller.text.trim() != "") {
|
||||
prefs.setString(
|
||||
"extensions_json_url", controller.text.trim());
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
text: context.tr("confirm"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
void showConnectionSuccessfulDialog(BuildContext context) {
|
||||
logger.i("Opened connection successful dialog");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const ConnectionSuccessfulDialog();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class ConnectionSuccessfulDialog extends StatelessWidget {
|
||||
const ConnectionSuccessfulDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Connection Successful",
|
||||
style: TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Ok", style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
import 'package:unyo/widgets/widgets.dart';
|
||||
|
||||
void showCreateLocalAccoutDialog(BuildContext context,
|
||||
void Function(int) setUserInfo, void Function() goToMainMenu) {
|
||||
logger.i("Opened create local account dialog");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => CreateLocalAccountDialog(setUserInfo: setUserInfo, goToMainMenu: goToMainMenu));
|
||||
}
|
||||
|
||||
class CreateLocalAccountDialog extends StatelessWidget {
|
||||
const CreateLocalAccountDialog({super.key, required this.setUserInfo, required this.goToMainMenu});
|
||||
final void Function(int)
|
||||
setUserInfo;
|
||||
final void Function() goToMainMenu;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextEditingController controller = TextEditingController();
|
||||
|
||||
return AlertDialog(
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
title: Text(
|
||||
context.tr("create_local_account_title"),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.4,
|
||||
height: MediaQuery.of(context).size.height * 0.25,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
StyledTextField(
|
||||
width: 300,
|
||||
controller: controller,
|
||||
color: Colors.white,
|
||||
hintColor: Colors.grey,
|
||||
hint: context.tr("insert_new_name"),
|
||||
),
|
||||
StyledButton(
|
||||
onPressed: () async{
|
||||
if (controller.text.trim() != "") {
|
||||
await prefs.loginUser(controller.text.trim());
|
||||
userName = controller.text.trim();
|
||||
prefs.setString("userName", userName!);
|
||||
setUserInfo(1);
|
||||
goToMainMenu();
|
||||
logger.i("Created local account with name: ${controller.text.trim()}");
|
||||
if(!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
text: context.tr("confirm"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
class DeleteUserMediaDialog extends StatelessWidget {
|
||||
const DeleteUserMediaDialog(
|
||||
{super.key,
|
||||
required this.totalHeight,
|
||||
required this.totalWidth,
|
||||
required this.currentMediaId,
|
||||
required this.deleteUserAnime,
|
||||
});
|
||||
|
||||
final double totalHeight;
|
||||
final double totalWidth;
|
||||
final int currentMediaId;
|
||||
final void Function(int) deleteUserAnime;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: totalHeight * 0.2,
|
||||
width: totalWidth * 0.1,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
logger.i("Deleting user media with id: $currentMediaId");
|
||||
deleteUserAnime(currentMediaId);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
child: const Text("Confirm"),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
export 'update_dialog.dart';
|
||||
export 'video_quality_dialog.dart';
|
||||
export 'wrong_title_dialog.dart';
|
||||
export 'media_info_dialog.dart';
|
||||
export 'delete_user_media_dialog.dart';
|
||||
export 'login_manually_dialog.dart';
|
||||
export 'connection_successful_dialog.dart';
|
||||
export 'error_dialog.dart';
|
||||
export 'simple_dialog.dart';
|
||||
export 'change_repo_dialog.dart';
|
||||
export 'no_extensions_dialog.dart';
|
||||
export 'log_out_dialog.dart';
|
||||
export 'create_local_account_dialog.dart';
|
||||
export 'sign_in_dialog.dart';
|
||||
@@ -1,57 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
void showErrorDialog(BuildContext context,
|
||||
{String? exception, void Function()? onPressedAfterPop}) {
|
||||
logger.i("Opened error dialog");
|
||||
logger.e("Unknown error", error: exception);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ErrorDialog(exception: exception);
|
||||
});
|
||||
}
|
||||
|
||||
class ErrorDialog extends StatelessWidget {
|
||||
const ErrorDialog({super.key, this.exception, this.onPressedAfterPop});
|
||||
|
||||
final String? exception;
|
||||
final void Function()? onPressedAfterPop;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("An error occured D:",
|
||||
style: TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(exception ?? "", style: const TextStyle(color: Colors.white)),
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
if (onPressedAfterPop != null) {
|
||||
logger.i("Executing provided error resulting callback");
|
||||
onPressedAfterPop!();
|
||||
}
|
||||
},
|
||||
child: const Text("Ok", style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
import 'package:unyo/widgets/widgets.dart';
|
||||
|
||||
void showLogOutDialog(
|
||||
BuildContext context,
|
||||
void Function(void Function()) setState,
|
||||
void Function() attemptLogin,
|
||||
double adjustedHeight,
|
||||
double adjustedWidth) {
|
||||
logger.i("Opened log out dialog");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => LogOutDialog(
|
||||
attemptLogin: attemptLogin,
|
||||
adjustedWidth: adjustedWidth,
|
||||
adjustedHeight: adjustedHeight,
|
||||
setState: setState,
|
||||
));
|
||||
}
|
||||
|
||||
class LogOutDialog extends StatelessWidget {
|
||||
const LogOutDialog(
|
||||
{super.key,
|
||||
required this.attemptLogin,
|
||||
required this.adjustedWidth,
|
||||
required this.adjustedHeight,
|
||||
required this.setState});
|
||||
|
||||
final void Function() attemptLogin;
|
||||
final double adjustedWidth;
|
||||
final double adjustedHeight;
|
||||
final void Function(void Function()) setState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(context.tr("logout_title"),
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: SizedBox(
|
||||
width: adjustedWidth * 0.15,
|
||||
height: adjustedHeight * 0.15,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
context.tr("logout_text"),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
StyledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
text: context.tr("cancel"),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
StyledButton(
|
||||
onPressed: () {
|
||||
logger.i("Logging out...");
|
||||
prefs.logOut();
|
||||
if (prefs.getBool("remote_endpoint") ?? false) {
|
||||
processManager.stopProcess();
|
||||
}
|
||||
setState(() {
|
||||
bannerImageUrl = null;
|
||||
avatarImageUrl = null;
|
||||
watchingList = null;
|
||||
readingList = null;
|
||||
userName = null;
|
||||
userId = null;
|
||||
accessToken = null;
|
||||
});
|
||||
attemptLogin();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
text: context.tr("confirm"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/widgets/widgets.dart';
|
||||
|
||||
class LoginManuallyDialog extends StatelessWidget {
|
||||
const LoginManuallyDialog(
|
||||
{super.key,
|
||||
required this.manualLoginController,
|
||||
required this.getCodeFunction,
|
||||
required this.loginFunction
|
||||
});
|
||||
|
||||
final TextEditingController manualLoginController;
|
||||
final void Function() getCodeFunction;
|
||||
final void Function() loginFunction;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
title: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("Paste the Authentication Code",
|
||||
style: TextStyle(color: Colors.white)),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.3,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
StyledTextField(
|
||||
width: 350,
|
||||
controller: manualLoginController,
|
||||
color: Colors.white,
|
||||
hintColor: Colors.grey,
|
||||
hint: "Paste your code here",
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: getCodeFunction,
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(Colors.black12),
|
||||
),
|
||||
child: const Text(
|
||||
"Get your Code!",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: const ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(Colors.black12),
|
||||
),
|
||||
child: Text(
|
||||
"cancel".tr(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: loginFunction,
|
||||
style: const ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(Colors.black12),
|
||||
),
|
||||
child: Text(
|
||||
"confirm".tr(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,452 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:unyo/util/utils.dart';
|
||||
import 'package:unyo/widgets/widgets.dart';
|
||||
|
||||
class MediaInfoDialogManager {
|
||||
static final MediaInfoDialogManager _instance =
|
||||
MediaInfoDialogManager._internal();
|
||||
MediaInfoDialogManager._internal();
|
||||
factory MediaInfoDialogManager() => _instance;
|
||||
|
||||
void openAnimeInfoDialog(BuildContext context, totalWidth, totalHeight) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
const Text(
|
||||
"List Editor",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
askForDeleteUserMedia();
|
||||
},
|
||||
icon: const Icon(Icons.delete, color: Colors.white),
|
||||
)
|
||||
],
|
||||
),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: MediaInfoDialog(
|
||||
id: widget.currentAnime.id,
|
||||
episodes: widget.currentAnime.episodes,
|
||||
totalWidth: totalWidth,
|
||||
totalHeight: totalHeight,
|
||||
statuses: statuses,
|
||||
query: query,
|
||||
progress: progress,
|
||||
currentEpisode: latestReleasedEpisode,
|
||||
score: score,
|
||||
setUserMediaModel: setUserAnimeModel,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
animeModel: widget.currentAnime),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void askForDeleteUserMedia() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Are you sure you wish to delete this media entry",
|
||||
style: TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: DeleteUserMediaDialog(
|
||||
totalHeight: totalHeight,
|
||||
totalWidth: totalWidth,
|
||||
currentMediaId: widget.currentAnime.id,
|
||||
deleteUserAnime: loggedUserModel.deleteUserAnime,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MediaInfoDialog extends StatefulWidget {
|
||||
const MediaInfoDialog({
|
||||
super.key,
|
||||
required this.totalWidth,
|
||||
required this.totalHeight,
|
||||
required this.statuses,
|
||||
required this.query,
|
||||
required this.episodes,
|
||||
required this.progress,
|
||||
required this.currentEpisode,
|
||||
required this.score,
|
||||
required this.setUserMediaModel,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.id,
|
||||
this.animeModel,
|
||||
this.mangaModel,
|
||||
});
|
||||
|
||||
final double totalWidth;
|
||||
final double totalHeight;
|
||||
final List<String> statuses;
|
||||
final Map<String, String> query;
|
||||
final int? episodes;
|
||||
final int id;
|
||||
final double progress;
|
||||
final int currentEpisode;
|
||||
final double score;
|
||||
final String startDate;
|
||||
final String endDate;
|
||||
final void Function() setUserMediaModel;
|
||||
final AnimeModel? animeModel;
|
||||
final MangaModel? mangaModel;
|
||||
|
||||
@override
|
||||
State<MediaInfoDialog> createState() => _MediaInfoDialogState();
|
||||
}
|
||||
|
||||
class _MediaInfoDialogState extends State<MediaInfoDialog> {
|
||||
late Map<String, String> query;
|
||||
late double progress;
|
||||
late int currentEpisode;
|
||||
late String startDate;
|
||||
late String endDate;
|
||||
late double score;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
query = widget.query;
|
||||
progress = widget.progress;
|
||||
currentEpisode = widget.currentEpisode;
|
||||
startDate = widget.startDate;
|
||||
endDate = widget.endDate;
|
||||
score = widget.score;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MediaInfoDialog oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.query != widget.query) {
|
||||
query = widget.query;
|
||||
} else if (oldWidget.progress != widget.progress) {
|
||||
progress = widget.progress;
|
||||
} else if (oldWidget.currentEpisode != widget.currentEpisode) {
|
||||
currentEpisode = widget.currentEpisode;
|
||||
} else if (oldWidget.score != widget.score) {
|
||||
score = widget.score;
|
||||
} else if (oldWidget.startDate != widget.startDate) {
|
||||
startDate = widget.startDate;
|
||||
} else if (oldWidget.endDate != widget.endDate) {
|
||||
endDate = widget.endDate;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return SizedBox(
|
||||
width: widget.totalWidth * 0.5,
|
||||
height: widget.totalHeight * 0.6,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
"status".tr(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
StyledDropDown(
|
||||
items: [
|
||||
...widget.statuses.map((status) => Text(status)),
|
||||
],
|
||||
horizontalPadding: 10,
|
||||
onTap: (index) {
|
||||
String newCurrentStatus = widget.statuses[index];
|
||||
logger.i("Selected status: $newCurrentStatus");
|
||||
widget.statuses.removeAt(index);
|
||||
widget.statuses.insert(0, newCurrentStatus);
|
||||
query.remove("status");
|
||||
query.addAll({"status": newCurrentStatus});
|
||||
},
|
||||
color: Colors.white,
|
||||
width: widget.totalWidth * 0.4,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
"progress".tr(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
progress.toInt().toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
activeColor: Colors.grey,
|
||||
min: 0,
|
||||
max: widget.episodes?.toDouble() ??
|
||||
currentEpisode.toDouble(),
|
||||
value: progress,
|
||||
label: progress.round().toString(),
|
||||
divisions: widget.episodes ??
|
||||
(currentEpisode > 0 ? currentEpisode : 1),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
progress =
|
||||
value; // Update the progress variable when slider value changes
|
||||
});
|
||||
},
|
||||
onChangeEnd: (value) {
|
||||
logger.i("Progress slider value updated: $value");
|
||||
query.remove("progress");
|
||||
query.addAll({"progress": progress.toInt().toString()});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
context.tr("score"),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
score.toInt().toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
activeColor: Colors.grey,
|
||||
min: 0,
|
||||
max: 10,
|
||||
value: score,
|
||||
divisions: 10,
|
||||
label: score.round().toString(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
score =
|
||||
value; // Update the progress variable when slider value changes
|
||||
});
|
||||
},
|
||||
onChangeEnd: (value) {
|
||||
logger.i("Score slider value updated: $value");
|
||||
query.remove("score");
|
||||
query.addAll({"score": score.toString()});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
"start_end_data".tr(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
logger.i("Opening date picker for start date");
|
||||
DateTime? chosenDateTime = await showDatePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(1970, 1, 1),
|
||||
lastDate: DateTime.now(),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: const ColorScheme.dark(
|
||||
// Use ColorScheme.dark to reflect a dark theme
|
||||
primary: Color.fromARGB(
|
||||
255, 44, 44, 44), // Header background color
|
||||
onPrimary: Colors.white, // Header text color
|
||||
surface: Color.fromARGB(
|
||||
255, 55, 44, 55), // Dialog background color
|
||||
onSurface: Colors.white, // Body text color
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 37,
|
||||
37, 37), // Button background color
|
||||
foregroundColor:
|
||||
Colors.white, // Button text color
|
||||
),
|
||||
),
|
||||
dialogBackgroundColor: const Color.fromARGB(255,
|
||||
44, 44, 44), // Entire dialog background color
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (chosenDateTime != null) {
|
||||
setState(() {
|
||||
startDate =
|
||||
"${chosenDateTime.day}/${chosenDateTime.month}/${chosenDateTime.year}";
|
||||
});
|
||||
query.remove("startDateDay");
|
||||
query.addAll(
|
||||
{"startDateDay": chosenDateTime.day.toString()});
|
||||
query.remove("startDateMonth");
|
||||
query.addAll({
|
||||
"startDateMonth": chosenDateTime.month.toString()
|
||||
});
|
||||
query.remove("startDateYear");
|
||||
query.addAll(
|
||||
{"startDateYear": chosenDateTime.year.toString()});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.calendar_month, color: Colors.grey),
|
||||
),
|
||||
Text(
|
||||
startDate,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Text(
|
||||
endDate,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
logger.i("Opening date picker for end date");
|
||||
DateTime? chosenDateTime = await showDatePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(1970, 1, 1),
|
||||
lastDate: DateTime.now(),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
// Use ColorScheme.dark to reflect a dark theme
|
||||
primary: Color.fromARGB(
|
||||
255, 44, 44, 44), // Header background color
|
||||
onPrimary: Colors.white, // Header text color
|
||||
surface: Color.fromARGB(
|
||||
255, 55, 44, 55), // Dialog background color
|
||||
onSurface: Colors.white, // Body text color
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 37,
|
||||
37, 37), // Button background color
|
||||
foregroundColor:
|
||||
Colors.white, // Button text color
|
||||
),
|
||||
),
|
||||
dialogBackgroundColor: const Color.fromARGB(255,
|
||||
44, 44, 44), // Entire dialog background color
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (chosenDateTime != null) {
|
||||
setState(() {
|
||||
endDate =
|
||||
"${chosenDateTime.day}/${chosenDateTime.month}/${chosenDateTime.year}";
|
||||
});
|
||||
query.remove("endDateDay");
|
||||
query.addAll(
|
||||
{"endDateDay": chosenDateTime.day.toString()});
|
||||
query.remove("endDateMonth");
|
||||
query.addAll(
|
||||
{"endDateMonth": chosenDateTime.month.toString()});
|
||||
query.remove("endDateYear");
|
||||
query.addAll(
|
||||
{"endDateYear": chosenDateTime.year.toString()});
|
||||
}
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.calendar_month,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
logger.i("Updated user media info");
|
||||
if (widget.animeModel != null) {
|
||||
loggedUserModel.setUserAnimeInfo(widget.id, query,
|
||||
animeModel: widget.animeModel);
|
||||
} else {
|
||||
loggedUserModel.setUserMangaInfo(widget.id, query,
|
||||
mangaModel: widget.mangaModel);
|
||||
}
|
||||
Timer(
|
||||
const Duration(milliseconds: 1500),
|
||||
() {
|
||||
widget.setUserMediaModel();
|
||||
},
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text("confirm".tr()),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text("cancel".tr()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
void showNoExtensionsDialog(
|
||||
BuildContext context) {
|
||||
logger.i("No extensions dialog opened");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return NoExtensionsDialog(
|
||||
title: context.tr("no_extensions_title"),
|
||||
message: context.tr("no_extensions_message"),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class NoExtensionsDialog extends StatelessWidget {
|
||||
const NoExtensionsDialog(
|
||||
{super.key, required this.message, required this.title});
|
||||
|
||||
final String message;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(title, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(message, style: const TextStyle(color: Colors.white)),
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Ok", style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/icons/anilist_icons.dart';
|
||||
import 'package:unyo/dialogs/dialogs.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
import 'package:unyo/widgets/widgets.dart';
|
||||
|
||||
void showSignInDialog(
|
||||
{required BuildContext context,
|
||||
required double totalHeight,
|
||||
required double totalWidth,
|
||||
required void Function() login,
|
||||
required void Function() getCodeFunction,
|
||||
required void Function() goToMainScreen,
|
||||
required void Function(int) setUserInfo,
|
||||
required void Function(String) manualLogin}) {
|
||||
logger.i("Opened sign in dialog");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => SignInDialog(
|
||||
totalHeight: totalHeight,
|
||||
login: login,
|
||||
getCodeFunction: getCodeFunction,
|
||||
manualLogin: manualLogin,
|
||||
goToMainScreen: goToMainScreen,
|
||||
setUserInfo: setUserInfo,
|
||||
totalWidth: totalWidth,
|
||||
));
|
||||
}
|
||||
|
||||
class SignInDialog extends StatelessWidget {
|
||||
const SignInDialog({
|
||||
super.key,
|
||||
required this.totalHeight,
|
||||
required this.login,
|
||||
required this.getCodeFunction,
|
||||
required this.manualLogin,
|
||||
required this.goToMainScreen,
|
||||
required this.setUserInfo,
|
||||
required this.totalWidth,
|
||||
});
|
||||
final double totalHeight;
|
||||
final double totalWidth;
|
||||
final void Function() login;
|
||||
final void Function() getCodeFunction;
|
||||
final void Function() goToMainScreen;
|
||||
final void Function(int) setUserInfo;
|
||||
final void Function(String) manualLogin;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextEditingController controller = TextEditingController();
|
||||
return AlertDialog(
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
title: Text(context.tr("sign_int_title"),
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
content: SizedBox(
|
||||
width: totalWidth * 0.5,
|
||||
height: totalHeight * 0.5,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StyledButton(
|
||||
onPressed: () {
|
||||
login();
|
||||
logger.i("Logged in to Anilist");
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: SizedBox(
|
||||
width: 240,
|
||||
height: 70,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Login to Anilist ",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
Icon(Anilist.anilist),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
StyledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return LoginManuallyDialog(
|
||||
manualLoginController: controller,
|
||||
getCodeFunction: getCodeFunction,
|
||||
loginFunction: () async {
|
||||
manualLogin(controller.text.trim());
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: SizedBox(
|
||||
width: 240,
|
||||
height: 70,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Login to Anilist (Copying Code) ",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
Icon(Anilist.anilist),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
StyledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
showCreateLocalAccoutDialog(
|
||||
context,
|
||||
setUserInfo,
|
||||
goToMainScreen,
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: SizedBox(
|
||||
width: 240,
|
||||
height: 70,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"${context.tr("create_local_account")} ",
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
const Icon(Icons.computer_rounded),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
void showSimpleDialog(BuildContext context, String title, String message) {
|
||||
logger.i("Opened simple dialog: $title - $message");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: title,
|
||||
message: message,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class SimpleDialog extends StatelessWidget {
|
||||
const SimpleDialog({super.key, required this.message, required this.title});
|
||||
|
||||
final String message;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(title, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(message, style: const TextStyle(color: Colors.white)),
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Ok", style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:smooth_list_view/smooth_list_view.dart';
|
||||
import 'package:unyo/util/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const String apiEndpoint =
|
||||
"https://api.github.com/repos/K3vinb5/Unyo/releases/latest";
|
||||
const String latestVersionEndpoint =
|
||||
"https://github.com/K3vinb5/Unyo/releases/latest";
|
||||
|
||||
class UpdateDialog extends StatelessWidget {
|
||||
const UpdateDialog({super.key, required this.markdown});
|
||||
|
||||
final String markdown;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
opacity: 0.1,
|
||||
image: NetworkImage("https://i.imgur.com/JEGaQWx.png"),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: SingleChildScrollView(
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
textTheme: Theme.of(context)
|
||||
.textTheme
|
||||
.apply(bodyColor: Colors.white)),
|
||||
child: MarkdownBody(data: markdown),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
foregroundColor: MaterialStatePropertyAll(Colors.white),
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37)),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Later"),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
foregroundColor: MaterialStatePropertyAll(Colors.white),
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37)),
|
||||
),
|
||||
onPressed: () {
|
||||
logger.i("Opening newest release on your browser");
|
||||
goToLatestRelease();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Download Update"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if [newVersion] is strictly greater than [currentVersion].
|
||||
bool _isVersionGreater(String newVersion, String currentVersion) {
|
||||
List<String> newParts = newVersion.split('.');
|
||||
List<String> currParts = currentVersion.split('.');
|
||||
int maxLength = newParts.length > currParts.length ? newParts.length : currParts.length;
|
||||
for (int i = 0; i < maxLength; i++) {
|
||||
int n = i < newParts.length ? int.tryParse(newParts[i]) ?? 0 : 0;
|
||||
int c = i < currParts.length ? int.tryParse(currParts[i]) ?? 0 : 0;
|
||||
if (n > c) return true;
|
||||
if (n < c) return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void showUpdateDialog(BuildContext context) async {
|
||||
var url = Uri.parse(apiEndpoint);
|
||||
var response = await http.get(url);
|
||||
if (!context.mounted) return;
|
||||
if (response.statusCode != 200) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const AlertDialog(
|
||||
backgroundColor: Color.fromARGB(255, 34, 33, 34),
|
||||
title: Text(
|
||||
"Error when looking for updates",
|
||||
style: TextStyle(color: Colors.white, fontSize: 22),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
final String markdown = jsonResponse["body"] as String;
|
||||
final String newVersion = jsonResponse["tag_name"] as String;
|
||||
|
||||
// Don't show dialog if version is not newer or tagged to ignore
|
||||
if (!_isVersionGreater(newVersion, currentVersion)) return;
|
||||
if (newVersion.contains("ignore")) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: const Color.fromARGB(255, 34, 33, 34),
|
||||
title: Text(
|
||||
"New version available, update to version $newVersion",
|
||||
style: const TextStyle(color: Colors.white, fontSize: 22),
|
||||
),
|
||||
content: UpdateDialog(
|
||||
markdown: markdown,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void goToLatestRelease() async {
|
||||
if (await canLaunchUrl(Uri.parse(latestVersionEndpoint))) {
|
||||
await launchUrl(Uri.parse(latestVersionEndpoint));
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loading_animation_widget/loading_animation_widget.dart';
|
||||
import 'package:smooth_list_view/smooth_list_view.dart';
|
||||
import 'package:unyo/api/aniskip_api.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:unyo/router/custom_page_route.dart';
|
||||
import 'package:unyo/screens/screens.dart';
|
||||
import 'package:unyo/sources/sources.dart';
|
||||
import 'package:unyo/util/utils.dart';
|
||||
|
||||
class VideoQualityDialog extends StatefulWidget {
|
||||
const VideoQualityDialog({
|
||||
super.key,
|
||||
required this.adjustedWidth,
|
||||
required this.adjustedHeight,
|
||||
required this.updateEntry,
|
||||
required this.animeEpisode,
|
||||
required this.animeModel,
|
||||
required this.currentAnimeSource,
|
||||
required this.id,
|
||||
required this.idMal,
|
||||
});
|
||||
|
||||
final double adjustedWidth;
|
||||
final double adjustedHeight;
|
||||
final int animeEpisode;
|
||||
final AnimeModel animeModel;
|
||||
final void Function(int) updateEntry;
|
||||
final AnimeSource currentAnimeSource;
|
||||
final String id;
|
||||
final String idMal;
|
||||
|
||||
@override
|
||||
State<VideoQualityDialog> createState() => _VideoQualityDialogState();
|
||||
}
|
||||
|
||||
class _VideoQualityDialogState extends State<VideoQualityDialog> {
|
||||
StreamData? streamData;
|
||||
int source = 0;
|
||||
VideoScreen? videoScreen;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getStreamInfo();
|
||||
}
|
||||
|
||||
void getStreamInfo() async {
|
||||
logger.i("Getting stream info for ${widget.animeModel.getDefaultTitle()} episode ${widget.animeEpisode}");
|
||||
streamData = await widget.currentAnimeSource.getAnimeStreamAndCaptions(
|
||||
widget.id,
|
||||
widget.animeModel.englishTitle ?? "",
|
||||
widget.animeEpisode,
|
||||
context);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void onStreamSelected(int selected, Map<String, double> timestamps) {
|
||||
logger.i("Selected stream $selected for ${widget.animeModel.getDefaultTitle()} episode ${widget.animeEpisode}");
|
||||
source = selected;
|
||||
Navigator.of(context).pop();
|
||||
videoScreen = VideoScreen(
|
||||
source: source,
|
||||
streamData: streamData!,
|
||||
updateEntry: () {
|
||||
widget.updateEntry(widget.animeEpisode);
|
||||
},
|
||||
title:
|
||||
"${widget.animeModel.getDefaultTitle()}, ${"episode".tr()} ${widget.animeEpisode}",
|
||||
mqqtKey:
|
||||
"${widget.animeModel.userPreferedTitle}-ep${widget.animeEpisode}",
|
||||
episode: widget.animeEpisode,
|
||||
timestamps: timestamps,
|
||||
);
|
||||
logger.i("Opening video screen for ${widget.animeModel.getDefaultTitle()} episode ${widget.animeEpisode}");
|
||||
if (!context.mounted) return;
|
||||
Navigator.push(
|
||||
context,
|
||||
customPageRouter(videoScreen!),
|
||||
);
|
||||
}
|
||||
|
||||
String getUtf8Text(String text) {
|
||||
List<int> bytes = text.codeUnits;
|
||||
try {
|
||||
return utf8.decode(bytes);
|
||||
} catch (e) {
|
||||
logger.e("Error decoding text: $text", error: e.toString());
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: widget.adjustedWidth * 0.4,
|
||||
height: widget.adjustedHeight * 0.7,
|
||||
child: streamData != null
|
||||
? streamData!.qualities.isNotEmpty
|
||||
? SmoothListView(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
children: [
|
||||
...streamData!.qualities.mapIndexed(
|
||||
(index, text) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0, vertical: 8.0),
|
||||
child: SizedBox(
|
||||
// height: 60,
|
||||
child: ElevatedButton(
|
||||
style: const ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 37, 37, 37),
|
||||
),
|
||||
foregroundColor: MaterialStatePropertyAll(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
Map<String, double> timestamps =
|
||||
await getOpeningSkipTimeStamps(widget.idMal,
|
||||
widget.animeEpisode.toString());
|
||||
onStreamSelected(index, timestamps);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 20.0, horizontal: 3.0),
|
||||
child: Text(getUtf8Text(text)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Center(
|
||||
child: Text(
|
||||
"quality_no_results".tr(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
LoadingAnimationWidget.inkDrop(
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
"please_wait_text".tr(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}}
|
||||
@@ -1,216 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:animated_snack_bar/animated_snack_bar.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:unyo/notification/notification_manager.dart';
|
||||
import 'package:unyo/sources/sources.dart';
|
||||
import 'package:unyo/widgets/widgets.dart';
|
||||
|
||||
class WrongTitleDialogManager {
|
||||
static final WrongTitleDialogManager _instance =
|
||||
WrongTitleDialogManager._internal();
|
||||
|
||||
WrongTitleDialogManager._internal();
|
||||
|
||||
factory WrongTitleDialogManager() => _instance;
|
||||
|
||||
bool manualTitleSelection = false;
|
||||
List<DropdownMenuEntry> wrongTitleEntries = [];
|
||||
String oldWrongTitleSearch = "";
|
||||
String? currentSearchString;
|
||||
int? currentSearchIndex;
|
||||
Timer wrongTitleSearchTimer = Timer(const Duration(milliseconds: 500), () {});
|
||||
void Function() wrongTitleSearchFunction = () {};
|
||||
TextEditingController wrongTitleSearchController = TextEditingController();
|
||||
List<String> results = [];
|
||||
|
||||
void openWrongTitleDialog(BuildContext context, double width, double height,
|
||||
{AnimeSource? currentAnimeSource, MangaSource? currentMangaSource}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
setWrongTitleSearch(setState,
|
||||
currentAnimeSource: currentAnimeSource,
|
||||
currentMangaSource: currentMangaSource);
|
||||
return AlertDialog(
|
||||
title: Text(context.tr("select_title"),
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
content: WrongTitleDialog(
|
||||
width: width,
|
||||
height: height,
|
||||
wrongTitleSearchController: wrongTitleSearchController,
|
||||
wrongTitleEntries: wrongTitleEntries,
|
||||
currentSearchString: manualTitleSelection
|
||||
? currentSearchString!
|
||||
: results.isNotEmpty
|
||||
? results[0]
|
||||
: "",
|
||||
onPressed: () async {
|
||||
wrongTitleSearchTimer.cancel();
|
||||
//NOTE dirty fix for a bug
|
||||
if (!context.mounted) return;
|
||||
NotificationManager().showWarningNotification(
|
||||
context,
|
||||
"Updating Title, don't close...",
|
||||
DesktopSnackBarPosition.topCenter);
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (!context.mounted) return;
|
||||
NotificationManager().showSuccessNotification(context,
|
||||
"Title Updated", DesktopSnackBarPosition.topCenter);
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onSelected: (value) {
|
||||
manualTitleSelection = true;
|
||||
currentSearchString = results[value];
|
||||
currentSearchIndex = value!;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void setWrongTitleSearch(void Function(void Function()) setDialogState,
|
||||
{AnimeSource? currentAnimeSource, MangaSource? currentMangaSource}) {
|
||||
oldWrongTitleSearch = "";
|
||||
//reset listener
|
||||
wrongTitleSearchController.removeListener(wrongTitleSearchFunction);
|
||||
wrongTitleSearchFunction = () {
|
||||
wrongTitleSearchTimer.cancel();
|
||||
wrongTitleSearchTimer =
|
||||
Timer(const Duration(milliseconds: 500), () async {
|
||||
if (wrongTitleSearchController.text != oldWrongTitleSearch &&
|
||||
wrongTitleSearchController.text != "") {
|
||||
if (currentMangaSource == null) {
|
||||
results = await currentAnimeSource!
|
||||
.getAnimeTitles(wrongTitleSearchController.text);
|
||||
} else {
|
||||
results = [];
|
||||
// searches = await currentMangaSource!.get(title);
|
||||
}
|
||||
setDialogState(() {
|
||||
wrongTitleEntries = [
|
||||
...results.mapIndexed(
|
||||
(index, title) {
|
||||
return DropdownMenuEntry(
|
||||
style: const ButtonStyle(
|
||||
foregroundColor: MaterialStatePropertyAll(Colors.white),
|
||||
),
|
||||
value: index,
|
||||
label: title,
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
});
|
||||
}
|
||||
oldWrongTitleSearch = wrongTitleSearchController.text;
|
||||
});
|
||||
};
|
||||
wrongTitleSearchController.addListener(wrongTitleSearchFunction);
|
||||
}
|
||||
|
||||
void clearProperties() {
|
||||
manualTitleSelection = false;
|
||||
currentSearchIndex = null;
|
||||
currentSearchString = null;
|
||||
}
|
||||
}
|
||||
|
||||
class WrongTitleDialog extends StatelessWidget {
|
||||
const WrongTitleDialog({
|
||||
super.key,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.wrongTitleSearchController,
|
||||
required this.onSelected,
|
||||
required this.onPressed,
|
||||
required this.wrongTitleEntries,
|
||||
required this.currentSearchString,
|
||||
});
|
||||
|
||||
final double width;
|
||||
final double height;
|
||||
final TextEditingController wrongTitleSearchController;
|
||||
final void Function(dynamic)? onSelected;
|
||||
final void Function() onPressed;
|
||||
final List<DropdownMenuEntry<dynamic>> wrongTitleEntries;
|
||||
final String currentSearchString;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width * 0.5,
|
||||
height: height * 0.5,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.bottomCenter,
|
||||
opacity: 0.1,
|
||||
image: NetworkImage("https://i.imgur.com/fUX8AXq.png"),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: height * 0.05,
|
||||
),
|
||||
Text("select_new_title_text".tr(),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 22)),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
// TODO Review DropdownMenu manualSelection field
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
DropdownMenu(
|
||||
// hintText: context.tr("search_from_website"),
|
||||
width: width * 0.4,
|
||||
textStyle: const TextStyle(color: Colors.white),
|
||||
menuStyle: const MenuStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Color.fromARGB(255, 44, 44, 44),
|
||||
),
|
||||
),
|
||||
controller: wrongTitleSearchController,
|
||||
onSelected: onSelected,
|
||||
initialSelection: /*manualSelection ?? 0*/ null,
|
||||
dropdownMenuEntries: wrongTitleEntries,
|
||||
menuHeight: height * 0.3,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: Text(
|
||||
"${context.tr("current_selection")}: $currentSearchString",
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 18),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
StyledButton(
|
||||
text: "confirm".tr(),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
1
lib/domain/entities/entities.dart
Normal file
1
lib/domain/entities/entities.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'user.dart';
|
||||
12
lib/domain/entities/user.dart
Normal file
12
lib/domain/entities/user.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
/// Represents a user entity.
|
||||
///
|
||||
/// The [User] type defines the basic properties of a user, including:
|
||||
/// - [id]: The unique identifier for the user.
|
||||
/// - [name]: The user's display name.
|
||||
/// - [email]: The user's email address.
|
||||
abstract class User {
|
||||
final String name;
|
||||
final String avatarImage;
|
||||
|
||||
User({required this.name, required this.avatarImage});
|
||||
}
|
||||
1
lib/domain/repositories/repositories.dart
Normal file
1
lib/domain/repositories/repositories.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'user_repository.dart';
|
||||
6
lib/domain/repositories/user_repository.dart
Normal file
6
lib/domain/repositories/user_repository.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:unyo/domain/entities/user.dart';
|
||||
|
||||
abstract class UserRepository {
|
||||
Future<List<User>> fetchAllUsers();
|
||||
Future<void> registerUser(User user);
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// ignore_for_file: always_specify_types
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: unused_field
|
||||
// ignore_for_file: unused_element
|
||||
|
||||
// AUTO GENERATED FILE, DO NOT EDIT.
|
||||
//
|
||||
// Generated by `package:ffigen`.
|
||||
// ignore_for_file: type=lint
|
||||
import 'dart:ffi' as ffi;
|
||||
|
||||
/// Bindings to `lib/ffi/libmtorrentserver.h`.
|
||||
class TorrentLibrary {
|
||||
/// Holds the symbol lookup function.
|
||||
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
||||
_lookup;
|
||||
|
||||
/// The symbols are looked up in [dynamicLibrary].
|
||||
TorrentLibrary(ffi.DynamicLibrary dynamicLibrary)
|
||||
: _lookup = dynamicLibrary.lookup;
|
||||
|
||||
/// The symbols are looked up with [lookup].
|
||||
TorrentLibrary.fromLookup(
|
||||
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
||||
lookup)
|
||||
: _lookup = lookup;
|
||||
|
||||
Start_return Start(
|
||||
ffi.Pointer<ffi.Char> mcfg,
|
||||
) {
|
||||
return _Start(
|
||||
mcfg,
|
||||
);
|
||||
}
|
||||
|
||||
late final _StartPtr =
|
||||
_lookup<ffi.NativeFunction<Start_return Function(ffi.Pointer<ffi.Char>)>>(
|
||||
'Start');
|
||||
late final _Start =
|
||||
_StartPtr.asFunction<Start_return Function(ffi.Pointer<ffi.Char>)>();
|
||||
}
|
||||
|
||||
final class max_align_t extends ffi.Opaque {}
|
||||
|
||||
final class _GoString_ extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Char> p;
|
||||
|
||||
@ptrdiff_t()
|
||||
external int n;
|
||||
}
|
||||
|
||||
typedef ptrdiff_t = ffi.Long;
|
||||
typedef Dartptrdiff_t = int;
|
||||
|
||||
final class GoInterface extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Void> t;
|
||||
|
||||
external ffi.Pointer<ffi.Void> v;
|
||||
}
|
||||
|
||||
final class GoSlice extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Void> data;
|
||||
|
||||
@GoInt()
|
||||
external int len;
|
||||
|
||||
@GoInt()
|
||||
external int cap;
|
||||
}
|
||||
|
||||
typedef GoInt = GoInt64;
|
||||
typedef GoInt64 = ffi.LongLong;
|
||||
typedef DartGoInt64 = int;
|
||||
|
||||
/// Return type for Start
|
||||
final class Start_return extends ffi.Struct {
|
||||
@GoInt()
|
||||
external int r0;
|
||||
|
||||
external ffi.Pointer<ffi.Char> r1;
|
||||
}
|
||||
|
||||
const int NULL = 0;
|
||||
@@ -1,39 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'generated_bindings.dart';
|
||||
|
||||
Future<int> start(String mcfg) async {
|
||||
var completer = Completer<int>();
|
||||
var res = _bindings.Start(mcfg.toNativeUtf8().cast());
|
||||
if (res.r1 != nullptr) {
|
||||
completer.completeError(Exception(res.r1.cast<Utf8>().toDartString()));
|
||||
} else {
|
||||
completer.complete(res.r0);
|
||||
}
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
const String _libName = 'libmtorrentserver';
|
||||
|
||||
final DynamicLibrary _dylib = () {
|
||||
if (kDebugMode){
|
||||
return DynamicLibrary.open('./$_libName.so');
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
return DynamicLibrary.open('$_libName.dylib');
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
return DynamicLibrary.open('$_libName.so');
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
return DynamicLibrary.open('$_libName.dll');
|
||||
}
|
||||
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
|
||||
}();
|
||||
|
||||
final TorrentLibrary _bindings = TorrentLibrary(_dylib);
|
||||
@@ -1,25 +0,0 @@
|
||||
/// Flutter icons Anilist
|
||||
/// Copyright (C) 2024 by original authors @ fluttericon.com, fontello.com
|
||||
/// This font was generated by FlutterIcon.com, which is derived from Fontello.
|
||||
///
|
||||
/// To use this font, place it in your fonts/ directory and include the
|
||||
/// following in your pubspec.yaml
|
||||
///
|
||||
/// flutter:
|
||||
/// fonts:
|
||||
/// - family: Anilist
|
||||
/// fonts:
|
||||
/// - asset: fonts/Anilist.ttf
|
||||
///
|
||||
///
|
||||
///
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class Anilist {
|
||||
Anilist._();
|
||||
|
||||
static const _kFontFam = 'Anilist';
|
||||
static const String? _kFontPkg = null;
|
||||
|
||||
static const IconData anilist = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
}
|
||||
214
lib/main.dart
214
lib/main.dart
@@ -1,119 +1,41 @@
|
||||
import 'dart:io';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
//Flutter dependencies
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_acrylic/window.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:unyo/models/adapters/anilist_user_model_adapter.dart';
|
||||
import 'package:unyo/models/adapters/anime_model_adapter.dart';
|
||||
import 'package:unyo/models/adapters/local_user_model_adapter.dart';
|
||||
import 'package:unyo/models/adapters/manga_model_adapter.dart';
|
||||
import 'package:unyo/models/adapters/user_media_model_adapter.dart';
|
||||
import 'package:unyo/router/router.dart';
|
||||
import 'package:fvp/fvp.dart' as fvp;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:unyo/util/utils.dart';
|
||||
import 'package:flutter_window_close/flutter_window_close.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
Future<void> shutdownCleanup() async {
|
||||
await discord.cleanup();
|
||||
processManager.stopProcess();
|
||||
}
|
||||
//Internal dependencies
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
import 'package:unyo/core/router/app_router.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
logger.i("Initializing dependencies");
|
||||
final _appRouter = AppRouter();
|
||||
|
||||
void main() async {
|
||||
// Initialize Flutter bindings
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Window.initialize();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
Hive.init(p.join(dir.path, "data"));
|
||||
Hive.registerAdapter(AnilistUserModelAdapter());
|
||||
Hive.registerAdapter(LocalUserModelAdapter());
|
||||
Hive.registerAdapter(UserMediaModelAdapter());
|
||||
Hive.registerAdapter(MangaModelAdapter());
|
||||
Hive.registerAdapter(AnimeModelAdapter());
|
||||
|
||||
if (Platform.isWindows) {
|
||||
fvp.registerWith(options: {
|
||||
'platforms': ['windows'],
|
||||
'video.decoders': ['DXVA', 'FFmpeg'],
|
||||
'player': {"avformat.extension_picky": "0"}
|
||||
});
|
||||
} else {
|
||||
fvp.registerWith(options: {
|
||||
'platforms': ['linux', 'macos'],
|
||||
});
|
||||
}
|
||||
|
||||
// Handle forced shutdown (Ctrl+C, SIGTERM)
|
||||
ProcessSignal.sigint.watch().listen((_) async {
|
||||
await shutdownCleanup();
|
||||
exit(0);
|
||||
});
|
||||
ProcessSignal.sigterm.watch().listen((_) async {
|
||||
await shutdownCleanup();
|
||||
exit(0);
|
||||
});
|
||||
|
||||
logger.i("Initializing Unyo");
|
||||
// Inject dependencies before running the app
|
||||
setupLocator();
|
||||
runApp(
|
||||
EasyLocalization(
|
||||
supportedLocales: const [
|
||||
Locale('en'),
|
||||
Locale('es'),
|
||||
Locale('fr'),
|
||||
Locale('de'),
|
||||
Locale('it'),
|
||||
Locale('pt'),
|
||||
Locale('ru'),
|
||||
Locale('ja'),
|
||||
Locale('bn'),
|
||||
Locale('hi'),
|
||||
],
|
||||
supportedLocales: [Locale('en')],
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: Locale('en'),
|
||||
useOnlyLangCode: true,
|
||||
path: 'assets/languages',
|
||||
fallbackLocale: const Locale('en'),
|
||||
child: const MyApp(),
|
||||
child: ScreenUtilInit(
|
||||
designSize: const Size(1280, 720),
|
||||
minTextAdapt: true,
|
||||
splitScreenMode: true,
|
||||
builder: (context, child) {
|
||||
return const MyApp();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
doWhenWindowReady(() {
|
||||
appWindow.position = const Offset(200, 200);
|
||||
appWindow.minSize = const Size(1280, 720);
|
||||
appWindow.title = "Unyo";
|
||||
appWindow.size = const Size(1280, 720);
|
||||
appWindow.show();
|
||||
});
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
FlutterWindowClose.setWindowShouldCloseHandler(() async {
|
||||
logger.i("Unyo is exiting...");
|
||||
await shutdownCleanup();
|
||||
logger.i("Cleanup done; exiting now.");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
Future.microtask(() async {
|
||||
await shutdownCleanup();
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
@@ -121,9 +43,93 @@ class _MyAppState extends State<MyApp> {
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: context.locale,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(),
|
||||
title: 'Unyo',
|
||||
routerConfig: router,
|
||||
title: "Unyo",
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: const Color.fromARGB(255, 44, 44, 44),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
// colorScheme: ColorScheme.dark(
|
||||
// primary: Colors.grey[200]!,
|
||||
// secondary: Colors.grey[300]!,
|
||||
// surface: Colors.white,
|
||||
// onSurface: Colors.grey[300]!,
|
||||
// ),
|
||||
textTheme: TextTheme(
|
||||
// Display styles (largest) - white
|
||||
displayLarge: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 35,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
||||
// Headline styles - white to light gray
|
||||
headlineLarge: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 22,
|
||||
// fontWeight: FontWeight.w600,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
// fontWeight: FontWeight.w600,
|
||||
),
|
||||
|
||||
// Title styles - light gray to mid gray
|
||||
titleLarge: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
// fontWeight: FontWeight.w500,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
// fontWeight: FontWeight.w500,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
// fontWeight: FontWeight.w500,
|
||||
),
|
||||
|
||||
// Body styles - dark gray to black
|
||||
bodyLarge: TextStyle(color: Colors.grey[900], fontSize: 14),
|
||||
bodyMedium: TextStyle(color: Colors.grey[900], fontSize: 12),
|
||||
bodySmall: TextStyle(color: Colors.grey[900], fontSize: 10),
|
||||
|
||||
// Label styles - black
|
||||
labelLarge: TextStyle(
|
||||
color: Colors.grey[900],
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelMedium: TextStyle(
|
||||
color: Colors.grey[900],
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
color: Colors.grey[900],
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
routerConfig: _appRouter.config(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
|
||||
class AnilistUserModelAdapter extends TypeAdapter<AnilistUserModel> {
|
||||
@override
|
||||
AnilistUserModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return AnilistUserModel(
|
||||
avatarImage: fields[0] as String?,
|
||||
bannerImage: fields[1] as String?,
|
||||
userName: fields[2] as String?,
|
||||
userId: fields[3] as int?
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get typeId => 0;
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, AnilistUserModel obj) {
|
||||
writer.writeByte(4);
|
||||
writer.writeByte(0);
|
||||
writer.write(obj.avatarImage);
|
||||
writer.writeByte(1);
|
||||
writer.write(obj.bannerImage);
|
||||
writer.writeByte(2);
|
||||
writer.write(obj.userName);
|
||||
writer.writeByte(3);
|
||||
writer.write(obj.userId);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
|
||||
class AnimeModelAdapter extends TypeAdapter<AnimeModel> {
|
||||
@override
|
||||
AnimeModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return AnimeModel(
|
||||
id: fields[0],
|
||||
idMal: fields[1],
|
||||
userPreferedTitle: fields[2],
|
||||
japaneseTitle: fields[3],
|
||||
englishTitle: fields[4],
|
||||
coverImage: fields[5],
|
||||
bannerImage: fields[6],
|
||||
startDate: fields[7],
|
||||
endDate: fields[8],
|
||||
type: fields[9],
|
||||
status: fields[10],
|
||||
description: fields[11],
|
||||
format: fields[12],
|
||||
averageScore: fields[13],
|
||||
episodes: fields[14],
|
||||
currentEpisode: fields[15],
|
||||
duration: fields[16],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get typeId => 3;
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, AnimeModel obj) {
|
||||
writer.writeByte(17);
|
||||
writer.writeByte(0);
|
||||
writer.write(obj.id);
|
||||
writer.writeByte(1);
|
||||
writer.write(obj.idMal);
|
||||
writer.writeByte(2);
|
||||
writer.write(obj.userPreferedTitle);
|
||||
writer.writeByte(3);
|
||||
writer.write(obj.japaneseTitle);
|
||||
writer.writeByte(4);
|
||||
writer.write(obj.englishTitle);
|
||||
writer.writeByte(5);
|
||||
writer.write(obj.coverImage);
|
||||
writer.writeByte(6);
|
||||
writer.write(obj.bannerImage);
|
||||
writer.writeByte(7);
|
||||
writer.write(obj.startDate);
|
||||
writer.writeByte(8);
|
||||
writer.write(obj.endDate);
|
||||
writer.writeByte(9);
|
||||
writer.write(obj.type);
|
||||
writer.writeByte(10);
|
||||
writer.write(obj.status);
|
||||
writer.writeByte(11);
|
||||
writer.write(obj.description);
|
||||
writer.writeByte(12);
|
||||
writer.write(obj.format);
|
||||
writer.writeByte(13);
|
||||
writer.write(obj.averageScore);
|
||||
writer.writeByte(14);
|
||||
writer.write(obj.episodes);
|
||||
writer.writeByte(15);
|
||||
writer.write(obj.currentEpisode);
|
||||
writer.writeByte(16);
|
||||
writer.write(obj.duration);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
|
||||
class LocalUserModelAdapter extends TypeAdapter<LocalUserModel> {
|
||||
@override
|
||||
LocalUserModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return LocalUserModel(
|
||||
avatarImage: fields[0] as String?,
|
||||
bannerImage: fields[1] as String?,
|
||||
userName: fields[2] as String?,
|
||||
userId: fields[3] as int?
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get typeId => 1;
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, LocalUserModel obj) {
|
||||
writer.writeByte(4);
|
||||
writer.writeByte(0);
|
||||
writer.write(obj.avatarImage);
|
||||
writer.writeByte(1);
|
||||
writer.write(obj.bannerImage);
|
||||
writer.writeByte(2);
|
||||
writer.write(obj.userName);
|
||||
writer.writeByte(3);
|
||||
writer.write(obj.userId);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
|
||||
class MangaModelAdapter extends TypeAdapter<MangaModel> {
|
||||
@override
|
||||
MangaModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return MangaModel(
|
||||
id: fields[0],
|
||||
idMal: fields[1],
|
||||
userPreferedTitle: fields[2],
|
||||
japaneseTitle: fields[3],
|
||||
englishTitle: fields[4],
|
||||
coverImage: fields[5],
|
||||
bannerImage: fields[6],
|
||||
startDate: fields[7],
|
||||
endDate: fields[8],
|
||||
type: fields[9],
|
||||
status: fields[10],
|
||||
description: fields[11],
|
||||
format: fields[12],
|
||||
averageScore: fields[13],
|
||||
chapters: fields[14],
|
||||
currentEpisode: fields[15],
|
||||
duration: fields[16],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get typeId => 4;
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, MangaModel obj) {
|
||||
writer.writeByte(17);
|
||||
writer.writeByte(0);
|
||||
writer.write(obj.id);
|
||||
writer.writeByte(1);
|
||||
writer.write(obj.idMal);
|
||||
writer.writeByte(2);
|
||||
writer.write(obj.userPreferedTitle);
|
||||
writer.writeByte(3);
|
||||
writer.write(obj.japaneseTitle);
|
||||
writer.writeByte(4);
|
||||
writer.write(obj.englishTitle);
|
||||
writer.writeByte(5);
|
||||
writer.write(obj.coverImage);
|
||||
writer.writeByte(6);
|
||||
writer.write(obj.bannerImage);
|
||||
writer.writeByte(7);
|
||||
writer.write(obj.startDate);
|
||||
writer.writeByte(8);
|
||||
writer.write(obj.endDate);
|
||||
writer.writeByte(9);
|
||||
writer.write(obj.type);
|
||||
writer.writeByte(10);
|
||||
writer.write(obj.status);
|
||||
writer.writeByte(11);
|
||||
writer.write(obj.description);
|
||||
writer.writeByte(12);
|
||||
writer.write(obj.format);
|
||||
writer.writeByte(13);
|
||||
writer.write(obj.averageScore);
|
||||
writer.writeByte(14);
|
||||
writer.write(obj.chapters);
|
||||
writer.writeByte(15);
|
||||
writer.write(obj.currentEpisode);
|
||||
writer.writeByte(16);
|
||||
writer.write(obj.duration);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
|
||||
class UserMediaModelAdapter extends TypeAdapter<UserMediaModel> {
|
||||
@override
|
||||
UserMediaModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return UserMediaModel(
|
||||
score: fields[0] as num?,
|
||||
progress: fields[1] as num?,
|
||||
repeat: fields[2] as int?,
|
||||
priority: fields[3] as int?,
|
||||
status: fields[4] as String?,
|
||||
startDate: fields[5] as String?,
|
||||
endDate: fields[6] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get typeId => 2;
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, UserMediaModel obj) {
|
||||
writer.writeByte(7);
|
||||
writer.writeByte(0);
|
||||
writer.write(obj.score);
|
||||
writer.writeByte(1);
|
||||
writer.write(obj.progress);
|
||||
writer.writeByte(2);
|
||||
writer.write(obj.repeat);
|
||||
writer.writeByte(3);
|
||||
writer.write(obj.priority);
|
||||
writer.writeByte(4);
|
||||
writer.write(obj.status);
|
||||
writer.writeByte(5);
|
||||
writer.write(obj.startDate);
|
||||
writer.writeByte(6);
|
||||
writer.write(obj.endDate);
|
||||
}
|
||||
}
|
||||
@@ -1,609 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:unyo/util/constants.dart' as constants;
|
||||
|
||||
class AnilistUserModel implements UserModel {
|
||||
final maxAttempts = 5;
|
||||
@override
|
||||
String? avatarImage;
|
||||
@override
|
||||
String? bannerImage;
|
||||
@override
|
||||
String? userName;
|
||||
@override
|
||||
int? userId;
|
||||
|
||||
AnilistUserModel(
|
||||
{this.avatarImage, this.bannerImage, this.userName, this.userId});
|
||||
|
||||
@override
|
||||
Future<List<String>> getUserNameAndId({int? newAttempt}) async {
|
||||
if (userName != null && userId != null) {
|
||||
return [userName!, userId!.toString()];
|
||||
}
|
||||
int attempt = newAttempt ?? 0;
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query": "query {Viewer{name id}}",
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer ${constants.accessToken}"
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
print(response.body);
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return getUserNameAndId(newAttempt: newAttempt);
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
userName = jsonResponse["data"]["Viewer"]["name"] as String?;
|
||||
userId = jsonResponse["data"]["Viewer"]["id"] as int?;
|
||||
return [
|
||||
jsonResponse["data"]["Viewer"]["name"],
|
||||
jsonResponse["data"]["Viewer"]["id"].toString()
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getUserbannerImageUrl({int? newAttempt}) async {
|
||||
if (bannerImage != null) {
|
||||
return bannerImage!;
|
||||
}
|
||||
int attempt = newAttempt ?? 0;
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query (\$id: Int \$name: String){User(id: \$id, name: \$name){bannerImage}}",
|
||||
"variables": {
|
||||
"name": constants.userName,
|
||||
}
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("User banner image: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
String returnString =
|
||||
await getUserbannerImageUrl(newAttempt: newAttempt);
|
||||
return returnString;
|
||||
}
|
||||
return "https://i.imgur.com/x6TGK1x.png";
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
bannerImage = jsonResponse["data"]["User"]["bannerImage"] as String?;
|
||||
return jsonResponse["data"]["User"]["bannerImage"];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getUserAvatarImageUrl({int? newAttempt}) async {
|
||||
if (avatarImage != null) {
|
||||
return avatarImage!;
|
||||
}
|
||||
int attempt = newAttempt ?? 0;
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query (\$id: Int \$name: String){User(id: \$id, name: \$name){avatar {medium}}}",
|
||||
"variables": {
|
||||
"name": constants.userName,
|
||||
}
|
||||
};
|
||||
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("User avatar image: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
String returnString =
|
||||
await getUserAvatarImageUrl(newAttempt: newAttempt);
|
||||
return returnString;
|
||||
}
|
||||
return "https://i.imgur.com/EKtChtm.png";
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
try {
|
||||
String returnString = jsonResponse["data"]["User"]["avatar"]["medium"];
|
||||
avatarImage = returnString as String?;
|
||||
return returnString;
|
||||
} catch (e) {
|
||||
return "https://i.imgur.com/EKtChtm.png";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AnimeModel>> getUserAnimeLists(String listName,
|
||||
{int? newAttempt}) async {
|
||||
int attempt = newAttempt ?? 0;
|
||||
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type,sort:UPDATED_TIME_DESC){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id idMal title{userPreferred romaji english}coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore description popularity isAdult countryOfOrigin genres bannerImage startDate{year month day} endDate{year month day}}}",
|
||||
"variables": {
|
||||
"userId": constants.userId,
|
||||
"type": "ANIME",
|
||||
}
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("User anime list $listName : $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return await getUserAnimeLists(listName, newAttempt: newAttempt);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
List<AnimeModel> animeModelList = [];
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
List<dynamic> animeLists =
|
||||
jsonResponse["data"]["MediaListCollection"]["lists"];
|
||||
for (int i = 0; i < animeLists.length; i++) {
|
||||
if (animeLists[i]["name"] == listName) {
|
||||
List<dynamic> wantedList = animeLists[i]["entries"];
|
||||
for (int i = 0; i < wantedList.length; i++) {
|
||||
Map<String, dynamic> json = wantedList[i]["media"];
|
||||
|
||||
animeModelList.add(AnimeModel.fromJson(json));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return animeModelList;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List<AnimeModel>>> getAllUserAnimeLists(
|
||||
{int? newAttempt}) async {
|
||||
int attempt = newAttempt ?? 0;
|
||||
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id idMal title{userPreferred romaji english }coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore description popularity isAdult countryOfOrigin genres bannerImage startDate{year month day} endDate{day month year}}}",
|
||||
"variables": {
|
||||
"userId": constants.userId,
|
||||
"type": "ANIME",
|
||||
}
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("All user anime lists: $attempt - failure");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return await getAllUserAnimeLists(newAttempt: newAttempt);
|
||||
}
|
||||
//NOTE empry Map
|
||||
return {};
|
||||
}
|
||||
Map<String, List<AnimeModel>> userAnimeListsMap = {};
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
List<dynamic> userAnimeLists =
|
||||
jsonResponse["data"]["MediaListCollection"]["lists"];
|
||||
for (int i = 0; i < userAnimeLists.length; i++) {
|
||||
List<dynamic> currentList = userAnimeLists[i]["entries"];
|
||||
|
||||
List<AnimeModel> animeModelList = [];
|
||||
|
||||
for (int j = 0; j < currentList.length; j++) {
|
||||
Map<String, dynamic> json = currentList[j]["media"];
|
||||
|
||||
animeModelList.add(AnimeModel.fromJson(json));
|
||||
}
|
||||
|
||||
userAnimeListsMap.addAll({userAnimeLists[i]["name"]: animeModelList});
|
||||
}
|
||||
return userAnimeListsMap;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserMediaModel?> getUserAnimeInfo(int mediaId,
|
||||
{int? newAttempt}) async {
|
||||
int attempt = newAttempt ?? 0;
|
||||
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query{ Media(id: $mediaId){ mediaListEntry { score progress repeat priority status startedAt{day month year} completedAt{day month year} } } }",
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
print("User anime info: $attempt - failure {$response.body}");
|
||||
if (attempt < maxAttempts) {
|
||||
int newAttempt = attempt + 1;
|
||||
return getUserAnimeInfo(mediaId, newAttempt: newAttempt);
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
if (jsonResponse["data"]["Media"]["mediaListEntry"] == null) {
|
||||
return UserMediaModel(
|
||||
score: 0,
|
||||
progress: 0,
|
||||
repeat: 0,
|
||||
priority: 0,
|
||||
status: "",
|
||||
startDate: "~/~/~",
|
||||
endDate: "~/~/~",
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> mediaListEntry =
|
||||
jsonResponse["data"]["Media"]["mediaListEntry"];
|
||||
return UserMediaModel(
|
||||
score: mediaListEntry["score"],
|
||||
progress: mediaListEntry["progress"],
|
||||
repeat: mediaListEntry["repeat"],
|
||||
priority: mediaListEntry["priority"],
|
||||
status: mediaListEntry["status"],
|
||||
startDate:
|
||||
"${mediaListEntry["startedAt"]["day"]}/${mediaListEntry["startedAt"]["month"]}/${mediaListEntry["startedAt"]["year"]}",
|
||||
endDate:
|
||||
"${mediaListEntry["completedAt"]["day"]}/${mediaListEntry["completedAt"]["month"]}/${mediaListEntry["completedAt"]["year"]}",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<MangaModel>> getUserMangaLists(String listName,
|
||||
{int? newAttempt}) async {
|
||||
int attempt = newAttempt ?? 0;
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type,sort:UPDATED_TIME_DESC){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id idMal title{userPreferred romaji english}coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore description popularity isAdult countryOfOrigin genres bannerImage startDate{year month day} endDate{year month day}}}",
|
||||
"variables": {
|
||||
"userId": constants.userId,
|
||||
"type": "MANGA",
|
||||
}
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
print(response.body);
|
||||
if (attempt < 5) {
|
||||
print("userMangaLists : $attempt - failure");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
int newAttempt = attempt + 1;
|
||||
return await getUserMangaLists(listName, newAttempt: newAttempt);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
List<MangaModel> animeModelList = [];
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
List<dynamic> animeLists =
|
||||
jsonResponse["data"]["MediaListCollection"]["lists"];
|
||||
for (int i = 0; i < animeLists.length; i++) {
|
||||
if (animeLists[i]["name"] == listName) {
|
||||
List<dynamic> wantedList = animeLists[i]["entries"];
|
||||
for (int i = 0; i < wantedList.length; i++) {
|
||||
var json = wantedList[i]["media"];
|
||||
animeModelList.add(MangaModel.fromJson(json));
|
||||
// animeModelList.add(
|
||||
// MangaModel(
|
||||
// id: wantedList[i]["media"]["id"],
|
||||
// title: wantedList[i]["media"]["title"]["userPreferred"],
|
||||
// coverImage: wantedList[i]["media"]["coverImage"]["large"],
|
||||
// bannerImage: wantedList[i]["media"]["bannerImage"],
|
||||
// startDate:
|
||||
// "${wantedList[i]["media"]["startDate"]["day"]}/${wantedList[i]["media"]["startDate"]["month"]}/${wantedList[i]["media"]["startDate"]["year"]}",
|
||||
// endDate: "",
|
||||
// //"${wantedList[i]["media"]["endDate"]["day"]}/${wantedList[i]["media"]["endDate"]["month"]}/${wantedList[i]["media"]["endDate"]["year"]}",
|
||||
// type: wantedList[i]["media"]["type"],
|
||||
// description: wantedList[i]["media"]["description"],
|
||||
// status: wantedList[i]["media"]["status"],
|
||||
// averageScore: wantedList[i]["media"]["averageScore"],
|
||||
// chapters: wantedList[i]["media"]["chapters"],
|
||||
// duration: wantedList[i]["media"]["episodes"],
|
||||
// format: wantedList[i]["media"]["format"],
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return animeModelList;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List<MangaModel>>> getAllUserMangaLists(
|
||||
{int? newAttempt}) async {
|
||||
int attempt = newAttempt ?? 0;
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query(\$userId:Int,\$userName:String,\$type:MediaType){MediaListCollection(userId:\$userId,userName:\$userName,type:\$type){lists{name isCustomList isCompletedList:isSplitCompletedList entries{...mediaListEntry}}user{id name avatar{large}mediaListOptions{scoreFormat rowOrder animeList{sectionOrder customLists splitCompletedSectionByFormat theme}mangaList{sectionOrder customLists splitCompletedSectionByFormat theme}}}}}fragment mediaListEntry on MediaList{id mediaId status score progress progressVolumes repeat priority private hiddenFromStatusLists customLists advancedScores notes updatedAt startedAt{year month day}completedAt{year month day}media{id idMal title{userPreferred romaji english}coverImage{extraLarge large}type format status(version:2)episodes volumes chapters averageScore description popularity isAdult countryOfOrigin genres bannerImage startDate{year month day} endDate{year month day}}}",
|
||||
"variables": {
|
||||
"userId": constants.userId,
|
||||
"type": "MANGA",
|
||||
}
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
print(response.body);
|
||||
if (attempt < 5) {
|
||||
print("allUserMangaLists : $attempt - failure");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
int newAttempt = attempt + 1;
|
||||
return await getAllUserMangaLists(newAttempt: newAttempt);
|
||||
}
|
||||
//NOTE empry Map
|
||||
return {};
|
||||
}
|
||||
Map<String, List<MangaModel>> userMangaListsMap = {};
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
List<dynamic> userMangaLists =
|
||||
jsonResponse["data"]["MediaListCollection"]["lists"];
|
||||
for (int i = 0; i < userMangaLists.length; i++) {
|
||||
List<dynamic> currentList = userMangaLists[i]["entries"];
|
||||
|
||||
List<MangaModel> mangaModelList = [];
|
||||
|
||||
for (int j = 0; j < currentList.length; j++) {
|
||||
var json = currentList[j]["media"];
|
||||
mangaModelList.add(MangaModel.fromJson(json));
|
||||
// mangaModelList.add(
|
||||
// MangaModel(
|
||||
// id: currentList[j]["media"]["id"],
|
||||
// title: currentList[j]["media"]["title"]["userPreferred"],
|
||||
// coverImage: currentList[j]["media"]["coverImage"]["large"],
|
||||
// bannerImage: currentList[j]["media"]["bannerImage"],
|
||||
// startDate:
|
||||
// "${currentList[j]["media"]["startDate"]["day"]}/${currentList[j]["media"]["startDate"]["month"]}/${currentList[j]["media"]["startDate"]["year"]}",
|
||||
// endDate: "",
|
||||
// //"${wantedList[i]["media"]["endDate"]["day"]}/${wantedList[i]["media"]["endDate"]["month"]}/${wantedList[i]["media"]["endDate"]["year"]}",
|
||||
// type: currentList[j]["media"]["type"],
|
||||
// description: currentList[j]["media"]["description"],
|
||||
// status: currentList[j]["media"]["status"],
|
||||
// averageScore: currentList[j]["media"]["averageScore"],
|
||||
// chapters: currentList[j]["media"]["chapters"],
|
||||
// duration: currentList[j]["media"]["episodes"],
|
||||
// format: currentList[j]["media"]["format"],
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
userMangaListsMap.addAll({userMangaLists[i]["name"]: mangaModelList});
|
||||
}
|
||||
print(userMangaListsMap);
|
||||
return userMangaListsMap;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserMediaModel?> getUserMangaInfo(int mediaId,
|
||||
{int? newAttempt}) async {
|
||||
int attempt = newAttempt ?? 0;
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"query{ Media(id: $mediaId){ mediaListEntry { score progress repeat priority status startedAt{day month year} completedAt{day month year} } } }",
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
if (attempt < 5) {
|
||||
print("userMangaInfo : $attempt - failure");
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
int newAttempt = attempt + 1;
|
||||
return getUserMangaInfo(mediaId, newAttempt: newAttempt);
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
if (jsonResponse["data"]["Media"]["mediaListEntry"] == null) {
|
||||
return UserMediaModel(
|
||||
score: 0,
|
||||
progress: 0,
|
||||
repeat: 0,
|
||||
priority: 0,
|
||||
status: "",
|
||||
startDate: "~/~/~",
|
||||
endDate: "~/~/~",
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> mediaListEntry =
|
||||
jsonResponse["data"]["Media"]["mediaListEntry"];
|
||||
return UserMediaModel(
|
||||
score: mediaListEntry["score"],
|
||||
progress: mediaListEntry["progress"],
|
||||
repeat: mediaListEntry["repeat"],
|
||||
priority: mediaListEntry["priority"],
|
||||
status: mediaListEntry["status"],
|
||||
startDate:
|
||||
"${mediaListEntry["startedAt"]["day"]}/${mediaListEntry["startedAt"]["month"]}/${mediaListEntry["startedAt"]["year"]}",
|
||||
endDate:
|
||||
"${mediaListEntry["completedAt"]["day"]}/${mediaListEntry["completedAt"]["month"]}/${mediaListEntry["completedAt"]["year"]}",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void setUserAnimeInfo(int mediaId, Map<String, String> receivedQuery,
|
||||
{AnimeModel? animeModel}) async {
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"mutation (\$mediaId: Int, \$status: MediaListStatus, \$score: Float, \$progress: Int, \$startedAt: FuzzyDateInput, \$completedAt: FuzzyDateInput) { SaveMediaListEntry(mediaId: \$mediaId, status: \$status, score: \$score, progress: \$progress, startedAt: \$startedAt, completedAt: \$completedAt) { mediaId status score progress startedAt { year month day } completedAt { year month day } } } ",
|
||||
"variables": {
|
||||
"mediaId": mediaId,
|
||||
"status": receivedQuery["status"],
|
||||
"score": double.parse(receivedQuery["score"]!),
|
||||
"progress": int.parse(receivedQuery["progress"]!),
|
||||
"startedAt": {
|
||||
"day": receivedQuery["startDateDay"],
|
||||
"month": receivedQuery["startDateMonth"],
|
||||
"year": receivedQuery["startDateYear"]
|
||||
},
|
||||
"completedAt": {
|
||||
"day": receivedQuery["endDateDay"],
|
||||
"month": receivedQuery["endDateMonth"],
|
||||
"year": receivedQuery["endDateYear"]
|
||||
},
|
||||
},
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
print(response.body);
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteUserAnime(int mediaId) async {
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
|
||||
Map<String, dynamic> query1 = {
|
||||
"query": "query(\$mediaId:Int){ MediaList(mediaId:\$mediaId){ id } }",
|
||||
"variables": {
|
||||
"mediaId": mediaId,
|
||||
},
|
||||
};
|
||||
var response1 = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query1),
|
||||
);
|
||||
|
||||
int entryId = jsonDecode(response1.body)["data"]["MediaList"]["id"];
|
||||
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"mutation (\$entryId: Int) {DeleteMediaListEntry(id: \$entryId){ deleted }}",
|
||||
"variables": {
|
||||
"entryId": entryId,
|
||||
},
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
print(response.body);
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteUserManga(int mediaId) async {
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
|
||||
Map<String, dynamic> query1 = {
|
||||
"query": "query(\$mediaId:Int){ MediaList(mediaId:\$mediaId){ id } }",
|
||||
"variables": {
|
||||
"mediaId": mediaId,
|
||||
},
|
||||
};
|
||||
var response1 = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query1),
|
||||
);
|
||||
|
||||
int entryId = jsonDecode(response1.body)["data"]["MediaList"]["id"];
|
||||
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"mutation (\$entryId: Int) {DeleteMediaListEntry(id: \$entryId){ deleted }}",
|
||||
"variables": {
|
||||
"entryId": entryId,
|
||||
},
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
print(response.body);
|
||||
}
|
||||
|
||||
@override
|
||||
void setUserMangaInfo(int mediaId, Map<String, String> receivedQuery,
|
||||
{MangaModel? mangaModel}) async {
|
||||
var url = Uri.parse(constants.anilistEndpoint);
|
||||
Map<String, dynamic> query = {
|
||||
"query":
|
||||
"mutation (\$mediaId: Int, \$status: MediaListStatus, \$score: Float, \$progress: Int, \$startedAt: FuzzyDateInput, \$completedAt: FuzzyDateInput) { SaveMediaListEntry(mediaId: \$mediaId, status: \$status, score: \$score, progress: \$progress, startedAt: \$startedAt, completedAt: \$completedAt) { mediaId status score progress startedAt { year month day } completedAt { year month day } } } ",
|
||||
"variables": {
|
||||
"mediaId": mediaId,
|
||||
"status": receivedQuery["status"],
|
||||
"score": double.parse(receivedQuery["score"]!),
|
||||
"progress": int.parse(receivedQuery["progress"]!),
|
||||
"startedAt": {
|
||||
"day": receivedQuery["startDateDay"],
|
||||
"month": receivedQuery["startDateMonth"],
|
||||
"year": receivedQuery["startDateYear"]
|
||||
},
|
||||
"completedAt": {
|
||||
"day": receivedQuery["endDateDay"],
|
||||
"month": receivedQuery["endDateMonth"],
|
||||
"year": receivedQuery["endDateYear"]
|
||||
},
|
||||
},
|
||||
};
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
"Authorization": "Bearer ${constants.accessToken}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json.encode(query),
|
||||
);
|
||||
print(response.body);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
class AnimeModel {
|
||||
AnimeModel({
|
||||
required this.id,
|
||||
this.idMal,
|
||||
required this.userPreferedTitle,
|
||||
this.englishTitle,
|
||||
this.japaneseTitle,
|
||||
required this.coverImage,
|
||||
required this.bannerImage,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.type,
|
||||
required this.status,
|
||||
required this.averageScore,
|
||||
required this.episodes,
|
||||
required this.duration,
|
||||
required this.description,
|
||||
required this.format,
|
||||
this.currentEpisode,
|
||||
});
|
||||
|
||||
int id;
|
||||
int? idMal;
|
||||
String? userPreferedTitle;
|
||||
String? englishTitle;
|
||||
String? japaneseTitle;
|
||||
String? coverImage;
|
||||
String? bannerImage;
|
||||
String? startDate;
|
||||
String? endDate;
|
||||
String? type;
|
||||
String? status;
|
||||
String? description;
|
||||
String? format;
|
||||
int? averageScore;
|
||||
int? episodes;
|
||||
int? currentEpisode;
|
||||
int? duration;
|
||||
|
||||
factory AnimeModel.fromJson(Map<String, dynamic> json) {
|
||||
return AnimeModel(
|
||||
id: json['id'],
|
||||
idMal: json['idMal'],
|
||||
userPreferedTitle: json["title"]["userPreferred"],
|
||||
japaneseTitle: json['title']['romaji'],
|
||||
englishTitle: json['title']['english'],
|
||||
coverImage: json['coverImage']['large'],
|
||||
bannerImage: json['bannerImage'],
|
||||
startDate:
|
||||
"${json["startDate"]["day"]}/${json["startDate"]["month"]}/${json["startDate"]["year"]}",
|
||||
endDate:
|
||||
"${json["endDate"]["day"]}/${json["endDate"]["month"]}/${json["endDate"]["year"]}",
|
||||
type: json['type'],
|
||||
status: json['status'],
|
||||
description: json['description'],
|
||||
format: json['format'],
|
||||
averageScore: json['averageScore'],
|
||||
episodes: json['episodes'],
|
||||
currentEpisode: json['currentEpisode'],
|
||||
duration: json['duration'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'malId': idMal,
|
||||
'userPreferedTitle': userPreferedTitle,
|
||||
'englishTitle': englishTitle,
|
||||
'japaneseTitle': japaneseTitle,
|
||||
'coverImage': coverImage,
|
||||
'bannerImage': bannerImage,
|
||||
'startDate': startDate,
|
||||
'endDate': endDate,
|
||||
'type': type,
|
||||
'status': status,
|
||||
'description': description,
|
||||
'format': format,
|
||||
'averageScore': averageScore,
|
||||
'episodes': episodes,
|
||||
'currentEpisode': currentEpisode,
|
||||
'duration': duration,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the first non-empty string in [candidates], or `''` if none.
|
||||
String _firstNonEmpty(List<String?> candidates) {
|
||||
for (final s in candidates) {
|
||||
if (s?.isNotEmpty ?? false) return s ?? "";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
String getDefaultTitle() {
|
||||
final defaultTitleType = prefs.getInt("default_title_type") ?? 0;
|
||||
|
||||
final orders = <List<String?>>[
|
||||
[userPreferedTitle, englishTitle, japaneseTitle], // 0
|
||||
[englishTitle, userPreferedTitle, japaneseTitle], // 1
|
||||
[japaneseTitle, userPreferedTitle, englishTitle], // 2
|
||||
];
|
||||
|
||||
return _firstNonEmpty(orders[defaultTitleType]);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "$id $userPreferedTitle";
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
class LocalUserModel implements UserModel {
|
||||
@override
|
||||
String? avatarImage;
|
||||
@override
|
||||
String? bannerImage;
|
||||
@override
|
||||
int? userId;
|
||||
@override
|
||||
String? userName;
|
||||
|
||||
LocalUserModel(
|
||||
{this.avatarImage, this.bannerImage, this.userName, this.userId});
|
||||
|
||||
@override
|
||||
void deleteUserAnime(int mediaId) {
|
||||
prefs.box.put(mediaId, null);
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteUserManga(int mediaId) {
|
||||
prefs.box.put(mediaId, null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List<AnimeModel>>> getAllUserAnimeLists() async {
|
||||
return (prefs.box.get("allUserAnimeLists") as Map<dynamic, dynamic>?)?.map(
|
||||
(key, value) =>
|
||||
MapEntry(key as String, (value as List).cast<AnimeModel>())) ??
|
||||
{};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List<MangaModel>>> getAllUserMangaLists() async {
|
||||
return (prefs.box.get("allUserMangaLists") as Map<dynamic, dynamic>?)?.map(
|
||||
(key, value) =>
|
||||
MapEntry(key as String, (value as List).cast<MangaModel>())) ??
|
||||
{};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserMediaModel?> getUserAnimeInfo(int mediaId) async {
|
||||
return prefs.box.get("userAnimeInfo-$mediaId") as UserMediaModel? ??
|
||||
UserMediaModel(
|
||||
score: 0,
|
||||
progress: 0,
|
||||
repeat: null,
|
||||
priority: null,
|
||||
status: "NOT SET",
|
||||
startDate: null,
|
||||
endDate: null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AnimeModel>> getUserAnimeLists(String listName) async {
|
||||
return (prefs.box.get("allUserAnimeLists")?[listName] as List<dynamic>?)
|
||||
?.map((e) => e as AnimeModel)
|
||||
.toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getUserAvatarImageUrl() async {
|
||||
// return prefs.box.get("avatarImageUrl") as String;
|
||||
avatarImage ??= "https://i.imgur.com/EKtChtm.png";
|
||||
return "https://i.imgur.com/EKtChtm.png";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserMediaModel?> getUserMangaInfo(int mediaId) async {
|
||||
return prefs.box.get("userMangaInfo-$mediaId") as UserMediaModel? ??
|
||||
UserMediaModel(
|
||||
score: 0,
|
||||
progress: 0,
|
||||
repeat: null,
|
||||
priority: null,
|
||||
status: "NOT SET",
|
||||
startDate: null,
|
||||
endDate: null);
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<MangaModel>> getUserMangaLists(String listName) async {
|
||||
return (prefs.box.get("allUserMangaLists")?[listName] as List<dynamic>?)
|
||||
?.map((e) => e as MangaModel)
|
||||
.toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getUserNameAndId() async {
|
||||
if (userName != null && userId != null) {
|
||||
return [userName!, userId!.toString()];
|
||||
}
|
||||
userName = prefs.box.get("userName") as String?;
|
||||
userId = prefs.box.get("userId") as int?;
|
||||
return [prefs.box.get("userName") as String, ""];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getUserbannerImageUrl() async {
|
||||
// return prefs.box.get("userBannerImage") as String;
|
||||
bannerImage ??= "https://i.imgur.com/x6TGK1x.png";
|
||||
return "https://i.imgur.com/x6TGK1x.png";
|
||||
}
|
||||
|
||||
@override
|
||||
void setUserAnimeInfo(int mediaId, Map<String, String> receivedQuery,
|
||||
{AnimeModel? animeModel}) {
|
||||
UserMediaModel userMediaModel = UserMediaModel(
|
||||
score: double.parse(receivedQuery["score"]!),
|
||||
progress: int.parse(receivedQuery["progress"]!),
|
||||
repeat: null,
|
||||
priority: null,
|
||||
status: receivedQuery["status"],
|
||||
startDate:
|
||||
"${receivedQuery["startDateDay"]}/${receivedQuery["startDateMonth"]}/${receivedQuery["startDateYear"]}",
|
||||
endDate:
|
||||
"${receivedQuery["endDateDay"]}/${receivedQuery["endDateMonth"]}/${receivedQuery["endDateYear"]}",
|
||||
);
|
||||
prefs.box.put("userAnimeInfo-$mediaId", userMediaModel);
|
||||
Map<String, List<AnimeModel>>? userAnimeLists = (prefs.box
|
||||
.get("allUserAnimeLists") as Map<dynamic, dynamic>?)
|
||||
?.map((key, value) =>
|
||||
MapEntry(key as String, (value as List).cast<AnimeModel>())) ??
|
||||
{};
|
||||
|
||||
for (var animeModelList in userAnimeLists.values) {
|
||||
animeModelList.remove(animeModel);
|
||||
}
|
||||
|
||||
switch (receivedQuery["status"]) {
|
||||
case "CURRENT":
|
||||
// if (userAnimeLists["Watching"] == null){
|
||||
// userAnimeLists["Watching"] = [];
|
||||
// }
|
||||
userAnimeLists["Watching"] ??= [];
|
||||
userAnimeLists["Watching"]!.add(animeModel!);
|
||||
break;
|
||||
case "COMPLETED":
|
||||
userAnimeLists["Completed"] ??= [];
|
||||
userAnimeLists["Completed"]!.add(animeModel!);
|
||||
break;
|
||||
case "PLANNING":
|
||||
userAnimeLists["Planning"] ??= [];
|
||||
userAnimeLists["Planning"]!.add(animeModel!);
|
||||
break;
|
||||
case "PAUSED":
|
||||
userAnimeLists["Paused"] ??= [];
|
||||
userAnimeLists["Paused"]!.add(animeModel!);
|
||||
break;
|
||||
case "DROPPED":
|
||||
userAnimeLists["Dropped"] ??= [];
|
||||
userAnimeLists["Dropped"]!.add(animeModel!);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
prefs.box.put("allUserAnimeLists", userAnimeLists);
|
||||
}
|
||||
|
||||
@override
|
||||
void setUserMangaInfo(int mediaId, Map<String, String> receivedQuery,
|
||||
{MangaModel? mangaModel}) {
|
||||
UserMediaModel userMediaModel = UserMediaModel(
|
||||
score: double.parse(receivedQuery["score"]!),
|
||||
progress: int.parse(receivedQuery["progress"]!),
|
||||
repeat: null,
|
||||
priority: null,
|
||||
status: receivedQuery["status"],
|
||||
startDate:
|
||||
"${receivedQuery["startDateDay"]}/${receivedQuery["startDateMonth"]}/${receivedQuery["startDateYear"]}",
|
||||
endDate:
|
||||
"${receivedQuery["endDateDay"]}/${receivedQuery["endDateMonth"]}/${receivedQuery["endDateYear"]}",
|
||||
);
|
||||
prefs.box.put("userMangaInfo-$mediaId", userMediaModel);
|
||||
Map<String, List<MangaModel>>? userMangaLists = (prefs.box
|
||||
.get("allUserMangaLists") as Map<dynamic, dynamic>?)
|
||||
?.map((key, value) =>
|
||||
MapEntry(key as String, (value as List).cast<MangaModel>())) ??
|
||||
{};
|
||||
|
||||
for (var mangaModelList in userMangaLists.values) {
|
||||
mangaModelList.remove(mangaModel!);
|
||||
}
|
||||
|
||||
switch (receivedQuery["status"]) {
|
||||
case "CURRENT":
|
||||
// if (userMangaLists["Watching"] == null){
|
||||
// userMangaLists["Watching"] = [];
|
||||
// }
|
||||
print("added");
|
||||
userMangaLists["Reading"] ??= [];
|
||||
userMangaLists["Reading"]!.add(mangaModel!);
|
||||
print(userMangaLists["Reading"]);
|
||||
break;
|
||||
case "COMPLETED":
|
||||
userMangaLists["Completed"] ??= [];
|
||||
userMangaLists["Completed"]!.add(mangaModel!);
|
||||
break;
|
||||
case "PLANNING":
|
||||
userMangaLists["Planning"] ??= [];
|
||||
userMangaLists["Planning"]!.add(mangaModel!);
|
||||
break;
|
||||
case "PAUSED":
|
||||
userMangaLists["Paused"] ??= [];
|
||||
userMangaLists["Paused"]!.add(mangaModel!);
|
||||
break;
|
||||
case "DROPPED":
|
||||
userMangaLists["Dropped"] ??= [];
|
||||
userMangaLists["Dropped"]!.add(mangaModel!);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
prefs.box.put("allUserMangaLists", userMangaLists);
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import 'package:unyo/util/constants.dart';
|
||||
|
||||
class MangaModel {
|
||||
MangaModel({
|
||||
required this.id,
|
||||
required this.idMal,
|
||||
required this.userPreferedTitle,
|
||||
required this.englishTitle,
|
||||
required this.japaneseTitle,
|
||||
required this.coverImage,
|
||||
required this.bannerImage,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.type,
|
||||
required this.status,
|
||||
required this.averageScore,
|
||||
required this.chapters,
|
||||
required this.duration,
|
||||
required this.description,
|
||||
required this.format,
|
||||
this.currentEpisode,
|
||||
});
|
||||
|
||||
int id;
|
||||
int? idMal;
|
||||
String? userPreferedTitle;
|
||||
String? englishTitle;
|
||||
String? japaneseTitle;
|
||||
String? coverImage;
|
||||
String? bannerImage;
|
||||
String? startDate;
|
||||
String? endDate;
|
||||
String? type;
|
||||
String? status;
|
||||
String? description;
|
||||
String? format;
|
||||
int? averageScore;
|
||||
int? chapters;
|
||||
int? currentEpisode;
|
||||
int? duration;
|
||||
|
||||
factory MangaModel.fromJson(Map<String, dynamic> json) {
|
||||
return MangaModel(
|
||||
id: json['id'],
|
||||
idMal: json['idMal'],
|
||||
userPreferedTitle: json["title"]["userPreferred"],
|
||||
japaneseTitle: json['title']['romaji'],
|
||||
englishTitle: json['title']['english'],
|
||||
coverImage: json['coverImage']['large'],
|
||||
bannerImage: json['bannerImage'],
|
||||
startDate:
|
||||
"${json["startDate"]["day"]}/${json["startDate"]["month"]}/${json["startDate"]["year"]}",
|
||||
endDate:
|
||||
"${json["endDate"]["day"]}/${json["endDate"]["month"]}/${json["endDate"]["year"]}",
|
||||
type: json['type'],
|
||||
status: json['status'],
|
||||
description: json['description'],
|
||||
format: json['format'],
|
||||
averageScore: json['averageScore'],
|
||||
chapters: json['chapters'],
|
||||
currentEpisode: json['currentEpisode'],
|
||||
duration: json['duration'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'malId': idMal,
|
||||
'userPreferedTitle': userPreferedTitle,
|
||||
'englishTitle': englishTitle,
|
||||
'japaneseTitle': japaneseTitle,
|
||||
'coverImage': coverImage,
|
||||
'bannerImage': bannerImage,
|
||||
'startDate': startDate,
|
||||
'endDate': endDate,
|
||||
'type': type,
|
||||
'status': status,
|
||||
'description': description,
|
||||
'format': format,
|
||||
'averageScore': averageScore,
|
||||
'chapters': chapters,
|
||||
'currentEpisode': currentEpisode,
|
||||
'duration': duration,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the first non-empty string in [candidates], or `''` if none.
|
||||
String _firstNonEmpty(List<String?> candidates) {
|
||||
for (final s in candidates) {
|
||||
if (s?.isNotEmpty ?? false) return s ?? "";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
String getDefaultTitle() {
|
||||
final defaultTitleType = prefs.getInt("default_title_type") ?? 0;
|
||||
// Define the three title‐orderings
|
||||
final orders = <List<String?>>[
|
||||
[userPreferedTitle, englishTitle, japaneseTitle], // 0
|
||||
[englishTitle, userPreferedTitle, japaneseTitle], // 1
|
||||
[japaneseTitle, userPreferedTitle, englishTitle], // 2
|
||||
];
|
||||
return _firstNonEmpty(orders[defaultTitleType]);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "$id $userPreferedTitle";
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
const String endpoint = "https://api.ani.zip/mappings?anilist_id=";
|
||||
|
||||
class MediaContentModel {
|
||||
MediaContentModel({required this.anilistId});
|
||||
|
||||
void init() async{
|
||||
var url = Uri.parse("$endpoint$anilistId");
|
||||
var response = await http.get(url);
|
||||
if (response.statusCode != 200){
|
||||
if (attempt < 5){
|
||||
|
||||
}
|
||||
}
|
||||
Map<String, dynamic> jsonResponse = json.decode(response.body);
|
||||
Map<String, dynamic> episodes = jsonResponse["episodes"];
|
||||
imageUrls = episodes.values.map((e) => e["image"] as String?).toList();
|
||||
titles = episodes.values.map((e) => e["title"]["en"] as String?).toList();
|
||||
fanart = jsonResponse["images"]?[2]["url"];
|
||||
banner = jsonResponse["images"]?[0]["url"];
|
||||
// if (titles != null){
|
||||
// titles!.addAll(List.filled(10, null));
|
||||
// }
|
||||
// if (imageUrls != null){
|
||||
// imageUrls!.addAll(List.filled(10, null));
|
||||
// }
|
||||
}
|
||||
|
||||
final int anilistId;
|
||||
String? fanart;
|
||||
String? banner;
|
||||
int attempt = 0;
|
||||
List<String?>? titles;
|
||||
List<String?>? imageUrls;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export 'anime_model.dart';
|
||||
export 'manga_model.dart';
|
||||
export 'user_media_model.dart';
|
||||
export 'media_content_model.dart';
|
||||
export 'user_model.dart';
|
||||
export 'anilist_user_model.dart';
|
||||
export 'preferences_model.dart';
|
||||
export 'local_user_model.dart';
|
||||
@@ -1,122 +0,0 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:unyo/models/models.dart';
|
||||
import 'package:unyo/util/utils.dart';
|
||||
|
||||
class PreferencesModel {
|
||||
late SharedPreferences sharedPreferences;
|
||||
late Box box;
|
||||
String? userName;
|
||||
|
||||
Future<void> init() async {
|
||||
sharedPreferences = await SharedPreferences.getInstance();
|
||||
userName = sharedPreferences.getString("user_logged");
|
||||
if (userName != null && userName != "null") {
|
||||
box = await Hive.openBox(userName!);
|
||||
var userBox = await Hive.openBox("users");
|
||||
List<dynamic> anilistSavedUsers = userBox.get("anilistUsers") ?? [];
|
||||
List<dynamic> localSavedUsers = userBox.get("localUsers") ?? [];
|
||||
|
||||
List<UserModel> savedUsers =
|
||||
List.from(anilistSavedUsers.map((e) => e as AnilistUserModel));
|
||||
savedUsers.addAll(localSavedUsers.map((e) => e as LocalUserModel));
|
||||
users = savedUsers;
|
||||
}
|
||||
String? version = sharedPreferences.getString("version");
|
||||
if(version == null || version != currentVersion){
|
||||
print("New version, updating api");
|
||||
processManager.downloadNewCore();
|
||||
}
|
||||
sharedPreferences.setString("version", currentVersion);
|
||||
}
|
||||
|
||||
void getUsers(void Function(void Function()) setState) async {
|
||||
var userBox = await Hive.openBox("users");
|
||||
List<dynamic> anilistSavedUsers = userBox.get("anilistUsers") ?? [];
|
||||
List<dynamic> localSavedUsers = userBox.get("localUsers") ?? [];
|
||||
|
||||
List<UserModel> savedUsers =
|
||||
List.from(anilistSavedUsers.map((e) => e as AnilistUserModel));
|
||||
savedUsers.addAll(localSavedUsers.map((e) => e as LocalUserModel));
|
||||
|
||||
print("saved users number: ${savedUsers.length}");
|
||||
setState(() {
|
||||
users = savedUsers;
|
||||
});
|
||||
}
|
||||
|
||||
void saveUser(UserModel user) async {
|
||||
print("Saving user: ${user.userName}");
|
||||
var userBox = await Hive.openBox("users");
|
||||
List<dynamic> anilistSavedUsers = userBox.get("anilistUsers") ?? [];
|
||||
List<dynamic> localSavedUsers = userBox.get("localUsers") ?? [];
|
||||
|
||||
List<UserModel> savedUsers =
|
||||
List.from(anilistSavedUsers.map((e) => e as AnilistUserModel));
|
||||
savedUsers.addAll(localSavedUsers.map((e) => e as LocalUserModel));
|
||||
|
||||
if (savedUsers
|
||||
.where((listUser) => listUser.userName == user.userName)
|
||||
.isEmpty) {
|
||||
if (user is AnilistUserModel) {
|
||||
anilistSavedUsers.add(user);
|
||||
} else if (user is LocalUserModel) {
|
||||
localSavedUsers.add(user);
|
||||
}
|
||||
}else{
|
||||
UserModel oldUser = savedUsers.where((listUser) => listUser.userName == user.userName).toList()[0];
|
||||
if (user.avatarImage == oldUser.avatarImage) return;
|
||||
if (user is AnilistUserModel){
|
||||
anilistSavedUsers.remove(oldUser);
|
||||
anilistSavedUsers.add(user);
|
||||
}else if(user is LocalUserModel){
|
||||
localSavedUsers.remove(oldUser);
|
||||
localSavedUsers.add(user);
|
||||
}
|
||||
}
|
||||
|
||||
userBox.put("anilistUsers", anilistSavedUsers);
|
||||
userBox.put("localUsers", localSavedUsers);
|
||||
users = savedUsers;
|
||||
}
|
||||
|
||||
Future<void> loginUser(String user) async {
|
||||
print("Logging user: $user");
|
||||
sharedPreferences.setString("user_logged", user);
|
||||
userName = user;
|
||||
box = await Hive.openBox(user);
|
||||
}
|
||||
|
||||
bool isUserLogged() {
|
||||
return userName != null && userName != "null";
|
||||
}
|
||||
|
||||
void logOut() {
|
||||
userName = null;
|
||||
sharedPreferences.setString("user_logged", "null");
|
||||
}
|
||||
|
||||
String? getString(String key) {
|
||||
return box.get(key) as String?;
|
||||
}
|
||||
|
||||
void setString(String key, String value) {
|
||||
box.put(key, value);
|
||||
}
|
||||
|
||||
int? getInt(String key) {
|
||||
return box.get(key) as int?;
|
||||
}
|
||||
|
||||
void setInt(String key, int value) {
|
||||
box.put(key, value);
|
||||
}
|
||||
|
||||
bool? getBool(String key) {
|
||||
return box.get(key) as bool?;
|
||||
}
|
||||
|
||||
void setBool(String key, bool value) {
|
||||
box.put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
class UserMediaModel{
|
||||
UserMediaModel({
|
||||
required this.score,
|
||||
required this.progress,
|
||||
required this.repeat,
|
||||
required this.priority,
|
||||
required this.status,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
|
||||
num? score;
|
||||
num? progress;
|
||||
int? repeat;
|
||||
int? priority;
|
||||
String? status;
|
||||
String? startDate;
|
||||
String? endDate;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'package:unyo/models/models.dart';
|
||||
|
||||
abstract class UserModel {
|
||||
String? avatarImage;
|
||||
String? bannerImage;
|
||||
String? userName;
|
||||
int? userId;
|
||||
|
||||
//user info
|
||||
Future<List<String>> getUserNameAndId();
|
||||
|
||||
Future<String> getUserbannerImageUrl();
|
||||
|
||||
Future<String> getUserAvatarImageUrl();
|
||||
//anime info
|
||||
Future<List<AnimeModel>> getUserAnimeLists(String listName);
|
||||
|
||||
Future<Map<String, List<AnimeModel>>> getAllUserAnimeLists();
|
||||
|
||||
Future<UserMediaModel?> getUserAnimeInfo(int mediaId);
|
||||
//manga info
|
||||
Future<List<MangaModel>> getUserMangaLists(String listName);
|
||||
|
||||
Future<Map<String, List<MangaModel>>> getAllUserMangaLists();
|
||||
|
||||
Future<UserMediaModel?> getUserMangaInfo(int mediaId);
|
||||
//anime setters
|
||||
void setUserAnimeInfo(int mediaId, Map<String, String> receivedQuery,
|
||||
{AnimeModel? animeModel});
|
||||
|
||||
void deleteUserAnime(int mediaId);
|
||||
//manga setters
|
||||
void setUserMangaInfo(int mediaId, Map<String, String> receivedQuery,
|
||||
{MangaModel? mangaModel});
|
||||
|
||||
void deleteUserManga(int mediaId);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import 'package:animated_snack_bar/animated_snack_bar.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class NotificationManager {
|
||||
// Singleton instance
|
||||
static final NotificationManager _instance = NotificationManager._internal();
|
||||
|
||||
// Private constructor
|
||||
NotificationManager._internal();
|
||||
|
||||
// Factory constructor to return the singleton instance
|
||||
factory NotificationManager() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/// Shows a success notification with the given message.
|
||||
void showSuccessNotification(
|
||||
BuildContext context, String message, DesktopSnackBarPosition position) {
|
||||
AnimatedSnackBar.material(
|
||||
message,
|
||||
type: AnimatedSnackBarType.success,
|
||||
desktopSnackBarPosition: position,
|
||||
).show(context);
|
||||
}
|
||||
|
||||
/// Shows an information notification with the given message.
|
||||
void showInfoNotification(
|
||||
BuildContext context, String message, DesktopSnackBarPosition position) {
|
||||
AnimatedSnackBar.material(
|
||||
message,
|
||||
type: AnimatedSnackBarType.info,
|
||||
desktopSnackBarPosition: position,
|
||||
).show(context);
|
||||
}
|
||||
|
||||
/// Shows a warning notification with the given message.
|
||||
void showWarningNotification(
|
||||
BuildContext context, String message, DesktopSnackBarPosition position) {
|
||||
AnimatedSnackBar.material(
|
||||
message,
|
||||
type: AnimatedSnackBarType.warning,
|
||||
desktopSnackBarPosition: position,
|
||||
).show(context);
|
||||
}
|
||||
|
||||
/// Shows an error notification with the given message.
|
||||
void showErrorNotification(
|
||||
BuildContext context, String message, DesktopSnackBarPosition position) {
|
||||
AnimatedSnackBar.material(
|
||||
message,
|
||||
type: AnimatedSnackBarType.error,
|
||||
desktopSnackBarPosition: position,
|
||||
).show(context);
|
||||
}
|
||||
}
|
||||
57
lib/presentation/screens/home_screen.dart
Normal file
57
lib/presentation/screens/home_screen.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
// Flutter dependencies
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// Internal dependencies
|
||||
import 'package:unyo/application/cubits/home_cubit.dart';
|
||||
import 'package:unyo/application/states/home_state.dart';
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
import 'package:unyo/core/services/effects/app_effect_handler.dart';
|
||||
import 'package:unyo/presentation/widgets/text/texts.dart';
|
||||
|
||||
@RoutePage()
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<HomeCubit>(),
|
||||
child: _HomeListener(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeListener extends StatelessWidget {
|
||||
const _HomeListener({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<HomeCubit, HomeState>(
|
||||
listener: (context, state) {
|
||||
if (state.effects.isNotEmpty) {
|
||||
sl<AppEffectHandler>().handleEffects(
|
||||
context,
|
||||
state.effects,
|
||||
context.read<HomeCubit>().clearEffects,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: _HomeView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeView extends StatelessWidget {
|
||||
const _HomeView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
return Column(children: []);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
106
lib/presentation/screens/login_screen.dart
Normal file
106
lib/presentation/screens/login_screen.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
// Flutter dependencies
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
//External dependencies
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
//Internal dependencies
|
||||
import 'package:unyo/application/cubits/login_cubit.dart';
|
||||
import 'package:unyo/application/states/login_state.dart';
|
||||
import 'package:unyo/core/services/effects/app_effect_handler.dart';
|
||||
import 'package:unyo/presentation/widgets/styled/styled.dart';
|
||||
import 'package:unyo/presentation/widgets/text/texts.dart';
|
||||
import 'package:unyo/core/di/locator.dart';
|
||||
|
||||
@RoutePage()
|
||||
class LoginScreen extends StatelessWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<LoginCubit>(),
|
||||
child: _LoginListener(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LoginListener extends StatelessWidget {
|
||||
const _LoginListener({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<LoginCubit, LoginState>(
|
||||
listener: (context, state) {
|
||||
if (state.effects.isNotEmpty) {
|
||||
sl<AppEffectHandler>().handleEffects(
|
||||
context,
|
||||
state.effects,
|
||||
context.read<LoginCubit>().clearEffects,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: _LoginView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LoginView extends StatefulWidget {
|
||||
const _LoginView({super.key});
|
||||
|
||||
@override
|
||||
State<_LoginView> createState() => _LoginViewState();
|
||||
}
|
||||
|
||||
class _LoginViewState extends State<_LoginView> {
|
||||
late final TextEditingController loginTextFieldController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
loginTextFieldController = TextEditingController();
|
||||
context.read<LoginCubit>().fetchAllUsers();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
loginTextFieldController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LoginCubit, LoginState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
TextDisplayLarge(text: context.tr("select_user")),
|
||||
SizedBox(height: 0.07.sh),
|
||||
SizedBox(
|
||||
height: 0.35.sh,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
...state.availableUsers.map(
|
||||
(user) => UserAvatar(user: user, onPressed: (){}),
|
||||
),
|
||||
AddUserAvatar(onPressed: () {}),
|
||||
// Display existing users
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/presentation/screens/root_scaffold_screen.dart
Normal file
18
lib/presentation/screens/root_scaffold_screen.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
//Flutter dependencies
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
//External dependencies
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
||||
@RoutePage()
|
||||
class RootScaffoldScreen extends StatelessWidget {
|
||||
const RootScaffoldScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
// Back
|
||||
body: AutoRouter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
54
lib/presentation/widgets/styled/add_user_avatar.dart
Normal file
54
lib/presentation/widgets/styled/add_user_avatar.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
// External dependencies
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
// Internal dependencies
|
||||
import 'package:unyo/config/config.dart' as config;
|
||||
import 'package:unyo/presentation/widgets/styled/hover_animated_container.dart';
|
||||
import 'package:unyo/presentation/widgets/text/text_headline_large.dart';
|
||||
|
||||
class AddUserAvatar extends StatelessWidget {
|
||||
final void Function() onPressed;
|
||||
|
||||
const AddUserAvatar({super.key, required this.onPressed});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 25.0.w),
|
||||
child: Column(
|
||||
children: [
|
||||
HoverAnimatedContainer(
|
||||
width: 0.25.sh,
|
||||
height: 0.25.sh,
|
||||
hoverWidth: 0.27.sh,
|
||||
hoverHeight: 0.27.sh,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(140.r),
|
||||
border: Border.all(color: Colors.white, width: 3.w),
|
||||
),
|
||||
hoverDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(150.r),
|
||||
border: Border.all(color: Colors.white, width: 6.w),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(140.r),
|
||||
onTap: onPressed,
|
||||
child: CircleAvatar(
|
||||
radius: 0.125.sh,
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundImage: NetworkImage(config.plusImageUrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextHeadlineLarge(text: context.tr("add_account")),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HoverAnimatedContainer extends StatefulWidget {
|
||||
final double width;
|
||||
final double hoverWidth;
|
||||
final double height;
|
||||
final double hoverHeight;
|
||||
final Curve curve;
|
||||
final Decoration decoration;
|
||||
final Decoration hoverDecoration;
|
||||
final SystemMouseCursor cursor;
|
||||
final Widget child;
|
||||
final EdgeInsetsGeometry margin;
|
||||
final EdgeInsetsGeometry hoverMargin;
|
||||
final Duration duration;
|
||||
final Alignment alignment;
|
||||
|
||||
const HoverAnimatedContainer({
|
||||
super.key,
|
||||
required this.width,
|
||||
required this.hoverWidth,
|
||||
required this.height,
|
||||
required this.hoverHeight,
|
||||
required this.decoration,
|
||||
required this.hoverDecoration,
|
||||
required this.child,
|
||||
this.curve = Curves.linear,
|
||||
this.cursor = SystemMouseCursors.basic,
|
||||
this.margin = EdgeInsets.zero,
|
||||
this.hoverMargin = EdgeInsets.zero,
|
||||
this.duration = const Duration(milliseconds: 200),
|
||||
this.alignment = Alignment.center,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HoverAnimatedContainer> createState() => _HoverAnimatedContainerState();
|
||||
}
|
||||
|
||||
class _HoverAnimatedContainerState extends State<HoverAnimatedContainer> {
|
||||
bool hovering = false;
|
||||
|
||||
void _onHover(bool value) => setState(() => hovering = value);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => _onHover(true),
|
||||
onExit: (_) => _onHover(false),
|
||||
cursor: widget.cursor,
|
||||
child: AnimatedContainer(
|
||||
duration: widget.duration,
|
||||
curve: widget.curve,
|
||||
width: hovering ? widget.hoverWidth : widget.width,
|
||||
height: hovering ? widget.hoverHeight : widget.height,
|
||||
decoration: hovering ? widget.hoverDecoration : widget.decoration,
|
||||
margin: hovering ? widget.hoverMargin : widget.margin,
|
||||
alignment: widget.alignment,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
3
lib/presentation/widgets/styled/styled.dart
Normal file
3
lib/presentation/widgets/styled/styled.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
export 'hover_animated_container.dart';
|
||||
export 'user_avatar.dart';
|
||||
export 'add_user_avatar.dart';
|
||||
53
lib/presentation/widgets/styled/user_avatar.dart
Normal file
53
lib/presentation/widgets/styled/user_avatar.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
// External dependencies
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
// Internal dependencies
|
||||
import 'package:unyo/domain/entities/user.dart';
|
||||
import 'package:unyo/presentation/widgets/styled/hover_animated_container.dart';
|
||||
import 'package:unyo/presentation/widgets/text/text_headline_large.dart';
|
||||
|
||||
class UserAvatar extends StatelessWidget {
|
||||
final User user;
|
||||
final void Function() onPressed;
|
||||
|
||||
const UserAvatar({super.key, required this.user, required this.onPressed});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 25.0.w),
|
||||
child: Column(
|
||||
children: [
|
||||
HoverAnimatedContainer(
|
||||
width: 0.25.sh,
|
||||
height: 0.25.sh,
|
||||
hoverWidth: 0.27.sh,
|
||||
hoverHeight: 0.27.sh,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(140.r),
|
||||
border: Border.all(color: Colors.white, width: 3.w),
|
||||
),
|
||||
hoverDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(250.r),
|
||||
border: Border.all(color: Colors.white, width: 6.w),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(140.r),
|
||||
onTap: onPressed,
|
||||
child: CircleAvatar(
|
||||
radius: 0.125.sh,
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundImage: NetworkImage(user.avatarImage),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextHeadlineLarge(text: user.name),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_body_large.dart
Normal file
30
lib/presentation/widgets/text/text_body_large.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextBodyLarge extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextBodyLarge({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.bodyLarge;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_body_medium.dart
Normal file
30
lib/presentation/widgets/text/text_body_medium.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextBodyMedium extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextBodyMedium({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.bodyMedium;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_body_small.dart
Normal file
30
lib/presentation/widgets/text/text_body_small.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextBodySmall extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextBodySmall({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.bodySmall;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_display_large.dart
Normal file
30
lib/presentation/widgets/text/text_display_large.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextDisplayLarge extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextDisplayLarge({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.displayLarge;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_display_medium.dart
Normal file
30
lib/presentation/widgets/text/text_display_medium.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextDisplayMedium extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextDisplayMedium({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.displayMedium;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_display_small.dart
Normal file
30
lib/presentation/widgets/text/text_display_small.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextDisplaySmall extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextDisplaySmall({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.displaySmall;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_headline_large.dart
Normal file
30
lib/presentation/widgets/text/text_headline_large.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextHeadlineLarge extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextHeadlineLarge({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.headlineLarge;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_headline_medium.dart
Normal file
30
lib/presentation/widgets/text/text_headline_medium.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextHeadlineMedium extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextHeadlineMedium({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.headlineMedium;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_headline_small.dart
Normal file
30
lib/presentation/widgets/text/text_headline_small.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextHeadlineSmall extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextHeadlineSmall({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.headlineSmall;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_label_large.dart
Normal file
30
lib/presentation/widgets/text/text_label_large.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextLabelLarge extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextLabelLarge({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.labelLarge;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_label_medium.dart
Normal file
30
lib/presentation/widgets/text/text_label_medium.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextLabelMedium extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextLabelMedium({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.labelMedium;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/presentation/widgets/text/text_label_small.dart
Normal file
30
lib/presentation/widgets/text/text_label_small.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextLabelSmall extends StatelessWidget {
|
||||
final String text;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final TextStyle? style;
|
||||
|
||||
const TextLabelSmall({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.textAlign,
|
||||
this.maxLines,
|
||||
this.overflow,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseStyle = Theme.of(context).textTheme.labelSmall;
|
||||
return Text(
|
||||
text,
|
||||
style: style?.merge(baseStyle) ?? baseStyle,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: overflow,
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user