diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b6f48eb..2be7ae7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + GoRouter( ), GoRoute( name: Routes.questionPage, - path: Routes.questionPage, + path: '${Routes.questionPage}/:id', builder: (context, state) => BlocProvider( - create: (context) => QuestionBloc(locator()), + create: (context) => + QuestionBloc(locator()) + ..add(GetLevelEvent(state.pathParameters['id'])), child: const QuestionPage(), ), ), diff --git a/lib/core/utils/storage_path.dart b/lib/core/utils/storage_path.dart new file mode 100644 index 0000000..4ecde62 --- /dev/null +++ b/lib/core/utils/storage_path.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +import 'package:path_provider/path_provider.dart'; + +class StoragePath { + static Directory applicationDir = Directory(''); + + static Future getApplicationDir() async { + applicationDir = await getApplicationDocumentsDirectory(); + } +} \ No newline at end of file diff --git a/lib/core/widgets/answer_box/answer_box.dart b/lib/core/widgets/answer_box/answer_box.dart index b22198e..5005146 100644 --- a/lib/core/widgets/answer_box/answer_box.dart +++ b/lib/core/widgets/answer_box/answer_box.dart @@ -1,39 +1,40 @@ import 'package:flutter/material.dart'; +import 'package:hadi_hoda_flutter/core/utils/storage_path.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; +import 'package:path_provider/path_provider.dart'; -class AnswerBox extends StatefulWidget { - const AnswerBox({super.key}); +class AnswerBox extends StatelessWidget { + const AnswerBox({super.key, required this.answer,this.selected, this.onTap, required this.index}); - @override - State createState() => _AnswerBoxState(); -} - -class _AnswerBoxState extends State { - - bool selected = false; + final AnswerEntity answer; + final bool? selected; + final Function(AnswerEntity answer)? onTap; + final int index; @override Widget build(BuildContext context) { return GestureDetector( - onTap: () { - setState(() { - selected = !selected; - }); - }, + onTap: () => onTap?.call(answer), child: SizedBox( child: Stack( alignment: Alignment.bottomCenter, clipBehavior: Clip.none, children: [ AnswerPictureBox( - selected: selected, + selected: selected ?? false, + index: index, + image: '${StoragePath.applicationDir.path}/data/${answer + .imageId}${answer.imageInfo?.extension ?? '.png'}', ), Positioned( left: 0, right: 0, bottom: -36, - child: AnswerTextBox(), + child: AnswerTextBox( + text: answer.title ?? '', + ), ), ], ), diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index a6e62c8..201baa2 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; @@ -6,9 +8,11 @@ import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; class AnswerPictureBox extends StatelessWidget { - const AnswerPictureBox({super.key, required this.selected}); + const AnswerPictureBox({super.key, required this.selected, required this.image, required this.index}); final bool selected; + final String image; + final int index; @override Widget build(BuildContext context) { @@ -19,10 +23,11 @@ class AnswerPictureBox extends StatelessWidget { clipper: _SvgCustomClipper(), child: Stack( children: [ - MyImage( - image: MyAssets.backgroundHome, + Image.file( + File(image), fit: BoxFit.cover, - size: 170, + height: 170, + width: 170, ), PositionedDirectional( top: MySpaces.s12, @@ -44,7 +49,7 @@ class AnswerPictureBox extends StatelessWidget { ), ), child: Text( - '1', + '$index', style: GoogleFonts.marhey( fontSize: 17, fontWeight: FontWeight.w600, @@ -54,14 +59,14 @@ class AnswerPictureBox extends StatelessWidget { ), ), ), - PositionedDirectional( - top: MySpaces.s14, - end: MySpaces.s12, - child: MyImage( - image: MyAssets.correct, - size: MySpaces.s40, - ), - ), + // PositionedDirectional( + // top: MySpaces.s14, + // end: MySpaces.s12, + // child: MyImage( + // image: MyAssets.correct, + // size: MySpaces.s40, + // ), + // ), ], ), ), diff --git a/lib/core/widgets/answer_box/styles/text_box.dart b/lib/core/widgets/answer_box/styles/text_box.dart index 85c7aa8..592237c 100644 --- a/lib/core/widgets/answer_box/styles/text_box.dart +++ b/lib/core/widgets/answer_box/styles/text_box.dart @@ -3,7 +3,9 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; class AnswerTextBox extends StatelessWidget { - const AnswerTextBox({super.key}); + const AnswerTextBox({super.key, required this.text}); + + final String text; @override Widget build(BuildContext context) { @@ -22,7 +24,7 @@ class AnswerTextBox extends StatelessWidget { ), ), child: Text( - 'We walk in the yard with a glass of juice ', + text, textAlign: TextAlign.center, style: GoogleFonts.marhey( fontSize: 14, diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index 5a64c25..71db678 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -62,9 +62,7 @@ class IntroBloc extends Bloc { }, ); }, - (error) { - print(error.errorMessage); - }, + (error) {}, ); }, ); diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index 9710e94..92c0dd5 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart'; @@ -56,7 +57,7 @@ class _IntroPageState extends State { Positioned _loading(BuildContext context) { return Positioned( - bottom: MediaQuery.viewPaddingOf(context).bottom, + bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s10, child: IntroLoadingWidget( percent: 80, loadingStream: context.read().loadingStream, diff --git a/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart index 42cf086..fdd2223 100644 --- a/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart +++ b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart @@ -38,7 +38,6 @@ class IntroLoadingWidget extends StatelessWidget { initialData: 0, stream: loadingStream, builder: (context, snapshot) { - print(snapshot.data); return Row( children: [ Expanded( diff --git a/lib/features/level/domain/usecases/get_level_usecase.dart b/lib/features/level/domain/usecases/get_levels_usecase.dart similarity index 83% rename from lib/features/level/domain/usecases/get_level_usecase.dart rename to lib/features/level/domain/usecases/get_levels_usecase.dart index f141d4c..3f12106 100644 --- a/lib/features/level/domain/usecases/get_level_usecase.dart +++ b/lib/features/level/domain/usecases/get_levels_usecase.dart @@ -5,10 +5,10 @@ import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart'; -class GetLevelUseCase implements UseCase, LevelParams> { +class GetLevelsUseCase implements UseCase, LevelParams> { final ILevelRepository repository; - const GetLevelUseCase(this.repository); + const GetLevelsUseCase(this.repository); @override Future, MyException>> call(LevelParams params) { diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart index a92b510..ed56711 100644 --- a/lib/features/level/presentation/bloc/level_bloc.dart +++ b/lib/features/level/presentation/bloc/level_bloc.dart @@ -1,23 +1,32 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:go_router/go_router.dart'; import 'package:hadi_hoda_flutter/core/params/level_params.dart'; +import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_location.dart'; -import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_level_usecase.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_levels_usecase.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_event.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_state.dart'; class LevelBloc extends Bloc { /// ------------constructor------------ LevelBloc( - this._getLevelUseCase, + this._getLeveslUseCase, ) : super(const LevelState()) { on(_getLevelListEvent); } + @override + Future close() { + scrollController.dispose(); + return super.close(); + } + /// ------------UseCases------------ - final GetLevelUseCase _getLevelUseCase; + final GetLevelsUseCase _getLeveslUseCase; /// ------------Variables------------ final List bottomLocationList = [ @@ -26,7 +35,7 @@ class LevelBloc extends Bloc { LevelLocation(bottom: 150, left: 60, index: 3), LevelLocation(bottom: 210, left: 120, index: 4), LevelLocation(bottom: 250, right: 60, index: 5), - LevelLocation(top: 170, right: 40, index: 6), + LevelLocation(top: 150, right: 40, index: 6), LevelLocation(top: 70, right: 70, index: 7), LevelLocation(top: -20, right: 70, index: 8), ]; @@ -49,21 +58,41 @@ class LevelBloc extends Bloc { final List bottom8LevelList = []; final List top12LevelList = []; + /// ------------Controllers------------ + final ScrollController scrollController = ScrollController(); /// ------------Functions------------ + void goToQuestionPage(BuildContext context, LevelEntity level){ + context.pushNamed( + Routes.questionPage, + pathParameters: { + 'id': '${level.id}', + }, + ); + } + /// ------------Api Calls------------ FutureOr _getLevelListEvent(GetLevelListEvent event, Emitter emit) async { - await _getLevelUseCase(LevelParams()).then((value) { + await _getLeveslUseCase(LevelParams()).then((value) { value.fold( - (data) { + (data) async { bottom8LevelList.addAll(data.take(8)); if(data.length > 8){ top12LevelList.addAll(data.sublist(8, data.length)); } - emit(state.copyWith(getLevelStatus: const BaseComplete(''))); + emit(state.copyWith( + getLevelStatus: const BaseComplete(''), + chooseLevel: data.first, + )); + await Future.delayed(Duration(milliseconds: 500)); + scrollController.animateTo( + scrollController.position.maxScrollExtent, + duration: Duration(seconds: 1), + curve: Curves.easeInOut, + ); }, (error) {}, ); diff --git a/lib/features/level/presentation/bloc/level_event.dart b/lib/features/level/presentation/bloc/level_event.dart index 7344eb5..6f9020c 100644 --- a/lib/features/level/presentation/bloc/level_event.dart +++ b/lib/features/level/presentation/bloc/level_event.dart @@ -1,5 +1,11 @@ +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; + sealed class LevelEvent { const LevelEvent(); } class GetLevelListEvent extends LevelEvent {} +class ChooseLevelEvent extends LevelEvent { + final LevelEntity level; + const ChooseLevelEvent(this.level); +} diff --git a/lib/features/level/presentation/bloc/level_state.dart b/lib/features/level/presentation/bloc/level_state.dart index 4ecb07c..adc10ed 100644 --- a/lib/features/level/presentation/bloc/level_state.dart +++ b/lib/features/level/presentation/bloc/level_state.dart @@ -1,15 +1,22 @@ import 'package:hadi_hoda_flutter/core/status/base_status.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; class LevelState { final BaseStatus getLevelStatus; + final LevelEntity? chooseLevel; - const LevelState({this.getLevelStatus = const BaseInit()}); + const LevelState({ + this.getLevelStatus = const BaseInit(), + this.chooseLevel, + }); LevelState copyWith({ BaseStatus? getLevelStatus, + LevelEntity? chooseLevel, }) { return LevelState( getLevelStatus: getLevelStatus ?? this.getLevelStatus, + chooseLevel: chooseLevel ?? chooseLevel, ); } } diff --git a/lib/features/level/presentation/ui/level_page.dart b/lib/features/level/presentation/ui/level_page.dart index d0bcb59..22335ce 100644 --- a/lib/features/level/presentation/ui/level_page.dart +++ b/lib/features/level/presentation/ui/level_page.dart @@ -4,7 +4,9 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_bloc.dart'; +import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_event.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_state.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/bottom_path.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/hint_level_widget.dart'; @@ -20,11 +22,12 @@ class LevelPage extends StatelessWidget { body: Stack( children: [ SingleChildScrollView( + controller: context.read().scrollController, child: Stack( alignment: Alignment.center, children: [ _background(), - _topPath(context), + // _topPath(context), _bottomPath(context), ], ), @@ -36,12 +39,28 @@ class LevelPage extends StatelessWidget { ); } - Positioned _hintMission(BuildContext context) { - return Positioned( - bottom: MediaQuery.viewPaddingOf(context).bottom, - right: MySpaces.s16, - left: MySpaces.s16, - child: HintLevelWidget(), + Widget _hintMission(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => + previous.chooseLevel?.id != current.chooseLevel?.id, + builder: (context, state) { + if (state.chooseLevel != null) { + return Positioned( + bottom: MediaQuery + .viewPaddingOf(context) + .bottom + 10, + right: MySpaces.s16, + left: MySpaces.s16, + child: HintLevelWidget( + level: state.chooseLevel ?? LevelEntity(), + onTap: (level) => + context.read().goToQuestionPage(context, level), + ), + ); + } else { + return SizedBox.shrink(); + } + } ); } @@ -88,6 +107,10 @@ class LevelPage extends StatelessWidget { left: context.read().topLocationList[index].left, child: LevelWidget( index: context.read().topLocationList[index].index ?? 0, + level: context.read().top12LevelList[index], + onTap: (LevelEntity level) { + + }, ), ), ), @@ -118,6 +141,11 @@ class LevelPage extends StatelessWidget { left: context.read().bottomLocationList[index].left, child: LevelWidget( index: context.read().bottomLocationList[index].index ?? 0, + level: context.read().bottom8LevelList[index], + type: index == 0 ? LevelType.current : LevelType.unFinished, + onTap: (LevelEntity level) { + + }, ), ), ), diff --git a/lib/features/level/presentation/ui/widgets/hint_level_widget.dart b/lib/features/level/presentation/ui/widgets/hint_level_widget.dart index 0ddb472..f1f1312 100644 --- a/lib/features/level/presentation/ui/widgets/hint_level_widget.dart +++ b/lib/features/level/presentation/ui/widgets/hint_level_widget.dart @@ -5,9 +5,13 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; class HintLevelWidget extends StatelessWidget { - const HintLevelWidget({super.key}); + const HintLevelWidget({super.key, required this.level, this.onTap,}); + + final LevelEntity level; + final Function(LevelEntity level)? onTap; @override Widget build(BuildContext context) { @@ -32,7 +36,7 @@ class HintLevelWidget extends StatelessWidget { spacing: MySpaces.s8, children: [ Text( - 'Step 3', + 'Step ${level.order ?? 0}', style: GoogleFonts.marhey( fontSize: 14, fontWeight: FontWeight.w700, @@ -40,7 +44,7 @@ class HintLevelWidget extends StatelessWidget { ), ), Text( - 'Toothbrushing etiquette every day', + level.title ?? '', maxLines: 3, overflow: TextOverflow.ellipsis, style: GoogleFonts.marhey( @@ -53,7 +57,9 @@ class HintLevelWidget extends StatelessWidget { ], ), ), - MyImage(image: MyAssets.play, size: 70), + InkWell( + onTap: () => onTap?.call(level), + child: MyImage(image: MyAssets.play, size: 70),), ], ), ), diff --git a/lib/features/level/presentation/ui/widgets/level_widget.dart b/lib/features/level/presentation/ui/widgets/level_widget.dart index ee6229a..ff5d9a7 100644 --- a/lib/features/level/presentation/ui/widgets/level_widget.dart +++ b/lib/features/level/presentation/ui/widgets/level_widget.dart @@ -3,6 +3,7 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; enum LevelType { unFinished, @@ -17,56 +18,67 @@ enum LevelType { } class LevelWidget extends StatelessWidget { - const LevelWidget({super.key, required this.index, this.type}); + const LevelWidget({ + super.key, + required this.index, + required this.level, + this.type, + this.onTap, + }); final int index; final LevelType? type; + final LevelEntity level; + final Function(LevelEntity level)? onTap; @override Widget build(BuildContext context) { - return Stack( - alignment: Alignment.topCenter, - clipBehavior: Clip.none, - children: [ - MyImage(image: LevelType.image[type] ?? MyAssets.level, size: 46), - Text( - '$index', - style: MyTextStyle.normal26.copyWith(color: context.primaryColor), - ), - if(type == LevelType.current) - Positioned( - top: -20, - child: MyImage( - image: MyAssets.location, - size: 26, - ), + return InkWell( + onTap: () => onTap?.call(level), + child: Stack( + alignment: Alignment.topCenter, + clipBehavior: Clip.none, + children: [ + MyImage(image: LevelType.image[type] ?? MyAssets.level, size: 46), + Text( + '$index', + style: MyTextStyle.normal26.copyWith(color: context.primaryColor), ), - if(type == LevelType.finished) - Positioned( - bottom: 0, - child: Container( - height: 17, - width: 17, - padding: EdgeInsets.all(3), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - width: 1, - color: Color(0XFF3CFF3C), - ), - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color(0XFF48D336), - Color(0XFF2D7C23), - ], + if(type == LevelType.current) + Positioned( + top: -20, + child: MyImage( + image: MyAssets.location, + size: 26, + ), + ), + if(type == LevelType.finished) + Positioned( + bottom: 0, + child: Container( + height: 17, + width: 17, + padding: EdgeInsets.all(3), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: 1, + color: Color(0XFF3CFF3C), + ), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0XFF48D336), + Color(0XFF2D7C23), + ], + ), ), + child: MyImage(image: MyAssets.doneRounded), ), - child: MyImage(image: MyAssets.doneRounded), ), - ), - ], + ], + ), ); } } diff --git a/lib/features/question/data/datasource/question_datasource.dart b/lib/features/question/data/datasource/question_datasource.dart index 2142b93..cd05fe3 100644 --- a/lib/features/question/data/datasource/question_datasource.dart +++ b/lib/features/question/data/datasource/question_datasource.dart @@ -1,12 +1,12 @@ -import 'package:hadi_hoda_flutter/core/constants/my_api.dart'; +import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/network/http_request.dart'; import 'package:hadi_hoda_flutter/core/params/question_params.dart'; -import 'package:hadi_hoda_flutter/core/response/base_response.dart'; -import 'package:hadi_hoda_flutter/features/question/data/model/question_model.dart'; -import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; +import 'package:hive/hive.dart'; abstract class IQuestionDatasource { - Future getData({required QuestionParams params}); + Future getLevel({required QuestionParams params}); } class QuestionDatasourceImpl implements IQuestionDatasource { @@ -15,14 +15,16 @@ class QuestionDatasourceImpl implements IQuestionDatasource { const QuestionDatasourceImpl(this.httpRequest); @override - Future getData({required QuestionParams params}) async { - final response = await httpRequest.get( - path: MyApi.baseUrl, - ); - - return BaseResponse.getData( - response?['data'], - (json) => QuestionModel.fromJson(json), - ); + Future getLevel({required QuestionParams params}) async { + try { + final Box levelBox = Hive.box(MyConstants.levelBox); + final LevelEntity findLevel = levelBox.values.singleWhere( + (e) => e.id == params.id, + orElse: () => LevelEntity(), + ); + return findLevel; + } catch (e) { + throw MyException(errorMessage: '$e'); + } } } diff --git a/lib/features/question/data/repository_impl/question_repository_impl.dart b/lib/features/question/data/repository_impl/question_repository_impl.dart index af5a1c6..ffdf456 100644 --- a/lib/features/question/data/repository_impl/question_repository_impl.dart +++ b/lib/features/question/data/repository_impl/question_repository_impl.dart @@ -2,8 +2,8 @@ import 'package:hadi_hoda_flutter/core/params/question_params.dart'; import 'package:flutter/foundation.dart'; import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/question/data/datasource/question_datasource.dart'; -import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/repository/question_repository.dart'; class QuestionRepositoryImpl implements IQuestionRepository { @@ -12,9 +12,9 @@ class QuestionRepositoryImpl implements IQuestionRepository { const QuestionRepositoryImpl(this.datasource); @override - Future> getData({required QuestionParams params}) async { + Future> getLevel({required QuestionParams params}) async { try { - final QuestionEntity response = await datasource.getData(params: params); + final LevelEntity response = await datasource.getLevel(params: params); return DataState.success(response); } on MyException catch (e) { return DataState.error(e); diff --git a/lib/features/question/domain/repository/question_repository.dart b/lib/features/question/domain/repository/question_repository.dart index 3c9e008..9c2fd60 100644 --- a/lib/features/question/domain/repository/question_repository.dart +++ b/lib/features/question/domain/repository/question_repository.dart @@ -1,8 +1,8 @@ import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/params/question_params.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; -import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; abstract class IQuestionRepository { - Future> getData({required QuestionParams params}); + Future> getLevel({required QuestionParams params}); } diff --git a/lib/features/question/domain/usecases/get_question_usecase.dart b/lib/features/question/domain/usecases/get_level_usecase.dart similarity index 55% rename from lib/features/question/domain/usecases/get_question_usecase.dart rename to lib/features/question/domain/usecases/get_level_usecase.dart index 697534b..4310bdf 100644 --- a/lib/features/question/domain/usecases/get_question_usecase.dart +++ b/lib/features/question/domain/usecases/get_level_usecase.dart @@ -2,16 +2,16 @@ import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/params/question_params.dart'; import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; -import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/repository/question_repository.dart'; -class GetQuestionUseCase implements UseCase { +class GetLevelUseCase implements UseCase { final IQuestionRepository repository; - const GetQuestionUseCase(this.repository); + const GetLevelUseCase(this.repository); @override - Future> call(QuestionParams params) { - return repository.getData(params: params); + Future> call(QuestionParams params) { + return repository.getLevel(params: params); } } diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index 92706ac..7d37a36 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -1,9 +1,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/cupertino.dart'; +import 'package:hadi_hoda_flutter/core/params/question_params.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart'; -import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; -import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_question_usecase.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_level_usecase.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart'; import 'package:showcaseview/showcaseview.dart'; @@ -11,13 +12,14 @@ import 'package:showcaseview/showcaseview.dart'; class QuestionBloc extends Bloc { /// ------------constructor------------ QuestionBloc( - this._getQuestionUseCase, + this._getLevelUseCase, ) : super(const QuestionState()) { - on(_getQuestionEvent); + on(_getLevelEvent); + on(_chooseAnswerEvent); } /// ------------UseCases------------ - final GetQuestionUseCase _getQuestionUseCase; + final GetLevelUseCase _getLevelUseCase; /// ------------Variables------------ final List keys = [ @@ -27,6 +29,7 @@ class QuestionBloc extends Bloc { GlobalKey(), ]; + /// ------------Controllers------------ /// ------------Functions------------ @@ -34,13 +37,20 @@ class QuestionBloc extends Bloc { ShowCaseWidget.of(context).startShowCase([keys[1]]); } + FutureOr _chooseAnswerEvent(ChooseAnswerEvent event, Emitter emit) { + emit(state.copyWith(chooseAnswer: event.answer)); + } + /// ------------Api Calls------------ - FutureOr _getQuestionEvent(event, emit) async { - await _getQuestionUseCase(event.questionParams).then( + FutureOr _getLevelEvent(GetLevelEvent event, Emitter emit) async { + await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then( (value) { value.fold( (data) { - emit(state.copyWith(getQuestionStatus: BaseComplete(data))); + emit(state.copyWith( + getQuestionStatus: BaseComplete(''), + levelEntity: data, + )); }, (error) { emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage))); diff --git a/lib/features/question/presentation/bloc/question_event.dart b/lib/features/question/presentation/bloc/question_event.dart index b4c4b21..fae2b60 100644 --- a/lib/features/question/presentation/bloc/question_event.dart +++ b/lib/features/question/presentation/bloc/question_event.dart @@ -1,5 +1,17 @@ +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; + sealed class QuestionEvent { const QuestionEvent(); } -class GetQuestionEvent extends QuestionEvent {} +class GetLevelEvent extends QuestionEvent { + final String? id; + + const GetLevelEvent(this.id); +} + +class ChooseAnswerEvent extends QuestionEvent { + final AnswerEntity? answer; + + const ChooseAnswerEvent(this.answer); +} diff --git a/lib/features/question/presentation/bloc/question_state.dart b/lib/features/question/presentation/bloc/question_state.dart index 3c1b18f..8cd1943 100644 --- a/lib/features/question/presentation/bloc/question_state.dart +++ b/lib/features/question/presentation/bloc/question_state.dart @@ -1,15 +1,31 @@ import 'package:hadi_hoda_flutter/core/status/base_status.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; class QuestionState { final BaseStatus getQuestionStatus; + final LevelEntity? levelEntity; + final int currentStep; + final AnswerEntity? chooseAnswer; - const QuestionState({this.getQuestionStatus = const BaseInit()}); + const QuestionState({ + this.getQuestionStatus = const BaseInit(), + this.levelEntity, + this.currentStep = 0, + this.chooseAnswer, + }); QuestionState copyWith({ BaseStatus? getQuestionStatus, + LevelEntity? levelEntity, + int? currentStep, + AnswerEntity? chooseAnswer, }) { return QuestionState( getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus, + levelEntity: levelEntity ?? this.levelEntity, + currentStep: currentStep ?? this.currentStep, + chooseAnswer: chooseAnswer ?? this.chooseAnswer, ); } } diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index abb2798..43cfd98 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -9,7 +9,10 @@ import 'package:hadi_hoda_flutter/core/utils/my_localization.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box.dart'; import 'package:hadi_hoda_flutter/core/widgets/showcase/question_showcase.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart'; +import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart'; +import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/glassy_button.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/left_blob.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart'; @@ -52,10 +55,10 @@ class QuestionPage extends StatelessWidget { MySpaces.s4.gapHeight, _topButtons(), MySpaces.s10.gapHeight, - QuestionStepper(), + _stepper(), _titles(), MySpaces.s14.gapHeight, - _questions(), + _answers(), _bottomDetail(context), ], ), @@ -67,18 +70,21 @@ class QuestionPage extends StatelessWidget { ); } + Widget _topButtons() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ GlassyButton(image: MyAssets.home, onTap: () {}), - Text( - 'Toothbrushing etiquette', - style: GoogleFonts.marhey( - fontSize: 14, - fontWeight: FontWeight.w700, - color: Colors.white, + BlocBuilder( + builder: (context, state) => Text( + state.levelEntity?.title ?? '', + style: GoogleFonts.marhey( + fontSize: 14, + fontWeight: FontWeight.w700, + color: Colors.white, + ), ), ), GlassyButton(image: MyAssets.music, onTap: () {}), @@ -86,46 +92,60 @@ class QuestionPage extends StatelessWidget { ); } + Widget _stepper() { + return BlocBuilder( + builder: (context, state) => QuestionStepper( + length: state.levelEntity?.questions?.length ?? 0, + currentStep: state.currentStep, + ), + ); + } + + Column _titles() { return Column( spacing: MySpaces.s4, children: [ - Text( - 'Question 1 / 5', - style: GoogleFonts.marhey( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Colors.white.withValues(alpha: 0.5), - shadows: [ - Shadow( - offset: Offset(0, 1), - blurRadius: 1, - color: Color(0xFF000000).withValues(alpha: 0.25), - ), - ], + BlocBuilder( + builder: (context, state) => Text( + 'Question ${state.currentStep} / ${state.levelEntity?.questions?.length ?? 0}', + style: GoogleFonts.marhey( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white.withValues(alpha: 0.5), + shadows: [ + Shadow( + offset: Offset(0, 1), + blurRadius: 1, + color: Color(0xFF000000).withValues(alpha: 0.25), + ), + ], + ), ), ), - Text( - 'Heda wants her teeth to be clean. Which of her actions do you think is correct?', - textAlign: TextAlign.center, - style: GoogleFonts.marhey( - fontSize: 22, - fontWeight: FontWeight.w600, - color: Colors.white, - shadows: [ - Shadow( - offset: Offset(0, 1), - blurRadius: 1, - color: Color(0xFF000000).withValues(alpha: 0.25), - ), - ], + BlocBuilder( + builder: (context, state) => Text( + state.levelEntity?.questions?[state.currentStep].title ?? '', + textAlign: TextAlign.center, + style: GoogleFonts.marhey( + fontSize: 22, + fontWeight: FontWeight.w600, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(0, 1), + blurRadius: 1, + color: Color(0xFF000000).withValues(alpha: 0.25), + ), + ], + ), ), ), ], ); } - Expanded _questions() { + Expanded _answers() { return Expanded( child: GridView.builder( itemCount: 4, @@ -137,7 +157,16 @@ class QuestionPage extends StatelessWidget { itemBuilder: (context, index) => QuestionShowcase( globalKey: context.read().keys[index], description: context.translate.tap_to_select, - child: AnswerBox(), + child: BlocBuilder( + builder: (context, state) => AnswerBox( + index: index + 1, + answer: state.levelEntity?.questions?[state.currentStep] + .answers?[index] ?? AnswerEntity(), + selected: state.levelEntity?.questions?[state.currentStep] + .answers?[index].id == state.chooseAnswer?.id, + onTap: (answer) => context.read().add(ChooseAnswerEvent(answer)), + ), + ), ), ), ); diff --git a/lib/features/question/presentation/ui/widgets/question_stepper.dart b/lib/features/question/presentation/ui/widgets/question_stepper.dart index c0fbf9d..dc7a384 100644 --- a/lib/features/question/presentation/ui/widgets/question_stepper.dart +++ b/lib/features/question/presentation/ui/widgets/question_stepper.dart @@ -4,14 +4,17 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; class QuestionStepper extends StatelessWidget { - const QuestionStepper({super.key}); + const QuestionStepper({super.key, this.length = 0, this.currentStep = 0}); + + final int length; + final int currentStep; @override Widget build(BuildContext context) { return SizedBox( height: 80, child: EasyStepper( - activeStep: 1, + activeStep: currentStep, lineStyle: LineStyle( lineLength: 20, lineType: LineType.normal, @@ -29,9 +32,9 @@ class QuestionStepper extends StatelessWidget { padding: EdgeInsets.all(0), enableStepTapping: false, steps: List.generate( - 6, + length, (index) => EasyStep( - customStep: index == 5 + customStep: index == length - 1 ? MyImage(image: MyAssets.diamond, size: 50) : ClipPath( clipper: _StepperClipper(), @@ -49,13 +52,13 @@ class QuestionStepper extends StatelessWidget { padding: EdgeInsets.all(6), decoration: BoxDecoration( shape: BoxShape.circle, - color: index < 1 + color: index < currentStep ? Color(0XFF21B738) - : index == 1 + : index == currentStep + 1 ? Color(0XFF847AC4) : Colors.transparent, ), - child: index < 1 ? MyImage(image: MyAssets.done) : null, + child: index < currentStep ? MyImage(image: MyAssets.done) : null, ), ), ), diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index a51866f..11a4ef3 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -17,14 +17,14 @@ import 'package:hadi_hoda_flutter/features/level/data/datasource/level_datasourc import 'package:hadi_hoda_flutter/features/level/data/repository_impl/level_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart'; -import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_level_usecase.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_levels_usecase.dart'; import 'package:hadi_hoda_flutter/features/question/data/datasource/question_datasource.dart'; import 'package:hadi_hoda_flutter/features/question/data/repository_impl/question_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/repository/question_repository.dart'; -import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_question_usecase.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_level_usecase.dart'; import 'package:hadi_hoda_flutter/features/sample/data/datasource/sample_datasource.dart'; import 'package:hadi_hoda_flutter/features/sample/data/repository_impl/sample_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/sample/domain/repository/sample_repository.dart'; @@ -59,12 +59,12 @@ void initBindings() { /// Question Feature locator.registerLazySingleton(() => QuestionDatasourceImpl(locator())); locator.registerLazySingleton(() => QuestionRepositoryImpl(locator())); - locator.registerLazySingleton(() => GetQuestionUseCase(locator())); + locator.registerLazySingleton(() => GetLevelUseCase(locator())); /// Level Feature locator.registerLazySingleton(() => LocalLevelDatasourceImpl()); locator.registerLazySingleton(() => LevelRepositoryImpl(locator())); - locator.registerLazySingleton(() => GetLevelUseCase(locator())); + locator.registerLazySingleton(() => GetLevelsUseCase(locator())); } Future initDataBase() async { diff --git a/lib/main.dart b/lib/main.dart index 4ca56b1..5940746 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:hadi_hoda_flutter/common_ui/theme/theme_service.dart'; import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; +import 'package:hadi_hoda_flutter/core/utils/storage_path.dart'; import 'package:hadi_hoda_flutter/l10n/app_localizations.dart'; import 'package:hadi_hoda_flutter/init_bindings.dart'; import 'package:flutter/material.dart'; @@ -14,6 +15,7 @@ Future main() async { await Future.wait([ LocalStorage.init(), initDataBase(), + StoragePath.getApplicationDir(), ]); AuthStorage.loadData(); runApp(const MainApp());