mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-05-10 11:30:41 +00:00
Merge pull request #445 from NBA2K1/main
Fix #444 + Kitsu detail change + episode/chapter name marquee effect
This commit is contained in:
commit
00d9b8373e
30 changed files with 425 additions and 706 deletions
177
lib/main.dart
177
lib/main.dart
|
|
@ -3,31 +3,25 @@ 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_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';
|
||||
|
|
@ -37,11 +31,7 @@ late Isar isar;
|
|||
WebViewEnvironment? webViewEnvironment;
|
||||
void main(List<String> 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)) {
|
||||
|
|
@ -62,19 +52,14 @@ void main(List<String> 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<void> postLaunchInit() async {
|
||||
await StorageProvider().requestPermission();
|
||||
await StorageProvider().deleteBtDirectory();
|
||||
}
|
||||
|
||||
class MyApp extends ConsumerStatefulWidget {
|
||||
|
|
@ -87,11 +72,12 @@ class MyApp extends ConsumerStatefulWidget {
|
|||
class _MyAppState extends ConsumerState<MyApp> {
|
||||
late AppLinks _appLinks;
|
||||
StreamSubscription<Uri>? _linkSubscription;
|
||||
Uri? lastUri;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_iniDateFormatting();
|
||||
initializeDateFormatting();
|
||||
_initDeepLinks();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
|
|
@ -100,67 +86,24 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
.read(totalChapterCacheSizeStateProvider.notifier)
|
||||
.clearCache(showToast: false);
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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 followSystem = ref.watch(followSystemThemeStateProvider);
|
||||
final forcedDark = ref.watch(themeModeStateProvider);
|
||||
final themeMode =
|
||||
followSystem
|
||||
? ThemeMode.system
|
||||
: (forcedDark ? ThemeMode.dark : ThemeMode.light);
|
||||
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,
|
||||
themeMode: isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
theme: themeLight,
|
||||
theme: ref.watch(lightThemeProvider),
|
||||
darkTheme: ref.watch(darkThemeProvider),
|
||||
themeMode: themeMode,
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
|
|
@ -182,6 +125,8 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
Future<void> _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"];
|
||||
|
|
@ -190,8 +135,8 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
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) {
|
||||
|
|
@ -215,78 +160,30 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
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<String>? 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);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -305,6 +305,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentPositionSub;
|
||||
_currentTotalDurationSub;
|
||||
_completed;
|
||||
|
|
@ -332,7 +333,6 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
|||
_setPlaybackSpeed(ref.read(defaultPlayBackSpeedStateProvider));
|
||||
_initAniSkip();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _loadAndroidFont() async {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class CustomSeekBarState extends State<CustomSeekBar> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
player.stream.position.listen((event) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
|
@ -55,7 +56,6 @@ class CustomSeekBarState extends State<CustomSeekBar> {
|
|||
position = player.state.position;
|
||||
duration = player.state.duration;
|
||||
buffer = player.state.buffer;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux;
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ class _CustomSubtitleViewState extends ConsumerState<CustomSubtitleView> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
subscription = widget.controller.player.stream.subtitle.listen((value) {
|
||||
setState(() {
|
||||
subtitle = value;
|
||||
});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class _CodeEditorPageState extends ConsumerState<CodeEditorPage> {
|
|||
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<CodeEditorPage> {
|
|||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
} catch (_) {}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
List<dynamic> filters = [];
|
||||
|
|
|
|||
|
|
@ -117,8 +117,8 @@ class SourceSearchScreen extends StatefulWidget {
|
|||
class _SourceSearchScreenState extends State<SourceSearchScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
_init();
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
String _errorMessage = "";
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen>
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabBarController = TabController(length: tabs, vsync: this);
|
||||
_tabBarController.animateTo(0);
|
||||
_tabBarController.addListener(tabListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final _textEditingController = TextEditingController();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class ListTileMangaCategory extends StatefulWidget {
|
|||
class _ListTileMangaCategoryState extends State<ListTileMangaCategory> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final res =
|
||||
widget.mangasList.where((element) {
|
||||
return element.categories == null
|
||||
|
|
@ -32,7 +33,6 @@ class _ListTileMangaCategoryState extends State<ListTileMangaCategory> {
|
|||
: element.categories!.contains(widget.category.id);
|
||||
}).toList();
|
||||
widget.res(res);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class MainScreen extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _MainScreenState extends ConsumerState<MainScreen> {
|
||||
Timer? _backupTimer;
|
||||
Timer? _syncTimer;
|
||||
String getHyphenatedUpdatesLabel(String languageCode, String defaultLabel) {
|
||||
switch (languageCode) {
|
||||
case 'de':
|
||||
|
|
@ -63,26 +65,19 @@ class _MainScreenState extends ConsumerState<MainScreen> {
|
|||
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<MainScreen> {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ class MangaReaderDetail extends ConsumerStatefulWidget {
|
|||
class _MangaReaderDetailState extends ConsumerState<MangaReaderDetail> {
|
||||
@override
|
||||
void initState() {
|
||||
_init();
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
_init() async {
|
||||
|
|
|
|||
|
|
@ -82,11 +82,11 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ class _MigrationSourceSearchScreenState
|
|||
extends State<MigrationSourceSearchScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
_init();
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
String _errorMessage = "";
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ class TrackerWidgetSearch extends ConsumerStatefulWidget {
|
|||
class _TrackerWidgetSearchState extends ConsumerState<TrackerWidgetSearch> {
|
||||
@override
|
||||
initState() {
|
||||
_init();
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
late String query = widget.track.title!.trim();
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ class TrackerWidget extends ConsumerStatefulWidget {
|
|||
class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
|
||||
@override
|
||||
initState() {
|
||||
_init();
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
_init() async {
|
||||
|
|
@ -491,7 +491,7 @@ class _TrackerWidgetState extends ConsumerState<TrackerWidget> {
|
|||
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<TrackerWidget> {
|
|||
text:
|
||||
widget.trackRes.finishedReadingDate != null &&
|
||||
widget.trackRes.finishedReadingDate! >
|
||||
DateTime(1970).millisecondsSinceEpoch
|
||||
DateTime.utc(1970).millisecondsSinceEpoch
|
||||
? dateFormat(
|
||||
widget.trackRes.finishedReadingDate.toString(),
|
||||
ref: ref,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ class _DoubleColummViewState extends State<DoubleColummView>
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scaleAnimationController = AnimationController(
|
||||
duration: _doubleTapAnimationDuration(),
|
||||
vsync: this,
|
||||
|
|
@ -78,8 +79,6 @@ class _DoubleColummViewState extends State<DoubleColummView>
|
|||
_animation.addListener(() {
|
||||
_photoViewController.scale = _animation.value;
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _toggleScale(Offset tapPosition) {
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ class _MangaChapterPageGalleryState
|
|||
StreamController<double>.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!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ class _ChapterListWidgetState extends State<ChapterListWidget> {
|
|||
);
|
||||
@override
|
||||
void initState() {
|
||||
_jumpTo();
|
||||
super.initState();
|
||||
_jumpTo();
|
||||
}
|
||||
|
||||
Future<void> _jumpTo() async {
|
||||
|
|
|
|||
|
|
@ -51,9 +51,13 @@ class _CircularProgressIndicatorAnimateRotateState
|
|||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
tween: Tween<double>(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);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,10 +27,9 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
|||
int tabs = 3;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabBarController = TabController(length: tabs, vsync: this);
|
||||
_tabBarController.animateTo(widget.data.$2);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -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<ThemeData>((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<ThemeData>((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,
|
||||
);
|
||||
});
|
||||
|
|
@ -19,10 +19,10 @@ class _ManageTrackersScreenState extends State<ManageTrackersScreen> {
|
|||
late List<TrackPreference> trackPreferences = [];
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
trackPreferences =
|
||||
isar.trackPreferences.filter().syncIdIsNotNull().findAllSync();
|
||||
// trackPreferences.insert(0, TrackPreference(syncId: -1));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -22,10 +22,9 @@ class _TrackingDetailState extends State<TrackingDetail>
|
|||
late TabController _tabBarController;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabBarController = TabController(length: 2, vsync: this);
|
||||
_tabBarController.animateTo(0);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -91,10 +91,10 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabBarController = TabController(length: tabs, vsync: this);
|
||||
_tabBarController.animateTo(0);
|
||||
_tabBarController.addListener(tabListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final _textEditingController = TextEditingController();
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
|
|||
bool isNotWebviewWindow = false;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (Platform.isLinux || Platform.isWindows) {
|
||||
_runWebViewDesktop();
|
||||
} else {
|
||||
|
|
@ -35,7 +36,6 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
|
|||
isNotWebviewWindow = true;
|
||||
});
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Webview? _desktopWebview;
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ class _MeasureWidgetSizeState extends State<MeasureWidgetSize> {
|
|||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => widget.onCalculateSize(_key.currentContext?.size),
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic>;
|
||||
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<Track?> addLibManga(Track track) async {
|
||||
Future<Track?> 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<String, dynamic>;
|
||||
|
|
@ -116,52 +110,14 @@ class Kitsu extends _$Kitsu {
|
|||
return track;
|
||||
}
|
||||
|
||||
Future<Track?> 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<String, dynamic>;
|
||||
track.libraryId = int.parse(jsonData['data']['id']);
|
||||
return track;
|
||||
}
|
||||
|
||||
Future<Track> updateLibManga(Track track) async {
|
||||
final accessToken = _getAccesToken();
|
||||
Future<Track> 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<Track> 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<List<TrackSearch>> 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<Map<String, dynamic>>.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<Track?> 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<Response> 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<Track?> 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<dynamic> 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<List<TrackSearch>> 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<Map<String, dynamic>>.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<List<TrackSearch>> 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<Map<String, dynamic>>.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<Track?> 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<dynamic> 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<Track?> 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<dynamic> 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<Track?> 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<dynamic> 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<Track?> 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<dynamic> 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<String, dynamic>,
|
||||
|
|
@ -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<TrackStatus> kitsuStatusListManga = [
|
||||
TrackStatus.reading,
|
||||
List<TrackStatus> kitsuStatusList(bool isManga) => [
|
||||
isManga ? TrackStatus.reading : TrackStatus.watching,
|
||||
TrackStatus.completed,
|
||||
TrackStatus.onHold,
|
||||
TrackStatus.dropped,
|
||||
TrackStatus.planToRead,
|
||||
];
|
||||
List<TrackStatus> 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",
|
||||
|
|
|
|||
16
pubspec.lock
16
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue