diff --git a/assets/images/diamond.png b/assets/images/diamond.png new file mode 100644 index 0000000..ba816b1 Binary files /dev/null and b/assets/images/diamond.png differ 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