Commit aa0d236034621df896e478b34841135d7f11c341

Authored by 吴启风
1 parent 46675a89

feat:过渡页-视频环节(再来一次)

lib/pages/section/subsection/base_section/bloc.dart
... ... @@ -12,8 +12,9 @@ abstract class BaseSectionBloc<E extends BaseSectionEvent,
12 12  
13 13 bool isCompleteDialogShow = false;
14 14  
15   - // 这里可以定义一些通用的逻辑
16   - void completeSection(final VoidCallback? nextSectionTap) {
  15 + ///这里可以定义一些通用的逻辑
  16 + void sectionComplete(final VoidCallback? nextSectionTap,
  17 + {BuildContext? context}) {
17 18 // 逻辑来标记步骤为已完成
18 19 // 比如更新状态
19 20 if (isCompleteDialogShow) {
... ... @@ -21,7 +22,7 @@ abstract class BaseSectionBloc<E extends BaseSectionEvent,
21 22 }
22 23 isCompleteDialogShow = true;
23 24 showDialog(
24   - context: AppRouter.context,
  25 + context: context ?? AppRouter.context,
25 26 barrierDismissible: false,
26 27 barrierColor: Colors.black54,
27 28 builder: (BuildContext context) {
... ... @@ -33,30 +34,24 @@ abstract class BaseSectionBloc<E extends BaseSectionEvent,
33 34 child: Row(
34 35 mainAxisAlignment: MainAxisAlignment.spaceBetween, // 图片之间分配空间
35 36 children: <Widget>[
36   - // 左侧可点击的图片
37 37 Expanded(
38 38 flex: 1,
39 39 child: GestureDetector(
40 40 onTap: () {
41   - isCompleteDialogShow = false;
42   - popPage();
43 41 add(SectionAgainEvent() as E);
  42 + popPage();
44 43 },
45 44 child: Image.asset('section_finish_again'.assetPng),
46 45 ),
47 46 ),
48   - // 中间的图片
49 47 Expanded(
50 48 flex: 2,
51 49 child: Image.asset('section_finish_steve'.assetPng),
52 50 ),
53   - // 右侧可点击的图片
54 51 Expanded(
55 52 flex: 1,
56 53 child: GestureDetector(
57 54 onTap: () {
58   - // 处理右侧图片的点击事件
59   - isCompleteDialogShow = false;
60 55 popPage();
61 56 nextSectionTap!();
62 57 },
... ... @@ -68,6 +63,6 @@ abstract class BaseSectionBloc&lt;E extends BaseSectionEvent,
68 63 ),
69 64 );
70 65 },
71   - );
  66 + ).then((value) => isCompleteDialogShow = false);
72 67 }
73 68 }
... ...
lib/pages/section/subsection/base_section/state.dart
1 1 abstract class BaseSectionState {}
2 2  
3   -class SectionCompleted extends BaseSectionState {}
  3 +///环节完成事件
  4 +class SectionCompletedState extends BaseSectionState {}
  5 +
  6 +///环节再来一次事件
  7 +class SectionAgainState extends BaseSectionState {}
... ...
lib/pages/video/lookvideo/bloc/look_video_bloc.dart
... ... @@ -8,7 +8,7 @@ import &#39;package:wow_english/pages/section/subsection/base_section/state.dart&#39;;
8 8 part 'look_video_event.dart';
9 9 part 'look_video_state.dart';
10 10  
11   -class LookVideoBloc extends BaseSectionBloc<LookVideoEvent, LookVideoState> {
  11 +class LookVideoBloc extends BaseSectionBloc<BaseSectionEvent, BaseSectionState> {
12 12  
13 13 VideoPlayerController? _controller;
14 14  
... ... @@ -25,5 +25,8 @@ class LookVideoBloc extends BaseSectionBloc&lt;LookVideoEvent, LookVideoState&gt; {
25 25 on<LookVideoEvent>((event, emit) {
26 26 // TODO: implement event handler
27 27 });
  28 + on<SectionAgainEvent>((event, emit) {
  29 + emit(SectionAgainState());
  30 + });
28 31 }
29 32 }
... ...
lib/pages/video/lookvideo/bloc/look_video_event.dart
... ... @@ -2,3 +2,5 @@ part of &#39;look_video_bloc.dart&#39;;
2 2  
3 3 @immutable
4 4 abstract class LookVideoEvent extends BaseSectionEvent {}
  5 +
  6 +class RestartVideoEvent extends LookVideoEvent {}
... ...
lib/pages/video/lookvideo/look_video_page.dart
1 1 import 'package:flutter/material.dart';
2 2 import 'package:flutter_bloc/flutter_bloc.dart';
  3 +import 'package:wow_english/pages/section/subsection/base_section/state.dart';
3 4 import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart';
4 5 import 'package:wow_english/pages/video/lookvideo/widgets/video_widget.dart';
5 6  
... ... @@ -22,7 +23,7 @@ class LookVideoPage extends StatelessWidget {
22 23 }
23 24  
24 25 Widget _buildPage(BuildContext context) {
25   - return BlocBuilder<LookVideoBloc, LookVideoState>(builder: (context, state) {
  26 + return BlocBuilder<LookVideoBloc, BaseSectionState>(builder: (context, state) {
26 27 final bloc = BlocProvider.of<LookVideoBloc>(context);
27 28 return Container(
28 29 color: Colors.white,
... ...
lib/pages/video/lookvideo/widgets/video_widget.dart
... ... @@ -7,10 +7,16 @@ import &#39;package:wow_english/common/extension/string_extension.dart&#39;;
7 7 import 'package:wow_english/pages/video/lookvideo/bloc/look_video_bloc.dart';
8 8 import 'package:wow_english/route/route.dart';
9 9  
  10 +import '../../../section/subsection/base_section/event.dart';
  11 +import '../../../section/subsection/base_section/state.dart';
10 12 import 'video_opera_widget.dart';
11 13  
12 14 class VideoWidget extends StatefulWidget {
13   - const VideoWidget({super.key, this.videoUrl = '',this.typeTitle, this.courseLessonId = '', this.isTopic = false});
  15 + const VideoWidget({super.key,
  16 + this.videoUrl = '',
  17 + this.typeTitle,
  18 + this.courseLessonId = '',
  19 + this.isTopic = false});
14 20  
15 21 final String videoUrl;
16 22 final String? typeTitle;
... ... @@ -33,45 +39,68 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
33 39  
34 40 String formatDuration(Duration duration) {
35 41 String hours = duration.inHours.toString().padLeft(2, '0');
36   - String minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
37   - String seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
  42 + String minutes = duration.inMinutes.remainder(60).toString().padLeft(
  43 + 2, '0');
  44 + String seconds = duration.inSeconds.remainder(60).toString().padLeft(
  45 + 2, '0');
38 46 return "$hours:$minutes:$seconds";
39 47 }
40 48  
41 49 void _addListener() {
42 50 _controller!.addListener(() {
43   - if(_controller!.value.isInitialized) {
  51 + if (_controller!.value.isInitialized) {
44 52 if (_controller!.value.isPlaying) {
45 53 setState(() {
46   - double currentSecond = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toDouble();
47   - int totalSecond = _controller!.value.duration.inMinutes.remainder(60)*60+_controller!.value.duration.inSeconds.remainder(60);
  54 + double currentSecond =
  55 + (_controller!.value.position.inMinutes.remainder(60) * 60 +
  56 + _controller!.value.position.inSeconds.remainder(60))
  57 + .toDouble();
  58 + int totalSecond =
  59 + _controller!.value.duration.inMinutes.remainder(60) * 60 +
  60 + _controller!.value.duration.inSeconds.remainder(60);
48 61 _currentTime = formatDuration(_controller!.value.position);
49   - _playDegree = currentSecond/totalSecond;
  62 + _playDegree = currentSecond / totalSecond;
50 63 if (_playDegree > 1.0) {
51 64 _playDegree = 1.0;
52 65 }
53   - if(_playDegree < 0) {
  66 + if (_playDegree < 0) {
54 67 _playDegree = 0.0;
55 68 }
56 69 });
57   - } else if (_controller!.value.isCompleted) {
58   - context.read<LookVideoBloc>().completeSection((){
59   - String currentTime = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toString();
60   - popPage(data:{'courseLessonId':widget.courseLessonId,'currentTime':currentTime,
61   - 'nextSection':widget.isTopic});
62   - } as VoidCallback);
  70 + } else if (widget.isTopic &&
  71 + //受限于video_player库没有唯一的播放完成状态标识,发现isBuffering=false的时候暂时是安全唯一的
  72 + _controller?.value.isBuffering == false &&
  73 + _controller!.value.isCompleted &&
  74 + _controller!.value.position.inSeconds ==
  75 + _controller!.value.duration.inSeconds) {
  76 + final lookVideoBloc = context.read<LookVideoBloc>();
  77 + lookVideoBloc.sectionComplete(() {
  78 + String currentTime =
  79 + (_controller!.value.position.inMinutes.remainder(60) * 60 +
  80 + _controller!.value.position.inSeconds.remainder(60))
  81 + .toString();
  82 + popPage(data: {
  83 + 'courseLessonId': widget.courseLessonId,
  84 + 'currentTime': currentTime,
  85 + 'nextSection': widget.isTopic
  86 + });
  87 + } as VoidCallback,
  88 + againSectionTap: (() {
  89 + lookVideoBloc.add(SectionAgainEvent());
  90 + }), context: context);
63 91 }
64 92 }
65 93 });
66 94 }
67 95  
68 96 //开始倒计时
69   - void startTimer() {
70   - if(timerUtil == null) {
71   - timerUtil = TimerUtil(mInterval: 1000,mTotalTime: 1000*10);
  97 + void startTimer() {
  98 + if (timerUtil == null) {
  99 + timerUtil = TimerUtil(mInterval: 1000, mTotalTime: 1000 * 10);
72 100 timerUtil!.setOnTimerTickCallback((int tick) {
73 101 double currentTick = tick / 1000;
74   - if (currentTick.toInt() == 0) {//倒计时结束
  102 + if (currentTick.toInt() == 0) {
  103 + //倒计时结束
75 104 setState(() {
76 105 _hiddenTipView = true;
77 106 });
... ... @@ -98,8 +127,14 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
98 127 popPage();
99 128 return;
100 129 }
101   - String currentTime = (_controller!.value.position.inMinutes.remainder(60)*60+_controller!.value.position.inSeconds.remainder(60)).toString();
102   - popPage(data:{'courseLessonId':widget.courseLessonId,'currentTime':currentTime});
  130 + String currentTime =
  131 + (_controller!.value.position.inMinutes.remainder(60) * 60 +
  132 + _controller!.value.position.inSeconds.remainder(60))
  133 + .toString();
  134 + popPage(data: {
  135 + 'courseLessonId': widget.courseLessonId,
  136 + 'currentTime': currentTime
  137 + });
103 138 }
104 139 } else if (type == OperationType.playState) {
105 140 if (_controller!.value.isPlaying) {
... ... @@ -107,9 +142,7 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
107 142 } else {
108 143 _controller!.play();
109 144 }
110   - setState(() {
111   -
112   - });
  145 + setState(() {});
113 146 }
114 147 }
115 148  
... ... @@ -118,7 +151,7 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
118 151 super.initState();
119 152 Uri uri = Uri.parse(widget.videoUrl);
120 153 _controller = VideoPlayerController.networkUrl(uri)
121   - ..initialize().then((_){
  154 + ..initialize().then((_) {
122 155 startTimer();
123 156 setState(() {
124 157 _currentTime = formatDuration(_controller!.value.position);
... ... @@ -133,82 +166,93 @@ class _VideoWidgetState extends State&lt;VideoWidget&gt; {
133 166  
134 167 @override
135 168 Widget build(BuildContext context) {
136   - return GestureDetector(
137   - onTap: () {
138   - setState(() {
139   - _hiddenTipView = !_hiddenTipView;
140   - if(!_hiddenTipView) {
141   - startTimer();
142   - } else {
143   - if (timerUtil!.isActive()) {
144   - cancelTimer();
145   - }
146   - }
147   - });
  169 + return BlocListener<LookVideoBloc, BaseSectionState>(
  170 + listener: (context, state) async {
  171 + if (state is SectionAgainState) {
  172 + await _controller?.seekTo(Duration.zero);
  173 + _controller?.play();
  174 + }
148 175 },
149   - onDoubleTap: () {
150   - if(_controller!.value.isInitialized) {
151   - if (_controller!.value.isPlaying) {
152   - _controller!.pause();
153   - } else {
154   - _controller!.play();
155   - }
  176 + child: GestureDetector(
  177 + onTap: () {
156 178 setState(() {
157   -
  179 + _hiddenTipView = !_hiddenTipView;
  180 + if (!_hiddenTipView) {
  181 + startTimer();
  182 + } else {
  183 + if (timerUtil!.isActive()) {
  184 + cancelTimer();
  185 + }
  186 + }
158 187 });
159   - }
160   - },
161   - child: Center(
162   - child: _controller!.value.isInitialized ? Stack(
163   - alignment: Alignment.center,
164   - children: [
165   - SizedBox(
166   - height: double.infinity,
167   - width: double.infinity,
168   - child: AspectRatio(
169   - aspectRatio: _controller!.value.aspectRatio,
170   - child: VideoPlayer(_controller!),
171   - ),
172   - ),
173   - Offstage(
174   - offstage: _hiddenTipView,
175   - child: VideoOperaWidget(
176   - title: widget.typeTitle??'song',
177   - degree: _playDegree,
178   - totalTime: _totalTime,
179   - currentTime: _currentTime,
180   - isPlay: _controller!.value.isPlaying,
181   - actionEvent: (OperationType type) {
182   - actionType(type);
183   - },
184   - sliderChangeEvent: (double degree) {
185   - int totalSecond = _controller!.value.duration.inMinutes.remainder(60)*60+_controller!.value.duration.inSeconds.remainder(60);
186   - int positionSecond = (totalSecond * degree).toInt();
187   - _controller!.seekTo(Duration(seconds: positionSecond));
188   - },
  188 + },
  189 + onDoubleTap: () {
  190 + if (_controller!.value.isInitialized) {
  191 + if (_controller!.value.isPlaying) {
  192 + _controller!.pause();
  193 + } else {
  194 + _controller!.play();
  195 + }
  196 + setState(() {});
  197 + }
  198 + },
  199 + child: Center(
  200 + child: _controller!.value.isInitialized
  201 + ? Stack(
  202 + alignment: Alignment.center,
  203 + children: [
  204 + SizedBox(
  205 + height: double.infinity,
  206 + width: double.infinity,
  207 + child: AspectRatio(
  208 + aspectRatio: _controller!.value.aspectRatio,
  209 + child: VideoPlayer(_controller!),
  210 + ),
189 211 ),
190   - ),
191   - Offstage(
192   - offstage: _controller!.value.isPlaying,
193   - child: IconButton(
194   - onPressed: () {
195   - _controller!.play();
196   - },
197   - icon: Image.asset(
198   - 'video_stop'.assetPng,
199   - width: 70.w,
200   - height: 70.h,
  212 + Offstage(
  213 + offstage: _hiddenTipView,
  214 + child: VideoOperaWidget(
  215 + title: widget.typeTitle ?? 'song',
  216 + degree: _playDegree,
  217 + totalTime: _totalTime,
  218 + currentTime: _currentTime,
  219 + isPlay: _controller!.value.isPlaying,
  220 + actionEvent: (OperationType type) {
  221 + actionType(type);
  222 + },
  223 + sliderChangeEvent: (double degree) {
  224 + int totalSecond = _controller!
  225 + .value.duration.inMinutes
  226 + .remainder(60) *
  227 + 60 +
  228 + _controller!.value.duration.inSeconds
  229 + .remainder(60);
  230 + int positionSecond = (totalSecond * degree).toInt();
  231 + _controller!
  232 + .seekTo(Duration(seconds: positionSecond));
  233 + },
201 234 ),
202 235 ),
203   - )
204   - ],
205   - ): Container(
206   - color: Colors.white,
207   - child: Text(
208   - '视频加载中....',
209   - style: TextStyle(
210   - fontSize: 20.sp,
211   - color: Colors.black
  236 + Offstage(
  237 + offstage: _controller!.value.isPlaying,
  238 + child: IconButton(
  239 + onPressed: () {
  240 + _controller!.play();
  241 + },
  242 + icon: Image.asset(
  243 + 'video_stop'.assetPng,
  244 + width: 70.w,
  245 + height: 70.h,
  246 + ),
  247 + ),
  248 + )
  249 + ],
  250 + )
  251 + : Container(
  252 + color: Colors.white,
  253 + child: Text(
  254 + '视频加载中....',
  255 + style: TextStyle(fontSize: 20.sp, color: Colors.black),
212 256 ),
213 257 ),
214 258 ),
... ...