Browse Source

Merge pull request 'feature/level' (#8) from feature/home into develop

Reviewed-on: https://git.nwhco.ir/amirreza.chegini/hade_hoda_flutter/pulls/8
pull/10/head
amirreza.chegini 1 week ago
parent
commit
27d254c192
  1. BIN
      assets/fonts/dinokids.ttf
  2. BIN
      assets/images/current_level.png
  3. 3
      assets/images/done_rounded.svg
  4. BIN
      assets/images/finished_level.png
  5. BIN
      assets/images/home_button.png
  6. BIN
      assets/images/level.png
  7. 13
      assets/images/location.svg
  8. BIN
      assets/images/map_background.png
  9. 62
      assets/images/play.svg
  10. 10
      lib/common_ui/resources/my_assets.dart
  11. 17
      lib/common_ui/resources/my_text_style.dart
  12. 13
      lib/core/params/level_params.dart
  13. 13
      lib/core/routers/my_routes.dart
  14. 4
      lib/core/widgets/answer_box/styles/text_box.dart
  15. 28
      lib/features/level/data/datasource/level_datasource.dart
  16. 13
      lib/features/level/data/model/level_model.dart
  17. 29
      lib/features/level/data/repository_impl/level_repository_impl.dart
  18. 14
      lib/features/level/domain/entity/level_entity.dart
  19. 15
      lib/features/level/domain/entity/level_location.dart
  20. 8
      lib/features/level/domain/repository/level_repository.dart
  21. 17
      lib/features/level/domain/usecases/get_level_usecase.dart
  22. 67
      lib/features/level/presentation/bloc/level_bloc.dart
  23. 5
      lib/features/level/presentation/bloc/level_event.dart
  24. 15
      lib/features/level/presentation/bloc/level_state.dart
  25. 124
      lib/features/level/presentation/ui/level_page.dart
  26. 87
      lib/features/level/presentation/ui/widgets/bottom_path.dart
  27. 62
      lib/features/level/presentation/ui/widgets/hint_level_widget.dart
  28. 72
      lib/features/level/presentation/ui/widgets/level_widget.dart
  29. 99
      lib/features/level/presentation/ui/widgets/top_path.dart
  30. 9
      lib/init_bindings.dart
  31. 12
      pubspec.lock
  32. 8
      pubspec.yaml

BIN
assets/fonts/dinokids.ttf

BIN
assets/images/current_level.png

After

Width: 45  |  Height: 50  |  Size: 2.1 KiB

3
assets/images/done_rounded.svg

@ -0,0 +1,3 @@
<svg width="9" height="8" viewBox="0 0 9 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.6381 0.678943C7.50841 0.591781 7.36285 0.531072 7.20973 0.500283C7.0566 0.469494 6.89891 0.469228 6.74567 0.4995C6.59242 0.529773 6.44662 0.589991 6.31659 0.676715C6.18656 0.763439 6.07485 0.87497 5.98784 1.00494L3.45518 4.7866L2.30197 3.41964C2.20218 3.29708 2.07901 3.19567 1.93966 3.12132C1.80031 3.04698 1.64756 3.00118 1.49034 2.98661C1.33312 2.97204 1.17456 2.98899 1.02392 3.03646C0.873285 3.08393 0.733586 3.16098 0.612974 3.26311C0.492362 3.36524 0.393254 3.4904 0.321432 3.6313C0.24961 3.7722 0.206513 3.926 0.194655 4.08375C0.182798 4.24149 0.202417 4.40001 0.252369 4.55005C0.30232 4.7001 0.381603 4.83867 0.485592 4.95768L2.65633 7.53079C2.86142 7.77455 3.15323 7.92336 3.46487 7.94951L3.62999 7.9514C3.81375 7.94119 3.99265 7.88838 4.1526 7.79715C4.31256 7.70591 4.4492 7.57873 4.55178 7.42562L7.96241 2.33203C8.04947 2.20212 8.11015 2.05631 8.14097 1.90293C8.17179 1.74955 8.17215 1.59159 8.14204 1.43809C8.11192 1.28458 8.05192 1.13853 7.96545 1.00828C7.87899 0.878025 7.76775 0.766116 7.6381 0.678943Z" fill="white"/>
</svg>

BIN
assets/images/finished_level.png

After

Width: 44  |  Height: 50  |  Size: 2.4 KiB

BIN
assets/images/home_button.png

After

Width: 66  |  Height: 55  |  Size: 4.9 KiB

BIN
assets/images/level.png

After

Width: 45  |  Height: 49  |  Size: 1.8 KiB

13
assets/images/location.svg

@ -0,0 +1,13 @@
<svg width="51" height="60" viewBox="0 0 51 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.4452 0.747754C28.8112 -0.0908679 34.7049 1.39966 40.1433 5.23053C45.5567 9.04398 48.7677 14.7022 49.7582 22.2514L49.7584 22.2524C50.0536 24.4941 49.9303 26.9151 49.3799 29.5183C48.8288 32.1247 47.8858 34.8913 46.5471 37.8188C45.209 40.7451 43.4634 43.8205 41.3088 47.0449C39.154 50.2695 36.6274 53.6234 33.7278 57.1056C33.2676 57.6497 32.7211 58.0828 32.087 58.4082C31.4444 58.7378 30.79 58.9454 30.1235 59.0332C29.4571 59.121 28.7719 59.0898 28.0661 58.9379C27.4564 58.8066 26.8902 58.5782 26.366 58.2507L26.1439 58.1046C22.4414 55.4922 19.1325 52.9071 16.216 50.3505C13.2999 47.7942 10.8183 45.2755 8.77 42.7954C6.72094 40.3142 5.0936 37.8863 3.88428 35.5117C2.67651 33.1401 1.92847 30.8337 1.63313 28.5922C0.638668 21.0432 2.27653 14.7456 6.51583 9.66044C10.7744 4.55224 16.0794 1.58646 22.4452 0.747754ZM24.7608 18.3175C22.973 18.5487 21.5214 19.3916 20.4261 20.8319C19.3325 22.2702 18.8948 23.8881 19.1223 25.6647C19.35 27.4437 20.1928 28.896 21.6352 30.0026C23.0775 31.1091 24.6974 31.5464 26.4725 31.3038L26.4714 31.303C28.2537 31.0681 29.706 30.2279 30.8079 28.7942C31.91 27.3601 32.3461 25.7394 32.1088 23.9549C31.8716 22.1711 31.0311 20.7198 29.5995 19.6215C28.1679 18.5232 26.5472 18.0866 24.7608 18.3175Z" fill="url(#paint0_radial_36_498)" stroke="url(#paint1_linear_36_498)" stroke-width="0.770492"/>
<defs>
<radialGradient id="paint0_radial_36_498" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20.599 12.4721) rotate(78.4712) scale(47.9096 39.6664)">
<stop stop-color="#EB662D"/>
<stop offset="1" stop-color="#D23F00"/>
</radialGradient>
<linearGradient id="paint1_linear_36_498" x1="22.3955" y1="0.366224" x2="30.1742" y2="59.4151" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF6E40"/>
<stop offset="1" stop-color="#C2390D"/>
</linearGradient>
</defs>
</svg>

BIN
assets/images/map_background.png

After

Width: 928  |  Height: 4096  |  Size: 706 KiB

62
assets/images/play.svg

@ -0,0 +1,62 @@
<svg width="100" height="86" viewBox="0 0 100 86" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.69637 74.2082C5.20378 87.9714 88.2126 89.0724 95.4442 74.7765C102.044 61.777 99.3382 15.2487 93.7188 5.97857C86.0629 -6.66576 14.8127 4.14474 8.61812 14.072C2.83843 23.3421 -1.93361 59.9656 1.69637 74.2082Z" fill="url(#paint0_linear_498_2035)"/>
<path d="M1.69611 74.208C5.20352 87.9711 88.2123 89.0721 95.444 74.7762C92.0469 65.8426 79.6025 56.1175 73.9831 46.8474C66.3271 34.203 35.6697 43.0654 29.4752 52.9926C23.6955 62.2627 6.56943 58.766 1.69611 74.208Z" fill="#A9BDFF"/>
<path d="M6.51906 69.0918C9.62367 81.2738 85.2286 84.7036 91.6744 72.0486C97.5879 60.5468 95.1337 19.2997 90.1368 11.0799C83.3066 -0.126305 16.217 7.73872 10.6287 16.5795C5.36559 24.7698 3.14833 56.4368 6.51906 69.0918Z" fill="url(#paint1_radial_498_2035)"/>
<path d="M6.51995 66.9331C9.62457 78.7602 84.7855 79.529 91.2017 67.3175C97.1153 56.2 94.7203 19.4768 89.7529 11.5527C82.9523 0.760423 16.0411 8.32975 10.5415 16.8453C5.54453 24.7694 3.56318 54.7512 6.51995 66.9331Z" fill="url(#paint2_linear_498_2035)"/>
<path d="M7.58466 65.898C10.5414 77.3111 84.1356 78.1095 90.3744 66.2528C96.081 55.4901 93.8332 19.8314 88.9841 12.1438C82.3313 1.6768 16.8684 9.18703 11.4871 17.2886C6.5197 24.9762 4.42091 54.0709 7.58466 65.898Z" fill="url(#paint3_linear_498_2035)"/>
<g filter="url(#filter0_di_498_2035)">
<mask id="path-6-inside-1_498_2035" fill="white">
<path d="M66.2624 43.312C66.733 42.9478 67.1139 42.4807 67.376 41.9465C67.638 41.4122 67.7743 40.8251 67.7743 40.23C67.7743 39.635 67.638 39.0478 67.376 38.5136C67.1139 37.9794 66.733 37.5123 66.2624 37.1481C60.1679 32.434 53.3636 28.7175 46.1035 26.1375L44.776 25.6659C42.2389 24.7653 39.5574 26.4811 39.2138 29.1016C38.2543 36.4895 38.2543 43.9705 39.2138 51.3585C39.5594 53.979 42.2389 55.6948 44.776 54.7942L46.1035 54.3225C53.3636 51.7426 60.1679 48.0261 66.2624 43.312Z"/>
</mask>
<path d="M66.2624 43.312C66.733 42.9478 67.1139 42.4807 67.376 41.9465C67.638 41.4122 67.7743 40.8251 67.7743 40.23C67.7743 39.635 67.638 39.0478 67.376 38.5136C67.1139 37.9794 66.733 37.5123 66.2624 37.1481C60.1679 32.434 53.3636 28.7175 46.1035 26.1375L44.776 25.6659C42.2389 24.7653 39.5574 26.4811 39.2138 29.1016C38.2543 36.4895 38.2543 43.9705 39.2138 51.3585C39.5594 53.979 42.2389 55.6948 44.776 54.7942L46.1035 54.3225C53.3636 51.7426 60.1679 48.0261 66.2624 43.312Z" fill="url(#paint4_linear_498_2035)"/>
<path d="M65.9949 42.9664C66.1732 43.1968 66.3515 43.4272 66.5298 43.6576C67.0496 43.2524 67.474 42.7272 67.7608 42.1352C68.0484 41.5437 68.1979 40.8865 68.1946 40.23C68.193 39.8191 68.1322 39.4087 68.0148 39.0154C67.9448 38.7805 67.8547 38.5516 67.7459 38.3322C67.4552 37.7449 67.0286 37.2265 66.5095 36.8288C60.3729 32.1252 53.4897 28.4083 46.2025 25.8589C45.7593 25.7037 45.316 25.5486 44.8728 25.3934C42.3041 24.4027 39.2203 26.3687 38.9619 29.0686C38.0343 36.4727 38.07 43.9918 39.0655 51.3777C39.345 54.0125 42.3584 55.8786 44.8143 54.9021C45.2561 54.7428 45.6979 54.5836 46.1397 54.4243C51.9179 52.3413 57.427 49.5248 62.4936 46.0656C63.7791 45.1882 65.0361 44.2697 66.2624 43.312C65.0272 44.2582 63.762 45.1646 62.4691 46.0294C57.3734 49.4389 51.8476 52.198 46.0674 54.2208C45.6241 54.3759 45.1809 54.5311 44.7376 54.6862C42.4046 55.5879 39.5962 53.7934 39.3621 51.3389C38.4391 43.9698 38.4748 36.4858 39.4658 29.1343C39.7177 26.7438 42.4592 25.0513 44.6793 25.9383C45.121 26.0976 45.5627 26.2569 46.0045 26.4162C53.2088 29.0125 59.9905 32.7591 66.0154 37.4674C66.4339 37.7943 66.7755 38.2179 67.0061 38.6951C67.0924 38.8734 67.1634 39.059 67.2182 39.2492C67.3099 39.5677 67.3558 39.8991 67.354 40.23C67.3516 40.7587 67.2262 41.2856 66.9911 41.7577C66.7567 42.2301 66.4129 42.6469 65.9949 42.9664ZM66.5298 43.6576L65.9949 42.9664L66.2624 43.312L66.5298 43.6576Z" fill="#FFF3C0" mask="url(#path-6-inside-1_498_2035)"/>
</g>
<path style="mix-blend-mode:screen" d="M11.3092 17.3176C11.3092 17.3176 62.5793 -2.16756 89.1902 12.3206C90.9184 16.8787 92.0614 21.6378 92.591 26.4836C86.127 33.028 77.4433 36.9106 68.256 37.3645C54.5662 38.3107 41.2317 20.8362 29.6707 18.6481C23.6411 17.3581 17.4618 16.9103 11.3092 17.3176Z" fill="url(#paint5_linear_498_2035)"/>
<path d="M11.9603 19.417C13.4817 16.6512 15.828 14.4289 18.6723 13.0599C24.6815 10.802 31.0057 9.4952 37.4178 9.18652C38.1865 9.74831 30.9425 12.3799 27.1874 13.74C23.4323 15.1001 13.8822 20.511 11.9603 19.417Z" fill="white"/>
<path d="M11.5457 20.5107C10.5995 20.6586 8.20499 28.1097 8.32326 29.3811C8.44153 30.6525 11.2798 26.72 11.8121 24.6502C12.0774 23.2702 11.9855 21.8454 11.5457 20.5107Z" fill="white"/>
<defs>
<filter id="filter0_di_498_2035" x="38.4941" y="25.4238" width="29.2802" height="32.2205" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2.60823"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 0.670588 0 0 0 0.4 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_498_2035"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_498_2035" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1.63032"/>
<feGaussianBlur stdDeviation="0.815162"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_498_2035"/>
</filter>
<linearGradient id="paint0_linear_498_2035" x1="49.5839" y1="85.0233" x2="49.5839" y2="-1.76438" gradientUnits="userSpaceOnUse">
<stop stop-color="#A6BADF"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<radialGradient id="paint1_radial_498_2035" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(32.0245 74.7689) scale(34.6015 65.2856)">
<stop stop-color="#FFBD00"/>
<stop offset="1" stop-color="#FF772C"/>
</radialGradient>
<linearGradient id="paint2_linear_498_2035" x1="49.9253" y1="76.1287" x2="49.9253" y2="6.23045" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFF300"/>
<stop offset="1" stop-color="#FF9700"/>
</linearGradient>
<linearGradient id="paint3_linear_498_2035" x1="49.9255" y1="74.7979" x2="49.9255" y2="6.96943" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCC00"/>
<stop offset="1" stop-color="#FF9700"/>
</linearGradient>
<linearGradient id="paint4_linear_498_2035" x1="53.1342" y1="25.4238" x2="53.1342" y2="55.0362" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9601F"/>
<stop offset="1" stop-color="#D93D16"/>
</linearGradient>
<linearGradient id="paint5_linear_498_2035" x1="11.3092" y1="22.1963" x2="92.591" y2="22.1963" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF9840"/>
<stop offset="0.1" stop-color="#EF8E3C"/>
<stop offset="0.29" stop-color="#C57531"/>
<stop offset="0.55" stop-color="#814D20"/>
<stop offset="0.88" stop-color="#241609"/>
<stop offset="1"/>
</linearGradient>
</defs>
</svg>

10
lib/common_ui/resources/my_assets.dart

@ -17,7 +17,7 @@ class MyAssets {
static const String language = 'assets/images/language.svg';
static const String newHorizon = 'assets/images/new_horizon.svg';
static const String khadijeLogo = 'assets/images/khadije_logo.png';
static const String home = 'assets/images/home.svg';
static const String home = 'assets/images/level.svg';
static const String music = 'assets/images/music.svg';
static const String pattern = 'assets/images/pattern.png';
static const String persons = 'assets/images/persons.png';
@ -28,4 +28,12 @@ class MyAssets {
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';
static const String mapBackground = 'assets/images/map_background.png';
static const String level = 'assets/images/level.png';
static const String finishedLevel = 'assets/images/finished_level.png';
static const String currentLevel = 'assets/images/current_level.png';
static const String location = 'assets/images/location.svg';
static const String play = 'assets/images/play.svg';
static const String homeButton = 'assets/images/home_button.png';
static const String doneRounded = 'assets/images/done_rounded.svg';
}

17
lib/common_ui/resources/my_text_style.dart

@ -5,11 +5,18 @@ class MyTextStyle {
const MyTextStyle._internal();
factory MyTextStyle() => _i;
static const String fontFamily = '';
static const String fontFamily = 'dinokids';
static const TextStyle lightXS = TextStyle(
fontFamily: fontFamily,
fontSize: 10,
fontWeight: FontWeight.w400,
static const TextStyle normal = TextStyle(
fontFamily: fontFamily,
fontSize: 26,
fontWeight: FontWeight.w400,
shadows: [
Shadow(
color: Color(0XFF5B5B5B),
blurRadius: 2.86,
offset: Offset(0, 2),
),
]
);
}

13
lib/core/params/level_params.dart

@ -0,0 +1,13 @@
class LevelParams {
int? id;
LevelParams({this.id});
LevelParams copyWith({
int? id,
}) {
return LevelParams(
id: id ?? this.id,
);
}
}

13
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/level/presentation/bloc/level_bloc.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/level_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';
@ -14,10 +16,11 @@ class Routes {
static const String introPage = '/intro_page';
static const String questionPage = '/question_page';
static const String levelPage = '/level_page';
}
GoRouter get appPages => GoRouter(
initialLocation: Routes.introPage,
initialLocation: Routes.levelPage,
navigatorKey: ContextProvider.navigatorKey,
routes: [
GoRoute(
@ -28,6 +31,14 @@ GoRouter get appPages => GoRouter(
child: const IntroPage(),
),
),
GoRoute(
name: Routes.levelPage,
path: Routes.levelPage,
builder: (context, state) => BlocProvider(
create: (context) => LevelBloc(locator()),
child: const LevelPage(),
),
),
GoRoute(
name: Routes.questionPage,
path: Routes.questionPage,

4
lib/core/widgets/answer_box/styles/text_box.dart

@ -8,7 +8,7 @@ class AnswerTextBox extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ClipPath(
clipper: _WavyBannerClipper(),
clipper: WavyBannerClipper(),
child: Container(
padding: EdgeInsets.all(MySpaces.s10),
decoration: BoxDecoration(
@ -35,7 +35,7 @@ class AnswerTextBox extends StatelessWidget {
}
}
class _WavyBannerClipper extends CustomClipper<Path> {
class WavyBannerClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final sx = size.width / 480.0;

28
lib/features/level/data/datasource/level_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/level_params.dart';
import 'package:hadi_hoda_flutter/core/response/base_response.dart';
import 'package:hadi_hoda_flutter/features/level/data/model/level_model.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
abstract class ILevelDatasource {
Future<LevelEntity> getData({required LevelParams params});
}
class LevelDatasourceImpl implements ILevelDatasource {
final IHttpRequest httpRequest;
const LevelDatasourceImpl(this.httpRequest);
@override
Future<LevelEntity> getData({required LevelParams params}) async {
final response = await httpRequest.get(
path: MyApi.baseUrl,
);
return BaseResponse.getData<LevelEntity>(
response?['data'],
(json) => LevelModel.fromJson(json),
);
}
}

13
lib/features/level/data/model/level_model.dart

@ -0,0 +1,13 @@
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
class LevelModel extends LevelEntity {
const LevelModel({
super.id,
});
factory LevelModel.fromJson(Map<String, dynamic> json) {
return LevelModel(
id: json['id'],
);
}
}

29
lib/features/level/data/repository_impl/level_repository_impl.dart

@ -0,0 +1,29 @@
import 'package:hadi_hoda_flutter/core/params/level_params.dart';
import 'package:flutter/foundation.dart';
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/utils/data_state.dart';
import 'package:hadi_hoda_flutter/features/level/data/datasource/level_datasource.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart';
class LevelRepositoryImpl implements ILevelRepository {
final ILevelDatasource datasource;
const LevelRepositoryImpl(this.datasource);
@override
Future<DataState<LevelEntity, MyException>> getData({required LevelParams params}) async {
try {
final LevelEntity 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'));
}
}
}
}

14
lib/features/level/domain/entity/level_entity.dart

@ -0,0 +1,14 @@
import 'package:equatable/equatable.dart';
class LevelEntity extends Equatable {
final int? id;
const LevelEntity({
this.id,
});
@override
List<Object?> get props => [
id,
];
}

15
lib/features/level/domain/entity/level_location.dart

@ -0,0 +1,15 @@
class MissionLocation {
final int? index;
final double? top;
final double? bottom;
final double? right;
final double? left;
const MissionLocation({
this.index,
this.top,
this.bottom,
this.right,
this.left,
});
}

8
lib/features/level/domain/repository/level_repository.dart

@ -0,0 +1,8 @@
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/params/level_params.dart';
import 'package:hadi_hoda_flutter/core/utils/data_state.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
abstract class ILevelRepository {
Future<DataState<LevelEntity, MyException>> getData({required LevelParams params});
}

17
lib/features/level/domain/usecases/get_level_usecase.dart

@ -0,0 +1,17 @@
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/params/level_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/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart';
class GetLevelUseCase implements UseCase<LevelEntity, LevelParams> {
final ILevelRepository repository;
const GetLevelUseCase(this.repository);
@override
Future<DataState<LevelEntity, MyException>> call(LevelParams params) {
return repository.getData(params: params);
}
}

67
lib/features/level/presentation/bloc/level_bloc.dart

@ -0,0 +1,67 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hadi_hoda_flutter/core/status/base_status.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_location.dart';
import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_event.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_state.dart';
class LevelBloc extends Bloc<LevelEvent, LevelState> {
/// ------------constructor------------
LevelBloc(
this._getLevelUseCase,
) : super(const LevelState()) {
on<GetLevelEvent>(_getLevelEvent);
}
/// ------------UseCases------------
final GetLevelUseCase _getLevelUseCase;
/// ------------Variables------------
final List<MissionLocation> bottomLocationList = [
MissionLocation(bottom: -30, left: 30, index: 1),
MissionLocation(bottom: 50, left: 100, index: 2),
MissionLocation(bottom: 150, left: 60, index: 3),
MissionLocation(bottom: 210, left: 120, index: 4),
MissionLocation(bottom: 250, right: 60, index: 5),
MissionLocation(top: 170, right: 40, index: 6),
MissionLocation(top: 70, right: 70, index: 7),
MissionLocation(top: -20, right: 70, index: 8),
];
final List<MissionLocation> topLocationList = [
MissionLocation(bottom: 30, right: 80, index: 9),
MissionLocation(bottom: 70, left: 20, index: 10),
MissionLocation(bottom: 150, left: 50, index: 11),
MissionLocation(bottom: 180, left: 140, index: 12),
MissionLocation(bottom: 260, right: 20, index: 13),
MissionLocation(bottom: 370, right: 30, index: 14),
MissionLocation(bottom: 420, left: 40, index: 15),
MissionLocation(top: 410, left: 0, index: 16),
MissionLocation(top: 320, left: 60, index: 17),
MissionLocation(top: 220, left: 80, index: 18),
MissionLocation(top: 130, left: 20, index: 19),
MissionLocation(top: 50, left: 70, index: 20),
];
/// ------------Controllers------------
/// ------------Functions------------
/// ------------Api Calls------------
FutureOr<void> _getLevelEvent(event, emit) async {
await _getLevelUseCase(event.levelParams).then(
(value) {
value.fold(
(data) {
emit(state.copyWith(getLevelStatus: BaseComplete<LevelEntity>(data)));
},
(error) {
emit(state.copyWith(getLevelStatus: BaseError(error.errorMessage)));
},
);
},
);
}
}

5
lib/features/level/presentation/bloc/level_event.dart

@ -0,0 +1,5 @@
sealed class LevelEvent {
const LevelEvent();
}
class GetLevelEvent extends LevelEvent {}

15
lib/features/level/presentation/bloc/level_state.dart

@ -0,0 +1,15 @@
import 'package:hadi_hoda_flutter/core/status/base_status.dart';
class LevelState {
final BaseStatus getLevelStatus;
const LevelState({this.getLevelStatus = const BaseInit()});
LevelState copyWith({
BaseStatus? getLevelStatus,
}) {
return LevelState(
getLevelStatus: getLevelStatus ?? this.getLevelStatus,
);
}
}

124
lib/features/level/presentation/ui/level_page.dart

@ -0,0 +1,124 @@
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/level/presentation/bloc/level_bloc.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/bottom_path.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/hint_level_widget.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/level_widget.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/top_path.dart';
class LevelPage extends StatelessWidget {
const LevelPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
SingleChildScrollView(
child: Stack(
alignment: Alignment.center,
children: [
_background(),
_topPath(context),
_bottomPath(context),
],
),
),
_topButtons(context),
_hintMission(context)
],
),
);
}
Positioned _hintMission(BuildContext context) {
return Positioned(
bottom: MediaQuery.viewPaddingOf(context).bottom,
right: MySpaces.s16,
left: MySpaces.s16,
child: HintLevelWidget(),
);
}
Positioned _topButtons(BuildContext context) {
return Positioned(
left: MySpaces.s16,
right: MySpaces.s16,
top: MediaQuery.viewPaddingOf(context).top,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyImage(
image: MyAssets.homeButton,
),
MyImage(
image: MyAssets.musicOn,
),
],
),
);
}
MyImage _background() {
return MyImage(image: MyAssets.mapBackground, fit: BoxFit.cover);
}
Positioned _topPath(BuildContext context) {
return Positioned(
top: context.heightScreen * 0.16,
left: context.widthScreen * 0.15,
child: Stack(
children: [
TopPath(
height: 950,
width: context.widthScreen * 0.6,
),
...List.generate(
context.read<LevelBloc>().topLocationList.length,
(index) => Positioned(
top: context.read<LevelBloc>().topLocationList[index].top,
bottom: context.read<LevelBloc>().topLocationList[index].bottom,
right: context.read<LevelBloc>().topLocationList[index].right,
left: context.read<LevelBloc>().topLocationList[index].left,
child: LevelWidget(
index: context.read<LevelBloc>().topLocationList[index].index ?? 0,
),
),
),
],
),
);
}
Positioned _bottomPath(BuildContext context) {
return Positioned(
bottom: context.heightScreen * 0.18,
left: context.widthScreen * 0.2,
child: Stack(
clipBehavior: Clip.none,
children: [
BottomPath(
width: context.widthScreen * 0.75,
height: context.heightScreen * 0.6,
),
...List.generate(
context.read<LevelBloc>().bottomLocationList.length,
(index) => Positioned(
top: context.read<LevelBloc>().bottomLocationList[index].top,
bottom: context.read<LevelBloc>().bottomLocationList[index].bottom,
right: context.read<LevelBloc>().bottomLocationList[index].right,
left: context.read<LevelBloc>().bottomLocationList[index].left,
child: LevelWidget(
index: context.read<LevelBloc>().bottomLocationList[index].index ?? 0,
),
),
),
],
),
);
}
}

87
lib/features/level/presentation/ui/widgets/bottom_path.dart

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:path_drawing/path_drawing.dart';
class BottomPath extends StatelessWidget {
const BottomPath({
super.key,
this.width = 500,
this.height = 1523,
this.color = Colors.white,
});
final double width;
final double height;
final Color color;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _Path(color),
size: Size(
width,
height,
), // or Size.infinite inside a parent with constraints
);
}
}
class _Path extends CustomPainter {
_Path(this.color);
final Color color;
// SVG viewBox
static const double _vbW = 500;
static const double _vbH = 1523;
// Your SVG path data (unchanged)
static const String _svgPath =
'M1.95892 1520.75C1.95892 1520.75 199.206 1423.63 169.156 1328.9C143.058 1246.63 30.8103 1281.9 15.4421 1225.27C-11.9488 1124.33 48.5736 1164.01 42.795 1033.8C38.5466 938.07 154.913 925.725 219.623 855.048C286.233 782.296 385.022 821.209 446.532 744.097C516.262 656.681 493.917 461.712 493.917 461.712C493.917 461.712 440.122 333.473 428.04 246.36C414.846 151.222 436.901 0.572754 436.901 0.572754';
@override
void paint(Canvas canvas, Size size) {
// Scale SVG viewBox -> current canvas while preserving aspect
final scale = _scaleToFit(
srcW: _vbW,
srcH: _vbH,
dstW: size.width,
dstH: size.height,
);
canvas.translate(
(size.width - _vbW * scale) / 2,
(size.height - _vbH * scale) / 2,
);
canvas.scale(scale);
// Parse and dash the path
final path = parseSvgPathData(_svgPath);
final dashed = dashPath(
path,
dashArray: CircularIntervalList<double>(const [3.08, 13.1]),
);
// Stroke paint
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 4.62295
..color = color
..strokeCap = StrokeCap.butt
..strokeJoin = StrokeJoin.miter;
canvas.drawPath(dashed, paint);
}
double _scaleToFit({
required double srcW,
required double srcH,
required double dstW,
required double dstH,
}) {
final sx = dstW / srcW;
final sy = dstH / srcH;
return sx < sy ? sx : sy; // contain
}
@override
bool shouldRepaint(covariant _Path oldDelegate) => oldDelegate.color != color;
}

62
lib/features/level/presentation/ui/widgets/hint_level_widget.dart

@ -0,0 +1,62 @@
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/my_image.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart';
class HintLevelWidget extends StatelessWidget {
const HintLevelWidget({super.key});
@override
Widget build(BuildContext context) {
return ClipPath(
clipper: WavyBannerClipper(),
child: Container(
width: context.widthScreen,
padding: EdgeInsets.symmetric(horizontal: 14, vertical: 16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [Color(0XFFCADCFF), Color(0XFFFFFFFF)],
),
),
child: Row(
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: MySpaces.s8,
children: [
Text(
'Step 3',
style: GoogleFonts.marhey(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Color(0xFFD8490B),
),
),
Text(
'Toothbrushing etiquette every day',
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.marhey(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF322386),
height: 1,
),
),
],
),
),
MyImage(image: MyAssets.play, size: 70),
],
),
),
);
}
}

72
lib/features/level/presentation/ui/widgets/level_widget.dart

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart';
import 'package:hadi_hoda_flutter/core/utils/my_image.dart';
enum LevelType {
unFinished,
finished,
current;
static Map<LevelType, String> get image => {
LevelType.unFinished: MyAssets.level,
LevelType.finished: MyAssets.finishedLevel,
LevelType.current: MyAssets.currentLevel,
};
}
class LevelWidget extends StatelessWidget {
const LevelWidget({super.key, required this.index, this.type});
final int index;
final LevelType? type;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.topCenter,
clipBehavior: Clip.none,
children: [
MyImage(image: LevelType.image[type] ?? MyAssets.level, size: 46),
Text(
'$index',
style: MyTextStyle.normal.copyWith(color: context.primaryColor),
),
if(type == LevelType.current)
Positioned(
top: -20,
child: MyImage(
image: MyAssets.location,
size: 26,
),
),
if(type == LevelType.finished)
Positioned(
bottom: 0,
child: Container(
height: 17,
width: 17,
padding: EdgeInsets.all(3),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 1,
color: Color(0XFF3CFF3C),
),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0XFF48D336),
Color(0XFF2D7C23),
],
),
),
child: MyImage(image: MyAssets.doneRounded),
),
),
],
);
}
}

99
lib/features/level/presentation/ui/widgets/top_path.dart

@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:path_drawing/path_drawing.dart';
class TopPath extends StatelessWidget {
const TopPath({
super.key,
this.width = 604,
this.height = 2651,
this.color = Colors.white,
});
final double width;
final double height;
final Color color;
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(width, height),
painter: _Path(color: color),
isComplex: true,
willChange: false,
);
}
}
class _Path extends CustomPainter {
_Path({required this.color});
final Color color;
// SVG viewBox
static const double _vbW = 604.0;
static const double _vbH = 2651.0;
// SVG stroke styling
static const double _strokeWidth = 4.62295;
static const List<double> _dashPattern = [3.08, 13.1];
// The original SVG "d" attribute:
static const String _d = '''
M323.844 1.5163
C323.844 1.5163 254.064 132.635 209.041 216.483
C167.229 294.353 105.588 327.363 101.557 415.655
C96.6703 522.7 235.238 655.278 235.238 655.278
C235.238 655.278 284.393 748.372 277.229 810.918
C270.331 871.151 205.959 948.836 205.959 948.836
C205.959 948.836 31.3055 963.687 11.4099 1046.3
C-1.94798 1101.77 54.9427 1185.76 54.9427 1185.76
C54.9427 1185.76 -32.6228 1326.5 19.8853 1386.09
C63.1414 1435.18 185.541 1411.13 185.541 1411.13
C185.541 1411.13 427.654 1354.52 472.164 1458.9
C492.877 1507.48 472.164 1594.12 472.164 1594.12
C472.164 1594.12 454.029 1680.13 464.844 1733.58
C476.306 1790.23 531.492 1865.72 531.492 1865.72
C531.492 1865.72 515.799 1970.22 472.164 2015.58
C437.994 2051.1 361.598 2076.45 361.598 2076.45
C361.598 2076.45 304.217 2117.23 262.59 2133.08
C221.475 2148.74 152.41 2156.58 152.41 2156.58
C152.41 2156.58 60.2151 2199.32 47.623 2253.66
C34.196 2311.61 108.877 2393.12 108.877 2393.12
C108.877 2393.12 201.293 2437.1 262.59 2460.15
C315.761 2480.15 400.893 2505.23 400.893 2505.23
C400.893 2505.23 509.508 2505.09 553.451 2548.76
C584.574 2579.7 601.221 2650.47 601.221 2650.47
''';
@override
void paint(Canvas canvas, Size size) {
// Scale to widgets size
final sx = size.width / _vbW;
final sy = size.height / _vbH;
canvas.save();
canvas.scale(sx, sy);
// Parse SVG path data to a Path
final Path original = parseSvgPathData(_d);
// Apply dash pattern
final Path dashed = dashPath(
original,
dashArray: CircularIntervalList<double>(_dashPattern),
);
// Stroke paint
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = _strokeWidth
..strokeCap = StrokeCap.butt
..strokeJoin = StrokeJoin.miter
..color = color;
canvas.drawPath(dashed, paint);
canvas.restore();
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

9
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/level/data/datasource/level_datasource.dart';
import 'package:hadi_hoda_flutter/features/level/data/repository_impl/level_repository_impl.dart';
import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart';
import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/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';
@ -34,4 +38,9 @@ void initBindings() {
locator.registerLazySingleton<IQuestionDatasource>(() => QuestionDatasourceImpl(locator()));
locator.registerLazySingleton<IQuestionRepository>(() => QuestionRepositoryImpl(locator()));
locator.registerLazySingleton<GetQuestionUseCase>(() => GetQuestionUseCase(locator()));
/// Level Feature
locator.registerLazySingleton<ILevelDatasource>(() => LevelDatasourceImpl(locator()));
locator.registerLazySingleton<ILevelRepository>(() => LevelRepositoryImpl(locator()));
locator.registerLazySingleton<GetLevelUseCase>(() => GetLevelUseCase(locator()));
}

12
pubspec.lock

@ -29,10 +29,10 @@ packages:
dependency: "direct main"
description:
name: bloc
sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189"
sha256: e18b8e7825e9921d67a6d256dba0b6015ece8a577eb0a411845c46a352994d78
url: "https://pub.dev"
source: hosted
version: "9.0.0"
version: "9.0.1"
boolean_selector:
dependency: transitive
description:
@ -317,6 +317,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_drawing:
dependency: "direct main"
description:
name: path_drawing
sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977
url: "https://pub.dev"
source: hosted
version: "1.0.1"
path_parsing:
dependency: transitive
description:

8
pubspec.yaml

@ -21,6 +21,7 @@ dependencies:
go_router: ^16.1.0
google_fonts: ^6.3.2
intl: ^0.20.2
path_drawing: ^1.0.1
pretty_dio_logger: ^1.4.0
shared_preferences: ^2.5.3
showcaseview: ^4.0.1
@ -36,3 +37,10 @@ flutter:
assets:
- assets/images/
- assets/fonts/
fonts:
- family: dinokids
fonts:
- asset: assets/fonts/dinokids.ttf
Loading…
Cancel
Save