part of 'comic_page.dart'; abstract mixin class _ComicPageActions { void update(); ComicDetails get comic; ComicSource get comicSource => ComicSource.find(comic.sourceKey)!; History? get history; bool isLiking = false; bool isLiked = false; void likeOrUnlike() async { if (isLiking) return; isLiking = true; update(); var res = await comicSource.likeOrUnlikeComic!(comic.id, isLiked); if (res.error) { App.rootContext.showMessage(message: res.errorMessage!); } else { isLiked = !isLiked; } isLiking = false; update(); } /// whether the comic is added to local favorite bool isAddToLocalFav = false; /// whether the comic is favorite on the server bool isFavorite = false; FavoriteItem _toFavoriteItem() { var tags = []; for (var e in comic.tags.entries) { tags.addAll(e.value.map((tag) => '${e.key}:$tag')); } return FavoriteItem( id: comic.id, name: comic.title, coverPath: comic.cover, author: comic.subTitle ?? comic.uploader ?? '', type: comic.comicType, tags: tags, ); } void openFavPanel() { showSideBar( App.rootContext, _FavoritePanel( cid: comic.id, type: comic.comicType, isFavorite: isFavorite, onFavorite: (local, network) { isFavorite = network ?? isFavorite; isAddToLocalFav = local ?? isAddToLocalFav; update(); }, favoriteItem: _toFavoriteItem(), ), ); } void quickFavorite() { var folder = appdata.settings['quickFavorite']; if (folder is! String) { return; } LocalFavoritesManager().addComic( folder, _toFavoriteItem(), ); isAddToLocalFav = true; update(); App.rootContext.showMessage(message: "Added".tl); } void share() { var text = comic.title; if (comic.url != null) { text += '\n${comic.url}'; } Share.shareText(text); } /// read the comic /// /// [ep] the episode number, start from 1 /// /// [page] the page number, start from 1 void read([int? ep, int? page]) { App.rootContext .to( () => Reader( type: comic.comicType, cid: comic.id, name: comic.title, chapters: comic.chapters, initialChapter: ep, initialPage: page, history: history ?? History.fromModel(model: comic, ep: 0, page: 0), author: comic.findAuthor() ?? '', tags: comic.plainTags, ), ) .then((_) { onReadEnd(); }); } void continueRead() { var ep = history?.ep ?? 1; var page = history?.page ?? 1; read(ep, page); } void onReadEnd(); void download() async { if (LocalManager().isDownloading(comic.id, comic.comicType)) { App.rootContext.showMessage(message: "The comic is downloading".tl); return; } if (comic.chapters == null && LocalManager().isDownloaded(comic.id, comic.comicType, 0)) { App.rootContext.showMessage(message: "The comic is downloaded".tl); return; } if (comicSource.archiveDownloader != null) { bool useNormalDownload = false; List? archives; int selected = -1; bool isLoading = false; bool isGettingLink = false; await showDialog( context: App.rootContext, builder: (context) { return StatefulBuilder( builder: (context, setState) { return ContentDialog( title: "Download".tl, content: Column( mainAxisSize: MainAxisSize.min, children: [ RadioListTile( value: -1, groupValue: selected, title: Text("Normal".tl), onChanged: (v) { setState(() { selected = v!; }); }, ), ExpansionTile( title: Text("Archive".tl), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), collapsedShape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), onExpansionChanged: (b) { if (!isLoading && b && archives == null) { isLoading = true; comicSource.archiveDownloader! .getArchives(comic.id) .then((value) { if (value.success) { archives = value.data; } else { App.rootContext .showMessage(message: value.errorMessage!); } setState(() { isLoading = false; }); }); } }, children: [ if (archives == null) const ListLoadingIndicator().toCenter() else for (int i = 0; i < archives!.length; i++) RadioListTile( value: i, groupValue: selected, onChanged: (v) { setState(() { selected = v!; }); }, title: Text(archives![i].title), subtitle: Text(archives![i].description), ) ], ) ], ), actions: [ Button.filled( isLoading: isGettingLink, onPressed: () async { if (selected == -1) { useNormalDownload = true; context.pop(); return; } setState(() { isGettingLink = true; }); var res = await comicSource.archiveDownloader!.getDownloadUrl( comic.id, archives![selected].id, ); if (res.error) { App.rootContext.showMessage(message: res.errorMessage!); setState(() { isGettingLink = false; }); } else if (context.mounted) { LocalManager() .addTask(ArchiveDownloadTask(res.data, comic)); App.rootContext .showMessage(message: "Download started".tl); context.pop(); } }, child: Text("Confirm".tl), ), ], ); }, ); }, ); if (!useNormalDownload) { return; } } if (comic.chapters == null) { LocalManager().addTask(ImagesDownloadTask( source: comicSource, comicId: comic.id, comic: comic, )); } else { List? selected; var downloaded = []; var localComic = LocalManager().find(comic.id, comic.comicType); if (localComic != null) { for (int i = 0; i < comic.chapters!.length; i++) { if (localComic.downloadedChapters .contains(comic.chapters!.keys.elementAt(i))) { downloaded.add(i); } } } await showSideBar( App.rootContext, _SelectDownloadChapter( comic.chapters!.values.toList(), (v) => selected = v, downloaded, ), ); if (selected == null) return; LocalManager().addTask(ImagesDownloadTask( source: comicSource, comicId: comic.id, comic: comic, chapters: selected!.map((i) { return comic.chapters!.keys.elementAt(i); }).toList(), )); } App.rootContext.showMessage(message: "Download started".tl); update(); } void onTapTag(String tag, String namespace) { var config = comicSource.handleClickTagEvent?.call(namespace, tag) ?? { 'action': 'search', 'keyword': tag, }; var context = App.mainNavigatorKey!.currentContext!; if (config['action'] == 'search') { context.to(() => SearchResultPage( text: config['keyword'] ?? '', sourceKey: comicSource.key, options: const [], )); } else if (config['action'] == 'category') { context.to( () => CategoryComicsPage( category: config['keyword'] ?? '', categoryKey: comicSource.categoryData!.key, param: config['param'], ), ); } } void showMoreActions() { var context = App.rootContext; showMenuX( context, Offset( context.width - 16, context.padding.top, ), [ MenuEntry( icon: Icons.copy, text: "Copy Title".tl, onClick: () { Clipboard.setData(ClipboardData(text: comic.title)); context.showMessage(message: "Copied".tl); }, ), MenuEntry( icon: Icons.copy_rounded, text: "Copy ID".tl, onClick: () { Clipboard.setData(ClipboardData(text: comic.id)); context.showMessage(message: "Copied".tl); }, ), if (comic.url != null) MenuEntry( icon: Icons.link, text: "Copy URL".tl, onClick: () { Clipboard.setData(ClipboardData(text: comic.url!)); context.showMessage(message: "Copied".tl); }, ), if (comic.url != null) MenuEntry( icon: Icons.open_in_browser, text: "Open in Browser".tl, onClick: () { launchUrlString(comic.url!); }, ), ]); } void showComments() { showSideBar( App.rootContext, CommentsPage( data: comic, source: comicSource, ), ); } void starRating() { if (!comicSource.isLogged) { return; } var rating = 0.0; var isLoading = false; showDialog( context: App.rootContext, builder: (dialogContext) => StatefulBuilder( builder: (context, setState) => SimpleDialog( title: const Text("Rating"), alignment: Alignment.center, children: [ SizedBox( height: 100, child: Center( child: SizedBox( width: 210, child: Column( children: [ const SizedBox( height: 10, ), RatingWidget( padding: 2, onRatingUpdate: (value) => rating = value, value: 1, selectable: true, size: 40, ), const Spacer(), Button.filled( isLoading: isLoading, onPressed: () { setState(() { isLoading = true; }); comicSource.starRatingFunc!(comic.id, rating.round()) .then((value) { if (value.success) { App.rootContext .showMessage(message: "Success".tl); Navigator.of(dialogContext).pop(); } else { App.rootContext .showMessage(message: value.errorMessage!); setState(() { isLoading = false; }); } }); }, child: Text("Submit".tl), ) ], ), ), ), ) ], ), ), ); } }