From 2596842567ffe084bc5f2a03e57912fd0adae4c2 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sun, 28 Sep 2025 21:49:26 +0330 Subject: [PATCH] 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: