reader_menu.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import 'package:flutter/material.dart';
  2. import 'dart:async';
  3. import 'package:shuqi/public.dart';
  4. class ReaderMenu extends StatefulWidget {
  5. final List<Chapter> chapters;
  6. final int articleIndex;
  7. final VoidCallback onTap;
  8. final VoidCallback onPreviousArticle;
  9. final VoidCallback onNextArticle;
  10. final void Function(Chapter chapter) onToggleChapter;
  11. ReaderMenu({required this.chapters, required this.articleIndex, required this.onTap, required this.onPreviousArticle, required this.onNextArticle, required this.onToggleChapter});
  12. @override
  13. _ReaderMenuState createState() => _ReaderMenuState();
  14. }
  15. class _ReaderMenuState extends State<ReaderMenu> with SingleTickerProviderStateMixin {
  16. late AnimationController animationController;
  17. late Animation<double> animation;
  18. late double progressValue;
  19. bool isTipVisible = false;
  20. @override
  21. initState() {
  22. super.initState();
  23. progressValue = this.widget.articleIndex / (this.widget.chapters.length - 1);
  24. animationController = AnimationController(duration: const Duration(milliseconds: 200), vsync: this);
  25. animation = Tween(begin: 0.0, end: 1.0).animate(animationController);
  26. animation.addListener(() {
  27. setState(() {});
  28. });
  29. animationController.forward();
  30. }
  31. @override
  32. void didUpdateWidget(ReaderMenu oldWidget) {
  33. super.didUpdateWidget(oldWidget);
  34. progressValue = this.widget.articleIndex / (this.widget.chapters.length - 1);
  35. }
  36. @override
  37. void dispose() {
  38. animationController.dispose();
  39. super.dispose();
  40. }
  41. hide() {
  42. animationController.reverse();
  43. Timer(Duration(milliseconds: 200), () {
  44. this.widget.onTap();
  45. });
  46. setState(() {
  47. isTipVisible = false;
  48. });
  49. }
  50. buildTopView(BuildContext context) {
  51. return Positioned(
  52. top: -Screen.navigationBarHeight * (1 - animation.value),
  53. left: 0,
  54. right: 0,
  55. child: Container(
  56. decoration: BoxDecoration(color: SQColor.paper, boxShadow: Styles.borderShadow),
  57. height: Screen.navigationBarHeight,
  58. padding: EdgeInsets.fromLTRB(5, Screen.topSafeHeight, 5, 0),
  59. child: Row(
  60. children: <Widget>[
  61. Container(
  62. width: 44,
  63. child: GestureDetector(
  64. onTap: () {
  65. Navigator.pop(context);
  66. },
  67. child: Image.asset('assets/img/pub_back_gray.png'),
  68. ),
  69. ),
  70. Expanded(child: Container()),
  71. Container(
  72. width: 44,
  73. child: Image.asset('assets/img/read_icon_voice.png'),
  74. ),
  75. Container(
  76. width: 44,
  77. child: Image.asset('assets/img/read_icon_more.png'),
  78. ),
  79. ],
  80. ),
  81. ),
  82. );
  83. }
  84. int currentArticleIndex() {
  85. return ((this.widget.chapters.length - 1) * progressValue).toInt();
  86. }
  87. buildProgressTipView() {
  88. if (!isTipVisible) {
  89. return Container();
  90. }
  91. Chapter chapter = this.widget.chapters[currentArticleIndex()];
  92. double percentage = chapter.index / (this.widget.chapters.length - 1) * 100;
  93. return Container(
  94. decoration: BoxDecoration(color: Color(0xff00C88D), borderRadius: BorderRadius.circular(5)),
  95. margin: EdgeInsets.fromLTRB(15, 0, 15, 10),
  96. padding: EdgeInsets.all(15),
  97. child: Row(
  98. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  99. children: <Widget>[
  100. Text(chapter.title, style: TextStyle(color: Colors.white, fontSize: 16)),
  101. Text('${percentage.toStringAsFixed(1)}%', style: TextStyle(color: SQColor.lightGray, fontSize: 12)),
  102. ],
  103. ),
  104. );
  105. }
  106. previousArticle() {
  107. if (this.widget.articleIndex == 0) {
  108. Toast.show('已经是第一章了');
  109. return;
  110. }
  111. this.widget.onPreviousArticle();
  112. setState(() {
  113. isTipVisible = true;
  114. });
  115. }
  116. nextArticle() {
  117. if (this.widget.articleIndex == this.widget.chapters.length - 1) {
  118. Toast.show('已经是最后一章了');
  119. return;
  120. }
  121. this.widget.onNextArticle();
  122. setState(() {
  123. isTipVisible = true;
  124. });
  125. }
  126. buildProgressView() {
  127. return Container(
  128. padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
  129. child: Row(
  130. children: <Widget>[
  131. GestureDetector(
  132. onTap: previousArticle,
  133. child: Container(
  134. padding: EdgeInsets.all(20),
  135. child: Image.asset('assets/img/read_icon_chapter_previous.png'),
  136. ),
  137. ),
  138. Expanded(
  139. child: Slider(
  140. value: progressValue,
  141. onChanged: (double value) {
  142. setState(() {
  143. isTipVisible = true;
  144. progressValue = value;
  145. });
  146. },
  147. onChangeEnd: (double value) {
  148. Chapter chapter = this.widget.chapters[currentArticleIndex()];
  149. this.widget.onToggleChapter(chapter);
  150. },
  151. activeColor: SQColor.primary,
  152. inactiveColor: SQColor.gray,
  153. ),
  154. ),
  155. GestureDetector(
  156. onTap: nextArticle,
  157. child: Container(
  158. padding: EdgeInsets.all(20),
  159. child: Image.asset('assets/img/read_icon_chapter_next.png'),
  160. ),
  161. )
  162. ],
  163. ),
  164. );
  165. }
  166. buildBottomView() {
  167. return Positioned(
  168. bottom: -(Screen.bottomSafeHeight + 110) * (1 - animation.value),
  169. left: 0,
  170. right: 0,
  171. child: Column(
  172. children: <Widget>[
  173. buildProgressTipView(),
  174. Container(
  175. decoration: BoxDecoration(color: SQColor.paper, boxShadow: Styles.borderShadow),
  176. padding: EdgeInsets.only(bottom: Screen.bottomSafeHeight),
  177. child: Column(
  178. children: <Widget>[
  179. buildProgressView(),
  180. buildBottomMenus(),
  181. ],
  182. ),
  183. )
  184. ],
  185. ),
  186. );
  187. }
  188. buildBottomMenus() {
  189. return Row(
  190. mainAxisAlignment: MainAxisAlignment.spaceAround,
  191. children: <Widget>[
  192. buildBottomItem('目录', 'assets/img/read_icon_catalog.png'),
  193. buildBottomItem('亮度', 'assets/img/read_icon_brightness.png'),
  194. buildBottomItem('字体', 'assets/img/read_icon_font.png'),
  195. buildBottomItem('设置', 'assets/img/read_icon_setting.png'),
  196. ],
  197. );
  198. }
  199. buildBottomItem(String title, String icon) {
  200. return Container(
  201. padding: EdgeInsets.symmetric(vertical: 7),
  202. child: Column(
  203. children: <Widget>[
  204. Image.asset(icon),
  205. SizedBox(height: 5),
  206. Text(title, style: TextStyle(fontSize: fixedFontSize(12), color: SQColor.darkGray)),
  207. ],
  208. ),
  209. );
  210. }
  211. @override
  212. Widget build(BuildContext context) {
  213. return Container(
  214. child: Stack(
  215. children: <Widget>[
  216. GestureDetector(
  217. onTapDown: (_) {
  218. hide();
  219. },
  220. child: Container(color: Colors.transparent),
  221. ),
  222. buildTopView(context),
  223. buildBottomView(),
  224. ],
  225. ),
  226. );
  227. }
  228. }