From 22b587e0edd961753adeb798df9a992e996608c8 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Thu, 2 Oct 2025 22:24:46 +0330 Subject: [PATCH] add:some changes --- android/app/src/main/AndroidManifest.xml | 1 + assets/json/levels.json | 196 +++++++++--------- lib/common_ui/theme/theme_service.dart | 2 +- lib/core/routers/my_routes.dart | 2 +- lib/core/utils/storage_path.dart | 11 + lib/core/widgets/answer_box/answer_box.dart | 33 +-- .../answer_box/styles/picture_box.dart | 31 +-- .../widgets/answer_box/styles/text_box.dart | 6 +- .../intro/presentation/ui/intro_page.dart | 3 +- .../level/presentation/bloc/level_bloc.dart | 27 ++- .../level/presentation/ui/level_page.dart | 14 +- .../presentation/bloc/question_bloc.dart | 6 + .../presentation/bloc/question_event.dart | 8 + .../presentation/bloc/question_state.dart | 9 + .../presentation/ui/question_page.dart | 103 +++++---- .../ui/widgets/question_stepper.dart | 17 +- lib/main.dart | 2 + 17 files changed, 283 insertions(+), 188 deletions(-) create mode 100644 lib/core/utils/storage_path.dart 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( - initialLocation: Routes.levelPage, + initialLocation: Routes.introPage, navigatorKey: ContextProvider.navigatorKey, routes: [ GoRoute( 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/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/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart index 56f7da1..ed56711 100644 --- a/lib/features/level/presentation/bloc/level_bloc.dart +++ b/lib/features/level/presentation/bloc/level_bloc.dart @@ -17,7 +17,12 @@ class LevelBloc extends Bloc { this._getLeveslUseCase, ) : super(const LevelState()) { on(_getLevelListEvent); - on(_chooseLevelEvent); + } + + @override + Future close() { + scrollController.dispose(); + return super.close(); } /// ------------UseCases------------ @@ -30,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), ]; @@ -55,12 +60,9 @@ class LevelBloc extends Bloc { /// ------------Controllers------------ + final ScrollController scrollController = ScrollController(); /// ------------Functions------------ - FutureOr _chooseLevelEvent(ChooseLevelEvent event, - Emitter emit) async { - emit(state.copyWith(chooseLevel: event.level)); - } void goToQuestionPage(BuildContext context, LevelEntity level){ context.pushNamed( @@ -76,12 +78,21 @@ class LevelBloc extends Bloc { Emitter emit) async { 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/ui/level_page.dart b/lib/features/level/presentation/ui/level_page.dart index 591d335..22335ce 100644 --- a/lib/features/level/presentation/ui/level_page.dart +++ b/lib/features/level/presentation/ui/level_page.dart @@ -22,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), ], ), @@ -47,7 +48,7 @@ class LevelPage extends StatelessWidget { return Positioned( bottom: MediaQuery .viewPaddingOf(context) - .bottom, + .bottom + 10, right: MySpaces.s16, left: MySpaces.s16, child: HintLevelWidget( @@ -107,7 +108,9 @@ class LevelPage extends StatelessWidget { child: LevelWidget( index: context.read().topLocationList[index].index ?? 0, level: context.read().top12LevelList[index], - onTap: (LevelEntity level) => context.read().add(ChooseLevelEvent(level)), + onTap: (LevelEntity level) { + + }, ), ), ), @@ -139,7 +142,10 @@ class LevelPage extends StatelessWidget { child: LevelWidget( index: context.read().bottomLocationList[index].index ?? 0, level: context.read().bottom8LevelList[index], - onTap: (LevelEntity level) => context.read().add(ChooseLevelEvent(level)), + type: index == 0 ? LevelType.current : LevelType.unFinished, + onTap: (LevelEntity level) { + + }, ), ), ), diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index ad63918..7d37a36 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -3,6 +3,7 @@ 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/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'; @@ -14,6 +15,7 @@ class QuestionBloc extends Bloc { this._getLevelUseCase, ) : super(const QuestionState()) { on(_getLevelEvent); + on(_chooseAnswerEvent); } /// ------------UseCases------------ @@ -35,6 +37,10 @@ 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 _getLevelEvent(GetLevelEvent event, Emitter emit) async { await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then( diff --git a/lib/features/question/presentation/bloc/question_event.dart b/lib/features/question/presentation/bloc/question_event.dart index f82b6f9..fae2b60 100644 --- a/lib/features/question/presentation/bloc/question_event.dart +++ b/lib/features/question/presentation/bloc/question_event.dart @@ -1,3 +1,5 @@ +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; + sealed class QuestionEvent { const QuestionEvent(); } @@ -7,3 +9,9 @@ class GetLevelEvent extends QuestionEvent { 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 f44e642..8cd1943 100644 --- a/lib/features/question/presentation/bloc/question_state.dart +++ b/lib/features/question/presentation/bloc/question_state.dart @@ -1,22 +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(), 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/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());