mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 23:22:07 +00:00
redesign manga reader view
This commit is contained in:
parent
4bea922334
commit
299460e2be
7 changed files with 616 additions and 640 deletions
|
|
@ -1,377 +0,0 @@
|
|||
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/manga_reader.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<Color>? backButtonColors;
|
||||
final Widget? action;
|
||||
final bool? isManga;
|
||||
final String? description;
|
||||
final ModelManga? modelManga;
|
||||
final List<String>? genre;
|
||||
final List<String>? 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<CardSliverAppBar> createState() => _CardSliverAppBarState();
|
||||
}
|
||||
|
||||
class _CardSliverAppBarState extends ConsumerState<CardSliverAppBar> {
|
||||
bool _reverse = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NotificationListener<UserScrollNotification>(
|
||||
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: () {
|
||||
pushMangaReaderView(
|
||||
context: context,
|
||||
modelManga: widget.modelManga!,
|
||||
index: index);
|
||||
},
|
||||
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)),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
351
lib/views/manga/detail/manga_detail_view.dart
Normal file
351
lib/views/manga/detail/manga_detail_view.dart
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:draggable_scrollbar/draggable_scrollbar.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/manga_reader.dart';
|
||||
import 'package:mangayomi/models/model_manga.dart';
|
||||
import 'package:mangayomi/utils/cached_network.dart';
|
||||
import 'package:mangayomi/utils/media_query.dart';
|
||||
import 'package:mangayomi/views/manga/detail/readmore.dart';
|
||||
|
||||
class MangaDetailView extends ConsumerStatefulWidget {
|
||||
final Function(bool) isExtended;
|
||||
|
||||
final int listLength;
|
||||
final Widget? titleDescription;
|
||||
final List<Color>? backButtonColors;
|
||||
final Widget? action;
|
||||
final ModelManga? modelManga;
|
||||
const MangaDetailView({
|
||||
super.key,
|
||||
required this.isExtended,
|
||||
this.titleDescription,
|
||||
this.backButtonColors,
|
||||
this.action,
|
||||
required this.modelManga,
|
||||
required this.listLength,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<MangaDetailView> createState() => _MangaDetailViewState();
|
||||
}
|
||||
|
||||
class _MangaDetailViewState extends ConsumerState<MangaDetailView> {
|
||||
@override
|
||||
void initState() {
|
||||
_scrollController = ScrollController()
|
||||
..addListener(() {
|
||||
ref.read(offetProvider.notifier).state = _scrollController.offset;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final offetProvider = StateProvider((ref) => 0.0);
|
||||
bool _reverse = false;
|
||||
ScrollController _scrollController = ScrollController();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NotificationListener<UserScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification.direction == ScrollDirection.forward) {
|
||||
widget.isExtended(true);
|
||||
}
|
||||
if (notification.direction == ScrollDirection.reverse) {
|
||||
widget.isExtended(false);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(AppBar().preferredSize.height),
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
return AppBar(
|
||||
title: ref.watch(offetProvider) > 200
|
||||
? Text(widget.modelManga!.name!)
|
||||
: null,
|
||||
backgroundColor: ref.watch(offetProvider) == 0.0
|
||||
? Colors.transparent
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
actions: [
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {},
|
||||
icon: Icon(Icons.download_outlined,
|
||||
color: Theme.of(context).hintColor)),
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () {},
|
||||
icon: Icon(Icons.filter_list_sharp,
|
||||
color: Theme.of(context).hintColor)),
|
||||
PopupMenuButton(
|
||||
color: Theme.of(context).hintColor,
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
const PopupMenuItem<int>(
|
||||
value: 0,
|
||||
child: Text("1"),
|
||||
),
|
||||
const PopupMenuItem<int>(
|
||||
value: 1,
|
||||
child: Text("2"),
|
||||
),
|
||||
const PopupMenuItem<int>(
|
||||
value: 2,
|
||||
child: Text("3"),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (value) {
|
||||
if (value == 0) {
|
||||
} else if (value == 1) {
|
||||
} else if (value == 2) {}
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
)),
|
||||
body: _listView(),
|
||||
));
|
||||
}
|
||||
|
||||
_listView() {
|
||||
return DraggableScrollbar.rrect(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
reverse: _reverse,
|
||||
itemCount: widget.listLength,
|
||||
itemBuilder: (context, index) {
|
||||
int finalIndex = index - 1;
|
||||
if (index == 0) {
|
||||
return _bodyContainer();
|
||||
}
|
||||
return ListTile(
|
||||
key: ObjectKey(widget.modelManga!.chapterUrl),
|
||||
onTap: () {
|
||||
pushMangaReaderView(
|
||||
context: context,
|
||||
modelManga: widget.modelManga!,
|
||||
index: finalIndex);
|
||||
},
|
||||
trailing: const Icon(FontAwesomeIcons.circleDown),
|
||||
subtitle: Text(
|
||||
widget.modelManga!.chapterDate![finalIndex],
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
title: Text(
|
||||
widget.modelManga!.chapterTitle![finalIndex],
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
Widget _bodyContainer() {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(top: 0, child: _backgroundConstructor()),
|
||||
Container(
|
||||
height: 300,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.6),
|
||||
Color(Theme.of(context).scaffoldBackgroundColor.value)
|
||||
],
|
||||
stops: const [0, .8],
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: AppBar().preferredSize.height,
|
||||
),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: Stack(
|
||||
children: [
|
||||
_titleConstructor(),
|
||||
_cardConstructor(),
|
||||
],
|
||||
),
|
||||
),
|
||||
_actionConstructor(),
|
||||
Container(
|
||||
key: const Key("widget_body"),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (widget.modelManga!.description != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ReadMoreWidget(
|
||||
text: widget.modelManga!.description!,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Wrap(
|
||||
children: [
|
||||
for (var i = 0;
|
||||
i < widget.modelManga!.genre!.length;
|
||||
i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 2, right: 2, bottom: 5),
|
||||
child: SizedBox(
|
||||
height: 30,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: BeveledRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(3))),
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
widget.modelManga!.genre![i],
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// log
|
||||
Column(
|
||||
children: [
|
||||
//Description
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(
|
||||
'${widget.modelManga!.chapterTitle!.length.toString()} chapter(s)',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _cardConstructor() {
|
||||
return Positioned(
|
||||
key: const Key("widget_card"),
|
||||
top: 20,
|
||||
left: 20,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: SizedBox(
|
||||
width: 65 * 1.5,
|
||||
height: 65 * 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 cachedNetworkImage(
|
||||
imageUrl: widget.modelManga!.imageUrl!,
|
||||
width: mediaWidth(context, 1),
|
||||
height: 300,
|
||||
fit: BoxFit.cover);
|
||||
}
|
||||
|
||||
Widget _titleConstructor() {
|
||||
return Positioned(
|
||||
key: const Key("widget_title"),
|
||||
top: 60,
|
||||
left: 30,
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.only(left: 100),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 70,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(widget.modelManga!.name!,
|
||||
style:
|
||||
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
widget.titleDescription!,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionConstructor() {
|
||||
return Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
widget.action!,
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
SizedBox(
|
||||
width: mediaWidth(context, 0.4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
elevation: 0),
|
||||
onPressed: () {},
|
||||
child: Column(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.travel_explore,
|
||||
size: 25,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text('WebView')
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,21 +5,19 @@ import 'package:hive_flutter/hive_flutter.dart';
|
|||
import 'package:mangayomi/models/model_manga.dart';
|
||||
import 'package:mangayomi/providers/hive_provider.dart';
|
||||
import 'package:mangayomi/utils/media_query.dart';
|
||||
import 'package:mangayomi/views/manga/detail/card_sliver_app_bar.dart';
|
||||
import 'package:mangayomi/views/manga/detail/manga_detail_view.dart';
|
||||
|
||||
final isExtended = StateProvider.autoDispose<bool>((ref) {
|
||||
return true;
|
||||
});
|
||||
|
||||
class MangaDetailsView extends ConsumerStatefulWidget {
|
||||
final bool isManga;
|
||||
final ModelManga modelManga;
|
||||
final Function(bool) isFavorite;
|
||||
const MangaDetailsView({
|
||||
super.key,
|
||||
required this.isFavorite,
|
||||
required this.modelManga,
|
||||
required this.isManga,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -56,233 +54,242 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
|
|||
Widget build(BuildContext context) {
|
||||
final manga = ref.watch(hiveBoxManga);
|
||||
return Scaffold(
|
||||
floatingActionButton: widget.isManga
|
||||
? widget.modelManga.chapterTitle!.isNotEmpty
|
||||
? ValueListenableBuilder<Box>(
|
||||
valueListenable: ref.watch(hiveBoxMangaInfo).listenable(),
|
||||
builder: (context, value, child) {
|
||||
final entries = value.get(
|
||||
"${widget.modelManga.source}/${widget.modelManga.name}-chapter_index",
|
||||
defaultValue: '');
|
||||
if (entries.isNotEmpty) {
|
||||
return Consumer(builder: (context, ref, child) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
height: 50,
|
||||
width: !ref.watch(isExtended)
|
||||
? 63
|
||||
: mediaWidth(context, 0.4),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeIn,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(20))),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
curve: Curves.easeIn,
|
||||
width: !ref.watch(isExtended)
|
||||
? 0
|
||||
: mediaWidth(context, 0.2),
|
||||
duration:
|
||||
const Duration(milliseconds: 400),
|
||||
child: Text(
|
||||
widget.modelManga.chapterTitle![
|
||||
int.parse(entries.toString())],
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.play_arrow,
|
||||
color: Colors.white,
|
||||
)
|
||||
],
|
||||
floatingActionButton: widget.modelManga.chapterTitle!.isNotEmpty
|
||||
? ValueListenableBuilder<Box>(
|
||||
valueListenable: ref.watch(hiveBoxMangaInfo).listenable(),
|
||||
builder: (context, value, child) {
|
||||
final entries = value.get(
|
||||
"${widget.modelManga.source}/${widget.modelManga.name}-chapter_index",
|
||||
defaultValue: '');
|
||||
if (entries.isNotEmpty) {
|
||||
return Consumer(builder: (context, ref, child) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
height: 50,
|
||||
width: !ref.watch(isExtended)
|
||||
? 63
|
||||
: mediaWidth(context, 0.3),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeIn,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15))),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
curve: Curves.easeIn,
|
||||
width: !ref.watch(isExtended)
|
||||
? 0
|
||||
: mediaWidth(context, 0.15),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: const Text(
|
||||
"Continue",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.play_arrow,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
return Consumer(builder: (context, ref, child) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
height: 50,
|
||||
width: !ref.watch(isExtended)
|
||||
? 60
|
||||
: mediaWidth(context, 0.25),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeIn,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15))),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
curve: Curves.easeIn,
|
||||
width: !ref.watch(isExtended) ? 0 : 30,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: const Text(
|
||||
"Read",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
return Consumer(builder: (context, ref, child) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
height: 50,
|
||||
width: !ref.watch(isExtended) ? null : null,
|
||||
duration: const Duration(microseconds: 500),
|
||||
curve: Curves.elasticOut,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20))),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
curve: Curves.elasticOut,
|
||||
width: !ref.watch(isExtended) ? 0 : null,
|
||||
duration: const Duration(microseconds: 500),
|
||||
child: Text(
|
||||
"Read",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
curve: Curves.elasticOut,
|
||||
width: !ref.watch(isExtended) ? 0 : 10,
|
||||
duration: const Duration(microseconds: 500),
|
||||
),
|
||||
const Icon(
|
||||
Icons.play_arrow,
|
||||
color: Colors.white,
|
||||
)
|
||||
],
|
||||
AnimatedContainer(
|
||||
curve: Curves.easeIn,
|
||||
width: !ref.watch(isExtended) ? 0 : 10,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
)
|
||||
: Container()
|
||||
: Container(),
|
||||
body: CardSliverAppBar(
|
||||
height: 200,
|
||||
title: Text(widget.modelManga.name!,
|
||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold)),
|
||||
titleDescription: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemCount: 2,
|
||||
itemBuilder: (context, index) {
|
||||
return index == 0
|
||||
? Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: Theme.of(context).cardColor,
|
||||
radius: 12,
|
||||
child: const Icon(
|
||||
FontAwesomeIcons.clock,
|
||||
size: 13,
|
||||
)),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Text(widget.modelManga.status!)
|
||||
],
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: Theme.of(context).cardColor,
|
||||
radius: 12,
|
||||
child: const Icon(
|
||||
FontAwesomeIcons.user,
|
||||
size: 13,
|
||||
)),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Text(widget.modelManga.author!)
|
||||
const Icon(
|
||||
Icons.play_arrow,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
body: MangaDetailView(
|
||||
titleDescription: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.modelManga.author!,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(widget.modelManga.status!),
|
||||
const Text(' • '),
|
||||
Text(widget.modelManga.source!)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
action: widget.isManga
|
||||
? ValueListenableBuilder<Box<ModelManga>>(
|
||||
valueListenable: ref.watch(hiveBoxManga).listenable(),
|
||||
builder: (context, value, child) {
|
||||
final entries = value.values
|
||||
.where(
|
||||
(element) => element.link == widget.modelManga.link)
|
||||
.toList();
|
||||
if (entries.isNotEmpty) {
|
||||
if (entries[0].favorite == true) {
|
||||
_checkFavorite(true);
|
||||
action: ValueListenableBuilder<Box<ModelManga>>(
|
||||
valueListenable: ref.watch(hiveBoxManga).listenable(),
|
||||
builder: (context, value, child) {
|
||||
final entries = value.values
|
||||
.where((element) => element.link == widget.modelManga.link)
|
||||
.toList();
|
||||
if (entries.isNotEmpty) {
|
||||
if (entries[0].favorite == true) {
|
||||
_checkFavorite(true);
|
||||
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
_setFavorite(false);
|
||||
manga.delete(widget.modelManga.link);
|
||||
},
|
||||
icon: const Icon(Icons.favorite));
|
||||
} else {
|
||||
_checkFavorite(false);
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
_setFavorite(true);
|
||||
final model = ModelManga(
|
||||
imageUrl: widget.modelManga.imageUrl,
|
||||
name: widget.modelManga.name,
|
||||
genre: widget.modelManga.genre,
|
||||
author: widget.modelManga.author,
|
||||
status: widget.modelManga.status,
|
||||
chapterDate: widget.modelManga.chapterDate,
|
||||
chapterTitle: widget.modelManga.chapterTitle,
|
||||
chapterUrl: widget.modelManga.chapterUrl,
|
||||
description: widget.modelManga.description,
|
||||
favorite: true,
|
||||
link: widget.modelManga.link,
|
||||
source: widget.modelManga.source,
|
||||
lang: widget.modelManga.lang);
|
||||
manga.put(widget.modelManga.link, model);
|
||||
},
|
||||
icon: const Icon(Icons.favorite_border_rounded));
|
||||
}
|
||||
}
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
_setFavorite(true);
|
||||
final model = ModelManga(
|
||||
imageUrl: widget.modelManga.imageUrl,
|
||||
name: widget.modelManga.name,
|
||||
genre: widget.modelManga.genre,
|
||||
author: widget.modelManga.author,
|
||||
status: widget.modelManga.status,
|
||||
chapterDate: widget.modelManga.chapterDate,
|
||||
chapterTitle: widget.modelManga.chapterTitle,
|
||||
chapterUrl: widget.modelManga.chapterUrl,
|
||||
description: widget.modelManga.description,
|
||||
favorite: true,
|
||||
link: widget.modelManga.link,
|
||||
source: widget.modelManga.source,
|
||||
lang: widget.modelManga.lang);
|
||||
manga.put(widget.modelManga.link, model);
|
||||
},
|
||||
icon: const Icon(Icons.favorite_border_rounded));
|
||||
return SizedBox(
|
||||
width: mediaWidth(context, 0.4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
elevation: 0),
|
||||
onPressed: () {
|
||||
_setFavorite(false);
|
||||
manga.delete(widget.modelManga.link);
|
||||
},
|
||||
child: Column(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
size: 25,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text('In library')
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
_checkFavorite(false);
|
||||
return SizedBox(
|
||||
width: mediaWidth(context, 0.4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
elevation: 0),
|
||||
onPressed: () {
|
||||
_setFavorite(true);
|
||||
final model = ModelManga(
|
||||
imageUrl: widget.modelManga.imageUrl,
|
||||
name: widget.modelManga.name,
|
||||
genre: widget.modelManga.genre,
|
||||
author: widget.modelManga.author,
|
||||
status: widget.modelManga.status,
|
||||
chapterDate: widget.modelManga.chapterDate,
|
||||
chapterTitle: widget.modelManga.chapterTitle,
|
||||
chapterUrl: widget.modelManga.chapterUrl,
|
||||
description: widget.modelManga.description,
|
||||
favorite: true,
|
||||
link: widget.modelManga.link,
|
||||
source: widget.modelManga.source,
|
||||
lang: widget.modelManga.lang);
|
||||
manga.put(widget.modelManga.link, model);
|
||||
},
|
||||
child: Column(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.favorite_border_rounded,
|
||||
size: 25,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text('Add to library')
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return SizedBox(
|
||||
width: mediaWidth(context, 0.4),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
elevation: 0),
|
||||
onPressed: () {
|
||||
_setFavorite(true);
|
||||
final model = ModelManga(
|
||||
imageUrl: widget.modelManga.imageUrl,
|
||||
name: widget.modelManga.name,
|
||||
genre: widget.modelManga.genre,
|
||||
author: widget.modelManga.author,
|
||||
status: widget.modelManga.status,
|
||||
chapterDate: widget.modelManga.chapterDate,
|
||||
chapterTitle: widget.modelManga.chapterTitle,
|
||||
chapterUrl: widget.modelManga.chapterUrl,
|
||||
description: widget.modelManga.description,
|
||||
favorite: true,
|
||||
link: widget.modelManga.link,
|
||||
source: widget.modelManga.source,
|
||||
lang: widget.modelManga.lang);
|
||||
manga.put(widget.modelManga.link, model);
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
chapterTitle: widget.modelManga.chapterTitle,
|
||||
genre: widget.modelManga.genre,
|
||||
isManga: widget.isManga,
|
||||
child: Column(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.favorite_border_rounded,
|
||||
size: 25,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text('Add to library')
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
modelManga: widget.modelManga,
|
||||
description: widget.modelManga.description,
|
||||
listLength: widget.modelManga.chapterUrl!.length + 1,
|
||||
isExtended: (value) {
|
||||
ref.read(isExtended.notifier).update((state) => value);
|
||||
ref.read(isExtended.notifier).state = value;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ class _MangaReaderDetailState extends ConsumerState<MangaReaderDetail> {
|
|||
_isFavorite = value;
|
||||
});
|
||||
},
|
||||
isManga: true,
|
||||
);
|
||||
}
|
||||
return MangaDetailsView(
|
||||
|
|
@ -100,7 +99,6 @@ class _MangaReaderDetailState extends ConsumerState<MangaReaderDetail> {
|
|||
_isFavorite = value;
|
||||
});
|
||||
},
|
||||
isManga: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -18,49 +18,39 @@ class ReadMoreWidgetState extends State<ReadMoreWidget>
|
|||
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: '',
|
||||
Stack(
|
||||
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: '',
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: expanded
|
||||
? const Icon(Icons.keyboard_arrow_up_sharp)
|
||||
: const Icon(Icons.keyboard_arrow_down_sharp))
|
||||
],
|
||||
),
|
||||
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)
|
||||
],
|
||||
),
|
||||
],
|
||||
))
|
||||
color: Theme.of(context).cardColor.withOpacity(0.15),
|
||||
width: mediaWidth(context, 1),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,6 +257,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
draggable_scrollbar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: draggable_scrollbar
|
||||
sha256: a906e27fc1ee056e2942d66989dd0cb5b70a361e7d44af566cfa1b584054eac3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
expandable_text:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -42,14 +42,13 @@ dependencies:
|
|||
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
|
||||
flex_color_scheme: ^7.0.0
|
||||
scrollable_positioned_list: ^0.3.5
|
||||
extended_image: ^7.0.2
|
||||
photo_view: ^0.14.0
|
||||
modal_bottom_sheet: ^3.0.0-pre
|
||||
draggable_scrollbar: ^0.1.0
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
|
|
|
|||
Loading…
Reference in a new issue