diff --git a/assets/images/road_bg.png b/assets/images/road_bg.png new file mode 100644 index 0000000..91161ed --- /dev/null +++ b/assets/images/road_bg.png diff --git a/assets/images/voice.png b/assets/images/voice.png new file mode 100644 index 0000000..01a1aed --- /dev/null +++ b/assets/images/voice.png diff --git a/lib/home/home_page.dart b/lib/home/home_page.dart index 01d2516..285ff64 100644 --- a/lib/home/home_page.dart +++ b/lib/home/home_page.dart @@ -35,7 +35,9 @@ class _HomePageView extends StatelessWidget { } else { // Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicPic); // Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicWord); - Navigator.of(AppRouter.context).pushNamed(AppRouteName.lookVideo); + // Navigator.of(AppRouter.context).pushNamed(AppRouteName.lookVideo); + // Navigator.of(AppRouter.context).pushNamed(AppRouteName.voicePic); + Navigator.of(AppRouter.context).pushNamed(AppRouteName.voiceWord); } } diff --git a/lib/practice/voicetopic/voicepicture/bloc/voice_pic_bloc.dart b/lib/practice/voicetopic/voicepicture/bloc/voice_pic_bloc.dart new file mode 100644 index 0000000..073782d --- /dev/null +++ b/lib/practice/voicetopic/voicepicture/bloc/voice_pic_bloc.dart @@ -0,0 +1,39 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'voice_pic_event.dart'; +part 'voice_pic_state.dart'; + +class VoicePicBloc extends Bloc { + final PageController pageController; + + final int modelCount; + + int _currentPage = 0; + + int _selectItem = 0; + + int get currentPage => _currentPage + 1; + + int get selectItem => _selectItem; + VoicePicBloc(this.pageController, this.modelCount) : super(VoicePicInitial()) { + on(_pageControllerChange); + on(_selectItemLoad); + } + + @override + Future close() { + pageController.dispose(); + return super.close(); + } + + void _pageControllerChange(CurrentPageIndexChangeEvent event,Emitter emitter) async { + _currentPage = event.pageIndex; + emitter(CurrentPageIndexState()); + } + + void _selectItemLoad(SelectItemEvent event,Emitter emitter) async { + _selectItem = event.selectIndex; + emitter(SelectItemChangeState()); + } +} diff --git a/lib/practice/voicetopic/voicepicture/bloc/voice_pic_event.dart b/lib/practice/voicetopic/voicepicture/bloc/voice_pic_event.dart new file mode 100644 index 0000000..c60b951 --- /dev/null +++ b/lib/practice/voicetopic/voicepicture/bloc/voice_pic_event.dart @@ -0,0 +1,14 @@ +part of 'voice_pic_bloc.dart'; + +@immutable +abstract class VoicePicEvent {} + +class CurrentPageIndexChangeEvent extends VoicePicEvent { + final int pageIndex; + CurrentPageIndexChangeEvent(this.pageIndex); +} + +class SelectItemEvent extends VoicePicEvent { + final int selectIndex; + SelectItemEvent(this.selectIndex); +} \ No newline at end of file diff --git a/lib/practice/voicetopic/voicepicture/bloc/voice_pic_state.dart b/lib/practice/voicetopic/voicepicture/bloc/voice_pic_state.dart new file mode 100644 index 0000000..cd05460 --- /dev/null +++ b/lib/practice/voicetopic/voicepicture/bloc/voice_pic_state.dart @@ -0,0 +1,10 @@ +part of 'voice_pic_bloc.dart'; + +@immutable +abstract class VoicePicState {} + +class VoicePicInitial extends VoicePicState {} + +class CurrentPageIndexState extends VoicePicState {} + +class SelectItemChangeState extends VoicePicState {} \ No newline at end of file diff --git a/lib/practice/voicetopic/voicepicture/voice_pic_page.dart b/lib/practice/voicetopic/voicepicture/voice_pic_page.dart new file mode 100644 index 0000000..b62d285 --- /dev/null +++ b/lib/practice/voicetopic/voicepicture/voice_pic_page.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:wow_english/common/extension/string_extension.dart'; +import 'package:wow_english/practice/voicetopic/voicepicture/bloc/voice_pic_bloc.dart'; +import 'package:wow_english/practice/widgets/practice_header_widget.dart'; + +class VoicePicPage extends StatelessWidget { + const VoicePicPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => VoicePicBloc(PageController(),4), + child: _VoicePicPage(), + ); + } +} + +class _VoicePicPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state){}, + child: _voicePicView(), + ); + } + + Widget _voicePicView() => BlocBuilder( + builder: (context, state){ + return _voicePictureView(); + }); + + Widget _voicePictureView() => BlocBuilder( + buildWhen: (_,s) => s is CurrentPageIndexState, + builder: (context,state){ + final bloc = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Stack( + children: [ + Image.asset( + 'road_bg'.assetPng, + height: double.infinity, + width: double.infinity + ), + Column( + children: [ + PracticeHeaderWidget( + title: '${bloc.currentPage}/8', + onTap: (){Navigator.pop(context);}, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset('voice'.assetPng,height: 33.h,width: 30.w,), + 10.horizontalSpace, + Text( + 'yellow', + style: TextStyle( + fontSize: 20.sp, + color: const Color(0xFF333333) + ) + ) + ], + ), + 26.verticalSpace, + Expanded( + child: PageView.builder( + itemCount: 8, + scrollDirection: Axis.horizontal, + controller: bloc.pageController, + onPageChanged: (int index) { + bloc.add(CurrentPageIndexChangeEvent(index)); + }, + itemBuilder: (BuildContext context,int index){ + return _pageViewItemWidget(); + }), + ) + ], + ) + ], + ), + ); + }); + + Widget _pageViewItemWidget() => BlocBuilder( + builder: (context, state){ + final bloc = BlocProvider.of(context); + return SafeArea( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Offstage( + offstage: (bloc.modelCount < 1), + child: _decodeImageWidget(1), + ), + Offstage( + offstage: (bloc.modelCount < 2), + child: _decodeImageWidget(2), + ), + Offstage( + offstage: (bloc.modelCount < 3), + child: _decodeImageWidget(3), + ), + Offstage( + offstage: (bloc.modelCount < 4), + child: _decodeImageWidget(4), + ) + ], + ) + ], + ), + ); + }); + + Widget _decodeImageWidget(int index) => BlocBuilder( + buildWhen: (_, s) => s is SelectItemChangeState, + builder: (context,state){ + final bloc = BlocProvider.of(context); + return GestureDetector( + onTap: () => bloc.add(SelectItemEvent(index)), + child: Container( + padding: const EdgeInsets.all(4.5), + decoration: BoxDecoration( + color: bloc.selectItem == index?const Color(0xFF00B6F1):Colors.white, + borderRadius: BorderRadius.circular(15), + ), + height: 143.h, + width: 143.w, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: Border.all( + width: 1.0, + color: const Color(0xFF140C10) + ), + image: const DecorationImage( + fit: BoxFit.fitWidth, + image: NetworkImage('https://img1.baidu.com/it/u=3392591833,1640391553&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=714') + ) + ), + ), + ), + ); + }); +} \ No newline at end of file diff --git a/lib/practice/voicetopic/voiceword/bloc/voice_word_bloc.dart b/lib/practice/voicetopic/voiceword/bloc/voice_word_bloc.dart new file mode 100644 index 0000000..c768bd4 --- /dev/null +++ b/lib/practice/voicetopic/voiceword/bloc/voice_word_bloc.dart @@ -0,0 +1,41 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'voice_word_event.dart'; +part 'voice_word_state.dart'; + +class VoiceWordBloc extends Bloc { + final PageController pageController; + + final int modelCount; + + int _currentPage = 0; + + int _selectItem = 0; + + int get currentPage => _currentPage + 1; + + int get selectItem => _selectItem; + VoiceWordBloc(this.pageController, this.modelCount) : super(VoiceWordInitial()) { + on(_pageControllerChange); + on(_selectItemLoad); + } + + @override + Future close() { + pageController.dispose(); + return super.close(); + } + + void _pageControllerChange(CurrentPageIndexChangeEvent event,Emitter emitter) async { + _currentPage = event.pageIndex; + emitter(CurrentPageIndexState()); + } + + void _selectItemLoad(SelectItemEvent event,Emitter emitter) async { + _selectItem = event.selectIndex; + emitter(SelectItemChangeState()); + } +} + diff --git a/lib/practice/voicetopic/voiceword/bloc/voice_word_event.dart b/lib/practice/voicetopic/voiceword/bloc/voice_word_event.dart new file mode 100644 index 0000000..1d556a2 --- /dev/null +++ b/lib/practice/voicetopic/voiceword/bloc/voice_word_event.dart @@ -0,0 +1,14 @@ +part of 'voice_word_bloc.dart'; + +@immutable +abstract class VoiceWordEvent {} + +class CurrentPageIndexChangeEvent extends VoiceWordEvent { + final int pageIndex; + CurrentPageIndexChangeEvent(this.pageIndex); +} + +class SelectItemEvent extends VoiceWordEvent { + final int selectIndex; + SelectItemEvent(this.selectIndex); +} \ No newline at end of file diff --git a/lib/practice/voicetopic/voiceword/bloc/voice_word_state.dart b/lib/practice/voicetopic/voiceword/bloc/voice_word_state.dart new file mode 100644 index 0000000..81993db --- /dev/null +++ b/lib/practice/voicetopic/voiceword/bloc/voice_word_state.dart @@ -0,0 +1,10 @@ +part of 'voice_word_bloc.dart'; + +@immutable +abstract class VoiceWordState {} + +class VoiceWordInitial extends VoiceWordState {} + +class CurrentPageIndexState extends VoiceWordState {} + +class SelectItemChangeState extends VoiceWordState {} diff --git a/lib/practice/voicetopic/voiceword/voice_word_page.dart b/lib/practice/voicetopic/voiceword/voice_word_page.dart new file mode 100644 index 0000000..55bdbfc --- /dev/null +++ b/lib/practice/voicetopic/voiceword/voice_word_page.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:wow_english/common/extension/string_extension.dart'; +import 'package:wow_english/practice/voicetopic/voiceword/bloc/voice_word_bloc.dart'; +import 'package:wow_english/practice/widgets/practice_header_widget.dart'; + +class VoiceWordPage extends StatelessWidget { + const VoiceWordPage({super.key}); + + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => VoiceWordBloc(PageController(),4), + child: _VoiceWordPage(), + ); + } +} + +class _VoiceWordPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state){}, + child: _voiceWorView(), + ); + } + + Widget _voiceWorView() => BlocBuilder( + builder: (context, state){ + return _voiceWordView(); + }); + + Widget _voiceWordView() => BlocBuilder( + buildWhen: (_,s) => s is CurrentPageIndexState, + builder: (context,state){ + final bloc = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Stack( + children: [ + Image.asset( + 'road_bg'.assetPng, + height: double.infinity, + width: double.infinity + ), + Column( + children: [ + PracticeHeaderWidget( + title: '${bloc.currentPage}/8', + onTap: (){Navigator.pop(context);}, + ), + Image.asset('voice'.assetPng,height: 33.h,width: 30.w,), + 26.verticalSpace, + Expanded( + child: PageView.builder( + itemCount: 8, + scrollDirection: Axis.horizontal, + controller: bloc.pageController, + onPageChanged: (int index) { + bloc.add(CurrentPageIndexChangeEvent(index)); + }, + itemBuilder: (BuildContext context,int index){ + return _pageViewItemWidget(); + }), + ) + ], + ) + ], + ), + ); + }); + + Widget _pageViewItemWidget() => BlocBuilder( + builder: (context, state){ + final bloc = BlocProvider.of(context); + return SafeArea( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Offstage( + offstage: (bloc.modelCount < 1), + child: _decodeImageWidget(1), + ), + Offstage( + offstage: (bloc.modelCount < 2), + child: _decodeImageWidget(2), + ), + Offstage( + offstage: (bloc.modelCount < 3), + child: _decodeImageWidget(3), + ), + Offstage( + offstage: (bloc.modelCount < 4), + child: _decodeImageWidget(4), + ) + ], + ) + ], + ), + ); + }); + + Widget _decodeImageWidget(int index) => BlocBuilder( + buildWhen: (_, s) => s is SelectItemChangeState, + builder: (context,state){ + final bloc = BlocProvider.of(context); + return GestureDetector( + onTap: () => bloc.add(SelectItemEvent(index)), + child: Container( + width: 143.w, + height: 143.h, + padding: EdgeInsets.only(left: 13.w,right: 13.w,top: 13.h,bottom: 13.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: Border.all( + width: 1.0, + color: const Color(0xFF140C10) + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: Container( + alignment: Alignment.center, + child: Text( + 'yellow', + style: TextStyle( + fontSize: 20.sp, + color: const Color(0xFF333333) + ) + ), + ), + ), + Container( + height: 30.h, + width: double.infinity, + decoration: BoxDecoration( + color: bloc.selectItem == index?const Color(0xFF00B6F1):Colors.white, + borderRadius: BorderRadius.circular(15.r), + border: Border.all( + width: 1.5, + color: const Color(0xFF140C10) + ), + ), + alignment: Alignment.center, + child: Image.asset('choose'.assetPng), + ) + ], + ), + ), + ); + }); +} \ No newline at end of file diff --git a/lib/route/route.dart b/lib/route/route.dart index 89411be..80362aa 100644 --- a/lib/route/route.dart +++ b/lib/route/route.dart @@ -10,6 +10,8 @@ import 'package:wow_english/login/loginpage/login_page.dart'; import 'package:wow_english/login/setpwd/set_pwd_page.dart'; import 'package:wow_english/practice/chosetopic/topicpicture/topic_picture_page.dart'; import 'package:wow_english/practice/chosetopic/topicword/topic_word_page.dart'; +import 'package:wow_english/practice/voicetopic/voicepicture/voice_pic_page.dart'; +import 'package:wow_english/practice/voicetopic/voiceword/voice_word_page.dart'; import 'package:wow_english/repeatafter/repeat_after_page.dart'; import 'package:wow_english/shop/exchane/exchange_lesson_page.dart'; import 'package:wow_english/shop/exchangelist/exchange_lesson_list_page.dart'; @@ -34,6 +36,8 @@ class AppRouteName { static const String reAfter = 'reAfter'; static const String topicPic = 'topicPic'; static const String topicWord = 'topicWord'; + static const String voicePic = 'voicePic'; + static const String voiceWord = 'voiceWord'; static const String user = 'user'; static const String lookVideo = 'lookVideo'; static const String tab = '/'; @@ -76,6 +80,10 @@ class AppRouter { return CupertinoPageRoute(builder: (_) => const TopicPicturePage()); case AppRouteName.topicWord: return CupertinoPageRoute(builder: (_) => const TopicWordPage()); + case AppRouteName.voicePic: + return CupertinoPageRoute(builder: (_) => const VoicePicPage()); + case AppRouteName.voiceWord: + return CupertinoPageRoute(builder: (_) => const VoiceWordPage()); case AppRouteName.lookVideo: return CupertinoPageRoute(builder: (_) => const LookVideoPage()); case AppRouteName.setPwd: diff --git a/lib/video/lookvideo/widgets/video_opera_widget.dart b/lib/video/lookvideo/widgets/video_opera_widget.dart index 028cb6a..af07ec4 100644 --- a/lib/video/lookvideo/widgets/video_opera_widget.dart +++ b/lib/video/lookvideo/widgets/video_opera_widget.dart @@ -19,6 +19,7 @@ class VideoOperaWidget extends StatefulWidget { this.totalTime = '00:00', this.degree = 0.0, this.actionEvent, + this.sliderChangeEvent, this.isPlay = true }); //当前播放时间 @@ -28,6 +29,7 @@ class VideoOperaWidget extends StatefulWidget { final double degree; final bool isPlay; final Function(OperationType type)? actionEvent; + final Function(double degree)? sliderChangeEvent; @override State createState() { @@ -154,6 +156,7 @@ class _VideoOperaWidgetState extends State { setState(() { isSlider = false; }); + widget.sliderChangeEvent?.call(value); }, onChanged: (value) { setState(() { diff --git a/lib/video/lookvideo/widgets/video_widget.dart b/lib/video/lookvideo/widgets/video_widget.dart index 75ad5ec..1fa1642 100644 --- a/lib/video/lookvideo/widgets/video_widget.dart +++ b/lib/video/lookvideo/widgets/video_widget.dart @@ -154,6 +154,11 @@ class _VideoWidgetState extends State { actionEvent: (OperationType type) { actionType(type); }, + sliderChangeEvent: (double degree) { + int totalSecond = _controller!.value.duration.inMinutes.remainder(60)*60+_controller!.value.duration.inSeconds.remainder(60); + int positionSecond = (totalSecond * degree).toInt(); + _controller!.seekTo(Duration(seconds: positionSecond)); + }, ), ), Offstage( @@ -171,7 +176,7 @@ class _VideoWidgetState extends State { ) ], ): Container( - color: Colors.black, + color: Colors.white, ), ), );