From 471232acc540db1582d29cc3dfac8cdc759993fe Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sat, 27 Sep 2025 12:54:45 +0330 Subject: [PATCH 1/6] add: question base feature --- lib/core/params/question_params.dart | 13 ++++++ .../data/datasource/question_datasource.dart | 28 +++++++++++++ .../question/data/model/question_model.dart | 13 ++++++ .../question_repository_impl.dart | 29 +++++++++++++ .../domain/entity/question_entity.dart | 14 +++++++ .../repository/question_repository.dart | 8 ++++ .../domain/usecases/get_question_usecase.dart | 17 ++++++++ .../presentation/bloc/question_bloc.dart | 41 +++++++++++++++++++ .../presentation/bloc/question_event.dart | 5 +++ .../presentation/bloc/question_state.dart | 15 +++++++ .../presentation/ui/question_page.dart | 10 +++++ lib/init_bindings.dart | 9 ++++ 12 files changed, 202 insertions(+) create mode 100644 lib/core/params/question_params.dart create mode 100644 lib/features/question/data/datasource/question_datasource.dart create mode 100644 lib/features/question/data/model/question_model.dart create mode 100644 lib/features/question/data/repository_impl/question_repository_impl.dart create mode 100644 lib/features/question/domain/entity/question_entity.dart create mode 100644 lib/features/question/domain/repository/question_repository.dart create mode 100644 lib/features/question/domain/usecases/get_question_usecase.dart create mode 100644 lib/features/question/presentation/bloc/question_bloc.dart create mode 100644 lib/features/question/presentation/bloc/question_event.dart create mode 100644 lib/features/question/presentation/bloc/question_state.dart create mode 100644 lib/features/question/presentation/ui/question_page.dart diff --git a/lib/core/params/question_params.dart b/lib/core/params/question_params.dart new file mode 100644 index 0000000..0c6fff1 --- /dev/null +++ b/lib/core/params/question_params.dart @@ -0,0 +1,13 @@ +class QuestionParams { + int? id; + + QuestionParams({this.id}); + + QuestionParams copyWith({ + int? id, + }) { + return QuestionParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/features/question/data/datasource/question_datasource.dart b/lib/features/question/data/datasource/question_datasource.dart new file mode 100644 index 0000000..2142b93 --- /dev/null +++ b/lib/features/question/data/datasource/question_datasource.dart @@ -0,0 +1,28 @@ +import 'package:hadi_hoda_flutter/core/constants/my_api.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'; + +abstract class IQuestionDatasource { + Future getData({required QuestionParams params}); +} + +class QuestionDatasourceImpl implements IQuestionDatasource { + final IHttpRequest httpRequest; + + 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), + ); + } +} diff --git a/lib/features/question/data/model/question_model.dart b/lib/features/question/data/model/question_model.dart new file mode 100644 index 0000000..489b66c --- /dev/null +++ b/lib/features/question/data/model/question_model.dart @@ -0,0 +1,13 @@ +import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; + +class QuestionModel extends QuestionEntity { + const QuestionModel({ + super.id, + }); + + factory QuestionModel.fromJson(Map json) { + return QuestionModel( + id: json['id'], + ); + } +} diff --git a/lib/features/question/data/repository_impl/question_repository_impl.dart b/lib/features/question/data/repository_impl/question_repository_impl.dart new file mode 100644 index 0000000..af5a1c6 --- /dev/null +++ b/lib/features/question/data/repository_impl/question_repository_impl.dart @@ -0,0 +1,29 @@ +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/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 { + final IQuestionDatasource datasource; + + const QuestionRepositoryImpl(this.datasource); + + @override + Future> getData({required QuestionParams params}) async { + try { + final QuestionEntity response = await datasource.getData(params: params); + return DataState.success(response); + } on MyException catch (e) { + return DataState.error(e); + } catch (e) { + if (kDebugMode) { + rethrow; + } else { + return DataState.error(MyException(errorMessage: '$e')); + } + } + } +} diff --git a/lib/features/question/domain/entity/question_entity.dart b/lib/features/question/domain/entity/question_entity.dart new file mode 100644 index 0000000..377f2b1 --- /dev/null +++ b/lib/features/question/domain/entity/question_entity.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class QuestionEntity extends Equatable { + final int? id; + + const QuestionEntity({ + this.id, + }); + + @override + List get props => [ + id, + ]; +} diff --git a/lib/features/question/domain/repository/question_repository.dart b/lib/features/question/domain/repository/question_repository.dart new file mode 100644 index 0000000..3c9e008 --- /dev/null +++ b/lib/features/question/domain/repository/question_repository.dart @@ -0,0 +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'; + +abstract class IQuestionRepository { + Future> getData({required QuestionParams params}); +} diff --git a/lib/features/question/domain/usecases/get_question_usecase.dart b/lib/features/question/domain/usecases/get_question_usecase.dart new file mode 100644 index 0000000..697534b --- /dev/null +++ b/lib/features/question/domain/usecases/get_question_usecase.dart @@ -0,0 +1,17 @@ +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/question/domain/repository/question_repository.dart'; + +class GetQuestionUseCase implements UseCase { + final IQuestionRepository repository; + + const GetQuestionUseCase(this.repository); + + @override + Future> call(QuestionParams params) { + return repository.getData(params: params); + } +} diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart new file mode 100644 index 0000000..69823db --- /dev/null +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -0,0 +1,41 @@ +import 'dart:async'; +import 'package:bloc/bloc.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/presentation/bloc/question_event.dart'; +import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart'; + +class QuestionBloc extends Bloc { + /// ------------constructor------------ + QuestionBloc( + this._getQuestionUseCase, + ) : super(const QuestionState()) { + on(_getQuestionEvent); + } + + /// ------------UseCases------------ + final GetQuestionUseCase _getQuestionUseCase; + + /// ------------Variables------------ + + /// ------------Controllers------------ + + /// ------------Functions------------ + + /// ------------Api Calls------------ + FutureOr _getQuestionEvent(event, emit) async { + await _getQuestionUseCase(event.questionParams).then( + (value) { + value.fold( + (data) { + emit(state.copyWith(getQuestionStatus: BaseComplete(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 new file mode 100644 index 0000000..b4c4b21 --- /dev/null +++ b/lib/features/question/presentation/bloc/question_event.dart @@ -0,0 +1,5 @@ +sealed class QuestionEvent { + const QuestionEvent(); +} + +class GetQuestionEvent extends QuestionEvent {} diff --git a/lib/features/question/presentation/bloc/question_state.dart b/lib/features/question/presentation/bloc/question_state.dart new file mode 100644 index 0000000..3c1b18f --- /dev/null +++ b/lib/features/question/presentation/bloc/question_state.dart @@ -0,0 +1,15 @@ +import 'package:hadi_hoda_flutter/core/status/base_status.dart'; + +class QuestionState { + final BaseStatus getQuestionStatus; + + const QuestionState({this.getQuestionStatus = const BaseInit()}); + + QuestionState copyWith({ + BaseStatus? getQuestionStatus, + }) { + return QuestionState( + getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus, + ); + } +} diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart new file mode 100644 index 0000000..e4aa915 --- /dev/null +++ b/lib/features/question/presentation/ui/question_page.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class QuestionPage extends StatelessWidget { + const QuestionPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index faea518..8bb826b 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -4,6 +4,10 @@ import 'package:hadi_hoda_flutter/features/intro/data/datasource/intro_datasourc import 'package:hadi_hoda_flutter/features/intro/data/repository_impl/intro_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/usecases/get_intro_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/repository/question_repository.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_question_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'; @@ -25,4 +29,9 @@ void initBindings() { locator.registerLazySingleton(() => IntroDatasourceImpl(locator())); locator.registerLazySingleton(() => IntroRepositoryImpl(locator())); locator.registerLazySingleton(() => GetIntroUseCase(locator())); + + /// Question Feature + locator.registerLazySingleton(() => QuestionDatasourceImpl(locator())); + locator.registerLazySingleton(() => QuestionRepositoryImpl(locator())); + locator.registerLazySingleton(() => GetQuestionUseCase(locator())); } From cf770a8b2e7c4bb5dc0669d68bb58cf725905052 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sat, 27 Sep 2025 13:00:25 +0330 Subject: [PATCH 2/6] add: question routes --- lib/core/routers/my_routes.dart | 11 +++++++++++ lib/features/intro/presentation/ui/intro_page.dart | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index f6b6730..55f4ca7 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -3,6 +3,8 @@ import 'package:go_router/go_router.dart'; import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/ui/intro_page.dart'; +import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart'; +import 'package:hadi_hoda_flutter/features/question/presentation/ui/question_page.dart'; import 'package:hadi_hoda_flutter/init_bindings.dart'; class Routes { @@ -11,6 +13,7 @@ class Routes { factory Routes() => _i; static const String introPage = '/intro_page'; + static const String questionPage = '/question_page'; } GoRouter get appPages => GoRouter( @@ -25,5 +28,13 @@ GoRouter get appPages => GoRouter( child: const IntroPage(), ), ), + GoRoute( + name: Routes.questionPage, + path: Routes.questionPage, + builder: (context, state) => BlocProvider( + create: (context) => QuestionBloc(locator()), + child: const QuestionPage(), + ), + ), ], ); diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index 4ef6fd6..276f06b 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; +import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; import 'package:hadi_hoda_flutter/core/utils/check_platform.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/about_us_dialog/about_us_dialog.dart'; class IntroPage extends StatelessWidget { const IntroPage({super.key}); @@ -79,7 +80,7 @@ class IntroPage extends StatelessWidget { size: checkSize(context: context, mobile: 90, tablet: 160), ), onTap: () { - showAboutUsDialog(context: context); + context.pushNamed(Routes.questionPage); }, ), MyImage( From 2dc264bdcb3b5582e3412ea1ddb28cb5d756a29e Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sat, 27 Sep 2025 15:05:50 +0330 Subject: [PATCH 3/6] add: question page --- assets/images/bubble_chat_left.svg | 10 + assets/images/bubble_chat_right.svg | 3 + assets/images/home.svg | 3 + assets/images/music.svg | 3 + assets/images/pattern.png | Bin 0 -> 3182 bytes assets/images/persons.png | Bin 0 -> 24063 bytes lib/common_ui/resources/my_assets.dart | 6 + lib/core/widgets/answer_box/answer_box.dart | 1 - .../intro/presentation/ui/intro_page.dart | 2 +- .../presentation/ui/question_page.dart | 208 +++++++++++++++++- 10 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 assets/images/bubble_chat_left.svg create mode 100644 assets/images/bubble_chat_right.svg create mode 100644 assets/images/home.svg create mode 100644 assets/images/music.svg create mode 100644 assets/images/pattern.png create mode 100644 assets/images/persons.png diff --git a/assets/images/bubble_chat_left.svg b/assets/images/bubble_chat_left.svg new file mode 100644 index 0000000..9d64c11 --- /dev/null +++ b/assets/images/bubble_chat_left.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/bubble_chat_right.svg b/assets/images/bubble_chat_right.svg new file mode 100644 index 0000000..b8ab88d --- /dev/null +++ b/assets/images/bubble_chat_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/home.svg b/assets/images/home.svg new file mode 100644 index 0000000..d9d0a2c --- /dev/null +++ b/assets/images/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/music.svg b/assets/images/music.svg new file mode 100644 index 0000000..03eb33b --- /dev/null +++ b/assets/images/music.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/pattern.png b/assets/images/pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc606242c5adeda279e7fad2d609347cb68e543 GIT binary patch literal 3182 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�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 { }); }, child: SizedBox( - width: 170, child: Stack( alignment: Alignment.bottomCenter, clipBehavior: Clip.none, diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index 276f06b..bc2c0d5 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -80,7 +80,7 @@ class IntroPage extends StatelessWidget { size: checkSize(context: context, mobile: 90, tablet: 160), ), onTap: () { - context.pushNamed(Routes.questionPage); + context.goNamed(Routes.questionPage); }, ), MyImage( diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index e4aa915..798d48a 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -1,10 +1,216 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.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/answer_box.dart'; class QuestionPage extends StatelessWidget { const QuestionPage({super.key}); @override Widget build(BuildContext context) { - return const Scaffold(); + return Scaffold( + body: Container( + height: context.heightScreen, + width: context.widthScreen, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFF6930DA), Color(0XFF263AA1)], + ), + image: DecorationImage( + image: AssetImage(MyAssets.pattern), + scale: 3, + repeat: ImageRepeat.repeat, + colorFilter: ColorFilter.mode( + Colors.black.withValues(alpha: 0.3), + BlendMode.srcIn, + ), + ), + ), + child: SafeArea( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 48, + height: 48, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withValues(alpha: 0.3), + Color(0XFF6930DA).withValues(alpha: 0.1), + ], + ), + border: Border.all( + color: Colors.white.withValues(alpha: 0.3), + ), + ), + child: MyImage(image: MyAssets.home), + ), + Text( + 'Toothbrushing etiquette', + style: GoogleFonts.marhey( + fontSize: 14, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + Container( + width: 48, + height: 48, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withValues(alpha: 0.3), + Color(0XFF6930DA).withValues(alpha: 0.1), + ], + ), + border: Border.all( + color: Colors.white.withValues(alpha: 0.3), + ), + ), + child: MyImage(image: MyAssets.music), + ), + ], + ), + Column( + 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), + ), + ], + ), + ), + Text( + 'Heda wants her teeth to be clean. Which of her actions do you think is correct?', + 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( + child: GridView.builder( + itemCount: 4, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 20, + mainAxisSpacing: 30, + ), + itemBuilder: (context, index) => AnswerBox(), + ), + ), + SizedBox( + width: context.widthScreen, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + Positioned.directional( + textDirection: Directionality.of(context), + start: 0, + top: -10, + child: Stack( + alignment: Alignment.center, + children: [ + MyImage(image: MyAssets.bubbleChatLeft), + Text( + 'Your answer\nwas not correct.', + textAlign: TextAlign.center, + style: GoogleFonts.marhey( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0XFFB5AEEE), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsetsDirectional.only(end: 90), + child: MyImage(image: MyAssets.persons), + ), + Positioned.directional( + textDirection: Directionality.of(context), + start: 220, + top: -20, + child: Stack( + alignment: Alignment.center, + children: [ + MyImage(image: MyAssets.bubbleChatRight), + Text( + 'Be more\ncareful.', + textAlign: TextAlign.center, + style: GoogleFonts.marhey( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0XFFB5AEEE), + ), + ), + ], + ), + ), + Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + bottom: 10, + child: Container( + height: 48, + width: 48, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFFA393FF), Color(0XFFC6BCFB)], + ), + ), + child: Icon( + Icons.refresh, + size: 40, + color: Color(0XFF263AA1), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); } } From 827e8358e7f217ba99e5278ac93d385623b20253 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sun, 28 Sep 2025 19:46:12 +0330 Subject: [PATCH 4/6] add: question page ui --- assets/images/diamond.png | Bin 0 -> 2675 bytes assets/images/done.svg | 4 + lib/common_ui/resources/my_assets.dart | 2 + .../presentation/ui/question_page.dart | 300 ++++++++---------- .../ui/widgets/glassy_button.dart | 41 +++ .../presentation/ui/widgets/left_blob.dart | 27 ++ .../ui/widgets/question_stepper.dart | 102 ++++++ .../ui/widgets/refresh_button.dart | 32 ++ .../presentation/ui/widgets/right_blob.dart | 27 ++ pubspec.lock | 34 +- pubspec.yaml | 1 + 11 files changed, 395 insertions(+), 175 deletions(-) create mode 100644 assets/images/diamond.png create mode 100644 assets/images/done.svg create mode 100644 lib/features/question/presentation/ui/widgets/glassy_button.dart create mode 100644 lib/features/question/presentation/ui/widgets/left_blob.dart create mode 100644 lib/features/question/presentation/ui/widgets/question_stepper.dart create mode 100644 lib/features/question/presentation/ui/widgets/refresh_button.dart create mode 100644 lib/features/question/presentation/ui/widgets/right_blob.dart diff --git a/assets/images/diamond.png b/assets/images/diamond.png new file mode 100644 index 0000000000000000000000000000000000000000..ba816b19d3e4f20ee1db5e8eb2a2d3017d5e9cc3 GIT binary patch literal 2675 zcmV-(3XJuMP)r5-#N;TD1{SwMkJWh}5c8`w-GdRS8nk^r4R>ytEH_ z2zg3hs-RX?r3Qtzsd`JBKuJX@5tjlH7bj~%0CO?6*L$0tnKS*Lv%AJ#yu>$h#PQ7R z%+CD2@BF{ZECMqanM|<<3VQ%`8B!hs;-}JSF@(=x2uyQa&XitIDtw8zXd8bf8iEj~ z5P>r~3@g@`PCKVC%VWym#>-LkI>9zi{##>>QKw-!r)Fx*l)y8+`5pYm>*KwwZxLad zgYc1_ry;FVQ<2)Fz%!Ys4XU7lcZ@$H@s0w%)#>6+QNax(c%Gse+7O6un z#O1*PcFYmDBW-Xo#(2DHfC_00jGV$qAWPum*GKTl#bE*p{G&C*m*+Rb9Sg3CJNb9t zgdV3f=;i1oOt2uw741{obZ5Kc9a0FJiSonC@$APmq zaQo5#1hpnz>N*qpKy(AH1nb`I%1pYf0lQ7ZG%KGjOkXH;N3_!h4jIdcmhy{)|~X^iA| zh17Ca+?V{WkE88HY)V+QXS5+KZ07d&_kgFTBo66)m*Cshug3NNoX54755hW^K|8e@l?D7FZ&!-o z7YImMl&dq$MHn1ZC^4^x+S4>3WU7mTN(?yEljN!?(%bV|p`19ci6QV9$YoBZLf_+k znO?6Cmnpe$6jG_Eu40b?AC-)5h&Z~E4!*l}4N~*wqVIPnaN%cv!o#;e!RjT{z@uQ(GE=Ysqj~_tuUd|J=h^v2-q+?r!9>5=UM=fp>oVHspub@x^rxXAH~J7B(d% zTmn1PtP>+JS0}*$ZB7xoiO@-bB;j?Ogai-5$`q|lke#iRj`qAnSmNZiJQCpSUVz7 zr30Czo^ra-8{o?MJR%w@cv4SO5I6;AR4780M&Q!$M5{!+-No}=DXdIZF{Ri-RUFd0 z_C(|g?9>Twu=kWf?W~hMqHPwP(I7f1U4^I1V26FJT5fXD)McK>DTj65C${6P9 z)kq0rgiejkt}w8Hr_6Unb+Q1^NUlnI6Qve|?d1_;{cxBv|4PlTOj={*a;TxlM?K$L ze3y6LtoQR?h-da+!Qc(TGqWfr>>%gy25bZ7r{J?{N1-lK%Jd-G%WhW;Esw;oaE*gi zkGlAFmxULPmQZ3AD&(1=n0Y+d>^s+wZGn^PZN^d=_2P%j6mUMz&!kPt5_|iAl0(09da#3otmdoT`-9E~nq$bzs z{Tw~??EZdSy^%t2Ct#rRLFA0}G#Q;L159|+9!9!&RQjcmtu0$%VV=lq1PcJlu;L+{ zRA>wk?5$L}zzUzmsxAjVcw!mWEt@wUrrLB(4*U|6zl`3k9iQy?vFlfPDCTxG%vP0N zx0j`0Gv{d|Gqf9)NfUm!tq1Fu&25C! z#~yv{q~G#0y}5mS!V9(ISN=7O-yE@_1_A>vyAsR_4w-~$Xtd`wbXX4QMMC>`i-~!M z&=*-;t&rJC<*{M0gI(J?uwiLyW8g^aO)p982d41zL?$!HH+@$JYL7l1VHfzF{X@8V zja9F|3=wyjMG{PRqUv!SoC@V!nclAU9j4df`7j){V8ddC-A^X4acN>wz_l^8-&N`) zb74T?o3An8nBLR~(-;HE=3A11)-Mc09#dyfc}PMSFGpSldgCG+yPt5eafvl0;Aw#y z_&Uk_`b2=Azv-ER;Eb15JtgbNBF33FvYG=qR5i&U16qRfqI0!%X6Cv{FuaS0M7{Az}HLWOo*5N#{gxc zRba4o^G)Kp$87@7j)yU8;P*%d`2IinQ`{Y3*Gh#QE5rirpS65tjRA=fMKk z#*D!A_^3C(pD(;mH@;$afoo&N;1$U@nF;@A;sF5H#*D%BsQlnB4pxpHEO2ei3b-Cd hG7+oY0|u^*{{yYKA1?Le*=Ya(002ovPDHLkV1j%rA0z+( literal 0 HcmV?d00001 diff --git a/assets/images/done.svg b/assets/images/done.svg new file mode 100644 index 0000000..28d7f58 --- /dev/null +++ b/assets/images/done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart index b1523d5..473d276 100644 --- a/lib/common_ui/resources/my_assets.dart +++ b/lib/common_ui/resources/my_assets.dart @@ -23,4 +23,6 @@ class MyAssets { static const String persons = 'assets/images/persons.png'; static const String bubbleChatLeft = 'assets/images/bubble_chat_left.svg'; static const String bubbleChatRight = 'assets/images/bubble_chat_right.svg'; + static const String diamond = 'assets/images/diamond.png'; + static const String done = 'assets/images/done.svg'; } \ No newline at end of file diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index 798d48a..c3ae025 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -1,9 +1,16 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.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/gap.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/answer_box.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'; +import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/refresh_button.dart'; +import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/right_blob.dart'; class QuestionPage extends StatelessWidget { const QuestionPage({super.key}); @@ -31,185 +38,130 @@ class QuestionPage extends StatelessWidget { ), ), child: SafeArea( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: 48, - height: 48, - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.white.withValues(alpha: 0.3), - Color(0XFF6930DA).withValues(alpha: 0.1), - ], - ), - border: Border.all( - color: Colors.white.withValues(alpha: 0.3), - ), - ), - child: MyImage(image: MyAssets.home), - ), - Text( - 'Toothbrushing etiquette', - style: GoogleFonts.marhey( - fontSize: 14, - fontWeight: FontWeight.w700, - color: Colors.white, - ), - ), - Container( - width: 48, - height: 48, - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.white.withValues(alpha: 0.3), - Color(0XFF6930DA).withValues(alpha: 0.1), - ], - ), - border: Border.all( - color: Colors.white.withValues(alpha: 0.3), - ), - ), - child: MyImage(image: MyAssets.music), - ), - ], - ), - Column( - 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), - ), - ], - ), - ), - Text( - 'Heda wants her teeth to be clean. Which of her actions do you think is correct?', - 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( - child: GridView.builder( - itemCount: 4, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 20, - mainAxisSpacing: 30, - ), - itemBuilder: (context, index) => AnswerBox(), - ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: MySpaces.s16), + child: Column( + children: [ + MySpaces.s4.gapHeight, + _topButtons(), + MySpaces.s10.gapHeight, + QuestionStepper(), + _titles(), + MySpaces.s14.gapHeight, + _questions(), + _bottomDetail(context), + ], + ), + ), + ), + ), + ); + } + + 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, + ), + ), + GlassyButton(image: MyAssets.music, onTap: () {}), + ], + ); + } + + 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), ), - SizedBox( - width: context.widthScreen, - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - Positioned.directional( - textDirection: Directionality.of(context), - start: 0, - top: -10, - child: Stack( - alignment: Alignment.center, - children: [ - MyImage(image: MyAssets.bubbleChatLeft), - Text( - 'Your answer\nwas not correct.', - textAlign: TextAlign.center, - style: GoogleFonts.marhey( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0XFFB5AEEE), - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsetsDirectional.only(end: 90), - child: MyImage(image: MyAssets.persons), - ), - Positioned.directional( - textDirection: Directionality.of(context), - start: 220, - top: -20, - child: Stack( - alignment: Alignment.center, - children: [ - MyImage(image: MyAssets.bubbleChatRight), - Text( - 'Be more\ncareful.', - textAlign: TextAlign.center, - style: GoogleFonts.marhey( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0XFFB5AEEE), - ), - ), - ], - ), - ), - Positioned.directional( - textDirection: Directionality.of(context), - end: 0, - bottom: 10, - child: Container( - height: 48, - width: 48, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0XFFA393FF), Color(0XFFC6BCFB)], - ), - ), - child: Icon( - Icons.refresh, - size: 40, - color: Color(0XFF263AA1), - ), - ), - ), - ], - ), + ], + ), + ), + 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), ), ], ), ), + ], + ); + } + + Expanded _questions() { + return Expanded( + child: GridView.builder( + itemCount: 4, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: MySpaces.s20, + mainAxisSpacing: 50, + ), + itemBuilder: (context, index) => AnswerBox(), + ), + ); + } + + Widget _bottomDetail(BuildContext context) { + return SizedBox( + width: context.widthScreen, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + Positioned.directional( + textDirection: Directionality.of(context), + start: 0, + top: -10, + child: LeftBlob(), + ), + Padding( + padding: const EdgeInsetsDirectional.only(end: 60), + child: MyImage(image: MyAssets.persons), + ), + Positioned.directional( + textDirection: Directionality.of(context), + start: 210, + top: -20, + child: RightBlob(), + ), + Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + bottom: 10, + child: RefreshButton( + onTap: () {}, + ), + ), + ], ), ); } diff --git a/lib/features/question/presentation/ui/widgets/glassy_button.dart b/lib/features/question/presentation/ui/widgets/glassy_button.dart new file mode 100644 index 0000000..e346f4d --- /dev/null +++ b/lib/features/question/presentation/ui/widgets/glassy_button.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; +import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; + +class GlassyButton extends StatelessWidget { + const GlassyButton({super.key, required this.image, this.onTap}); + + final String image; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Ink( + width: 48, + height: 48, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withValues(alpha: 0.3), + Color(0XFF6930DA).withValues(alpha: 0.1), + ], + ), + border: Border.all(color: Colors.white.withValues(alpha: 0.3)), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.all(Radius.circular(100)), + child: Padding( + padding: const EdgeInsets.all(MySpaces.s12), + child: MyImage(image: image), + ), + ), + ), + ); + } +} diff --git a/lib/features/question/presentation/ui/widgets/left_blob.dart b/lib/features/question/presentation/ui/widgets/left_blob.dart new file mode 100644 index 0000000..0964766 --- /dev/null +++ b/lib/features/question/presentation/ui/widgets/left_blob.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; + +class LeftBlob extends StatelessWidget { + const LeftBlob({super.key}); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + MyImage(image: MyAssets.bubbleChatLeft), + Text( + 'Your answer\nwas not correct.', + textAlign: TextAlign.center, + style: GoogleFonts.marhey( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0XFFB5AEEE), + ), + ), + ], + ); + } +} diff --git a/lib/features/question/presentation/ui/widgets/question_stepper.dart b/lib/features/question/presentation/ui/widgets/question_stepper.dart new file mode 100644 index 0000000..c0fbf9d --- /dev/null +++ b/lib/features/question/presentation/ui/widgets/question_stepper.dart @@ -0,0 +1,102 @@ +import 'package:easy_stepper/easy_stepper.dart'; +import 'package:flutter/material.dart'; +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}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 80, + child: EasyStepper( + activeStep: 1, + lineStyle: LineStyle( + lineLength: 20, + lineType: LineType.normal, + defaultLineColor: Color(0XFFDFDDF6), + lineThickness: 5, + finishedLineColor: Color(0XFF21B738), + ), + activeStepBackgroundColor: Colors.transparent, + finishedStepBackgroundColor: Colors.transparent, + unreachedStepBackgroundColor: Colors.transparent, + internalPadding: 0, + showLoadingAnimation: false, + stepRadius: 18, + showStepBorder: false, + padding: EdgeInsets.all(0), + enableStepTapping: false, + steps: List.generate( + 6, + (index) => EasyStep( + customStep: index == 5 + ? MyImage(image: MyAssets.diamond, size: 50) + : ClipPath( + clipper: _StepperClipper(), + child: Container( + height: 32, + width: 32, + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + color: Color(0XFFDFDDF6), + shape: BoxShape.circle, + ), + child: ClipPath( + clipper: _StepperClipper(), + child: Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: index < 1 + ? Color(0XFF21B738) + : index == 1 + ? Color(0XFF847AC4) + : Colors.transparent, + ), + child: index < 1 ? MyImage(image: MyAssets.done) : null, + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +class _StepperClipper extends CustomClipper { + @override + Path getClip(Size size) { + // Original SVG viewBox: width=34, height=33 + final sx = size.width / 34.0; + final sy = size.height / 33.0; + + final p = Path() + ..moveTo(33.3479 * sx, 14.8127 * sy) + ..cubicTo( + 33.3479 * sx, 23.7042 * sy, + 27.2015 * sx, 32.9501 * sy, + 17.8599 * sx, 32.9501 * sy, + )..cubicTo( + 8.51818 * sx, 32.9501 * sy, + 0.945251 * sx, 25.7421 * sy, + 0.945251 * sx, 16.8507 * sy, + )..cubicTo( + 0.945251 * sx, 7.95917 * sy, + 8.51818 * sx, 0.751205 * sy, + 17.8599 * sx, 0.751205 * sy, + )..cubicTo( + 27.2015 * sx, 0.751205 * sy, + 33.3479 * sx, 5.92127 * sy, + 33.3479 * sx, 14.8127 * sy, + )..close(); + + return p; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) => false; +} diff --git a/lib/features/question/presentation/ui/widgets/refresh_button.dart b/lib/features/question/presentation/ui/widgets/refresh_button.dart new file mode 100644 index 0000000..801678e --- /dev/null +++ b/lib/features/question/presentation/ui/widgets/refresh_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; + +class RefreshButton extends StatelessWidget { + const RefreshButton({super.key, this.onTap,}); + + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return Material( + color: context.noColor, + child: Ink( + height: 48, + width: 48, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFFA393FF), Color(0XFFC6BCFB)], + ), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.all(Radius.circular(100)), + child: Icon(Icons.refresh, size: 40, color: Color(0XFF263AA1)), + ), + ), + ); + } +} diff --git a/lib/features/question/presentation/ui/widgets/right_blob.dart b/lib/features/question/presentation/ui/widgets/right_blob.dart new file mode 100644 index 0000000..b10237a --- /dev/null +++ b/lib/features/question/presentation/ui/widgets/right_blob.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; + +class RightBlob extends StatelessWidget { + const RightBlob({super.key}); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + MyImage(image: MyAssets.bubbleChatRight), + Text( + 'Be more\ncareful.', + textAlign: TextAlign.center, + style: GoogleFonts.marhey( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0XFFB5AEEE), + ), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 51806b4..2c648e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" args: dependency: transitive description: @@ -81,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + easy_stepper: + dependency: "direct main" + description: + name: easy_stepper + sha256: "63f66314a509ec690c8152a41288961fd96ba9e92ef184299f068a5e78bd16ad" + url: "https://pub.dev" + source: hosted + version: "0.8.5+1" equatable: dependency: "direct main" description: @@ -245,6 +261,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + lottie: + dependency: transitive + description: + name: lottie + sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" + url: "https://pub.dev" + source: hosted + version: "3.3.2" matcher: dependency: transitive description: @@ -373,6 +397,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" pretty_dio_logger: dependency: "direct main" description: @@ -572,4 +604,4 @@ packages: version: "6.6.1" sdks: dart: ">=3.9.2 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index dd2a1d6..366e5a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: bloc: ^9.0.0 dio: ^5.9.0 + easy_stepper: ^0.8.5+1 equatable: ^2.0.7 flutter: sdk: flutter From 48289bf6eb77564f5e27b82aae8bc842ca07e164 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sun, 28 Sep 2025 20:18:57 +0330 Subject: [PATCH 5/6] fix: answer box --- assets/images/correct.svg | 17 ++++ assets/images/wrong.svg | 17 ++++ lib/common_ui/resources/my_assets.dart | 2 + lib/core/widgets/answer_box/answer_box.dart | 5 +- .../answer_box/styles/picture_box.dart | 86 ++++++++++++++++++- .../widgets/answer_box/styles/text_box.dart | 62 ++++++------- .../presentation/ui/question_page.dart | 9 +- 7 files changed, 152 insertions(+), 46 deletions(-) create mode 100644 assets/images/correct.svg create mode 100644 assets/images/wrong.svg diff --git a/assets/images/correct.svg b/assets/images/correct.svg new file mode 100644 index 0000000..17b0900 --- /dev/null +++ b/assets/images/correct.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/images/wrong.svg b/assets/images/wrong.svg new file mode 100644 index 0000000..ebf05ff --- /dev/null +++ b/assets/images/wrong.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart index 473d276..f2a20e9 100644 --- a/lib/common_ui/resources/my_assets.dart +++ b/lib/common_ui/resources/my_assets.dart @@ -25,4 +25,6 @@ class MyAssets { static const String bubbleChatRight = 'assets/images/bubble_chat_right.svg'; static const String diamond = 'assets/images/diamond.png'; static const String done = 'assets/images/done.svg'; + static const String correct = 'assets/images/correct.svg'; + static const String wrong = 'assets/images/wrong.svg'; } \ 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 6c54390..b22198e 100644 --- a/lib/core/widgets/answer_box/answer_box.dart +++ b/lib/core/widgets/answer_box/answer_box.dart @@ -33,10 +33,7 @@ class _AnswerBoxState extends State { left: 0, right: 0, bottom: -36, - child: SizedBox( - height: 60, - child: AnswerTextBox(), - ), + child: AnswerTextBox(), ), ], ), diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index 7263d0f..1b9ab51 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.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/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; class AnswerPictureBox extends StatelessWidget { @@ -14,8 +17,53 @@ class AnswerPictureBox extends StatelessWidget { foregroundPainter: _SvgCustomPainter(selected), child: ClipPath( clipper: _SvgCustomClipper(), - child: MyImage( - image: MyAssets.backgroundIntro, fit: BoxFit.cover, size: 170), + child: Stack( + children: [ + MyImage( + image: MyAssets.backgroundIntro, + fit: BoxFit.cover, + size: 170, + ), + PositionedDirectional( + top: MySpaces.s12, + start: MySpaces.s12, + child: ClipPath( + clipper: _CountClipper(), + child: Container( + height: MySpaces.s32, + width: MySpaces.s32, + alignment: Alignment.center, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0XFF5732CB), + Color(0XFF322386), + ], + ), + ), + child: Text( + '1', + style: GoogleFonts.marhey( + fontSize: 17, + fontWeight: FontWeight.w600, + color: context.primaryColor, + ), + ), + ), + ), + ), + PositionedDirectional( + top: MySpaces.s14, + end: MySpaces.s12, + child: MyImage( + image: MyAssets.correct, + size: MySpaces.s40, + ), + ), + ], + ), ), ); } @@ -124,4 +172,38 @@ class _SvgCustomPainter extends CustomPainter { @override bool shouldRepaint(CustomPainter oldDelegate) => true; +} + +class _CountClipper extends CustomClipper { + @override + Path getClip(Size size) { + // Original SVG viewBox: width=34, height=33 + final sx = size.width / 34.0; + final sy = size.height / 33.0; + + final p = Path() + ..moveTo(33.3479 * sx, 14.8127 * sy) + ..cubicTo( + 33.3479 * sx, 23.7042 * sy, + 27.2015 * sx, 32.9501 * sy, + 17.8599 * sx, 32.9501 * sy, + )..cubicTo( + 8.51818 * sx, 32.9501 * sy, + 0.945251 * sx, 25.7421 * sy, + 0.945251 * sx, 16.8507 * sy, + )..cubicTo( + 0.945251 * sx, 7.95917 * sy, + 8.51818 * sx, 0.751205 * sy, + 17.8599 * sx, 0.751205 * sy, + )..cubicTo( + 27.2015 * sx, 0.751205 * sy, + 33.3479 * sx, 5.92127 * sy, + 33.3479 * sx, 14.8127 * sy, + )..close(); + + return p; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) => false; } \ No newline at end of file diff --git a/lib/core/widgets/answer_box/styles/text_box.dart b/lib/core/widgets/answer_box/styles/text_box.dart index ed40b1d..c636cfa 100644 --- a/lib/core/widgets/answer_box/styles/text_box.dart +++ b/lib/core/widgets/answer_box/styles/text_box.dart @@ -1,39 +1,43 @@ import 'package:flutter/material.dart'; 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}); @override Widget build(BuildContext context) { - return AspectRatio( - aspectRatio: 480 / 149.0, - child: Stack( - alignment: Alignment.center, - children: [ - Positioned.fill( - child: CustomPaint( - painter: _WavyBannerPainter(), - ), + return ClipPath( + clipper: _WavyBannerClipper(), + child: Container( + padding: EdgeInsets.all(MySpaces.s10), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0XFFFFFFFF), + Color(0XFFCADCFF), + ], ), - Text( - 'We walk in the yard with a glass of juice', - textAlign: TextAlign.center, - style: GoogleFonts.marhey( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0XFF322386) - ), - ) - ], + ), + child: Text( + 'We walk in the yard with a glass of juice ', + textAlign: TextAlign.center, + style: GoogleFonts.marhey( + fontSize: 14, + fontWeight: FontWeight.w700, + color: Color(0XFF322386) + ), + ), ), ); } } -class _WavyBannerPainter extends CustomPainter { +class _WavyBannerClipper extends CustomClipper { @override - void paint(Canvas canvas, Size size) { + Path getClip(Size size) { final sx = size.width / 480.0; final sy = size.height / 149.0; @@ -44,19 +48,9 @@ class _WavyBannerPainter extends CustomPainter { ..cubicTo(488.753 * sx, 118.634 * sy, 483.484 * sx, 26.7097 * sy, 459.365 * sx, 10.3813 * sy) ..cubicTo(435.246 * sx, -5.94701 * sy, 41.0302 * sx, -3.23423 * sy, 14.0623 * sx, 20.0845 * sy) ..close(); - - final paint = Paint() - ..style = PaintingStyle.fill - ..isAntiAlias = true - ..shader = const LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [Color(0xFFCADCFF), Colors.white], - ).createShader(Offset.zero & size); - - canvas.drawPath(path, paint); + return path; } @override - bool shouldRepaint(covariant _WavyBannerPainter oldDelegate) => false; -} + bool shouldReclip(covariant CustomClipper oldClipper) => false; +} \ No newline at end of file diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index c3ae025..b1ecdf6 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -137,8 +137,7 @@ class QuestionPage extends StatelessWidget { clipBehavior: Clip.none, alignment: Alignment.center, children: [ - Positioned.directional( - textDirection: Directionality.of(context), + PositionedDirectional( start: 0, top: -10, child: LeftBlob(), @@ -147,14 +146,12 @@ class QuestionPage extends StatelessWidget { padding: const EdgeInsetsDirectional.only(end: 60), child: MyImage(image: MyAssets.persons), ), - Positioned.directional( - textDirection: Directionality.of(context), + PositionedDirectional( start: 210, top: -20, child: RightBlob(), ), - Positioned.directional( - textDirection: Directionality.of(context), + PositionedDirectional( end: 0, bottom: 10, child: RefreshButton( From 2596842567ffe084bc5f2a03e57912fd0adae4c2 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sun, 28 Sep 2025 21:49:26 +0330 Subject: [PATCH 6/6] add: showcase feature --- assets/images/hand_point.svg | 4 + lib/common_ui/resources/my_assets.dart | 1 + .../widgets/showcase/question_showcase.dart | 50 +++++++++++ .../presentation/bloc/question_bloc.dart | 11 +++ .../presentation/ui/question_page.dart | 85 +++++++++++-------- lib/l10n/app_en.arb | 3 +- lib/l10n/app_localizations.dart | 6 ++ lib/l10n/app_localizations_en.dart | 3 + pubspec.lock | 8 ++ pubspec.yaml | 1 + 10 files changed, 135 insertions(+), 37 deletions(-) create mode 100644 assets/images/hand_point.svg create mode 100644 lib/core/widgets/showcase/question_showcase.dart diff --git a/assets/images/hand_point.svg b/assets/images/hand_point.svg new file mode 100644 index 0000000..5c28ef9 --- /dev/null +++ b/assets/images/hand_point.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart index f2a20e9..aafeaa3 100644 --- a/lib/common_ui/resources/my_assets.dart +++ b/lib/common_ui/resources/my_assets.dart @@ -27,4 +27,5 @@ class MyAssets { static const String done = 'assets/images/done.svg'; static const String correct = 'assets/images/correct.svg'; static const String wrong = 'assets/images/wrong.svg'; + static const String handPoint = 'assets/images/hand_point.svg'; } \ No newline at end of file diff --git a/lib/core/widgets/showcase/question_showcase.dart b/lib/core/widgets/showcase/question_showcase.dart new file mode 100644 index 0000000..f6401c0 --- /dev/null +++ b/lib/core/widgets/showcase/question_showcase.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.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/core/utils/screen_size.dart'; +import 'package:showcaseview/showcaseview.dart'; + +class QuestionShowcase extends StatelessWidget { + const QuestionShowcase({ + super.key, + required this.globalKey, + required this.child, + this.description, + }); + + final GlobalKey globalKey; + final String? description; + final Widget child; + + @override + Widget build(BuildContext context) { + return Showcase( + key: globalKey, + blurValue: 10, + targetShapeBorder: CircleBorder(), + tooltipBackgroundColor: Colors.transparent, + disableMovingAnimation: true, + textColor: context.primaryColor, + descriptionTextAlign: TextAlign.center, + descTextStyle: GoogleFonts.marhey( + fontSize: 12, + fontWeight: FontWeight.w700, + color: context.primaryColor, + ), + disableScaleAnimation: true, + tooltipPadding: EdgeInsets.only(top: 60), + floatingActionWidget: FloatingActionWidget( + height: 60, + width: 60, + right: context.widthScreen * 0.17, + top: context.widthScreen * 1.17, + child: MyImage(image: MyAssets.handPoint), + ), + overlayColor: Color(0XFF0F0041), + description: description ?? '', + child: child, + ); + } +} diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index 69823db..92706ac 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -1,10 +1,12 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:flutter/cupertino.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/presentation/bloc/question_event.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart'; +import 'package:showcaseview/showcaseview.dart'; class QuestionBloc extends Bloc { /// ------------constructor------------ @@ -18,10 +20,19 @@ class QuestionBloc extends Bloc { final GetQuestionUseCase _getQuestionUseCase; /// ------------Variables------------ + final List keys = [ + GlobalKey(), + GlobalKey(), + GlobalKey(), + GlobalKey(), + ]; /// ------------Controllers------------ /// ------------Functions------------ + void startShowCase({required BuildContext context}) { + ShowCaseWidget.of(context).startShowCase([keys[1]]); + } /// ------------Api Calls------------ FutureOr _getQuestionEvent(event, emit) async { diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index b1ecdf6..abb2798 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -1,60 +1,69 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.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/gap.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; +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/presentation/bloc/question_bloc.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'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/refresh_button.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/right_blob.dart'; +import 'package:showcaseview/showcaseview.dart'; class QuestionPage extends StatelessWidget { const QuestionPage({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - height: context.heightScreen, - width: context.widthScreen, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0XFF6930DA), Color(0XFF263AA1)], - ), - image: DecorationImage( - image: AssetImage(MyAssets.pattern), - scale: 3, - repeat: ImageRepeat.repeat, - colorFilter: ColorFilter.mode( - Colors.black.withValues(alpha: 0.3), - BlendMode.srcIn, + return ShowCaseWidget( + builder: (context) { + return Scaffold( + body: Container( + height: context.heightScreen, + width: context.widthScreen, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFF6930DA), Color(0XFF263AA1)], + ), + image: DecorationImage( + image: AssetImage(MyAssets.pattern), + scale: 3, + repeat: ImageRepeat.repeat, + colorFilter: ColorFilter.mode( + Colors.black.withValues(alpha: 0.3), + BlendMode.srcIn, + ), + ), ), - ), - ), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: MySpaces.s16), - child: Column( - children: [ - MySpaces.s4.gapHeight, - _topButtons(), - MySpaces.s10.gapHeight, - QuestionStepper(), - _titles(), - MySpaces.s14.gapHeight, - _questions(), - _bottomDetail(context), - ], + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: MySpaces.s16), + child: Column( + children: [ + MySpaces.s4.gapHeight, + _topButtons(), + MySpaces.s10.gapHeight, + QuestionStepper(), + _titles(), + MySpaces.s14.gapHeight, + _questions(), + _bottomDetail(context), + ], + ), + ), ), ), - ), - ), + ); + }, ); } @@ -125,7 +134,11 @@ class QuestionPage extends StatelessWidget { crossAxisSpacing: MySpaces.s20, mainAxisSpacing: 50, ), - itemBuilder: (context, index) => AnswerBox(), + itemBuilder: (context, index) => QuestionShowcase( + globalKey: context.read().keys[index], + description: context.translate.tap_to_select, + child: AnswerBox(), + ), ), ); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 41f04cd..f21e3f9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,4 +1,5 @@ { "about_us": "About us", - "about_us_desc" : "Rive combines an interactive design tool, a new stateful graphics format, a lightweight multi-platform runtime, and a blazing-fast vector renderer. \nThis end-to-end pipeline brings interfaces to life with motion. It gives designers and devs the tools to build." + "about_us_desc" : "Rive combines an interactive design tool, a new stateful graphics format, a lightweight multi-platform runtime, and a blazing-fast vector renderer. \nThis end-to-end pipeline brings interfaces to life with motion. It gives designers and devs the tools to build.", + "tap_to_select": "Tap the correct option to select." } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index b1de57e..6e94635 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -105,6 +105,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Rive combines an interactive design tool, a new stateful graphics format, a lightweight multi-platform runtime, and a blazing-fast vector renderer. \nThis end-to-end pipeline brings interfaces to life with motion. It gives designers and devs the tools to build.'** String get about_us_desc; + + /// No description provided for @tap_to_select. + /// + /// In en, this message translates to: + /// **'Tap the correct option to select.'** + String get tap_to_select; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 6967cb9..6b8bd59 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -14,4 +14,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get about_us_desc => 'Rive combines an interactive design tool, a new stateful graphics format, a lightweight multi-platform runtime, and a blazing-fast vector renderer. \nThis end-to-end pipeline brings interfaces to life with motion. It gives designers and devs the tools to build.'; + + @override + String get tap_to_select => 'Tap the correct option to select.'; } diff --git a/pubspec.lock b/pubspec.lock index 2c648e0..b9bc647 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -477,6 +477,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + showcaseview: + dependency: "direct main" + description: + name: showcaseview + sha256: "82e013ac2de1ae92cc6e652badf676606057c8e17aa3afd91e78866c4b4e85b1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 366e5a5..7b50df4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: intl: ^0.20.2 pretty_dio_logger: ^1.4.0 shared_preferences: ^2.5.3 + showcaseview: ^4.0.1 dev_dependencies: flutter_test: