From dfade2f78ea72812e8380be99f06874ee6b547b0 Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:05:01 +0200 Subject: [PATCH 01/10] Kitsu no shows "Start date" and "Finish Date" when date is not set Instead of "01 Jan 1970" show "Start date" and "Finish Date" on kitsu. Just like MyAnimeList --- lib/modules/manga/detail/widgets/tracker_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/manga/detail/widgets/tracker_widget.dart b/lib/modules/manga/detail/widgets/tracker_widget.dart index 9d7d5fc2..62d13a15 100644 --- a/lib/modules/manga/detail/widgets/tracker_widget.dart +++ b/lib/modules/manga/detail/widgets/tracker_widget.dart @@ -491,7 +491,7 @@ class _TrackerWidgetState extends ConsumerState { text: widget.trackRes.startedReadingDate != null && widget.trackRes.startedReadingDate! > - DateTime(1970).millisecondsSinceEpoch + DateTime.utc(1970).millisecondsSinceEpoch ? dateFormat( widget.trackRes.startedReadingDate.toString(), ref: ref, @@ -532,7 +532,7 @@ class _TrackerWidgetState extends ConsumerState { text: widget.trackRes.finishedReadingDate != null && widget.trackRes.finishedReadingDate! > - DateTime(1970).millisecondsSinceEpoch + DateTime.utc(1970).millisecondsSinceEpoch ? dateFormat( widget.trackRes.finishedReadingDate.toString(), ref: ref, From ef4abc47f2cebee5c9db3d90c9b846a13078218b Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:10:09 +0200 Subject: [PATCH 02/10] Fix #444 + Remove code duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tracker Kitsu now displays "ì" correctly - Removed a lot of code duplication in Kitsu. --- .../providers/track_state_providers.dart | 107 +--- lib/services/trackers/kitsu.dart | 519 +++++------------- 2 files changed, 156 insertions(+), 470 deletions(-) diff --git a/lib/modules/manga/detail/providers/track_state_providers.dart b/lib/modules/manga/detail/providers/track_state_providers.dart index 466fdab9..f6f8279c 100644 --- a/lib/modules/manga/detail/providers/track_state_providers.dart +++ b/lib/modules/manga/detail/providers/track_state_providers.dart @@ -54,24 +54,11 @@ class TrackState extends _$TrackState { ) .updateLibAnime(track!), }, - _ => switch (itemType) { - ItemType.manga => ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .updateLibManga(track!), - _ => ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .updateLibAnime(track!), - }, + _ => ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .updateLib(track!, _isManga), }; ref @@ -205,18 +192,9 @@ class TrackState extends _$TrackState { ) .addLibAnime(track); } else if (syncId == 3) { - findManga = - _isManga - ? await ref - .read( - kitsuProvider(syncId: syncId, itemType: itemType).notifier, - ) - .addLibManga(track) - : await ref - .read( - kitsuProvider(syncId: syncId, itemType: itemType).notifier, - ) - .addLibAnime(track); + findManga = await ref + .read(kitsuProvider(syncId: syncId, itemType: itemType).notifier) + .addLib(track, _isManga); } ref @@ -266,24 +244,11 @@ class TrackState extends _$TrackState { ) .aniListStatusListAnime; } else if (track!.syncId == 3) { - statusList = - _isManga - ? ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .kitsuStatusListManga - : ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .kitsuStatusListAnime; + statusList = ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .kitsuStatusList(_isManga); } for (var element in TrackStatus.values) { if (statusList.contains(element)) { @@ -324,24 +289,11 @@ class TrackState extends _$TrackState { ) .findLibAnime(track!); } else if (track!.syncId == 3) { - findManga = - _isManga - ? await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .findLibManga(track!) - : await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .findLibAnime(track!); + findManga = await ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .findLibItem(track!, _isManga); } return findManga; } @@ -377,24 +329,11 @@ class TrackState extends _$TrackState { ) .searchAnime(query); } else if (track!.syncId == 3) { - tracks = - _isManga - ? await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .search(query) - : await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .searchAnime(query); + tracks = await ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .search(query, _isManga); } return tracks; } diff --git a/lib/services/trackers/kitsu.dart b/lib/services/trackers/kitsu.dart index 35f38298..348c1e49 100644 --- a/lib/services/trackers/kitsu.dart +++ b/lib/services/trackers/kitsu.dart @@ -1,4 +1,3 @@ -import 'dart:developer'; import 'package:http_interceptor/http_interceptor.dart'; import 'package:intl/intl.dart'; import 'package:mangayomi/eval/model/m_bridge.dart'; @@ -26,18 +25,13 @@ class Kitsu extends _$Kitsu { final String _algoliaUrl = 'https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/'; final String _algoliaAppId = 'AWQO5J657S'; - final String _algoliaFilter = - '&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D'; - final String _algoliaFilterAnime = - '&facetFilters=%5B%22kind%3Aanime%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22episodeCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D'; + String _algoliaFilter(bool isManga) => + '&facetFilters=%5B%22kind%3A${isManga ? 'manga' : 'anime'}%22%5D' + '&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22' + '${isManga ? 'chapter' : 'episode'}Count%22%2C%22posterImage%22%2C%22' + 'startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D'; - String _mangaUrl(int id) { - return 'https://kitsu.io/manga/$id'; - } - - String _animeUrl(int id) { - return 'https://kitsu.io/anime/$id'; - } + String _mediaUrl(String type, int id) => 'https://kitsu.io/$type/$id'; @override void build({required int syncId, ItemType? itemType}) {} @@ -60,14 +54,14 @@ class Kitsu extends _$Kitsu { jsonDecode(await response.stream.bytesToString()) as Map; final aKOAuth = OAuth.fromJson(res); - final currenUser = await _getCurrentUser(aKOAuth.accessToken!); + final currentUser = await _getCurrentUser(aKOAuth.accessToken!); ref .read(tracksProvider(syncId: syncId).notifier) .login( TrackPreference( - username: currenUser.$1, + username: currentUser.$1, syncId: syncId, - prefs: jsonEncode({"ratingSystem": currenUser.$2}), + prefs: jsonEncode({"ratingSystem": currentUser.$2}), oAuth: jsonEncode(aKOAuth.toJson()), ), ); @@ -78,14 +72,14 @@ class Kitsu extends _$Kitsu { } } - Future addLibManga(Track track) async { + Future addLib(Track track, bool isManga) async { final userId = _getUserId(); - final accessToken = _getAccesToken(); + final accessToken = _getAccessToken(); var data = jsonEncode({ 'data': { 'type': 'libraryEntries', 'attributes': { - 'status': toKitsuStatusManga(track.status), + 'status': toKitsuStatus(track.status, isManga), 'progress': track.lastChapterRead, }, 'relationships': { @@ -93,7 +87,7 @@ class Kitsu extends _$Kitsu { 'data': {'id': userId, 'type': 'users'}, }, 'media': { - 'data': {'id': track.mediaId, 'type': 'manga'}, + 'data': {'id': track.mediaId, 'type': isManga ? 'manga' : 'anime'}, }, }, }, @@ -108,7 +102,7 @@ class Kitsu extends _$Kitsu { body: data, ); if (response.statusCode != 200) { - return await findLibManga(track); + return await findLibItem(track, true); } var jsonData = jsonDecode(response.body) as Map; @@ -116,52 +110,14 @@ class Kitsu extends _$Kitsu { return track; } - Future addLibAnime(Track track) async { - final userId = _getUserId(); - log(track.mediaId.toString()); - final accessToken = _getAccesToken(); - var data = jsonEncode({ - 'data': { - 'type': 'libraryEntries', - 'attributes': { - 'status': tokitsuStatusAnime(track.status), - 'progress': track.lastChapterRead, - }, - 'relationships': { - 'user': { - 'data': {'id': userId, 'type': 'users'}, - }, - 'media': { - 'data': {'id': track.mediaId, 'type': 'anime'}, - }, - }, - }, - }); - - var response = await http.post( - Uri.parse('${_baseUrl}library-entries'), - headers: { - 'Content-Type': 'application/vnd.api+json', - 'Authorization': 'Bearer $accessToken', - }, - body: data, - ); - if (response.statusCode != 200) { - return await findLibAnime(track); - } - var jsonData = jsonDecode(response.body) as Map; - track.libraryId = int.parse(jsonData['data']['id']); - return track; - } - - Future updateLibManga(Track track) async { - final accessToken = _getAccesToken(); + Future updateLib(Track track, bool isManga) async { + final accessToken = _getAccessToken(); final data = jsonEncode({ "data": { "type": "libraryEntries", - "id": track.mediaId, + "id": track.libraryId, "attributes": { - "status": toKitsuStatusManga(track.status), + "status": toKitsuStatus(track.status, isManga), "progress": track.lastChapterRead, "ratingTwenty": _toKitsuScore(track.score!), "startedAt": _convertDate(track.startedReadingDate!), @@ -171,7 +127,7 @@ class Kitsu extends _$Kitsu { }); await http.patch( - Uri.parse('$_baseUrl/library-entries/${track.mediaId}'), + Uri.parse('${_baseUrl}library-entries/${track.libraryId}'), headers: { "Content-Type": "application/vnd.api+json", 'Authorization': 'Bearer $accessToken', @@ -181,302 +137,119 @@ class Kitsu extends _$Kitsu { return track; } - Future updateLibAnime(Track track) async { - final accessToken = _getAccesToken(); - final data = jsonEncode({ - "data": { - "type": "libraryEntries", - "id": track.mediaId, - "attributes": { - "status": tokitsuStatusAnime(track.status), - "progress": track.lastChapterRead, - "ratingTwenty": _toKitsuScore(track.score!), - "startedAt": _convertDate(track.startedReadingDate!), - "finishedAt": _convertDate(track.finishedReadingDate!), - }, - }, - }); + Future> search(String search, bool isManga) async { + final accessToken = _getAccessToken(); - await http.patch( - Uri.parse('$_baseUrl/library-entries/${track.mediaId}'), + final url = Uri.parse(_algoliaKeyUrl); + final algoliaKeyResponse = await makeGetRequest(url, accessToken); + final key = json.decode(algoliaKeyResponse.body)["media"]["key"]; + final response = await http.post( + Uri.parse(_algoliaUrl), headers: { - "Content-Type": "application/vnd.api+json", + "Content-Type": "application/json", + "X-Algolia-Application-Id": _algoliaAppId, + "X-Algolia-API-Key": key, + }, + body: json.encode({ + 'params': + 'query=${Uri.encodeComponent(search)}${_algoliaFilter(isManga)}', + }), + ); + final data = json.decode(response.body); + + final entries = + List>.from( + data['hits'], + ).where((element) => element["subtype"] != "novel").toList(); + final totalChapter = isManga ? "chapterCount" : "episodeCount"; + return entries + .map( + (jsonRes) => TrackSearch( + libraryId: jsonRes['id'], + syncId: syncId, + trackingUrl: _mediaUrl(isManga ? 'manga' : 'anime', jsonRes['id']), + mediaId: jsonRes['id'], + summary: jsonRes['synopsis'] ?? "", + totalChapter: (jsonRes[totalChapter] ?? 0), + coverUrl: jsonRes['posterImage']['original'] ?? "", + title: jsonRes['canonicalTitle'], + startDate: "", + publishingType: (jsonRes["subtype"] ?? ""), + publishingStatus: + jsonRes['endDate'] == null ? "Publishing" : "Finished", + ), + ) + .toList(); + } + + Future findLibItem(Track track, bool isManga) async { + final type = isManga ? "manga" : "anime"; + final userId = _getUserId(); + final accessToken = _getAccessToken(); + + final url = Uri.parse( + '${_baseUrl}library-entries?filter[${type}_id]=${track.libraryId}&filter[user_id]=$userId&include=$type', + ); + Response response = await makeGetRequest(url, accessToken); + if (response.statusCode == 200) { + final parsed = parseTrackResponse(response, track, type); + if (parsed != null) return parsed; + } + return await getItem(track, type); + } + + Future makeGetRequest(Uri url, String accessToken) async { + final response = await http.get( + url, + headers: { + "Content-Type": "application/json", 'Authorization': 'Bearer $accessToken', }, - body: data, ); + return response; + } + + Future getItem(Track track, String type) async { + final accessToken = _getAccessToken(); + final url = Uri.parse( + '${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=$type', + ); + Response response = await makeGetRequest(url, accessToken); + if (response.statusCode == 200) { + return parseTrackResponse(response, track, type); + } + return null; + } + + Track? parseTrackResponse(Response response, Track track, String type) { + final jsonResponse = jsonDecode(utf8.decode(response.bodyBytes)); + + final List data = jsonResponse['data']; + + if (data.isEmpty) return null; + + final obj = data[0]; + final attributes = obj["attributes"]; + final included = jsonResponse['included'][0]["attributes"]; + final id = int.parse(obj["id"]); + final totalChapter = type == 'manga' ? "chapterCount" : "episodeCount"; + track.mediaId = id; + track.libraryId = id; + track.syncId = syncId; + track.trackingUrl = _mediaUrl(type, id); + track.totalChapter = included[totalChapter] ?? 0; + track.status = getKitsuTrackStatus(attributes["status"], type); + track.score = ((attributes["ratingTwenty"] ?? 0) / 2).toInt(); + track.title = included["canonicalTitle"]; + track.lastChapterRead = attributes["progress"]; + track.startedReadingDate = _parseDate(attributes["startedAt"]); + track.finishedReadingDate = _parseDate(attributes["finishedAt"]); return track; } - Future> search(String search) async { - final accessToken = _getAccesToken(); - - final algoliaKeyResponse = await http.get( - Uri.parse(_algoliaKeyUrl), - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - final key = json.decode(algoliaKeyResponse.body)["media"]["key"]; - final response = await http.post( - Uri.parse(_algoliaUrl), - headers: { - "Content-Type": "application/json", - "X-Algolia-Application-Id": _algoliaAppId, - "X-Algolia-API-Key": key, - }, - body: json.encode({ - 'params': 'query=${Uri.encodeComponent(search)}$_algoliaFilter', - }), - ); - final data = json.decode(response.body); - - final entries = - List>.from( - data['hits'], - ).where((element) => element["subtype"] != "novel").toList(); - return entries - .map( - (jsonRes) => TrackSearch( - libraryId: jsonRes['id'], - syncId: syncId, - trackingUrl: _mangaUrl(jsonRes['id']), - mediaId: jsonRes['id'], - summary: jsonRes['synopsis'] ?? "", - totalChapter: jsonRes['chapterCount'] ?? 0, - coverUrl: jsonRes['posterImage']['original'] ?? "", - title: jsonRes['canonicalTitle'], - startDate: "", - publishingType: jsonRes["subtype"] ?? "s", - publishingStatus: - jsonRes['endDate'] == null ? "Publishing" : "Finished", - ), - ) - .toList(); - } - - Future> searchAnime(String search) async { - final accessToken = _getAccesToken(); - - final algoliaKeyResponse = await http.get( - Uri.parse(_algoliaKeyUrl), - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - final key = json.decode(algoliaKeyResponse.body)["media"]["key"]; - final response = await http.post( - Uri.parse(_algoliaUrl), - headers: { - "Content-Type": "application/json", - "X-Algolia-Application-Id": _algoliaAppId, - "X-Algolia-API-Key": key, - }, - body: json.encode({ - 'params': 'query=${Uri.encodeComponent(search)}$_algoliaFilterAnime', - }), - ); - final data = json.decode(response.body); - - final entries = - List>.from( - data['hits'], - ).where((element) => element["subtype"] != "novel").toList(); - return entries - .map( - (jsonRes) => TrackSearch( - libraryId: jsonRes['id'], - syncId: syncId, - trackingUrl: _animeUrl(jsonRes['id']), - mediaId: jsonRes['id'], - summary: jsonRes['synopsis'] ?? "", - totalChapter: jsonRes['episodeCount'] ?? 0, - coverUrl: jsonRes['posterImage']['original'] ?? "", - title: jsonRes['canonicalTitle'], - startDate: "", - publishingType: jsonRes["subtype"] ?? "", - publishingStatus: - jsonRes['endDate'] == null ? "Publishing" : "Finished", - ), - ) - .toList(); - } - - Future getManga(Track track) async { - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=manga', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - - if (data.isNotEmpty) { - final obj = data[0]; - track.mediaId = int.parse(obj["id"]); - track.libraryId = int.parse(obj["id"]); - track.syncId = syncId; - track.trackingUrl = _mangaUrl(int.parse(obj["id"])); - track.status = _getKitsuTrackStatusManga(obj["attributes"]["status"]); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["chapterCount"] ?? 0; - track.score = ((obj["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.lastChapterRead = obj["attributes"]["progress"]; - track.startedReadingDate = _parseDate(obj["attributes"]["startedAt"]); - track.finishedReadingDate = _parseDate(obj["attributes"]["finishedAt"]); - return track; - } - } - return null; - } - - Future findLibManga(Track track) async { - final userId = _getUserId(); - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[manga_id]=${track.mediaId}&filter[user_id]=$userId&include=manga', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - - if (data.isNotEmpty) { - final obj = data[0]; - track.mediaId = int.parse(obj["id"]); - track.libraryId = int.parse(obj["id"]); - track.syncId = syncId; - track.trackingUrl = _mangaUrl(int.parse(obj["id"])); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["chapterCount"] ?? 0; - track.status = _getKitsuTrackStatusManga(obj["attributes"]["status"]); - track.score = ((obj["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.lastChapterRead = obj["attributes"]["progress"]; - track.startedReadingDate = _parseDate(obj["attributes"]["startedAt"]); - track.finishedReadingDate = _parseDate(obj["attributes"]["finishedAt"]); - return track; - } - } - return await getManga(track); - } - - Future findLibAnime(Track track) async { - final userId = _getUserId(); - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[anime_id]=${track.mediaId}&filter[user_id]=$userId&include=anime', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - if (data.isNotEmpty) { - track.mediaId = int.parse(data[0]["id"]); - track.libraryId = int.parse(data[0]["id"]); - track.syncId = syncId; - track.trackingUrl = _animeUrl(int.parse(data[0]["id"])); - track.status = _getKitsuTrsackStatusAnime( - data[0]["attributes"]["status"], - ); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["episodeCount"] ?? 0; - track.score = - ((data[0]["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.lastChapterRead = data[0]["attributes"]["progress"]; - track.startedReadingDate = _parseDate( - data[0]["attributes"]["startedAt"], - ); - track.finishedReadingDate = _parseDate( - data[0]["attributes"]["finishedAt"], - ); - return track; - } - } - return await getAnime(track); - } - - Future getAnime(Track track) async { - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=anime', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - if (data.isNotEmpty) { - track.mediaId = int.parse(data[0]["id"]); - track.libraryId = int.parse(data[0]["id"]); - track.syncId = syncId; - track.trackingUrl = _animeUrl(int.parse(data[0]["id"])); - track.status = _getKitsuTrsackStatusAnime( - data[0]["attributes"]["status"], - ); - track.score = - ((data[0]["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["episodeCount"] ?? 0; - track.lastChapterRead = data[0]["attributes"]["progress"]; - track.startedReadingDate = _parseDate( - data[0]["attributes"]["startedAt"], - ); - track.finishedReadingDate = _parseDate( - data[0]["attributes"]["finishedAt"], - ); - return track; - } - } - return null; - } - Future<(String, String)> _getCurrentUser(String accessToken) async { - final response = await http.get( - Uri.parse("${_baseUrl}users?filter[self]=true"), - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); + final url = Uri.parse('${_baseUrl}users?filter[self]=true'); + Response response = await makeGetRequest(url, accessToken); final data = json.decode(response.body)['data'][0]; return ( data['id'].toString(), @@ -484,7 +257,7 @@ class Kitsu extends _$Kitsu { ); } - String _getAccesToken() { + String _getAccessToken() { final track = ref.watch(tracksProvider(syncId: syncId)); final mAKOAuth = OAuth.fromJson( jsonDecode(track!.oAuth!) as Map, @@ -503,54 +276,28 @@ class Kitsu extends _$Kitsu { return track!.username!; } - TrackStatus _getKitsuTrsackStatusAnime(String status) { + TrackStatus getKitsuTrackStatus(String status, String type) { return switch (status) { - "current" => TrackStatus.watching, + "current" => type == "manga" ? TrackStatus.reading : TrackStatus.watching, "completed" => TrackStatus.completed, "on_hold" => TrackStatus.onHold, "dropped" => TrackStatus.dropped, - _ => TrackStatus.planToWatch, + _ => type == "manga" ? TrackStatus.planToRead : TrackStatus.planToWatch, }; } - TrackStatus _getKitsuTrackStatusManga(String status) { - return switch (status) { - "current" => TrackStatus.reading, - "completed" => TrackStatus.completed, - "on_hold" => TrackStatus.onHold, - "dropped" => TrackStatus.dropped, - _ => TrackStatus.planToRead, - }; - } - - List kitsuStatusListManga = [ - TrackStatus.reading, + List kitsuStatusList(bool isManga) => [ + isManga ? TrackStatus.reading : TrackStatus.watching, TrackStatus.completed, TrackStatus.onHold, TrackStatus.dropped, - TrackStatus.planToRead, - ]; - List kitsuStatusListAnime = [ - TrackStatus.watching, - TrackStatus.completed, - TrackStatus.onHold, - TrackStatus.dropped, - TrackStatus.planToWatch, + isManga ? TrackStatus.planToRead : TrackStatus.planToWatch, ]; - String? toKitsuStatusManga(TrackStatus status) { + String? toKitsuStatus(TrackStatus status, bool isManga) { return switch (status) { - TrackStatus.reading => "current", - TrackStatus.completed => "completed", - TrackStatus.onHold => "on_hold", - TrackStatus.dropped => "dropped", - _ => "planned", - }; - } - - String? tokitsuStatusAnime(TrackStatus status) { - return switch (status) { - TrackStatus.watching => "current", + TrackStatus.reading when isManga => "current", + TrackStatus.watching when !isManga => "current", TrackStatus.completed => "completed", TrackStatus.onHold => "on_hold", TrackStatus.dropped => "dropped", From 6d99d9a690a90782acbc6883bd7877a75447e430 Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Mon, 21 Apr 2025 22:47:23 +0200 Subject: [PATCH 03/10] Implemented text scroll to chapter/episode list title text scroll (like a marquee effect) when the chapter/episode name is too long. --- .../widgets/chapter_list_tile_widget.dart | 45 ++++++++++++++++--- pubspec.lock | 16 +++++++ pubspec.yaml | 1 + 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart b/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart index b110faa2..ca7520d0 100644 --- a/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart +++ b/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:marquee/marquee.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; @@ -64,13 +65,7 @@ class ChapterListTileWidget extends ConsumerWidget { chapter.isBookmarked! ? Icon(Icons.bookmark, size: 16, color: context.primaryColor) : Container(), - Flexible( - child: Text( - chapter.name!, - style: const TextStyle(fontSize: 13), - overflow: TextOverflow.ellipsis, - ), - ), + Flexible(child: _buildTitle(chapter.name!, context)), ], ), subtitle: Row( @@ -141,4 +136,40 @@ class ChapterListTileWidget extends ConsumerWidget { ), ); } + + Widget _buildTitle(String text, BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final textPainter = TextPainter( + text: TextSpan(text: text, style: const TextStyle(fontSize: 13)), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout( + maxWidth: (constraints.maxWidth - (35 + 5)), + ); // - Download icon size (download_page_widget.dart, Widget Build SizedBox width: 35) + + final isOverflowing = textPainter.didExceedMaxLines; + + if (isOverflowing) { + return SizedBox( + height: 20, + child: Marquee( + text: text, + style: const TextStyle(fontSize: 13), + blankSpace: 40.0, + velocity: 30.0, + pauseAfterRound: const Duration(seconds: 1), + startPadding: 10.0, + ), + ); + } else { + return Text( + text, + style: const TextStyle(fontSize: 13), + overflow: TextOverflow.ellipsis, + ); + } + }, + ); + } } diff --git a/pubspec.lock b/pubspec.lock index f6cffcd6..6029b86a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -479,6 +479,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.6" + fading_edge_scrollview: + dependency: transitive + description: + name: fading_edge_scrollview + sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef" + url: "https://pub.dev" + source: hosted + version: "4.1.1" fake_async: dependency: transitive description: @@ -1117,6 +1125,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3-main.0" + marquee: + dependency: "direct main" + description: + name: marquee + sha256: a87e7e80c5d21434f90ad92add9f820cf68be374b226404fe881d2bba7be0862 + url: "https://pub.dev" + source: hosted + version: "2.3.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d523b674..53644b1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,6 +81,7 @@ dependencies: protobuf: ^3.1.0 device_info_plus: ^11.3.3 flutter_app_installer: ^1.0.0 + marquee: ^2.2.3 dependency_overrides: ffi: ^2.1.3 From b5d65168fb289bd2d43e3d31f6422adb040a68dc Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Tue, 22 Apr 2025 00:39:00 +0200 Subject: [PATCH 04/10] Fix Exception ``` [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: Bad state: Cannot use "ref" after the widget was disposed. #0 ConsumerStatefulElement._assertNotDisposed (package:flutter_riverpod/src/consumer.dart:550:7) consumer.dart:550 #1 ConsumerStatefulElement.read (package:flutter_riverpod/src/consumer.dart:619:5) consumer.dart:619 #2 _MainScreenState.initState.. (package:mangayomi/modules/main_view/main_screen.dart:70:13) main_screen.dart:70 #3 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:410:19) timer_impl.dart:410 #4 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:441:5) timer_impl.dart:441 #5 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:194:12) isolate_patch.dart:194 ``` --- lib/modules/main_view/main_screen.dart | 59 ++++++++++++++++++-------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/lib/modules/main_view/main_screen.dart b/lib/modules/main_view/main_screen.dart index b64cf99a..d63a2c5e 100644 --- a/lib/modules/main_view/main_screen.dart +++ b/lib/modules/main_view/main_screen.dart @@ -38,6 +38,8 @@ class MainScreen extends ConsumerStatefulWidget { } class _MainScreenState extends ConsumerState { + Timer? _backupTimer; + Timer? _syncTimer; String getHyphenatedUpdatesLabel(String languageCode, String defaultLabel) { switch (languageCode) { case 'de': @@ -63,26 +65,19 @@ class _MainScreenState extends ConsumerState { late String defaultLocation = navigationOrder.first; @override initState() { + super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.go(defaultLocation); - Timer.periodic(Duration(minutes: 5), (timer) { - ref.read(checkAndBackupProvider); - }); + _backupTimer = Timer.periodic( + const Duration(minutes: 5), + _onBackupTimerTick, + ); if (autoSyncFrequency != 0) { - final l10n = l10nLocalizations(context)!; - Timer.periodic(Duration(seconds: autoSyncFrequency), (timer) { - try { - ref - .read(syncServerProvider(syncId: 1).notifier) - .startSync(l10n, true); - } catch (e) { - botToast( - "Failed to sync! Maybe the sync server is down. Restart the app to resume auto sync.", - ); - timer.cancel(); - } - }); + _syncTimer = Timer.periodic( + Duration(seconds: autoSyncFrequency), + _onSyncTimerTick, + ); } ref.watch(checkForUpdateProvider(context: context)); @@ -90,8 +85,38 @@ class _MainScreenState extends ConsumerState { ref.watch(fetchAnimeSourcesListProvider(id: null, reFresh: false)); ref.watch(fetchNovelSourcesListProvider(id: null, reFresh: false)); }); + } - super.initState(); + void _onBackupTimerTick(Timer timer) { + if (!mounted) { + timer.cancel(); + return; + } + ref.read(checkAndBackupProvider); + } + + void _onSyncTimerTick(Timer timer) { + if (!mounted) { + timer.cancel(); + return; + } + try { + final l10n = l10nLocalizations(context)!; + ref.read(syncServerProvider(syncId: 1).notifier).startSync(l10n, true); + } catch (e) { + botToast( + "Failed to sync! Maybe the sync server is down. " + "Restart the app to resume auto sync.", + ); + timer.cancel(); + } + } + + @override + void dispose() { + _backupTimer?.cancel(); + _syncTimer?.cancel(); + super.dispose(); } @override From 3e2d7ed5bd8733c20aaf9a698c6327ca5a19d54b Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Tue, 22 Apr 2025 01:17:17 +0200 Subject: [PATCH 05/10] check if System theme has been changed Why was this removed? --- lib/main.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 61c47860..72ef53f7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -109,6 +110,16 @@ class _MyAppState extends ConsumerState { } else { ref.read(themeModeStateProvider.notifier).setDarkTheme(); } + // Listen to System theme changes and adjust accordingly + final dispatcher = SchedulerBinding.instance.platformDispatcher; + dispatcher.onPlatformBrightnessChanged = () { + var newBrightness = dispatcher.platformBrightness; + if (newBrightness == Brightness.light) { + ref.read(themeModeStateProvider.notifier).setLightTheme(); + } else { + ref.read(themeModeStateProvider.notifier).setDarkTheme(); + } + }; } }); } From 59b2389631270b03292aa5c81a00db83d13f01df Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Tue, 22 Apr 2025 03:23:12 +0200 Subject: [PATCH 06/10] Fix Exception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` ════════ Exception caught by widgets library ═══════════════════════════════════ The following UnsupportedError was thrown building CircularProgressIndicator(100.0%, dependencies: [InheritedCupertinoTheme, _InheritedTheme, _LocalizationsScope-[GlobalKey#3b6da]], state: _CircularProgressIndicatorState#8c03b(ticker inactive)): Unsupported operation: Infinity or NaN toInt The relevant error-causing widget was: CircularProgressIndicator CircularProgressIndicator (package:mangayomi/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart:56:27) ``` --- .../circular_progress_indicator_animate_rotate.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart b/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart index 3fe2de49..08c729ea 100644 --- a/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart +++ b/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart @@ -51,9 +51,13 @@ class _CircularProgressIndicatorAnimateRotateState duration: const Duration(milliseconds: 500), curve: Curves.easeInOut, tween: Tween(begin: 0, end: widget.progress), - builder: - (context, value, _) => - CircularProgressIndicator(value: value), + builder: (context, value, _) { + final safeValue = + value.isNaN || value.isInfinite + ? null + : value.clamp(0.0, 1.0); + return CircularProgressIndicator(value: safeValue); + }, ), ), ); From 3ecbef80b244a85c94139f1d90bca21bfd815695 Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Tue, 22 Apr 2025 03:49:31 +0200 Subject: [PATCH 07/10] Fix Exception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At line 963, when scrolling too fast in manga reader and page didn't load before. ``` ═══════ Exception caught by foundation library ════════════════════════════════ The following StateError was thrown while dispatching notifications for ValueNotifier>: Bad state: No element ``` --- lib/modules/manga/reader/reader_view.dart | 89 ++++++++++++----------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index f8e4e75a..63755ae7 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -189,6 +189,7 @@ class _MangaChapterPageGalleryState StreamController.broadcast(); @override void initState() { + super.initState(); _doubleClickAnimationController = AnimationController( duration: _doubleTapAnimationDuration(), vsync: this, @@ -203,8 +204,6 @@ class _MangaChapterPageGalleryState _animation.addListener(() => _photoViewController.scale = _animation.value); _itemPositionsListener.itemPositions.addListener(_readProgressListener); _initCurrentIndex(); - - super.initState(); } final double _horizontalScaleValue = 1.0; @@ -960,53 +959,55 @@ class _MangaChapterPageGalleryState } void _readProgressListener() { - _currentIndex = _itemPositionsListener.itemPositions.value.first.index; + final itemPositions = _itemPositionsListener.itemPositions.value; - int pagesLength = - (_pageMode == PageMode.doublePage && - !(ref.watch(_currentReaderMode) == - ReaderMode.horizontalContinuous)) - ? (_uChapDataPreload.length / 2).ceil() + 1 - : _uChapDataPreload.length; + if (itemPositions.isNotEmpty) { + _currentIndex = itemPositions.first.index; + int pagesLength = + (_pageMode == PageMode.doublePage && + !(ref.watch(_currentReaderMode) == + ReaderMode.horizontalContinuous)) + ? (_uChapDataPreload.length / 2).ceil() + 1 + : _uChapDataPreload.length; - if (_currentIndex! >= 0 && _currentIndex! < pagesLength) { - if (_readerController.chapter.id != - _uChapDataPreload[_currentIndex!].chapter!.id) { - _readerController.setPageIndex( - _geCurrentIndex(_uChapDataPreload[_currentIndex! - 1].index!), - false, - ); - if (mounted) { - setState(() { - _readerController = ref.read( - readerControllerProvider( - chapter: _uChapDataPreload[_currentIndex!].chapter!, - ).notifier, - ); + if (_currentIndex! >= 0 && _currentIndex! < pagesLength) { + if (_readerController.chapter.id != + _uChapDataPreload[_currentIndex!].chapter!.id) { + _readerController.setPageIndex( + _geCurrentIndex(_uChapDataPreload[_currentIndex! - 1].index!), + false, + ); + if (mounted) { + setState(() { + _readerController = ref.read( + readerControllerProvider( + chapter: _uChapDataPreload[_currentIndex!].chapter!, + ).notifier, + ); - chapter = _uChapDataPreload[_currentIndex!].chapter!; - _chapterUrlModel = - _uChapDataPreload[_currentIndex!].chapterUrlModel!; - _isBookmarked = _readerController.getChapterBookmarked(); - }); + chapter = _uChapDataPreload[_currentIndex!].chapter!; + _chapterUrlModel = + _uChapDataPreload[_currentIndex!].chapterUrlModel!; + _isBookmarked = _readerController.getChapterBookmarked(); + }); + } + } + if (itemPositions.last.index == pagesLength - 1) { + try { + ref + .watch( + getChapterPagesProvider( + chapter: _readerController.getNextChapter(), + ).future, + ) + .then((value) => _preloadNextChapter(value, chapter)); + } catch (_) {} } - } - if (_itemPositionsListener.itemPositions.value.last.index == - pagesLength - 1) { - try { - ref - .watch( - getChapterPagesProvider( - chapter: _readerController.getNextChapter(), - ).future, - ) - .then((value) => _preloadNextChapter(value, chapter)); - } catch (_) {} - } - ref - .read(currentIndexProvider(chapter).notifier) - .setCurrentIndex(_uChapDataPreload[_currentIndex!].index!); + ref + .read(currentIndexProvider(chapter).notifier) + .setCurrentIndex(_uChapDataPreload[_currentIndex!].index!); + } } } From ffd776dbf1a0456905e3a91b77f3d061b4ad437b Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Tue, 22 Apr 2025 03:53:28 +0200 Subject: [PATCH 08/10] tidy up main.dart and move theme handling to provider removed code duplication --- lib/main.dart | 208 +++++------------- .../appearance/providers/theme_provider.dart | 61 +++++ 2 files changed, 120 insertions(+), 149 deletions(-) create mode 100644 lib/modules/more/settings/appearance/providers/theme_provider.dart diff --git a/lib/main.dart b/lib/main.dart index 72ef53f7..38c4d89c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,32 +3,26 @@ import 'dart:io'; import 'package:app_links/app_links.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart'; -import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:intl/date_symbol_data_local.dart'; -import 'package:intl/intl.dart'; import 'package:isar/isar.dart'; import 'package:mangayomi/eval/model/m_bridge.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/more/data_and_storage/providers/storage_usage.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/app_font_family.dart'; import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; import 'package:mangayomi/providers/storage_provider.dart'; import 'package:mangayomi/router/router.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/blend_level_state_provider.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/flex_scheme_color_state_provider.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart'; import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart'; import 'package:mangayomi/l10n/generated/app_localizations.dart'; import 'package:mangayomi/src/rust/frb_generated.dart'; import 'package:mangayomi/utils/url_protocol/api.dart'; +import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart'; import 'package:media_kit/media_kit.dart'; import 'package:path_provider/path_provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -38,11 +32,7 @@ late Isar isar; WebViewEnvironment? webViewEnvironment; void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); - if (Platform.isLinux) { - if (runWebViewTitleBarWidget(args)) { - return; - } - } + if (Platform.isLinux && runWebViewTitleBarWidget(args)) return; MediaKit.ensureInitialized(); await RustLib.init(); if (!(Platform.isAndroid || Platform.isIOS)) { @@ -63,19 +53,14 @@ void main(List args) async { } } isar = await StorageProvider().initDB(null, inspector: kDebugMode); - await StorageProvider().requestPermission(); - await StorageProvider().deleteBtDirectory(); - GoogleFonts.aBeeZee(); runApp(const ProviderScope(child: MyApp())); + unawaited(postLaunchInit()); // Defer non-essential async operations } -void _iniDateFormatting() { - initializeDateFormatting(); - final supportedLocales = DateFormat.allLocalesWithSymbols(); - for (var locale in supportedLocales) { - initializeDateFormatting(locale); - } +Future postLaunchInit() async { + await StorageProvider().requestPermission(); + await StorageProvider().deleteBtDirectory(); } class MyApp extends ConsumerStatefulWidget { @@ -88,90 +73,61 @@ class MyApp extends ConsumerStatefulWidget { class _MyAppState extends ConsumerState { late AppLinks _appLinks; StreamSubscription? _linkSubscription; + Uri? lastUri; @override void initState() { super.initState(); - _iniDateFormatting(); + initializeDateFormatting(); _initDeepLinks(); WidgetsBinding.instance.addPostFrameCallback((_) { - if (ref.read(clearChapterCacheOnAppLaunchStateProvider)) { - ref - .read(totalChapterCacheSizeStateProvider.notifier) - .clearCache(showToast: false); + checkAndClearCache(); + handleThemeSync(); + }); + } + + void checkAndClearCache() { + if (ref.read(clearChapterCacheOnAppLaunchStateProvider)) { + ref + .read(totalChapterCacheSizeStateProvider.notifier) + .clearCache(showToast: false); + } + } + + void handleThemeSync() { + // Check if System theme has changed since last app start and adjust + if (ref.read(followSystemThemeStateProvider)) { + var brightness = + WidgetsBinding.instance.platformDispatcher.platformBrightness; + if (brightness == Brightness.light) { + ref.read(themeModeStateProvider.notifier).setLightTheme(); + } else { + ref.read(themeModeStateProvider.notifier).setDarkTheme(); } - // Check if System theme has changed since last app start and adjust - if (ref.read(followSystemThemeStateProvider)) { - var brightness = - WidgetsBinding.instance.platformDispatcher.platformBrightness; - if (brightness == Brightness.light) { + // Listen to System theme changes and adjust accordingly + final dispatcher = SchedulerBinding.instance.platformDispatcher; + dispatcher.onPlatformBrightnessChanged = () { + var newBrightness = dispatcher.platformBrightness; + if (newBrightness == Brightness.light) { ref.read(themeModeStateProvider.notifier).setLightTheme(); } else { ref.read(themeModeStateProvider.notifier).setDarkTheme(); } - // Listen to System theme changes and adjust accordingly - final dispatcher = SchedulerBinding.instance.platformDispatcher; - dispatcher.onPlatformBrightnessChanged = () { - var newBrightness = dispatcher.platformBrightness; - if (newBrightness == Brightness.light) { - ref.read(themeModeStateProvider.notifier).setLightTheme(); - } else { - ref.read(themeModeStateProvider.notifier).setDarkTheme(); - } - }; - } - }); + }; + } } @override Widget build(BuildContext context) { final isDarkTheme = ref.watch(themeModeStateProvider); - final blendLevel = ref.watch(blendLevelStateProvider); - final appFontFamily = ref.watch(appFontFamilyProvider); - final pureBlackDarkMode = ref.watch(pureBlackDarkModeStateProvider); final locale = ref.watch(l10nLocaleStateProvider); - ThemeData themeLight = FlexThemeData.light( - colors: ref.watch(flexSchemeColorStateProvider), - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: blendLevel.toInt(), - appBarOpacity: 0.00, - subThemesData: const FlexSubThemesData( - blendOnLevel: 10, - thinBorderWidth: 2.0, - unselectedToggleIsColored: true, - inputDecoratorRadius: 24.0, - chipRadius: 24.0, - ), - useMaterial3ErrorColors: true, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - useMaterial3: true, - fontFamily: appFontFamily, - ); - ThemeData themeDark = FlexThemeData.dark( - colors: ref.watch(flexSchemeColorStateProvider), - surfaceMode: FlexSurfaceMode.level, - blendLevel: blendLevel.toInt(), - appBarOpacity: 0.00, - scaffoldBackground: pureBlackDarkMode ? Colors.black : null, - subThemesData: const FlexSubThemesData( - blendOnLevel: 10, - thinBorderWidth: 2.0, - unselectedToggleIsColored: true, - inputDecoratorRadius: 24.0, - chipRadius: 24.0, - ), - useMaterial3ErrorColors: true, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - useMaterial3: true, - fontFamily: appFontFamily, - ); final router = ref.watch(routerProvider); return MaterialApp.router( - darkTheme: themeDark, + theme: ref.watch(lightThemeProvider), + darkTheme: ref.watch(darkThemeProvider), themeMode: isDarkTheme ? ThemeMode.dark : ThemeMode.light, - theme: themeLight, debugShowCheckedModeBanner: false, locale: locale, localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -193,6 +149,8 @@ class _MyAppState extends ConsumerState { Future _initDeepLinks() async { _appLinks = AppLinks(); _linkSubscription = _appLinks.uriLinkStream.listen((uri) { + if (uri == lastUri) return; // Debouncing Deep Links + lastUri = uri; switch (uri.host) { case "add-repo": final repoName = uri.queryParameters["repo_name"]; @@ -201,8 +159,8 @@ class _MyAppState extends ConsumerState { final animeRepoUrls = uri.queryParametersAll["anime_url"]; final novelRepoUrls = uri.queryParametersAll["novel_url"]; final context = navigatorKey.currentContext; - if (!(context?.mounted ?? false)) return; - final l10n = context!.l10n; + if (context == null || !context.mounted) return; + final l10n = context.l10n; showDialog( context: navigatorKey.currentContext!, builder: (BuildContext context) { @@ -226,78 +184,30 @@ class _MyAppState extends ConsumerState { child: Text(l10n.add), onPressed: () { Navigator.of(context).pop(); - if (mangaRepoUrls != null) { - final mangaRepos = - ref - .read( - extensionsRepoStateProvider(ItemType.manga), - ) - .toList(); - mangaRepos.addAll( - mangaRepoUrls.map( + + void addRepos(ItemType type, List? urls) { + if (urls == null) return; + final current = ref.read( + extensionsRepoStateProvider(type), + ); + final updated = [ + ...current, + ...urls.map( (e) => Repo( name: repoName, jsonUrl: e, website: repoUrl, ), ), - ); + ]; ref - .read( - extensionsRepoStateProvider( - ItemType.manga, - ).notifier, - ) - .set(mangaRepos); - } - if (animeRepoUrls != null) { - final animeRepos = - ref - .read( - extensionsRepoStateProvider(ItemType.anime), - ) - .toList(); - animeRepos.addAll( - animeRepoUrls.map( - (e) => Repo( - name: repoName, - jsonUrl: e, - website: repoUrl, - ), - ), - ); - ref - .read( - extensionsRepoStateProvider( - ItemType.anime, - ).notifier, - ) - .set(animeRepos); - } - if (novelRepoUrls != null) { - final novelRepos = - ref - .read( - extensionsRepoStateProvider(ItemType.novel), - ) - .toList(); - novelRepos.addAll( - novelRepoUrls.map( - (e) => Repo( - name: repoName, - jsonUrl: e, - website: repoUrl, - ), - ), - ); - ref - .read( - extensionsRepoStateProvider( - ItemType.novel, - ).notifier, - ) - .set(novelRepos); + .read(extensionsRepoStateProvider(type).notifier) + .set(updated); } + + addRepos(ItemType.manga, mangaRepoUrls); + addRepos(ItemType.anime, animeRepoUrls); + addRepos(ItemType.novel, novelRepoUrls); botToast(l10n.repo_added); }, ), diff --git a/lib/modules/more/settings/appearance/providers/theme_provider.dart b/lib/modules/more/settings/appearance/providers/theme_provider.dart new file mode 100644 index 00000000..a5273051 --- /dev/null +++ b/lib/modules/more/settings/appearance/providers/theme_provider.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'blend_level_state_provider.dart'; +import 'flex_scheme_color_state_provider.dart'; +import 'pure_black_dark_mode_state_provider.dart'; +import 'app_font_family.dart'; + +/// Provides the light theme for the app, recomputed only when +/// flex scheme colors, blend level, or font family change. +final lightThemeProvider = Provider((ref) { + final colors = ref.watch(flexSchemeColorStateProvider); + final blendLevel = ref.watch(blendLevelStateProvider).toInt(); + final fontFamily = ref.watch(appFontFamilyProvider); + + return FlexThemeData.light( + colors: colors, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: blendLevel, + appBarOpacity: 0.00, + subThemesData: const FlexSubThemesData( + blendOnLevel: 10, + thinBorderWidth: 2.0, + unselectedToggleIsColored: true, + inputDecoratorRadius: 24.0, + chipRadius: 24.0, + ), + useMaterial3ErrorColors: true, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + useMaterial3: true, + fontFamily: fontFamily, + ); +}); + +/// Provides the dark theme for the app, recomputed only when +/// flex scheme colors, blend level, font family, or pure-black toggle change. +final darkThemeProvider = Provider((ref) { + final colors = ref.watch(flexSchemeColorStateProvider); + final blendLevel = ref.watch(blendLevelStateProvider).toInt(); + final fontFamily = ref.watch(appFontFamilyProvider); + final pureBlack = ref.watch(pureBlackDarkModeStateProvider); + + return FlexThemeData.dark( + colors: colors, + surfaceMode: FlexSurfaceMode.level, + blendLevel: blendLevel, + appBarOpacity: 0.00, + scaffoldBackground: pureBlack ? Colors.black : null, + subThemesData: const FlexSubThemesData( + blendOnLevel: 10, + thinBorderWidth: 2.0, + unselectedToggleIsColored: true, + inputDecoratorRadius: 24.0, + chipRadius: 24.0, + ), + useMaterial3ErrorColors: true, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + useMaterial3: true, + fontFamily: fontFamily, + ); +}); From dabd0da20620c4b795c2e8ee0295864167eefd24 Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Tue, 22 Apr 2025 04:55:46 +0200 Subject: [PATCH 09/10] App now adjusts theme without scheduler Flutter already knows how to switch the theme automatically. No need to listen to onPlatformBrightnessChanged. This works on cold start. Works when app is running and system theme changes. And works when backup was done on different system theme and restore is being done on different system theme. --- lib/main.dart | 48 ++++++++++++------------------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 38c4d89c..699bafd4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,6 @@ import 'package:bot_toast/bot_toast.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/date_symbol_data_local.dart'; @@ -82,52 +81,29 @@ class _MyAppState extends ConsumerState { _initDeepLinks(); WidgetsBinding.instance.addPostFrameCallback((_) { - checkAndClearCache(); - handleThemeSync(); - }); - } - - void checkAndClearCache() { - if (ref.read(clearChapterCacheOnAppLaunchStateProvider)) { - ref - .read(totalChapterCacheSizeStateProvider.notifier) - .clearCache(showToast: false); - } - } - - void handleThemeSync() { - // Check if System theme has changed since last app start and adjust - if (ref.read(followSystemThemeStateProvider)) { - var brightness = - WidgetsBinding.instance.platformDispatcher.platformBrightness; - if (brightness == Brightness.light) { - ref.read(themeModeStateProvider.notifier).setLightTheme(); - } else { - ref.read(themeModeStateProvider.notifier).setDarkTheme(); + if (ref.read(clearChapterCacheOnAppLaunchStateProvider)) { + ref + .read(totalChapterCacheSizeStateProvider.notifier) + .clearCache(showToast: false); } - // Listen to System theme changes and adjust accordingly - final dispatcher = SchedulerBinding.instance.platformDispatcher; - dispatcher.onPlatformBrightnessChanged = () { - var newBrightness = dispatcher.platformBrightness; - if (newBrightness == Brightness.light) { - ref.read(themeModeStateProvider.notifier).setLightTheme(); - } else { - ref.read(themeModeStateProvider.notifier).setDarkTheme(); - } - }; - } + }); } @override Widget build(BuildContext context) { - final isDarkTheme = ref.watch(themeModeStateProvider); + final followSystem = ref.watch(followSystemThemeStateProvider); + final forcedDark = ref.watch(themeModeStateProvider); + final themeMode = + followSystem + ? ThemeMode.system + : (forcedDark ? ThemeMode.dark : ThemeMode.light); final locale = ref.watch(l10nLocaleStateProvider); final router = ref.watch(routerProvider); return MaterialApp.router( theme: ref.watch(lightThemeProvider), darkTheme: ref.watch(darkThemeProvider), - themeMode: isDarkTheme ? ThemeMode.dark : ThemeMode.light, + themeMode: themeMode, debugShowCheckedModeBanner: false, locale: locale, localizationsDelegates: AppLocalizations.localizationsDelegates, From 2446756552e69a1fc0b013f069da1864b9c922db Mon Sep 17 00:00:00 2001 From: Enbiya Olgun <78034913+NBA2K1@users.noreply.github.com> Date: Tue, 22 Apr 2025 04:57:52 +0200 Subject: [PATCH 10/10] moved super() call of initState to top of method --- lib/modules/anime/anime_player_view.dart | 2 +- lib/modules/anime/widgets/custom_seekbar.dart | 2 +- lib/modules/anime/widgets/subtitle_view.dart | 2 +- lib/modules/browse/extension/edit_code.dart | 2 +- lib/modules/browse/global_search/global_search_screen.dart | 2 +- lib/modules/history/history_screen.dart | 2 +- lib/modules/library/widgets/list_tile_manga_category.dart | 2 +- lib/modules/manga/detail/manga_detail_main.dart | 2 +- lib/modules/manga/detail/manga_detail_view.dart | 2 +- lib/modules/manga/detail/widgets/migrate_screen.dart | 2 +- lib/modules/manga/detail/widgets/tracker_search_widget.dart | 2 +- lib/modules/manga/detail/widgets/tracker_widget.dart | 2 +- lib/modules/manga/reader/double_columm_view_center.dart | 3 +-- lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart | 2 +- lib/modules/more/categories/categories_screen.dart | 3 +-- .../more/settings/track/manage_trackers/manage_trackers.dart | 2 +- .../more/settings/track/manage_trackers/tracking_detail.dart | 3 +-- lib/modules/updates/updates_screen.dart | 2 +- lib/modules/webview/webview.dart | 2 +- lib/modules/widgets/custom_draggable_tabbar.dart | 2 +- 20 files changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/modules/anime/anime_player_view.dart b/lib/modules/anime/anime_player_view.dart index a62b2818..951b5b29 100644 --- a/lib/modules/anime/anime_player_view.dart +++ b/lib/modules/anime/anime_player_view.dart @@ -305,6 +305,7 @@ class _AnimeStreamPageState extends riv.ConsumerState @override void initState() { + super.initState(); _currentPositionSub; _currentTotalDurationSub; _completed; @@ -332,7 +333,6 @@ class _AnimeStreamPageState extends riv.ConsumerState _setPlaybackSpeed(ref.read(defaultPlayBackSpeedStateProvider)); _initAniSkip(); }); - super.initState(); } Future _loadAndroidFont() async { diff --git a/lib/modules/anime/widgets/custom_seekbar.dart b/lib/modules/anime/widgets/custom_seekbar.dart index 1215851a..5f923641 100644 --- a/lib/modules/anime/widgets/custom_seekbar.dart +++ b/lib/modules/anime/widgets/custom_seekbar.dart @@ -31,6 +31,7 @@ class CustomSeekBarState extends State { @override void initState() { + super.initState(); player.stream.position.listen((event) { if (mounted) { setState(() { @@ -55,7 +56,6 @@ class CustomSeekBarState extends State { position = player.state.position; duration = player.state.duration; buffer = player.state.buffer; - super.initState(); } final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux; diff --git a/lib/modules/anime/widgets/subtitle_view.dart b/lib/modules/anime/widgets/subtitle_view.dart index 625e2b7d..7bd379bd 100644 --- a/lib/modules/anime/widgets/subtitle_view.dart +++ b/lib/modules/anime/widgets/subtitle_view.dart @@ -40,12 +40,12 @@ class _CustomSubtitleViewState extends ConsumerState { @override void initState() { + super.initState(); subscription = widget.controller.player.stream.subtitle.listen((value) { setState(() { subtitle = value; }); }); - super.initState(); } @override diff --git a/lib/modules/browse/extension/edit_code.dart b/lib/modules/browse/extension/edit_code.dart index 8de1905d..4b6ef767 100644 --- a/lib/modules/browse/extension/edit_code.dart +++ b/lib/modules/browse/extension/edit_code.dart @@ -63,6 +63,7 @@ class _CodeEditorPageState extends ConsumerState { final _scrollController = ScrollController(); @override void initState() { + super.initState(); _controller.text = source?.sourceCode ?? ""; useLogger = true; _logStreamController.stream.asBroadcastStream().listen((event) async { @@ -72,7 +73,6 @@ class _CodeEditorPageState extends ConsumerState { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } catch (_) {} }); - super.initState(); } List filters = []; diff --git a/lib/modules/browse/global_search/global_search_screen.dart b/lib/modules/browse/global_search/global_search_screen.dart index 37179630..a7affc27 100644 --- a/lib/modules/browse/global_search/global_search_screen.dart +++ b/lib/modules/browse/global_search/global_search_screen.dart @@ -117,8 +117,8 @@ class SourceSearchScreen extends StatefulWidget { class _SourceSearchScreenState extends State { @override void initState() { - _init(); super.initState(); + _init(); } String _errorMessage = ""; diff --git a/lib/modules/history/history_screen.dart b/lib/modules/history/history_screen.dart index 426534b0..2bd57fa0 100644 --- a/lib/modules/history/history_screen.dart +++ b/lib/modules/history/history_screen.dart @@ -44,10 +44,10 @@ class _HistoryScreenState extends ConsumerState @override void initState() { + super.initState(); _tabBarController = TabController(length: tabs, vsync: this); _tabBarController.animateTo(0); _tabBarController.addListener(tabListener); - super.initState(); } final _textEditingController = TextEditingController(); diff --git a/lib/modules/library/widgets/list_tile_manga_category.dart b/lib/modules/library/widgets/list_tile_manga_category.dart index f612a7c5..f80736b8 100644 --- a/lib/modules/library/widgets/list_tile_manga_category.dart +++ b/lib/modules/library/widgets/list_tile_manga_category.dart @@ -25,6 +25,7 @@ class ListTileMangaCategory extends StatefulWidget { class _ListTileMangaCategoryState extends State { @override void initState() { + super.initState(); final res = widget.mangasList.where((element) { return element.categories == null @@ -32,7 +33,6 @@ class _ListTileMangaCategoryState extends State { : element.categories!.contains(widget.category.id); }).toList(); widget.res(res); - super.initState(); } @override diff --git a/lib/modules/manga/detail/manga_detail_main.dart b/lib/modules/manga/detail/manga_detail_main.dart index 9f6c3bba..49438f30 100644 --- a/lib/modules/manga/detail/manga_detail_main.dart +++ b/lib/modules/manga/detail/manga_detail_main.dart @@ -20,8 +20,8 @@ class MangaReaderDetail extends ConsumerStatefulWidget { class _MangaReaderDetailState extends ConsumerState { @override void initState() { - _init(); super.initState(); + _init(); } _init() async { diff --git a/lib/modules/manga/detail/manga_detail_view.dart b/lib/modules/manga/detail/manga_detail_view.dart index 661ff0c6..26c7053c 100644 --- a/lib/modules/manga/detail/manga_detail_view.dart +++ b/lib/modules/manga/detail/manga_detail_view.dart @@ -82,11 +82,11 @@ class _MangaDetailViewState extends ConsumerState with TickerProviderStateMixin { @override void initState() { + super.initState(); _scrollController = ScrollController()..addListener(() { ref.read(offetProvider.notifier).state = _scrollController.offset; }); - super.initState(); } final offetProvider = StateProvider((ref) => 0.0); diff --git a/lib/modules/manga/detail/widgets/migrate_screen.dart b/lib/modules/manga/detail/widgets/migrate_screen.dart index 0e9cae0a..21423567 100644 --- a/lib/modules/manga/detail/widgets/migrate_screen.dart +++ b/lib/modules/manga/detail/widgets/migrate_screen.dart @@ -93,8 +93,8 @@ class _MigrationSourceSearchScreenState extends State { @override void initState() { - _init(); super.initState(); + _init(); } String _errorMessage = ""; diff --git a/lib/modules/manga/detail/widgets/tracker_search_widget.dart b/lib/modules/manga/detail/widgets/tracker_search_widget.dart index 98039012..6445ce0a 100644 --- a/lib/modules/manga/detail/widgets/tracker_search_widget.dart +++ b/lib/modules/manga/detail/widgets/tracker_search_widget.dart @@ -29,8 +29,8 @@ class TrackerWidgetSearch extends ConsumerStatefulWidget { class _TrackerWidgetSearchState extends ConsumerState { @override initState() { - _init(); super.initState(); + _init(); } late String query = widget.track.title!.trim(); diff --git a/lib/modules/manga/detail/widgets/tracker_widget.dart b/lib/modules/manga/detail/widgets/tracker_widget.dart index 62d13a15..d86e46e4 100644 --- a/lib/modules/manga/detail/widgets/tracker_widget.dart +++ b/lib/modules/manga/detail/widgets/tracker_widget.dart @@ -35,8 +35,8 @@ class TrackerWidget extends ConsumerStatefulWidget { class _TrackerWidgetState extends ConsumerState { @override initState() { - _init(); super.initState(); + _init(); } _init() async { diff --git a/lib/modules/manga/reader/double_columm_view_center.dart b/lib/modules/manga/reader/double_columm_view_center.dart index beaceef8..42e22ba0 100644 --- a/lib/modules/manga/reader/double_columm_view_center.dart +++ b/lib/modules/manga/reader/double_columm_view_center.dart @@ -68,6 +68,7 @@ class _DoubleColummViewState extends State @override void initState() { + super.initState(); _scaleAnimationController = AnimationController( duration: _doubleTapAnimationDuration(), vsync: this, @@ -78,8 +79,6 @@ class _DoubleColummViewState extends State _animation.addListener(() { _photoViewController.scale = _animation.value; }); - - super.initState(); } void _toggleScale(Offset tapPosition) { diff --git a/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart b/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart index d5d85fb5..66b31f27 100644 --- a/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart +++ b/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart @@ -57,8 +57,8 @@ class _ChapterListWidgetState extends State { ); @override void initState() { - _jumpTo(); super.initState(); + _jumpTo(); } Future _jumpTo() async { diff --git a/lib/modules/more/categories/categories_screen.dart b/lib/modules/more/categories/categories_screen.dart index 3d07c1b2..29e7977c 100644 --- a/lib/modules/more/categories/categories_screen.dart +++ b/lib/modules/more/categories/categories_screen.dart @@ -27,10 +27,9 @@ class _CategoriesScreenState extends ConsumerState int tabs = 3; @override void initState() { + super.initState(); _tabBarController = TabController(length: tabs, vsync: this); _tabBarController.animateTo(widget.data.$2); - - super.initState(); } @override diff --git a/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart b/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart index 28cff105..f30bb0ff 100644 --- a/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart +++ b/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart @@ -19,10 +19,10 @@ class _ManageTrackersScreenState extends State { late List trackPreferences = []; @override void initState() { + super.initState(); trackPreferences = isar.trackPreferences.filter().syncIdIsNotNull().findAllSync(); // trackPreferences.insert(0, TrackPreference(syncId: -1)); - super.initState(); } @override diff --git a/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart b/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart index 30f9bfd4..65d5e6b1 100644 --- a/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart +++ b/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart @@ -22,10 +22,9 @@ class _TrackingDetailState extends State late TabController _tabBarController; @override void initState() { + super.initState(); _tabBarController = TabController(length: 2, vsync: this); _tabBarController.animateTo(0); - - super.initState(); } @override diff --git a/lib/modules/updates/updates_screen.dart b/lib/modules/updates/updates_screen.dart index bf6cbf01..660e908a 100644 --- a/lib/modules/updates/updates_screen.dart +++ b/lib/modules/updates/updates_screen.dart @@ -91,10 +91,10 @@ class _UpdatesScreenState extends ConsumerState @override void initState() { + super.initState(); _tabBarController = TabController(length: tabs, vsync: this); _tabBarController.animateTo(0); _tabBarController.addListener(tabListener); - super.initState(); } final _textEditingController = TextEditingController(); diff --git a/lib/modules/webview/webview.dart b/lib/modules/webview/webview.dart index d02123ed..18452000 100644 --- a/lib/modules/webview/webview.dart +++ b/lib/modules/webview/webview.dart @@ -28,6 +28,7 @@ class _MangaWebViewState extends ConsumerState { bool isNotWebviewWindow = false; @override void initState() { + super.initState(); if (Platform.isLinux || Platform.isWindows) { _runWebViewDesktop(); } else { @@ -35,7 +36,6 @@ class _MangaWebViewState extends ConsumerState { isNotWebviewWindow = true; }); } - super.initState(); } Webview? _desktopWebview; diff --git a/lib/modules/widgets/custom_draggable_tabbar.dart b/lib/modules/widgets/custom_draggable_tabbar.dart index debd5031..96328727 100644 --- a/lib/modules/widgets/custom_draggable_tabbar.dart +++ b/lib/modules/widgets/custom_draggable_tabbar.dart @@ -25,10 +25,10 @@ class _MeasureWidgetSizeState extends State { @override initState() { + super.initState(); WidgetsBinding.instance.addPostFrameCallback( (_) => widget.onCalculateSize(_key.currentContext?.size), ); - super.initState(); } @override