diff --git a/lib/constant.dart b/lib/constant.dart new file mode 100644 index 0000000..29883ee --- /dev/null +++ b/lib/constant.dart @@ -0,0 +1,4 @@ +class HiveConstant { + static String get hiveBoxManga => "manga_box"; + static String get hiveBoxMangaInfo => "manga_box_info"; +} diff --git a/lib/main.dart b/lib/main.dart index b6822d0..e7cd43d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:mangayomi/constant.dart'; +import 'package:mangayomi/models/model_manga.dart'; import 'package:mangayomi/router/router.dart'; -void main() { +void main() async { + await Hive.initFlutter(); + Hive.registerAdapter(ModelMangaAdapter()); + await Hive.openBox( + HiveConstant.hiveBoxManga, + ); await Hive.openBox( + HiveConstant.hiveBoxMangaInfo, + ); runApp(const ProviderScope(child: MyApp())); } diff --git a/lib/providers/hive_provider.dart b/lib/providers/hive_provider.dart new file mode 100644 index 0000000..847c30a --- /dev/null +++ b/lib/providers/hive_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:mangayomi/constant.dart'; +import 'package:mangayomi/models/model_manga.dart'; + +final hiveBoxManga = Provider>((ref) { + return Hive.box(HiveConstant.hiveBoxManga); +}); + +final hiveBoxMangaInfo = Provider((ref) { + return Hive.box(HiveConstant.hiveBoxMangaInfo); +}); diff --git a/lib/router/router.dart b/lib/router/router.dart index cb2cc65..3f1f4e0 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -2,10 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:mangayomi/models/manga_type.dart'; +import 'package:mangayomi/models/model_manga.dart'; import 'package:mangayomi/views/browse/browse_screen.dart'; import 'package:mangayomi/views/general/general_screen.dart'; import 'package:mangayomi/views/history/history_screen.dart'; import 'package:mangayomi/views/library/library_screen.dart'; +import 'package:mangayomi/views/manga/detail/manga_reader_detail.dart'; import 'package:mangayomi/views/manga/home/home.dart'; import 'package:mangayomi/views/more/more_screen.dart'; import 'package:mangayomi/views/updates/updates_screen.dart'; @@ -90,6 +92,28 @@ class AsyncRouterNotifier extends ChangeNotifier { ), ); }), + GoRoute( + path: '/manga-reader/detail', + builder: (context, state) { + ModelManga? model; + + model = state.extra as ModelManga; + + return MangaReaderDetail( + modelManga: model, + ); + }, + pageBuilder: (context, state) { + ModelManga? model; + + model = state.extra as ModelManga; + + return CustomTransition( + key: state.pageKey, + child: MangaReaderDetail( + modelManga: model, + )); + }), ]; } diff --git a/lib/services/get_manga_detail.dart b/lib/services/get_manga_detail.dart index 28160c7..1611852 100644 --- a/lib/services/get_manga_detail.dart +++ b/lib/services/get_manga_detail.dart @@ -1,28 +1,30 @@ import 'dart:async'; -import 'package:http/http.dart' as http; -import 'package:html/dom.dart' as dom; +import 'dart:developer'; +import 'package:mangayomi/services/http_res_to_dom_html.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'get_manga_detail.g.dart'; class GetMangaDetailModel { List genre = []; - List detail = []; List chapterTitle = []; List chapterUrl = []; List chapterDate = []; + String? author; + String? status; String? source; String? url; String? name; - String? image; - String? synopsys; + String? imageUrl; + String? description; GetMangaDetailModel({ required this.genre, - required this.detail, + required this.author, + required this.status, required this.chapterDate, required this.chapterTitle, required this.chapterUrl, - required this.image, - required this.synopsys, + required this.imageUrl, + required this.description, required this.url, required this.name, required this.source, @@ -31,69 +33,71 @@ class GetMangaDetailModel { @riverpod Future getMangaDetail(GetMangaDetailRef ref, - {required String image, + {required String imageUrl, required String url, required String name, - String lang = '', + required String lang, required String source}) async { List genre = []; - List detail = []; + String? author; + String? status; List chapterTitle = []; List chapterUrl = []; List chapterDate = []; - String? synopsys; + source = source.toLowerCase(); + String? description; if (source == "mangahere") { - final response = await http.get(Uri.parse("http://www.mangahere.cc$url"), + final dom = await httpResToDom( + url: "http://www.mangahere.cc$url", headers: { "Referer": "https://www.mangahere.cc/", "Cookie": "isAdult=1" }); - dom.Document htmll = dom.Document.html(response.body); - if (htmll + if (dom .querySelectorAll( ' body > div > div > div.detail-info-right > p.detail-info-right-title > span.detail-info-right-title-tip') .isNotEmpty) { - final tt = htmll + final tt = dom .querySelectorAll( ' body > div > div > div.detail-info-right > p.detail-info-right-title > span.detail-info-right-title-tip') .map((e) => e.text.trim()) .toList(); - detail.add(tt[0]); + status = tt[0]; } else { - detail.add(""); + status = ""; } - if (htmll + if (dom .querySelectorAll( ' body > div > div > div.detail-info-right > p.detail-info-right-say > a') .isNotEmpty) { - final tt = htmll + final tt = dom .querySelectorAll( ' body > div > div > div.detail-info-right > p.detail-info-right-say > a') .map((e) => e.text.trim()) .toList(); - detail.add(tt[0]); + author = tt[0]; } else { - detail.add(""); + author = ""; } - if (htmll + if (dom .querySelectorAll( 'body > div > div > div.detail-info-right > p.detail-info-right-content') .isNotEmpty) { - final tt = htmll + final tt = dom .querySelectorAll( 'body > div > div > div.detail-info-right > p.detail-info-right-content') .map((e) => e.text.trim()) .toList(); - synopsys = tt.first; + description = tt.first; } - if (htmll.querySelectorAll('ul > li > a').isNotEmpty) { - final udl = htmll + if (dom.querySelectorAll('ul > li > a').isNotEmpty) { + final udl = dom .querySelectorAll('ul > li > a ') .where((e) => e.attributes.containsKey('href')) .map((e) => e.attributes['href']) @@ -103,8 +107,8 @@ Future getMangaDetail(GetMangaDetailRef ref, chapterUrl.add(ok!); } } - if (htmll.querySelectorAll('ul > li > a > div > p.title3').isNotEmpty) { - final tt = htmll + if (dom.querySelectorAll('ul > li > a > div > p.title3').isNotEmpty) { + final tt = dom .querySelectorAll('ul > li > a > div > p.title3') .map((e) => e.text.trim()) .toList(); @@ -112,8 +116,8 @@ Future getMangaDetail(GetMangaDetailRef ref, chapterTitle.add(ok); } } - if (htmll.querySelectorAll('ul > li > a > div > p.title2').isNotEmpty) { - final tt = htmll + if (dom.querySelectorAll('ul > li > a > div > p.title2').isNotEmpty) { + final tt = dom .querySelectorAll('ul > li > a > div > p.title2') .map((e) => e.text.trim()) .toList(); @@ -121,11 +125,11 @@ Future getMangaDetail(GetMangaDetailRef ref, chapterDate.add(ok); } } - if (htmll + if (dom .querySelectorAll( ' body > div > div > div.detail-info-right > p.detail-info-right-tag-list > a') .isNotEmpty) { - final tt = htmll + final tt = dom .querySelectorAll( ' body > div > div > div.detail-info-right > p.detail-info-right-tag-list > a') .map((e) => e.text.trim()) @@ -136,15 +140,18 @@ Future getMangaDetail(GetMangaDetailRef ref, } } } + return GetMangaDetailModel( - chapterDate: chapterDate, - chapterTitle: chapterTitle, - chapterUrl: chapterUrl, - detail: detail, - genre: genre, - image: image, - synopsys: synopsys, - name: name, - url: url, - source: source); + chapterDate: chapterDate, + chapterTitle: chapterTitle, + chapterUrl: chapterUrl, + status: status, + genre: genre, + author: author, + description: description, + name: name, + url: url, + source: source, + imageUrl: imageUrl, + ); } diff --git a/lib/services/get_manga_detail.g.dart b/lib/services/get_manga_detail.g.dart index 83242bb..4f51bc6 100644 --- a/lib/services/get_manga_detail.g.dart +++ b/lib/services/get_manga_detail.g.dart @@ -6,7 +6,7 @@ part of 'get_manga_detail.dart'; // RiverpodGenerator // ************************************************************************** -String _$getMangaDetailHash() => r'f8a4047fc7d1661b6abdfcfa0786e0d78a20c8ed'; +String _$getMangaDetailHash() => r'17d72a984e6428e71778ade3650b56062ee779d7'; /// Copied from Dart SDK class _SystemHash { @@ -42,14 +42,14 @@ class GetMangaDetailFamily extends Family> { /// See also [getMangaDetail]. GetMangaDetailProvider call({ - required String image, + required String imageUrl, required String url, required String name, - String lang = '', + required String lang, required String source, }) { return GetMangaDetailProvider( - image: image, + imageUrl: imageUrl, url: url, name: name, lang: lang, @@ -62,7 +62,7 @@ class GetMangaDetailFamily extends Family> { covariant GetMangaDetailProvider provider, ) { return call( - image: provider.image, + imageUrl: provider.imageUrl, url: provider.url, name: provider.name, lang: provider.lang, @@ -90,15 +90,15 @@ class GetMangaDetailProvider extends AutoDisposeFutureProvider { /// See also [getMangaDetail]. GetMangaDetailProvider({ - required this.image, + required this.imageUrl, required this.url, required this.name, - this.lang = '', + required this.lang, required this.source, }) : super.internal( (ref) => getMangaDetail( ref, - image: image, + imageUrl: imageUrl, url: url, name: name, lang: lang, @@ -115,7 +115,7 @@ class GetMangaDetailProvider GetMangaDetailFamily._allTransitiveDependencies, ); - final String image; + final String imageUrl; final String url; final String name; final String lang; @@ -124,7 +124,7 @@ class GetMangaDetailProvider @override bool operator ==(Object other) { return other is GetMangaDetailProvider && - other.image == image && + other.imageUrl == imageUrl && other.url == url && other.name == name && other.lang == lang && @@ -134,7 +134,7 @@ class GetMangaDetailProvider @override int get hashCode { var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, image.hashCode); + hash = _SystemHash.combine(hash, imageUrl.hashCode); hash = _SystemHash.combine(hash, url.hashCode); hash = _SystemHash.combine(hash, name.hashCode); hash = _SystemHash.combine(hash, lang.hashCode); diff --git a/lib/services/get_popular_manga.dart b/lib/services/get_popular_manga.dart index 8510ab5..1d12ea7 100644 --- a/lib/services/get_popular_manga.dart +++ b/lib/services/get_popular_manga.dart @@ -20,10 +20,11 @@ Future getPopularManga(GetPopularMangaRef ref, List url = []; List name = []; List image = []; - + source = source.toLowerCase(); //mangahere if (source == "mangahere") { - final dom = await httpResToDom(url: 'https://www.mangahere.cc/ranking/'); + final dom = await httpResToDom( + url: 'https://www.mangahere.cc/ranking/', headers: {}); if (dom .querySelectorAll( 'body > div.container.weekrank.ranking > div > div > ul > li > a') diff --git a/lib/services/get_popular_manga.g.dart b/lib/services/get_popular_manga.g.dart index 4649f58..1745720 100644 --- a/lib/services/get_popular_manga.g.dart +++ b/lib/services/get_popular_manga.g.dart @@ -6,7 +6,7 @@ part of 'get_popular_manga.dart'; // RiverpodGenerator // ************************************************************************** -String _$getPopularMangaHash() => r'4ab45f760fe457710bb9d09c72faef2fc09261e2'; +String _$getPopularMangaHash() => r'0b6445ff81dbbe1d337f37ed46544f5834805088'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/services/http_res_to_dom_html.dart b/lib/services/http_res_to_dom_html.dart index 0eabda7..6b15490 100644 --- a/lib/services/http_res_to_dom_html.dart +++ b/lib/services/http_res_to_dom_html.dart @@ -1,7 +1,8 @@ import 'package:html/dom.dart'; import 'package:http/http.dart' as http; -Future httpResToDom({required String url}) async { - final response = await http.get(Uri.parse(url)); +Future httpResToDom( + {required String url, required Map? headers}) async { + final response = await http.get(Uri.parse(url), headers: headers); return Document.html(response.body); } diff --git a/lib/views/browse/sources.dart b/lib/views/browse/sources.dart index 132d50e..27beb4f 100644 --- a/lib/views/browse/sources.dart +++ b/lib/views/browse/sources.dart @@ -18,7 +18,7 @@ class _SourcesScreenState extends State { onTap: () { context.push('/mangaHome', extra: MangaType( - isFullData: true, lang: 'en', source: 'mangahere')); + isFullData: true, lang: 'en', source: 'MangaHere')); }, leading: Container( height: 37, diff --git a/lib/views/manga/detail/card_sliver_app_bar.dart b/lib/views/manga/detail/card_sliver_app_bar.dart new file mode 100644 index 0000000..ae08cd1 --- /dev/null +++ b/lib/views/manga/detail/card_sliver_app_bar.dart @@ -0,0 +1,371 @@ +import 'package:blur/blur.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:draggable_home/draggable_home.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:mangayomi/models/model_manga.dart'; +import 'package:mangayomi/utils/media_query.dart'; +import 'package:mangayomi/views/manga/detail/readmore.dart'; + +class CardSliverAppBar extends ConsumerStatefulWidget { + final double? height; + final Function(bool) isExtended; + final double? appBarHeight = 65; + final Text? title; + final Widget? titleDescription; + final List? backButtonColors; + final Widget? action; + final bool? isManga; + final String? description; + final ModelManga? modelManga; + final List? genre; + final List? chapterTitle; + const CardSliverAppBar({ + super.key, + required this.isExtended, + required this.isManga, + required this.height, + required this.title, + this.titleDescription, + this.backButtonColors, + this.action, + required this.description, + required this.genre, + required this.chapterTitle, + required this.modelManga, + }); + + @override + ConsumerState createState() => _CardSliverAppBarState(); +} + +class _CardSliverAppBarState extends ConsumerState { + bool _reverse = false; + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: (notification) { + if (notification.direction == ScrollDirection.forward) { + widget.isExtended(true); + } + if (notification.direction == ScrollDirection.reverse) { + widget.isExtended(false); + } + return true; + }, + child: DraggableHome( + centerTitle: false, + leading: const BackButton(), + headerExpandedHeight: mediaHeight(context, 1) <= 600 ? 0.50 : 0.35, + title: Text(widget.modelManga!.name!), + actions: [ + IconButton( + icon: _reverse + ? const Icon(FontAwesomeIcons.arrowDownShortWide) + : const Icon(FontAwesomeIcons.arrowUpShortWide), + iconSize: 25, + onPressed: () { + setState(() { + _reverse = !_reverse; + }); + }, + ), + ], + headerWidget: Stack( + children: [ + _backgroundConstructor(), + SafeArea( + child: Stack( + children: [ + _titleConstructor(), + _cardConstructor(), + if (widget.action != null) _actionConstructor(), + _backButtonConstructor(), + _filterConstructor(), + ], + ), + ) + ], + ), + body: [ + _bodyContainer(), + _listView(), + const SizedBox( + height: 50, + ) + ], + fullyStretchable: false, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBarColor: Theme.of(context).scaffoldBackgroundColor, + ), + ); + } + + ListView _listView() { + return ListView.builder( + // controller: _scrollController, + padding: const EdgeInsets.only(top: 0), + physics: const NeverScrollableScrollPhysics(), + reverse: _reverse, + itemCount: widget.modelManga!.chapterTitle!.length, + shrinkWrap: true, + itemBuilder: (context, index) { + return ListTile( + key: ObjectKey(widget.modelManga!.chapterUrl), + onTap: () {}, + trailing: const Icon(FontAwesomeIcons.circleDown), + subtitle: widget.isManga == true + ? Text( + widget.modelManga!.chapterDate![index], + style: const TextStyle(fontSize: 13), + ) + : const SizedBox( + height: 10, + ), + title: Text( + widget.modelManga!.chapterTitle![index], + style: const TextStyle(fontSize: 15), + ), + ); + }); + } + + Widget _backButtonConstructor() { + return Positioned( + top: 7, + left: 5, + child: Column( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back), + iconSize: 25, + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + ), + ); + } + + Widget _filterConstructor() { + return Positioned( + top: 7, + right: 0, + child: IconButton( + icon: _reverse + ? const Icon(FontAwesomeIcons.arrowDownShortWide) + : const Icon(FontAwesomeIcons.arrowUpShortWide), + iconSize: 25, + onPressed: () { + setState(() { + _reverse = !_reverse; + }); + }, + ), + ); + } + + Widget _bodyContainer() { + return Container( + key: const Key("widget_body"), + color: Theme.of(context).scaffoldBackgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Wrap( + children: [ + for (var i = 0; i < widget.genre!.length; i++) + GestureDetector( + onTap: () {}, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 2, right: 2, bottom: 5), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + border: Border.all(width: 1, color: Colors.white), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 2, horizontal: 8), + child: Text( + widget.genre![i], + style: const TextStyle(fontSize: 12), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ), + // log + Column( + children: [ + //Description + if (widget.description != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: ReadMoreWidget( + text: widget.description!, + ), + ), + + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + '${widget.chapterTitle!.length.toString()} chapter(s)', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ) + ], + ), + ), + ], + ), + ], + ), + // _body + // , + ); + } + + Widget _cardConstructor() { + return Positioned( + key: const Key("widget_card"), + top: widget.height! - (widget.appBarHeight! * 1.8), + left: 20, + child: GestureDetector( + onTap: () {}, + child: SizedBox( + width: widget.appBarHeight! * 1.5, + height: widget.appBarHeight! * 2.3, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(5)), + image: DecorationImage( + image: CachedNetworkImageProvider(widget.modelManga!.imageUrl!), + fit: BoxFit.cover, + ), + ), + ), + ), + ), + ); + } + + Widget _backgroundConstructor() { + return SizedBox( + key: const Key("widget_background"), + height: widget.height! + 400, + width: MediaQuery.of(context).size.width, + // color: Colors.black, + child: Blur( + blur: 10, + blurColor: Theme.of(context).primaryColor, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(5)), + image: DecorationImage( + image: CachedNetworkImageProvider(widget.modelManga!.imageUrl!), + fit: BoxFit.cover, + ), + ), + ), + ), + ); + } + + Widget _titleConstructor() { + return Positioned( + key: const Key("widget_title"), + top: widget.height! - widget.appBarHeight!, + left: 30, + child: Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 100), + width: MediaQuery.of(context).size.width, + color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.3), + height: 70, + child: _titleDescriptionHandler(), + ), + ); + } + + Widget _titleDescriptionHandler() { + var titleContainer = Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only( + bottom: 50, + ), + child: widget.title, + ); + + var titleDescriptionContainer = Opacity( + opacity: 1.0, + child: Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only( + top: 25, + ), + child: widget.titleDescription, + ), + ); + + return Stack( + alignment: Alignment.centerLeft, + children: [ + titleContainer, + titleDescriptionContainer, + ], + ); + } + + Widget _actionConstructor() { + return Positioned( + key: const Key("widget_action"), + top: widget.height! - widget.appBarHeight! - 45, + right: 10, + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: const BorderRadius.all(Radius.circular(50)), + boxShadow: const [ + BoxShadow(color: Colors.black54, blurRadius: 3.0) + ]), + child: widget.action, + ), + const SizedBox( + width: 5, + ), + Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: const BorderRadius.all(Radius.circular(50)), + boxShadow: const [ + BoxShadow(color: Colors.black54, blurRadius: 3.0) + ]), + child: IconButton( + onPressed: () async {}, + icon: const Icon(Icons.travel_explore)), + ), + ], + )); + } +} diff --git a/lib/views/manga/detail/manga_reader_detail.dart b/lib/views/manga/detail/manga_reader_detail.dart new file mode 100644 index 0000000..d66e278 --- /dev/null +++ b/lib/views/manga/detail/manga_reader_detail.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:mangayomi/models/model_manga.dart'; +import 'package:mangayomi/providers/hive_provider.dart'; +import 'package:mangayomi/services/get_manga_detail.dart'; +import 'package:mangayomi/views/manga/detail/manga_details_view.dart'; + +class MangaReaderDetail extends ConsumerStatefulWidget { + final ModelManga modelManga; + const MangaReaderDetail({super.key, required this.modelManga}); + + @override + ConsumerState createState() => _MangaReaderDetailState(); +} + +class _MangaReaderDetailState extends ConsumerState { + bool _isFavorite = false; + @override + void initState() { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + super.initState(); + } + + @override + void dispose() { + SystemChrome.setPreferredOrientations(DeviceOrientation.values); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: RefreshIndicator( + onRefresh: () async { + if (_isFavorite) { + bool isOk = false; + ref + .watch(getMangaDetailProvider( + imageUrl: '', + lang: widget.modelManga.lang!, + name: widget.modelManga.name!, + source: widget.modelManga.source!, + url: widget.modelManga.link!) + .future) + .then((value) { + final model = ModelManga( + imageUrl: widget.modelManga.imageUrl, + name: widget.modelManga.name, + genre: widget.modelManga.genre, + author: widget.modelManga.author, + chapterDate: value.chapterDate, + chapterTitle: value.chapterTitle, + chapterUrl: value.chapterUrl, + description: widget.modelManga.description, + status: value.status, + favorite: _isFavorite, + link: widget.modelManga.link, + source: widget.modelManga.source, + lang: widget.modelManga.lang); + ref.watch(hiveBoxManga).put(widget.modelManga.link!, model); + setState(() { + isOk = true; + }); + }); + await Future.doWhile(() async { + await Future.delayed(const Duration(seconds: 1)); + if (isOk == true) { + return false; + } + return true; + }); + } + }, + child: ValueListenableBuilder>( + valueListenable: ref.watch(hiveBoxManga).listenable(), + builder: (context, value, child) { + final entries = value.values + .where((element) => element.link == widget.modelManga.link) + .toList(); + if (entries.isNotEmpty) { + return MangaDetailsView( + modelManga: entries[0], + isFavorite: (value) { + setState(() { + _isFavorite = value; + }); + }, + isManga: true, + ); + } + return MangaDetailsView( + modelManga: widget.modelManga, + isFavorite: (value) { + setState(() { + _isFavorite = value; + }); + }, + isManga: true, + ); + }, + ), + ), + ); + } +} diff --git a/lib/views/manga/detail/readmore.dart b/lib/views/manga/detail/readmore.dart new file mode 100644 index 0000000..ab24836 --- /dev/null +++ b/lib/views/manga/detail/readmore.dart @@ -0,0 +1,67 @@ +import 'package:expandable_text/expandable_text.dart'; +import 'package:flutter/material.dart'; +import 'package:mangayomi/utils/media_query.dart'; + +class ReadMoreWidget extends StatefulWidget { + const ReadMoreWidget({Key? key, required this.text}) : super(key: key); + + final String text; + + @override + ReadMoreWidgetState createState() => ReadMoreWidgetState(); +} + +class ReadMoreWidgetState extends State + with TickerProviderStateMixin { + bool expanded = false; + @override + Widget build(BuildContext context) { + return Column( + children: [ + ExpandableText( + animationDuration: const Duration(milliseconds: 500), + onExpandedChanged: (ok) { + setState(() => expanded = ok); + }, + expandOnTextTap: true, + widget.text, + expandText: '', + maxLines: 3, + expanded: false, + onPrefixTap: () { + setState(() => expanded = !expanded); + }, + linkColor: Theme.of(context).scaffoldBackgroundColor, + animation: true, + collapseOnTextTap: true, + prefixText: '', + ), + Container( + color: Theme.of(context).cardColor.withOpacity(0.15), + width: mediaWidth(context, 1), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + expanded + ? Row( + children: const [ + Text( + "Show less", + ), + Icon(Icons.keyboard_arrow_up_sharp) + ], + ) + : Row( + children: const [ + Text( + "Show more", + ), + Icon(Icons.keyboard_arrow_down_sharp) + ], + ), + ], + )) + ], + ); + } +} diff --git a/lib/views/manga/home/home.dart b/lib/views/manga/home/home.dart index b59b6c9..7a9419e 100644 --- a/lib/views/manga/home/home.dart +++ b/lib/views/manga/home/home.dart @@ -150,11 +150,11 @@ class _MangaHomeImageCardState extends ConsumerState Widget build(BuildContext context) { super.build(context); final getMangaDetail = ref.watch(getMangaDetailProvider( - source: widget.source, - image: widget.image, - name: widget.name, - url: widget.url, - )); + source: widget.source, + imageUrl: widget.image, + name: widget.name, + url: widget.url, + lang: widget.lang)); return getMangaDetail.when( data: (data) { diff --git a/lib/views/widgets/manga_image_card_widget.dart b/lib/views/widgets/manga_image_card_widget.dart index 31b4e2a..2ffa5f7 100644 --- a/lib/views/widgets/manga_image_card_widget.dart +++ b/lib/views/widgets/manga_image_card_widget.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mangayomi/models/model_manga.dart'; import 'package:mangayomi/services/get_manga_detail.dart'; import 'package:mangayomi/utils/cached_network.dart'; import 'package:mangayomi/views/widgets/bottom_text_widget.dart'; @@ -27,14 +29,32 @@ class _MangaImageCardWidgetState extends ConsumerState { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () async {}, + onTap: () async { + final modelManga = ModelManga( + imageUrl: widget.getMangaDetailModel!.imageUrl, + name: widget.getMangaDetailModel!.name, + genre: widget.getMangaDetailModel!.genre, + author: widget.getMangaDetailModel!.author, + chapterDate: widget.getMangaDetailModel!.chapterDate, + chapterTitle: widget.getMangaDetailModel!.chapterTitle, + chapterUrl: widget.getMangaDetailModel!.chapterUrl, + status: widget.getMangaDetailModel!.status, + description: widget.getMangaDetailModel!.description, + favorite: false, + link: widget.getMangaDetailModel!.url, + source: widget.getMangaDetailModel!.source, + lang: widget.lang); + if (mounted) { + context.push('/manga-reader/detail', extra: modelManga); + } + }, child: CoverViewWidget(children: [ cachedNetworkImage( headers: { "Referer": "https://www.mangahere.cc/", "Cookie": "isAdult=1" }, - imageUrl: widget.getMangaDetailModel!.image!, + imageUrl: widget.getMangaDetailModel!.imageUrl!, width: 200, height: 270, fit: BoxFit.cover), diff --git a/pubspec.lock b/pubspec.lock index 1fa8076..d23f770 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.10.0" + blur: + dependency: "direct main" + description: + name: blur + sha256: fd23f1247faee4a7d1a3efb6b7c3cea134f3b939d72e5f8d45233deb0776259f + url: "https://pub.dev" + source: hosted + version: "3.1.0" boolean_selector: dependency: transitive description: @@ -241,6 +249,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + draggable_home: + dependency: "direct main" + description: + name: draggable_home + sha256: "4fa3c71f7784bb9a0ab722bb6245e071c261dcf2a3e1e004ee681bda60cfbbbb" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + expandable_text: + dependency: "direct main" + description: + name: expandable_text + sha256: "7d03ea48af6987b20ece232678b744862aa3250d4a71e2aaf1e4af90015d76b1" + url: "https://pub.dev" + source: hosted + version: "2.3.0" fake_async: dependency: transitive description: @@ -328,6 +352,14 @@ packages: description: flutter source: sdk version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + sha256: "959ef4add147753f990b4a7c6cccb746d5792dbdc81b1cde99e62e7edb31b206" + url: "https://pub.dev" + source: hosted + version: "10.4.0" freezed_annotation: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c9fc7a2..b621f28 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,10 @@ dependencies: riverpod_annotation: ^2.0.2 html: ^0.15.2 flutter_js: ^0.6.0 + font_awesome_flutter: ^10.1.0 + blur: ^3.1.0 + draggable_home: ^1.0.4 + expandable_text: ^2.3.0 # The following adds the Cupertino Icons font to your application.