diff --git a/lib/app/app.dart b/lib/app/app.dart index 3e2cbfe..43409be 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -32,6 +32,8 @@ class App extends StatelessWidget { fontFamily: 'HannotateSC', colorScheme: ColorScheme.fromSeed(seedColor: Colors.white), useMaterial3: true, + ///系统主题色 + primaryColor: const Color(0xFF00B6F1) ), builder: EasyLoading.init( builder: (context, child) => ResponsiveBreakpoints(breakpoints: const [ diff --git a/lib/app/splash_page.dart b/lib/app/splash_page.dart index 0973226..5cb8f40 100644 --- a/lib/app/splash_page.dart +++ b/lib/app/splash_page.dart @@ -19,6 +19,7 @@ import 'package:wow_english/utils/log_util.dart'; import 'package:wow_english/utils/sp_util.dart'; import '../common/core/app_consts.dart'; +import '../common/core/module_cache.dart'; import '../common/widgets/webview_dialog.dart'; class SplashPage extends StatelessWidget { @@ -167,6 +168,7 @@ class _TransitionViewState extends State { void init() async { changeDevice(); await SpUtil.preInit(); + ModuleCache.instance.init(); AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.welcomeToWow); startTime(); } diff --git a/lib/common/core/module_cache.dart b/lib/common/core/module_cache.dart new file mode 100644 index 0000000..ccfd6d4 --- /dev/null +++ b/lib/common/core/module_cache.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; +import 'dart:ui'; + +import '../../models/course_module_entity.dart'; +import '../../utils/sp_util.dart'; +import '../utils/color_parser.dart'; + +///模块缓存类 +class ModuleCache { + // Private constructor + ModuleCache._privateConstructor(); + + // Singleton instance + static final ModuleCache _instance = ModuleCache._privateConstructor(); + + // Public accessor for the singleton instance + static ModuleCache get instance => _instance; + + // Variable to store the current module entity + CourseModuleEntity? _currentModule; + + // Key for SharedPreferences + static const String _moduleKey = "courseModule"; + + // Initialize the cache by loading data from SharedPreferences + Future init() async { + String? jsonString = SpUtil.getInstance().get(_moduleKey); + if (jsonString != null) { + _currentModule = CourseModuleEntity.fromJson(json.decode(jsonString)); + } + } + + // Getter and setter for the current module + CourseModuleEntity? get currentModule => _currentModule; + set currentModule(CourseModuleEntity? value) { + _currentModule = value; + _saveToPrefs(value); + } + + // Method to clear the cached module data + void clear() { + _currentModule = null; + _clearPrefs(); + } + + // Save module data to SharedPreferences + void _saveToPrefs(CourseModuleEntity? module) { + if (module != null) { + String jsonString = json.encode(module.toJson()); + SpUtil.getInstance().setData(_moduleKey, jsonString); + } else { + SpUtil.getInstance().remove(_moduleKey); + } + } + + String getCurrentThemeColorStr({String? colorStr}) { + return colorStr ?? _currentModule.getSafeThemeColor(); + } + + ///根据当前课程阶段获取系统主题色 + Color getCurrentThemeColor({String? colorStr}) { + return parseColor(getCurrentThemeColorStr(colorStr: colorStr)); + } + + ///根据当前课程阶段获取系统主题名称 + String getCurrentThemeName({String? name}) { + return name ?? _currentModule.getSafeName(); + } + + // Clear all data from SharedPreferences + void _clearPrefs() async { + SpUtil.getInstance().remove(_moduleKey); + } +} \ No newline at end of file diff --git a/lib/common/utils/color_parser.dart b/lib/common/utils/color_parser.dart new file mode 100644 index 0000000..5581a51 --- /dev/null +++ b/lib/common/utils/color_parser.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +/// 将"#80FFFF00"字符串类型的颜色值转成Color对象类型 +Color parseColor(String color) { + // Remove the '#' character from the beginning of the string + String hexColor = color.replaceAll("#", ""); + + // Check if the color is in the correct length (6 or 8) + if (hexColor.length == 6) { + // Add 'FF' for the alpha value if not provided + hexColor = "FF$hexColor"; + } else if (hexColor.length != 8) { + ///如果数据异常,返回主题色兜底 + // throw const FormatException("Invalid color format"); + hexColor = "FF00B6F1"; + } + + // Convert the hex string to an integer and create a Color object + return Color(int.parse(hexColor, radix: 16)); +} \ No newline at end of file diff --git a/lib/generated/json/base/json_convert_content.dart b/lib/generated/json/base/json_convert_content.dart index f673a33..4e904a5 100644 --- a/lib/generated/json/base/json_convert_content.dart +++ b/lib/generated/json/base/json_convert_content.dart @@ -139,7 +139,12 @@ class JsonConvert { if (value == null) { return null; } - return convertFuncMap[type]!(value as Map) as T; + var covertFunc = convertFuncMap[type]!; + if (covertFunc is Map) { + return covertFunc(value as Map) as T; + } else { + return covertFunc(Map.from(value)) as T; + } } else { throw UnimplementedError( '$type unimplemented,you can try running the app again'); diff --git a/lib/models/course_module_entity.dart b/lib/models/course_module_entity.dart index 0be37ea..9c167ce 100644 --- a/lib/models/course_module_entity.dart +++ b/lib/models/course_module_entity.dart @@ -21,14 +21,49 @@ class CourseModuleEntity { int? status; String? courseModuleThemeColor; - CourseModuleEntity(); + // 无参构造函数 + CourseModuleEntity.empty(); - factory CourseModuleEntity.fromJson(Map json) => $CourseModuleEntityFromJson(json); + // 命名构造函数 + CourseModuleEntity( + {int? id, + String? code, + String? courseModuleName, + String? courseModuleThemeColor}); + + factory CourseModuleEntity.fromJson(Map json) => + $CourseModuleEntityFromJson(json); Map toJson() => $CourseModuleEntityToJson(this); + // Factory constructor for creating an instance with only three parameters + factory CourseModuleEntity.of(int? courseModuleId, String? courseModuleCode, + String? courseModuleName, String? courseModuleThemeColor) { + return CourseModuleEntity( + id: courseModuleId, + code: courseModuleCode, + courseModuleName: courseModuleName, + courseModuleThemeColor: courseModuleThemeColor, + // Set default values or leave other fields null + ); + } + @override String toString() { return jsonEncode(this); } } + +///对可空的CourseModuleEntity对象扩展 +extension PersonSafeExt on CourseModuleEntity? { + + ///获取非空的主题色,系统主题色0xFF00B6F1兜底 + String getSafeThemeColor() { + return this?.courseModuleThemeColor ?? '0xFF00B6F1'; + } + + ///获取非空的阶段名称 + String getSafeName() { + return this?.name ?? 'learn wow!'; + } +} diff --git a/lib/pages/module/course_module_page.dart b/lib/pages/module/course_module_page.dart index a827b88..806b372 100644 --- a/lib/pages/module/course_module_page.dart +++ b/lib/pages/module/course_module_page.dart @@ -5,9 +5,10 @@ import 'package:wow_english/common/widgets/we_app_bar.dart'; import 'package:wow_english/models/course_module_entity.dart'; import 'package:wow_english/route/route.dart'; +import '../../common/core/module_cache.dart'; import '../../common/utils/click_with_music_controller.dart'; +import '../../common/utils/color_parser.dart'; import '../../utils/audio_player_util.dart'; -import '../section/courese_module_model.dart'; import 'bloc/module_bloc.dart'; import 'widgets/module_item_widget.dart'; @@ -94,12 +95,11 @@ class _LessonPageView extends StatelessWidget { // ? Colors.red // : Colors.white, color: - CourseModuleModel(model?.code ?? 'Phase-1') - .color + parseColor(model.getSafeThemeColor()) .withOpacity( bloc.currentPageIndex == index ? 1 - : 0.35), + : 0.15), borderRadius: BorderRadius.circular(5.r), border: Border.all( width: 0.5, @@ -167,6 +167,7 @@ class _LessonPageView extends StatelessWidget { model: model, isSelected: bloc.currentPageIndex == index, onClickEvent: () { + ModuleCache.instance.currentModule = model; ///todo 不同阶段音乐文件待提供 ClickWithMusicController.instance.playMusicAndPerformAction( context, diff --git a/lib/pages/module/widgets/module_item_widget.dart b/lib/pages/module/widgets/module_item_widget.dart index 81e53f8..89dce77 100644 --- a/lib/pages/module/widgets/module_item_widget.dart +++ b/lib/pages/module/widgets/module_item_widget.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:wow_english/common/extension/string_extension.dart'; +import 'package:wow_english/common/utils/color_parser.dart'; import 'package:wow_english/common/widgets/ow_image_widget.dart'; import 'package:wow_english/models/course_module_entity.dart'; -import '../../section/courese_module_model.dart'; - ///阶段(模块)item布局 class ModuleItemWidget extends StatelessWidget { - const ModuleItemWidget({super.key, required this.isSelected, this.model, this.onClickEvent}); + const ModuleItemWidget( + {super.key, required this.isSelected, this.model, this.onClickEvent}); + ///是否被选中 final bool isSelected; final CourseModuleEntity? model; @@ -23,7 +24,7 @@ class ModuleItemWidget extends StatelessWidget { } onClickEvent?.call(); }, - child: isSelected?_selectWidget():_unSelectWidget(), + child: isSelected ? _selectWidget() : _unSelectWidget(), ); } @@ -31,12 +32,9 @@ class ModuleItemWidget extends StatelessWidget { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage('gendubeij'.assetPng) - ) - ), + image: DecorationImage(image: AssetImage('gendubeij'.assetPng))), child: OwImageWidget( - name: model?.picUrl??'', + name: model?.picUrl ?? '', ), ); } @@ -45,48 +43,37 @@ class ModuleItemWidget extends StatelessWidget { return Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage( - 'gendubeij'.assetPng, - ), - fit: BoxFit.fill - ), + image: DecorationImage( + image: AssetImage( + 'gendubeij'.assetPng, + ), + fit: BoxFit.fill), ), - child: Column( + child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: OwImageWidget( - name: model?.picUrl??'', + name: model?.picUrl ?? '', ), ), 10.verticalSpace, Container( decoration: BoxDecoration( - color: CourseModuleModel(model?.code??'Phase-1').color, - borderRadius: BorderRadius.circular(6.r), - border: Border.all( - color: const Color(0xFF333333), - width: 1.0 - ) - ), + color: parseColor(model.getSafeThemeColor()), + borderRadius: BorderRadius.circular(6.r), + border: Border.all(color: const Color(0xFF333333), width: 1.0)), padding: EdgeInsets.symmetric(horizontal: 10.w), child: Column( children: [ Text( - model?.name??'', - style: TextStyle( - color: Colors.white, - fontSize: 12.sp - ), + model?.getSafeName() ?? '', + style: TextStyle(color: Colors.white, fontSize: 12.sp), ), Text( - model?.des??'', + model?.des ?? '', maxLines: 1, - style: TextStyle( - color: Colors.white, - fontSize: 12.sp - ), + style: TextStyle(color: Colors.white, fontSize: 12.sp), ) ], ), @@ -95,4 +82,4 @@ class ModuleItemWidget extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/section/bloc/section_bloc.dart b/lib/pages/section/bloc/section_bloc.dart index b27e492..afcb1d7 100644 --- a/lib/pages/section/bloc/section_bloc.dart +++ b/lib/pages/section/bloc/section_bloc.dart @@ -38,6 +38,7 @@ class SectionBloc extends Bloc { ScrollController get indicatorSrollController => _indicatorSrollController; + ///之前传进来是用于根据courseModuleCode获取主题色的,现在不用了。参数还是先留着,预防后面需要 CourseUnitEntity _courseUnitEntity; CourseUnitEntity get courseUnitEntity => _courseUnitEntity; diff --git a/lib/pages/section/section_page.dart b/lib/pages/section/section_page.dart index 096dac0..f2d32d4 100644 --- a/lib/pages/section/section_page.dart +++ b/lib/pages/section/section_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:nested_scroll_views/material.dart'; +import 'package:wow_english/common/core/module_cache.dart'; import 'package:wow_english/common/core/user_util.dart'; import 'package:wow_english/common/utils/click_with_music_controller.dart'; import 'package:wow_english/models/course_unit_entity.dart'; @@ -138,9 +139,7 @@ class _SectionPageView extends StatelessWidget { await bloc.requestEnterClass(courseLessonId, () { pushNamed(AppRouteName.topicPic, arguments: { 'courseLessonId': courseLessonId, - 'moduleColor': CourseModuleModel( - bloc.courseUnitEntity.courseModuleCode ?? 'Phase-1') - .color + 'moduleColor': ModuleCache.instance.getCurrentThemeColor() }).then((value) { if (value != null) { Map dataMap = @@ -175,7 +174,6 @@ class _SectionPageView extends StatelessWidget { children: [ SectionHeaderWidget( title: bloc.getCourseUnitDetail().name, - courseModuleCode: bloc.courseUnitEntity.courseModuleCode, onBack: () { popPage(data: { 'needRefresh': bloc.courseUnitEntityChanged, @@ -281,9 +279,7 @@ Widget _itemTransCard( const SizedBox(height: 16.0), // 间距 CircularProgressIndicator( - color: CourseModuleModel( - bloc.courseUnitEntity.courseModuleCode ?? 'Phase-1') - .color), + color: ModuleCache.instance.getCurrentThemeColor()), // 加载动画 ], ), @@ -332,7 +328,6 @@ Widget _itemTransCard( sectionData.id.toString(), sectionData.courseType)); }, child: SectionItem( - courseModuleId: bloc.courseUnitEntity.courseModuleCode, lessons: sectionData, ), ); diff --git a/lib/pages/section/widgets/section_header_widget.dart b/lib/pages/section/widgets/section_header_widget.dart index 82739c5..4c0d0c7 100644 --- a/lib/pages/section/widgets/section_header_widget.dart +++ b/lib/pages/section/widgets/section_header_widget.dart @@ -4,17 +4,15 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/pages/user/bloc/user_bloc.dart'; -import '../courese_module_model.dart'; +import '../../../common/core/module_cache.dart'; /// 环节(课程)列表页标题栏 class SectionHeaderWidget extends StatelessWidget { const SectionHeaderWidget( - {super.key, this.title, this.courseModuleCode, this.onBack}); + {super.key, this.title, this.onBack}); final String? title; - final String? courseModuleCode; - final VoidCallback? onBack; @override @@ -24,7 +22,7 @@ class SectionHeaderWidget extends StatelessWidget { return Container( height: 45, width: double.infinity, - color: CourseModuleModel(courseModuleCode ?? 'Phase-1').color, + color: ModuleCache.instance.getCurrentThemeColor(), padding: EdgeInsets.symmetric(horizontal: 9.5.w), child: Row( children: [ @@ -49,9 +47,7 @@ class SectionHeaderWidget extends StatelessWidget { 20.horizontalSpace, Expanded( child: Text( - title ?? - CourseModuleModel(courseModuleCode ?? 'Phase-1') - .courseModuleTitle, + title ?? ModuleCache.instance.getCurrentThemeName(), textAlign: TextAlign.left, style: const TextStyle(color: Colors.white, fontSize: 30.0), )), diff --git a/lib/pages/section/widgets/section_item.dart b/lib/pages/section/widgets/section_item.dart index 1adb3f8..eb0b7c4 100644 --- a/lib/pages/section/widgets/section_item.dart +++ b/lib/pages/section/widgets/section_item.dart @@ -3,15 +3,13 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/common/widgets/ow_image_widget.dart'; +import '../../../common/core/module_cache.dart'; import '../../../models/course_section_entity.dart'; -import '../../../models/course_unit_entity.dart'; -import '../courese_module_model.dart'; ///环节item布局 class SectionItem extends StatelessWidget { - const SectionItem({super.key, this.lessons, this.courseModuleId}); + const SectionItem({super.key, this.lessons}); - final String? courseModuleId; final CourseSectionEntity? lessons; @override @@ -62,7 +60,7 @@ class SectionItem extends StatelessWidget { width: 2, color: const Color(0xFF140C10), ), - color: CourseModuleModel(courseModuleId ?? 'Phase-1').color, + color: ModuleCache.instance.getCurrentThemeColor(), borderRadius: BorderRadius.circular(6) ), padding: EdgeInsets.symmetric(horizontal: 10.w), diff --git a/lib/pages/unit/bloc.dart b/lib/pages/unit/bloc.dart index 5f45097..6a2ce17 100644 --- a/lib/pages/unit/bloc.dart +++ b/lib/pages/unit/bloc.dart @@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart'; import 'package:wow_english/pages/unit/widget/home_tab_header_widget.dart'; import 'package:wow_english/utils/audio_player_util.dart'; +import '../../common/core/module_cache.dart'; import '../../common/request/dao/lesson_dao.dart'; import '../../common/request/exception.dart'; import '../../models/course_module_entity.dart'; @@ -24,6 +25,8 @@ class UnitBloc extends Bloc { bool exchangeResult = false; UnitBloc(CourseModuleEntity? courseEntity) : super(UnitState().init()) { + ///缓存当前模块信息 + setIfNotNull(courseEntity); on(_requestUnitDatas); on((event, emit) { AudioPlayerUtil.getInstance().playAudio(AudioPlayerUtilType.inMyTummy); @@ -35,6 +38,7 @@ class UnitBloc extends Bloc { try { await loading(() async { _unitData = await LessonDao.courseUnit(event.moduleId); + setIfNull(_unitData); emitter(UnitDataLoadState()); }); } catch (e) { @@ -44,6 +48,27 @@ class UnitBloc extends Bloc { } } + ///如果从模块页选择回来,则刷新缓存,否则认为是从 + void setIfNotNull(CourseModuleEntity? courseModuleEntity) { + if (courseModuleEntity != null) { + _moduleEntity = courseModuleEntity; + } else { + _moduleEntity = ModuleCache.instance.currentModule; + } + } + + ///理论上只有没有缓存的情况下首次进入单元列表,数据请求成功后构建第一次缓存 + void setIfNull(CourseUnitEntity? courseUnitData) { + if (_moduleEntity == null && courseUnitData != null) { + _moduleEntity = CourseModuleEntity.of( + courseUnitData.nowCourseModuleId, + courseUnitData.courseModuleCode, + courseUnitData.nowCourseModuleName, + courseUnitData.courseModuleThemeColor); + ModuleCache.instance.currentModule = _moduleEntity; + } + } + String? getCourseModuleCode() { return _moduleEntity?.code ?? _unitData?.courseModuleCode; } diff --git a/lib/pages/unit/widget/home_tab_header_widget.dart b/lib/pages/unit/widget/home_tab_header_widget.dart index 4436510..dc6eede 100644 --- a/lib/pages/unit/widget/home_tab_header_widget.dart +++ b/lib/pages/unit/widget/home_tab_header_widget.dart @@ -5,7 +5,7 @@ import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/pages/user/bloc/user_bloc.dart'; import '../../../common/core/app_config_helper.dart'; -import '../../section/courese_module_model.dart'; +import '../../../common/core/module_cache.dart'; enum HeaderActionType { //视频跟读 @@ -35,7 +35,7 @@ class HomeTabHeaderWidget extends StatelessWidget { return Container( height: 45, width: double.infinity, - color: CourseModuleModel(courseModuleCode ?? 'Phase-1').color, + color: ModuleCache.instance.getCurrentThemeColor(), padding: EdgeInsets.symmetric(horizontal: 9.5.w), child: Row( children: [ @@ -60,8 +60,7 @@ class HomeTabHeaderWidget extends StatelessWidget { 20.horizontalSpace, Expanded( child: Text( - CourseModuleModel(courseModuleCode ?? 'Phase-1') - .courseModuleTitle, + ModuleCache.instance.getCurrentThemeName(), textAlign: TextAlign.left, style: const TextStyle(color: Colors.white, fontSize: 30.0), )), diff --git a/lib/route/route.dart b/lib/route/route.dart index 9f66df3..7cbc566 100644 --- a/lib/route/route.dart +++ b/lib/route/route.dart @@ -119,7 +119,7 @@ class AppRouter { case AppRouteName.courseModule: return CupertinoPageRoute(builder: (_) => const CourseModulePage()); case AppRouteName.courseUnit: - CourseModuleEntity courseModuleEntity = CourseModuleEntity(); + CourseModuleEntity? courseModuleEntity; if (settings.arguments != null) { courseModuleEntity = (settings.arguments as Map) .getOrNull('courseModuleEntity') as CourseModuleEntity;