From 60e47f7c9f6568ae7c18cd87b81321790caec725 Mon Sep 17 00:00:00 2001 From: lcy <2503978335@qq.com> Date: Tue, 13 Jun 2023 23:28:14 +0800 Subject: [PATCH] feat:课程选择功能 --- assets/images/back.png | Bin 0 -> 10855 bytes lib/common/widgets/we_app_bar.dart | 21 ++++++++++++++++++++- lib/home/bloc/home_bloc.dart | 15 +++++++++++++++ lib/home/bloc/home_event.dart | 4 ++++ lib/home/bloc/home_state.dart | 6 ++++++ lib/home/home_page.dart | 48 ++++++++++++++++++++++++++++++++++++++++-------- lib/home/widgets/home_tab_header_widget.dart | 40 +++++++++++++++++++++++++++++++++++----- lib/lessons/bloc/lesson_bloc.dart | 33 +++++++++++++++++++++++++++++++++ lib/lessons/bloc/lesson_event.dart | 9 +++++++++ lib/lessons/bloc/lesson_state.dart | 8 ++++++++ lib/lessons/lesson_page.dart | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/lessons/widgets/lesson_item_widget.dart | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/login/loginpage/login_page.dart | 1 + lib/route/route.dart | 4 ++++ 14 files changed, 443 insertions(+), 14 deletions(-) create mode 100644 assets/images/back.png create mode 100644 lib/home/bloc/home_bloc.dart create mode 100644 lib/home/bloc/home_event.dart create mode 100644 lib/home/bloc/home_state.dart create mode 100644 lib/lessons/bloc/lesson_bloc.dart create mode 100644 lib/lessons/bloc/lesson_event.dart create mode 100644 lib/lessons/bloc/lesson_state.dart create mode 100644 lib/lessons/lesson_page.dart create mode 100644 lib/lessons/widgets/lesson_item_widget.dart diff --git a/assets/images/back.png b/assets/images/back.png new file mode 100644 index 0000000..9815920 Binary files /dev/null and b/assets/images/back.png differ diff --git a/lib/common/widgets/we_app_bar.dart b/lib/common/widgets/we_app_bar.dart index a40fae7..9c4ac67 100644 --- a/lib/common/widgets/we_app_bar.dart +++ b/lib/common/widgets/we_app_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:wow_english/common/extension/string_extension.dart'; class WEAppBar extends StatelessWidget implements PreferredSizeWidget { final String? titleText; @@ -6,12 +7,16 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { final VoidCallback? onBack; final Color? backgroundColor; final PreferredSizeWidget? bottom; + final Widget? leading; + final List? actions; const WEAppBar({ this.titleText, this.centerTitle = true, this.onBack, this.backgroundColor, this.bottom, + this.leading, + this.actions, super.key}); @override @@ -19,7 +24,21 @@ class WEAppBar extends StatelessWidget implements PreferredSizeWidget { return AppBar( centerTitle: centerTitle, title: Text(titleText??''), - backgroundColor: backgroundColor??Theme.of(context).colorScheme.inversePrimary, + leading: leading??GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + alignment: Alignment.center, + child: Image.asset( + 'back'.assetPng, + height: 43, + width: 43, + ), + ), + ), + backgroundColor: backgroundColor??Colors.white, + actions: actions??[], ); } diff --git a/lib/home/bloc/home_bloc.dart b/lib/home/bloc/home_bloc.dart new file mode 100644 index 0000000..ad57422 --- /dev/null +++ b/lib/home/bloc/home_bloc.dart @@ -0,0 +1,15 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'home_event.dart'; +part 'home_state.dart'; + +class HomeBloc extends Bloc { + HomeBloc() : super(HomeInitial()) { + on((event, emit) { + // TODO: implement event handler + }); + } +} diff --git a/lib/home/bloc/home_event.dart b/lib/home/bloc/home_event.dart new file mode 100644 index 0000000..33589ea --- /dev/null +++ b/lib/home/bloc/home_event.dart @@ -0,0 +1,4 @@ +part of 'home_bloc.dart'; + +@immutable +abstract class HomeEvent {} diff --git a/lib/home/bloc/home_state.dart b/lib/home/bloc/home_state.dart new file mode 100644 index 0000000..423dfab --- /dev/null +++ b/lib/home/bloc/home_state.dart @@ -0,0 +1,6 @@ +part of 'home_bloc.dart'; + +@immutable +abstract class HomeState {} + +class HomeInitial extends HomeState {} diff --git a/lib/home/home_page.dart b/lib/home/home_page.dart index ffb649a..25ed3bb 100644 --- a/lib/home/home_page.dart +++ b/lib/home/home_page.dart @@ -1,19 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:wow_english/home/bloc/home_bloc.dart'; import 'package:wow_english/home/widgets/home_tab_header_widget.dart'; +import 'package:wow_english/route/route.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - color: Colors.white, - child: const Center( - child: Column( - children: [ - HomeTabHeaderWidget(), - ], + return BlocProvider( + create: (context) => HomeBloc(), + child: _HomePageView(), + ); + } +} + +class _HomePageView extends StatelessWidget { + void _headerActionEvent(HeaderActionType type) { + if (type == HeaderActionType.video) { + + } else if (type == HeaderActionType.phase) { + Navigator.of(AppRouter.context).pushNamed(AppRouteName.lesson); + } else if (type == HeaderActionType.listen) { + + } else { + + } + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state){}, + child: Scaffold( + body: Container( + color: Colors.white, + child: Center( + child: Column( + children: [ + HomeTabHeaderWidget( + actionTap: (HeaderActionType type) { + _headerActionEvent(type); + }, + ), + ], + ), ), ), ), diff --git a/lib/home/widgets/home_tab_header_widget.dart b/lib/home/widgets/home_tab_header_widget.dart index 32799ea..cc43ebd 100644 --- a/lib/home/widgets/home_tab_header_widget.dart +++ b/lib/home/widgets/home_tab_header_widget.dart @@ -2,8 +2,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:wow_english/common/extension/string_extension.dart'; +enum HeaderActionType { + //视频跟读 + video, + //阶段选择 + phase, + //磨耳朵 + listen, + //购买 + shop, +} + class HomeTabHeaderWidget extends StatelessWidget { - const HomeTabHeaderWidget({super.key}); + + const HomeTabHeaderWidget({super.key, this.actionTap}); + + final Function(HeaderActionType type)? actionTap; @override Widget build(BuildContext context) { @@ -42,19 +56,35 @@ class HomeTabHeaderWidget extends StatelessWidget { ) ), IconButton( - onPressed: (){}, + onPressed: (){ + if(actionTap != null) { + actionTap!(HeaderActionType.video); + } + }, icon: Image.asset('video'.assetPng) ), IconButton( - onPressed: (){}, + onPressed: (){ + if(actionTap != null) { + actionTap!(HeaderActionType.phase); + } + }, icon: Image.asset('home'.assetPng) ), IconButton( - onPressed: (){}, + onPressed: (){ + if(actionTap != null) { + actionTap!(HeaderActionType.listen); + } + }, icon: Image.asset('listen'.assetPng) ), IconButton( - onPressed: (){}, + onPressed: (){ + if(actionTap != null) { + actionTap!(HeaderActionType.shop); + } + }, icon: Image.asset('shop'.assetPng) ), ScreenUtil().bottomBarHeight.horizontalSpace, diff --git a/lib/lessons/bloc/lesson_bloc.dart b/lib/lessons/bloc/lesson_bloc.dart new file mode 100644 index 0000000..91c603f --- /dev/null +++ b/lib/lessons/bloc/lesson_bloc.dart @@ -0,0 +1,33 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'lesson_event.dart'; +part 'lesson_state.dart'; + +class LessonBloc extends Bloc { + + final int pageIndex; + + final PageController pageController; + + int _currentPageIndex = 0; + + int get currentPageIndex => _currentPageIndex; + + LessonBloc(this.pageIndex,this.pageController) : super(LessonInitial()) { + _currentPageIndex = pageIndex; + on(_pageIndexChange); + } + + void _pageIndexChange(PageViewChangeIndexEvent event,Emitter emitter) async { + _currentPageIndex = event.index; + emitter(PageIndexChangeState()); + } + + @override + Future close() { + pageController.dispose(); + return super.close(); + } +} diff --git a/lib/lessons/bloc/lesson_event.dart b/lib/lessons/bloc/lesson_event.dart new file mode 100644 index 0000000..13338d6 --- /dev/null +++ b/lib/lessons/bloc/lesson_event.dart @@ -0,0 +1,9 @@ +part of 'lesson_bloc.dart'; + +@immutable +abstract class LessonEvent {} + +class PageViewChangeIndexEvent extends LessonEvent { + final int index; + PageViewChangeIndexEvent(this.index); +} diff --git a/lib/lessons/bloc/lesson_state.dart b/lib/lessons/bloc/lesson_state.dart new file mode 100644 index 0000000..5c21ecb --- /dev/null +++ b/lib/lessons/bloc/lesson_state.dart @@ -0,0 +1,8 @@ +part of 'lesson_bloc.dart'; + +@immutable +abstract class LessonState {} + +class LessonInitial extends LessonState {} + +class PageIndexChangeState extends LessonState {} diff --git a/lib/lessons/lesson_page.dart b/lib/lessons/lesson_page.dart new file mode 100644 index 0000000..868e654 --- /dev/null +++ b/lib/lessons/lesson_page.dart @@ -0,0 +1,211 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:wow_english/common/extension/string_extension.dart'; +import 'package:wow_english/common/widgets/we_app_bar.dart'; +import 'package:wow_english/lessons/bloc/lesson_bloc.dart'; +import 'package:wow_english/lessons/widgets/lesson_item_widget.dart'; + +class LessonPage extends StatelessWidget { + const LessonPage({super.key, this.starPageIndex}); + + final int? starPageIndex; + + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => LessonBloc( + starPageIndex??0, + PageController( + initialPage: starPageIndex??0, + viewportFraction: 0.3 + ),), + child: _LessonPageView(), + ); + } +} + +class _LessonPageView extends StatelessWidget { + + final double _cardHeight = 240.0; + + final double _numItemHeight = 32.0; + + final double _scale = 0.8; + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state){}, + child: Scaffold( + appBar: WEAppBar( + actions: [ + IconButton( + icon: Image.asset('shop'.assetPng), + color: Colors.white, + onPressed: () { + EasyLoading.showToast('购买'); + }, + ) + ], + ), + body: _lessViewWidget(), + ), + ); + } + + Widget _lessViewWidget() => BlocBuilder( + builder: (context, state){ + final bloc = BlocProvider.of(context); + return Center( + child: SafeArea( + child: Column( + children: [ + SizedBox( + height: _cardHeight, + child: PageView.builder( + itemCount: 10, + controller: bloc.pageController, + onPageChanged: (int index) { + bloc.add(PageViewChangeIndexEvent(index)); + }, + itemBuilder: (context,index) => _itemTransCard(index) + ), + ), + 32.verticalSpace, + SizedBox( + height: 32, + width: 660, + child: ListView.builder( + itemCount: 10, + scrollDirection: Axis.horizontal, + itemBuilder: (BuildContext context,int index){ + return Container( + height: 32, + width: 66, + padding: const EdgeInsets.symmetric(horizontal: 5), + child: GestureDetector( + onTap: () { + if (index == bloc.currentPageIndex) { + return; + } + int mill = (index - bloc.currentPageIndex) > 0 ? 100 * (index - bloc.currentPageIndex):100 * (bloc.currentPageIndex-index); + bloc.pageController.animateToPage(index, duration: Duration(milliseconds: mill), curve: Curves.ease); + }, + child: Container( + height: bloc.currentPageIndex == index ? 32:20, + decoration: BoxDecoration( + color: bloc.currentPageIndex == index ? Colors.red:Colors.white, + border: Border.all( + width: 0.5, + color: Colors.black, + ), + ), + alignment: Alignment.center, + child: Text( + (index+1).toString(), + style: TextStyle( + color: bloc.currentPageIndex == index ? Colors.white:Colors.black + ), + ), + ), + ), + ); + }), + // child: PageView.builder( + // itemCount: 10, + // controller: bloc.pageController, + // onPageChanged: (int index) { + // bloc.add(PageViewChangeIndexEvent(index)); + // }, + // itemBuilder: (context,index) => _itemNumCard(index) + // ), + ) + ], + ), + ), + ); + }); + + Widget _itemTransCard(int index) => BlocBuilder( + builder: (context, state) { + final bloc = BlocProvider.of(context); + Matrix4 matrix4 = Matrix4.identity(); + if (index == bloc.currentPageIndex.floor()) { + //当前的item + double currScale = (1 - (bloc.currentPageIndex - index) * (1 - _scale)).toDouble(); + var currTrans = _cardHeight * (1 - currScale) / 2; + + matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) + ..setTranslationRaw(0.0, currTrans, 0.0); + } else if (index == bloc.currentPageIndex.floor() + 1) { + //右边的item + var currScale = _scale + (bloc.currentPageIndex - index + 1) * (1 - _scale); + var currTrans = _cardHeight * (1 - currScale) / 2; + + matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) + ..setTranslationRaw(0.0, currTrans, 0.0); + } else if (index == bloc.currentPageIndex - 1) { + //左边 + var currScale = (1 - (bloc.currentPageIndex - index) * (1 - _scale)).toDouble(); + var currTrans = _cardHeight * (1 - currScale) / 2; + + matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) + ..setTranslationRaw(0.0, currTrans, 0.0); + } else { + //其他,不在屏幕显示的item + matrix4 = Matrix4.diagonal3Values(1.0, _scale, 1.0) + ..setTranslationRaw(0.0, _cardHeight * (1 - _scale) / 2, 0.0); + } + + return Transform( + transform: matrix4, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: LessonItemWidget(isSelected: bloc.currentPageIndex == index), + ), + ); + }); + + Widget _itemNumCard(int index) => BlocBuilder( + builder: (context, state) { + final bloc = BlocProvider.of(context); + bool isSelected = bloc.currentPageIndex.floor() == index; + Matrix4 matrix4 = Matrix4.identity(); + if (index == bloc.currentPageIndex.floor()) { + //当前的item + double currScale = (1 - (bloc.currentPageIndex - index) * (1 - _scale)).toDouble(); + var currTrans = _numItemHeight * (1 - currScale) / 2; + + matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) + ..setTranslationRaw(0.0, currTrans, 0.0); + } else if (index == bloc.currentPageIndex.floor() + 1) { + //右边的item + var currScale = _scale + (bloc.currentPageIndex - index + 1) * (1 - _scale); + var currTrans = _numItemHeight * (1 - currScale) / 2; + + matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) + ..setTranslationRaw(0.0, currTrans, 0.0); + } else if (index == bloc.currentPageIndex - 1) { + //左边 + var currScale = (1 - (bloc.currentPageIndex - index) * (1 - _scale)).toDouble(); + var currTrans = _numItemHeight * (1 - currScale) / 2; + + matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) + ..setTranslationRaw(0.0, currTrans, 0.0); + } else { + //其他,不在屏幕显示的item + matrix4 = Matrix4.diagonal3Values(1.0, _scale, 1.0) + ..setTranslationRaw(0.0, _numItemHeight * (1 - _scale) / 2, 0.0); + } + + return Transform( + transform: matrix4, + child: Container( + color: isSelected?Colors.red:Colors.yellow, + ), + ); + }); +} \ No newline at end of file diff --git a/lib/lessons/widgets/lesson_item_widget.dart b/lib/lessons/widgets/lesson_item_widget.dart new file mode 100644 index 0000000..f75319c --- /dev/null +++ b/lib/lessons/widgets/lesson_item_widget.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class LessonItemWidget extends StatelessWidget { + const LessonItemWidget({super.key, required this.isSelected}); + ///是否被选中 + final bool isSelected; + + @override + Widget build(BuildContext context) { + return isSelected?_selectWidget():_unSelectWidget(); + } + + Widget _unSelectWidget() { + return Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: NetworkImage('https://img.liblibai.com/web/648331d033b41.png?image_process=format,webp&x-oss-process=image/resize,w_2980,m_lfit/format,webp'), + ) + ), + ); + } + + Widget _selectWidget() { + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + border: Border.all( + width: 2.0, + color: Colors.red, + style: BorderStyle.solid + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Image.network( + 'https://img.liblibai.com/web/648331d5a2cb5.png?image_process=format,webp&x-oss-process=image/resize,w_2980,m_lfit/format,webp', + ), + ), + 10.verticalSpace, + Container( + color: Colors.red, + width: double.infinity, + child: const Column( + children: [ + Text('red'), + Text('第三段') + ], + ), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/login/loginpage/login_page.dart b/lib/login/loginpage/login_page.dart index 6ed68b5..81aaf2f 100644 --- a/lib/login/loginpage/login_page.dart +++ b/lib/login/loginpage/login_page.dart @@ -28,6 +28,7 @@ class _LoginPageView extends StatelessWidget { listener: (context, state){ if (state is LoginResultChangeState) { EasyLoading.showToast('登陆接口回调'); + Navigator.of(context).pushNamed(AppRouteName.home); } }, child: _buildLoginViewWidget(), diff --git a/lib/route/route.dart b/lib/route/route.dart index 1971f04..7ef389f 100644 --- a/lib/route/route.dart +++ b/lib/route/route.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:wow_english/app/splash_page.dart'; import 'package:wow_english/common/pages/wow_web_page.dart'; import 'package:wow_english/home/home_page.dart'; +import 'package:wow_english/lessons/lesson_page.dart'; import 'package:wow_english/login/forgetpwd/forget_password_home_page.dart'; import 'package:wow_english/login/loginpage/login_page.dart'; import 'package:wow_english/login/setpwd/set_pwd_page.dart'; @@ -16,6 +17,7 @@ class AppRouteName { static const String fogPwd = 'fogPwd'; static const String setPwd = 'setPwd'; static const String webView = 'webView'; + static const String lesson = 'lesson'; static const String tab = '/'; } @@ -38,6 +40,8 @@ class AppRouter { return CupertinoPageRoute(builder: (_) => const HomePage()); case AppRouteName.fogPwd: return CupertinoPageRoute(builder: (_) => const ForgetPasswordHomePage()); + case AppRouteName.lesson: + return CupertinoPageRoute(builder: (_) => const LessonPage()); case AppRouteName.setPwd: final phoneNum = (settings.arguments as Map)['phoneNumber'] as String; return CupertinoPageRoute(builder: (_) => SetPassWordPage(phoneNum: phoneNum)); -- libgit2 0.22.2