Browse Source

Merge pull request 'develop' (#63) from develop into main

Reviewed-on: https://git.nwhco.ir/amirreza.chegini/hade_hoda_flutter/pulls/63
main
amirreza.chegini 4 days ago
parent
commit
72c81e0eed
  1. 8
      .gitignore
  2. 33
      .metadata
  3. 30
      README.md
  4. 13
      analysis_options.yaml
  5. 27
      android/app/build.gradle.kts
  6. 3
      android/app/src/main/AndroidManifest.xml
  7. BIN
      assets/images/gift.png
  8. BIN
      assets/images/gift_background.png
  9. BIN
      assets/images/gift_disable.png
  10. 10
      assets/svg/icon_play_video.svg
  11. BIN
      assets/videos/intro_1.mp4
  12. BIN
      assets/videos/intro_2.mp4
  13. BIN
      assets/videos/intro_3.mp4
  14. BIN
      assets/videos/intro_4.mp4
  15. BIN
      assets/videos/intro_5.mp4
  16. 4
      compile_svg.zsh
  17. 2
      ios/Podfile
  18. 25
      ios/Podfile.lock
  19. 5
      ios/Runner/Info.plist
  20. 69
      lib/common_ui/resources/my_assets.dart
  21. 11
      lib/common_ui/theme/my_theme.dart
  22. 2
      lib/core/constants/my_api.dart
  23. 4
      lib/core/constants/my_constants.dart
  24. 10
      lib/core/middlewares/my_middlewares.dart
  25. 4
      lib/core/routers/hero_dialog_route.dart
  26. 25
      lib/core/routers/my_routes.dart
  27. 12
      lib/core/services/audio_service.dart
  28. 32
      lib/core/utils/app_life_cycle.dart
  29. 11
      lib/core/utils/pre_cache_image.dart
  30. 4
      lib/core/utils/storage_path.dart
  31. 4
      lib/core/widgets/animations/fade_anim.dart
  32. 39
      lib/core/widgets/animations/fade_anim_controller.dart
  33. 59
      lib/core/widgets/animations/fade_anim_delayed.dart
  34. 38
      lib/core/widgets/animations/globe_animation.dart
  35. 2
      lib/core/widgets/animations/rotation_anim.dart
  36. 4
      lib/core/widgets/animations/scale_anim.dart
  37. 4
      lib/core/widgets/animations/ship_anim.dart
  38. 2
      lib/core/widgets/animations/slide_anim.dart
  39. 6
      lib/core/widgets/animations/slide_down_fade.dart
  40. 6
      lib/core/widgets/animations/slide_up_fade.dart
  41. 30
      lib/core/widgets/answer_box/answer_box.dart
  42. 6
      lib/core/widgets/answer_box/answer_box_show.dart
  43. 111
      lib/core/widgets/answer_box/answer_box_showcase.dart
  44. 118
      lib/core/widgets/answer_box/styles/image_box.dart
  45. 46
      lib/core/widgets/answer_box/styles/picture_box.dart
  46. 6
      lib/core/widgets/answer_box/styles/text_box.dart
  47. 2
      lib/core/widgets/button/my_blue_button.dart
  48. 2
      lib/core/widgets/button/my_white_button.dart
  49. 2
      lib/core/widgets/button/my_yellow_button.dart
  50. 60
      lib/core/widgets/confetti/my_confetti.dart
  51. 17
      lib/core/widgets/dialog/about_us_dialog.dart
  52. 25
      lib/core/widgets/dialog/exit_dialog.dart
  53. 8
      lib/core/widgets/dialog/hadith_dialog.dart
  54. 386
      lib/core/widgets/dialog/reward_dialog.dart
  55. 6
      lib/core/widgets/error/error_state.dart
  56. 5
      lib/core/widgets/pop_scope/my_pop_scope.dart
  57. 20
      lib/core/widgets/showcase/my_showcase_widget.dart
  58. 76
      lib/core/widgets/video/my_video_player.dart
  59. 22
      lib/features/download/data/datasource/download_datasource.dart
  60. 6
      lib/features/download/presentation/bloc/download_bloc.dart
  61. 8
      lib/features/download/presentation/ui/download_page.dart
  62. 10
      lib/features/download/presentation/ui/widgets/download_loading_widget.dart
  63. 33
      lib/features/guider/data/datasource/guider_datasource.dart
  64. 28
      lib/features/guider/data/repository_impl/guider_repository_impl.dart
  65. 7
      lib/features/guider/domain/repository/guider_repository.dart
  66. 17
      lib/features/guider/domain/usecases/get_first_level_usecase.dart
  67. 131
      lib/features/guider/presentation/bloc/guider_bloc.dart
  68. 8
      lib/features/guider/presentation/bloc/guider_event.dart
  69. 27
      lib/features/guider/presentation/bloc/guider_state.dart
  70. 248
      lib/features/guider/presentation/ui/guider_page.dart
  71. 60
      lib/features/home/presentation/bloc/home_bloc.dart
  72. 8
      lib/features/home/presentation/ui/home_page.dart
  73. 105
      lib/features/intro/presentation/bloc/intro_bloc.dart
  74. 4
      lib/features/intro/presentation/bloc/intro_event.dart
  75. 8
      lib/features/intro/presentation/ui/intro_page.dart
  76. 54
      lib/features/intro/presentation/ui/screens/intro_1_screen.dart
  77. 38
      lib/features/intro/presentation/ui/screens/intro_2_screen.dart
  78. 38
      lib/features/intro/presentation/ui/screens/intro_3_screen.dart
  79. 37
      lib/features/intro/presentation/ui/screens/intro_4_screen.dart
  80. 35
      lib/features/intro/presentation/ui/screens/intro_5_screen.dart
  81. 12
      lib/features/intro/presentation/ui/widgets/bubble_chat_widget.dart
  82. 4
      lib/features/language/presentation/bloc/language_bloc.dart
  83. 23
      lib/features/language/presentation/ui/language_page.dart
  84. 14
      lib/features/language/presentation/ui/widgets/language_widget.dart
  85. 10
      lib/features/level/data/datasource/level_datasource.dart
  86. 25
      lib/features/level/data/model/node_model.dart
  87. 21
      lib/features/level/data/model/prize_model.dart
  88. 6
      lib/features/level/data/repository_impl/level_repository_impl.dart
  89. 30
      lib/features/level/domain/entity/node_entity.dart
  90. 86
      lib/features/level/domain/entity/node_entity.g.dart
  91. 25
      lib/features/level/domain/entity/prize_entity.dart
  92. 53
      lib/features/level/domain/entity/prize_entity.g.dart
  93. 6
      lib/features/level/domain/entity/total_data_entity.dart
  94. 4
      lib/features/level/domain/entity/total_data_entity.g.dart
  95. 6
      lib/features/level/domain/repository/level_repository.dart
  96. 6
      lib/features/level/domain/usecases/get_levels_usecase.dart
  97. 31
      lib/features/level/presentation/bloc/level_bloc.dart
  98. 2
      lib/features/level/presentation/bloc/level_event.dart
  99. 26
      lib/features/level/presentation/ui/level_page.dart
  100. 2
      lib/features/level/presentation/ui/widgets/bottom_path.dart

8
.gitignore

@ -32,6 +32,9 @@ migrate_working_dir/
.pub/
/build/
/coverage/
/pubspec.lock
/.metadata
/assets/svg/*.vec
# Symbolication related
app.*.symbols
@ -43,3 +46,8 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
# Keystore files (sensitive - keep secure)
/android/app/*.jks
/android/app/*.keystore
/android/keystore.properties

33
.metadata

@ -1,33 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "d693b4b9dbac2acd4477aea4555ca6dcbea44ba2"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
- platform: android
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
- platform: ios
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

30
README.md

@ -1,3 +1,33 @@
# hadi_hoda_flutter
A new Flutter project.
## Building for Google Play Release
This project includes a keystore for signing release builds for Google Play Store.
### Security Setup
- **Keystore file**: `android/app/upload-keystore.jks` (excluded from git)
- **Properties file**: `android/keystore.properties` (excluded from git)
- **Key alias**: `upload`
### Important Security Notes
- 🔐 Keystore files and passwords are excluded from version control
- 🔐 Keep `upload-keystore.jks` and `keystore.properties` files secure
- 🔐 Backup these files in a safe location separate from your code
- 🔐 Never share passwords or keystore files with unauthorized parties
### Building Release APK
To build a release APK for Google Play:
```bash
flutter build apk --release
```
The APK will be generated in `build/app/outputs/flutter-apk/` with the name `Hadi & Hoda v{version}+{versionCode}.apk`.
### Important Security Notes
- Keep the keystore file (`upload-keystore.jks`) secure and don't commit it to version control
- Backup the keystore file in a safe location
- Never share the passwords with unauthorized parties

13
analysis_options.yaml

@ -1 +1,14 @@
include: package:flutter_lints/flutter.yaml
linter:
rules:
prefer_const_constructors: true
prefer_const_declarations: true
prefer_const_constructors_in_immutables: true
prefer_final_fields: true
prefer_final_locals: true
avoid_classes_with_only_static_members: true
avoid_print: true
always_declare_return_types: true
use_key_in_widget_constructors: true
require_trailing_commas: true

27
android/app/build.gradle.kts

@ -2,6 +2,8 @@ import kotlin.text.all
import kotlin.text.find
import kotlin.text.isNotEmpty
import kotlin.text.replace
import java.io.FileInputStream
import java.util.Properties
plugins {
id("com.android.application")
@ -10,9 +12,15 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('keystore.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
namespace = "com.example.hadi_hoda_flutter"
compileSdk = flutter.compileSdkVersion
compileSdk = 36
ndkVersion = flutter.ndkVersion
compileOptions {
@ -29,17 +37,24 @@ android {
applicationId = "com.example.hadi_hoda_flutter"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
minSdk = 24
targetSdk = 36
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties['keyAlias']
keyPassword = keystoreProperties['keyPassword']
storeFile = file(keystoreProperties['storeFile'])
storePassword = keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.getByName("release")
}
}

3
android/app/src/main/AndroidManifest.xml

@ -3,7 +3,8 @@
<application
android:label="Hadi &amp; Hoda"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:enableOnBackInvokedCallback="true">
<activity
android:name=".MainActivity"
android:exported="true"

BIN
assets/images/gift.png

After

Width: 71  |  Height: 75  |  Size: 4.3 KiB

BIN
assets/images/gift_background.png

After

Width: 101  |  Height: 105  |  Size: 12 KiB

BIN
assets/images/gift_disable.png

After

Width: 95  |  Height: 100  |  Size: 5.3 KiB

10
assets/svg/icon_play_video.svg

@ -0,0 +1,10 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="22.1758" y="19.1177" width="22.1765" height="24.4706" fill="white"/>
<path d="M30.5879 6.61768C36.9451 6.61771 43.0418 9.14309 47.5371 13.6382C52.0325 18.1335 54.5586 24.231 54.5586 30.5884C54.5586 36.9457 52.0324 43.0423 47.5371 47.5376C43.0418 52.0329 36.9452 54.5591 30.5879 54.5591C24.2305 54.5591 18.1331 52.033 13.6377 47.5376C9.1426 43.0423 6.61722 36.9456 6.61719 30.5884C6.61719 24.231 9.14234 18.1335 13.6377 13.6382C18.1331 9.14283 24.2305 6.61768 30.5879 6.61768ZM27.7012 20.9155C27.0578 20.8844 26.4176 21.0286 25.8496 21.3325C25.2817 21.6365 24.8066 22.0886 24.4756 22.6411C24.1446 23.1937 23.9697 23.8261 23.9697 24.4702V36.7056C23.9696 37.3497 24.1447 37.9821 24.4756 38.5347C24.8066 39.0873 25.2817 39.5403 25.8496 39.8442C26.4176 40.1481 27.0578 40.2924 27.7012 40.2612C28.3447 40.23 28.9679 40.0241 29.5039 39.6665L38.6807 33.5493C39.168 33.2243 39.5674 32.7836 39.8438 32.2671C40.1201 31.7506 40.2646 31.1741 40.2646 30.5884C40.2646 30.0026 40.1202 29.4252 39.8438 28.9087C39.5674 28.3924 39.1679 27.9523 38.6807 27.6274L29.5039 21.5093C28.968 21.1518 28.3446 20.9468 27.7012 20.9155Z" fill="#EB511A" stroke="url(#paint0_linear_42_2463)"/>
<defs>
<linearGradient id="paint0_linear_42_2463" x1="30.5878" y1="6.11768" x2="30.5878" y2="55.0589" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCB32"/>
<stop offset="1" stop-color="#FFA105"/>
</linearGradient>
</defs>
</svg>

BIN
assets/videos/intro_1.mp4

BIN
assets/videos/intro_2.mp4

BIN
assets/videos/intro_3.mp4

BIN
assets/videos/intro_4.mp4

BIN
assets/videos/intro_5.mp4

4
compile_svg.zsh

@ -0,0 +1,4 @@
for f in assets/svg/*.svg; do
echo "Compiling $f"
fvm dart run vector_graphics_compiler -i "$f" -o "${f}.vec"
done

2
ios/Podfile

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
platform :ios, '15.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

25
ios/Podfile.lock

@ -8,12 +8,19 @@ PODS:
- just_audio (0.0.1):
- Flutter
- FlutterMacOS
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- Flutter
- ZIPFoundation (0.9.19)
DEPENDENCIES:
@ -21,8 +28,11 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_archive (from `.symlinks/plugins/flutter_archive/ios`)
- just_audio (from `.symlinks/plugins/just_audio/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
@ -37,20 +47,29 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_archive/ios"
just_audio:
:path: ".symlinks/plugins/just_audio/darwin"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_archive: ad8edfd7f7d1bb12058d05424ba93e27d9930efe
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
ZIPFoundation: b8c29ea7ae353b309bc810586181fd073cb3312c
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
PODFILE CHECKSUM: 53a6aebc29ccee84c41f92f409fc20cd4ca011f1
COCOAPODS: 1.16.2

5
ios/Runner/Info.plist

@ -2,6 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>

69
lib/common_ui/resources/my_assets.dart

@ -36,39 +36,43 @@ class MyAssets {
static const String satellite = 'assets/images/satellite.png';
static const String planetFinal = 'assets/images/planet_final.png';
static const String behindDiamond = 'assets/images/behind_diamond.png';
static const String gift = 'assets/images/gift.png';
static const String giftDisable = 'assets/images/gift_disable.png';
static const String giftBackground = 'assets/images/gift_background.png';
/// SVG
static const String closeBtn = 'assets/svg/close_btn.svg';
static const String musicOff = 'assets/svg/music_off.svg';
static const String musicOn = 'assets/svg/music_on.svg';
static const String button = 'assets/svg/button.svg';
static const String buttonTablet = 'assets/svg/button_tablet.svg';
static const String button2 = 'assets/svg/button_2.svg';
static const String button2Tablet = 'assets/svg/button_2_tablet.svg';
static const String button3 = 'assets/svg/button_3.svg';
static const String button3Tablet = 'assets/svg/button_3_tablet.svg';
static const String theme = 'assets/svg/theme.svg';
static const String facebook = 'assets/svg/facebook.svg';
static const String whatsapp = 'assets/svg/whatsapp.svg';
static const String youtube = 'assets/svg/youtube.svg';
static const String instagram = 'assets/svg/instagram.svg';
static const String language = 'assets/svg/language.svg';
static const String newHorizon = 'assets/svg/new_horizon.svg';
static const String home = 'assets/svg/home.svg';
static const String music = 'assets/svg/music.svg';
static const String done = 'assets/svg/done.svg';
static const String correct = 'assets/svg/correct.svg';
static const String wrong = 'assets/svg/wrong.svg';
static const String handPoint = 'assets/svg/hand_point.svg';
static const String location = 'assets/svg/location.svg';
static const String doneRounded = 'assets/svg/done_rounded.svg';
static const String lang = 'assets/svg/lang.svg';
static const String unMusic = 'assets/svg/unmusic.svg';
static const String globe = 'assets/svg/globe.svg';
static const String homeButton = 'assets/svg/home_button.svg';
static const String diamondContainer = 'assets/svg/diamond_container.svg';
static const String iconPlay = 'assets/svg/icon_play.svg';
static const String iconNotif = 'assets/svg/icon_notif.svg';
static const String closeBtn = 'assets/svg/close_btn.svg.vec';
static const String musicOff = 'assets/svg/music_off.svg.vec';
static const String musicOn = 'assets/svg/music_on.svg.vec';
static const String button = 'assets/svg/button.svg.vec';
static const String buttonTablet = 'assets/svg/button_tablet.svg.vec';
static const String button2 = 'assets/svg/button_2.svg.vec';
static const String button2Tablet = 'assets/svg/button_2_tablet.svg.vec';
static const String button3 = 'assets/svg/button_3.svg.vec';
static const String button3Tablet = 'assets/svg/button_3_tablet.svg.vec';
static const String theme = 'assets/svg/theme.svg.vec';
static const String facebook = 'assets/svg/facebook.svg.vec';
static const String whatsapp = 'assets/svg/whatsapp.svg.vec';
static const String youtube = 'assets/svg/youtube.svg.vec';
static const String instagram = 'assets/svg/instagram.svg.vec';
static const String language = 'assets/svg/language.svg.vec';
static const String newHorizon = 'assets/svg/new_horizon.svg.vec';
static const String home = 'assets/svg/home.svg.vec';
static const String music = 'assets/svg/music.svg.vec';
static const String done = 'assets/svg/done.svg.vec';
static const String correct = 'assets/svg/correct.svg.vec';
static const String wrong = 'assets/svg/wrong.svg.vec';
static const String handPoint = 'assets/svg/hand_point.svg.vec';
static const String location = 'assets/svg/location.svg.vec';
static const String doneRounded = 'assets/svg/done_rounded.svg.vec';
static const String lang = 'assets/svg/lang.svg.vec';
static const String unMusic = 'assets/svg/unmusic.svg.vec';
static const String globe = 'assets/svg/globe.svg.vec';
static const String homeButton = 'assets/svg/home_button.svg.vec';
static const String diamondContainer = 'assets/svg/diamond_container.svg.vec';
static const String iconPlay = 'assets/svg/icon_play.svg.vec';
static const String iconNotif = 'assets/svg/icon_notif.svg.vec';
static const String iconPlayVideo = 'assets/svg/icon_play_video.svg.vec';
static final List<String> images = [
backgroundHome,
@ -103,5 +107,8 @@ class MyAssets {
satellite,
planetFinal,
behindDiamond,
gift,
giftDisable,
giftBackground,
];
}

11
lib/common_ui/theme/my_theme.dart

@ -1,10 +1,17 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
class MyTheme {
static const MyTheme _i = MyTheme._internal();
const MyTheme._internal();
factory MyTheme() => _i;
static final ThemeData light = ThemeData(brightness: Brightness.light);
static final ThemeData dark = ThemeData(brightness: Brightness.dark);
static final ThemeData light = ThemeData(
brightness: Brightness.light,
scaffoldBackgroundColor: MyColors.purple,
);
static final ThemeData dark = ThemeData(
brightness: Brightness.dark,
scaffoldBackgroundColor: MyColors.purple,
);
}

2
lib/core/constants/my_api.dart

@ -10,7 +10,7 @@ class MyApi {
static const String baseUrl = 'https://hadihoda.newhorizonco.uk/api';
static const String levels = '/quiz/optimized/v2/levels/';
static const String levels = '/quiz/optimized/v3/levels/';
static const String images = '/quiz/optimized/download-all-files/images/';
static const String audios = '/quiz/optimized/v2/download-all-files/audio/';
}

4
lib/core/constants/my_constants.dart

@ -24,7 +24,9 @@ class MyConstants {
static const String firstShowcase = 'FIRST_SHOWCASE';
/// Other
static const double mainAudioVolume = 0.3;
static const double questionAudioVolume = 1.0;
static const double musicAudioVolume = 0.3;
static const double effectAudioVolume = 0.2;
static const String defaultLanguage = 'en';
static const List<LanguageEntity> languages = [
LanguageEntity(title: 'English (English)', code: 'en', locale: Locale('en','US')),

10
lib/core/middlewares/my_middlewares.dart

@ -30,4 +30,14 @@ class MyMiddlewares {
return null;
}
}
static FutureOr<String?> question(BuildContext context, GoRouterState state) {
final String? firstShowCase = LocalStorage.readData(
key: MyConstants.firstShowcase);
if (firstShowCase == 'true') {
return null;
} else {
return '${Routes.guiderPage}/${state.pathParameters['id']}';
}
}
}

4
lib/core/routers/hero_dialog_route.dart

@ -16,7 +16,7 @@ class HeroDialogRoute<T> extends PageRoute<T> {
bool get fullscreenDialog => false;
@override
bool get barrierDismissible => true;
bool get barrierDismissible => false;
@override
Duration get transitionDuration => const Duration(seconds: 1); // Adjust as needed
@ -25,7 +25,7 @@ class HeroDialogRoute<T> extends PageRoute<T> {
bool get maintainState => false;
@override
Color get barrierColor => Color(0XFF322386).withValues(alpha: 0.3); // Or your desired barrier color
Color get barrierColor => const Color(0XFF322386).withValues(alpha: 0.3); // Or your desired barrier color
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {

25
lib/core/routers/my_routes.dart

@ -3,9 +3,13 @@ import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/middlewares/my_middlewares.dart';
import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
import 'package:hadi_hoda_flutter/core/widgets/video/my_video_player.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_bloc.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_event.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/ui/download_page.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_bloc.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_event.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/ui/guider_page.dart';
import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_bloc.dart';
import 'package:hadi_hoda_flutter/features/home/presentation/ui/home_page.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart';
@ -37,7 +41,9 @@ class Routes {
static const String languagePage = '/language_page';
static const String homePage = '/home_page';
static const String questionPage = '/question_page';
static const String guiderPage = '/guider_page';
static const String levelPage = '/level_page';
static const String videoPage = '/video_page';
}
final GoRouter appPages = _appPages();
@ -89,7 +95,7 @@ GoRouter _appPages() => GoRouter(
name: Routes.languagePage,
path: Routes.languagePage,
builder: (context, state) => BlocProvider(
create: (context) => LanguageBloc()..add(InitLanguageEvent()),
create: (context) => LanguageBloc()..add(const InitLanguageEvent()),
child: const LanguagePage(),
),
),
@ -118,9 +124,20 @@ GoRouter _appPages() => GoRouter(
child: const LevelPage(),
),
),
GoRoute(
name: Routes.guiderPage,
path: '${Routes.guiderPage}/:id',
builder: (context, state) => BlocProvider(
create: (context) => GuiderBloc(locator())..add(GetFirstLevelEvent(
id: state.pathParameters['id'],
)),
child: const GuiderPage(),
),
),
GoRoute(
name: Routes.questionPage,
path: '${Routes.questionPage}/:id',
redirect: MyMiddlewares.question,
builder: (context, state) => BlocProvider(
create: (context) =>
QuestionBloc(
@ -132,5 +149,11 @@ GoRouter _appPages() => GoRouter(
child: const QuestionPage(),
),
),
GoRoute(
name: Routes.videoPage,
path: Routes.videoPage,
builder: (context, state) =>
MyVideoPlayer(videoURL: state.extra as String),
),
],
);

12
lib/core/services/audio_service.dart

@ -6,10 +6,10 @@ import 'package:just_audio/just_audio.dart';
class AudioService {
final AudioPlayer _player = AudioPlayer();
final StreamController<bool> _streamController = StreamController.broadcast();
final double? volume;
final double volume;
AudioService({this.volume}) {
_player.setVolume(volume ?? 1);
AudioService({required this.volume}){
setVolume(volume: volume);
}
Future<Duration?> setAudio({String? filePath, String? assetPath}) async {
@ -72,10 +72,10 @@ class AudioService {
}
}
Future<void> changeMute() async {
Future<void> changeMute({double? newVolume}) async {
try {
if (_player.volume == 0) {
await _player.setVolume(volume ?? 1);
await _player.setVolume(newVolume ?? volume);
} else {
await _player.setVolume(0);
}
@ -98,6 +98,8 @@ class AudioService {
Stream<double> volumeStream() => _player.volumeStream;
double get audioVolume => _player.volume;
Stream<bool> playingStream() async* {
_player.processingStateStream.listen((event) {
if (event == ProcessingState.ready) {

32
lib/core/utils/app_life_cycle.dart

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/services/audio_service.dart';
import 'package:hadi_hoda_flutter/init_bindings.dart';
class AppLifeCycleController extends WidgetsBindingObserver {
AppLifeCycleController() {
WidgetsBinding.instance.addObserver(this);
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final AudioService mainAudio = locator(
instanceName: MyConstants.mainAudioService,
);
final AudioService effect = locator(
instanceName: MyConstants.effectAudioService,
);
if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) {
mainAudio.pause();
effect.pause();
} else if (state == AppLifecycleState.resumed) {
mainAudio.play();
effect.play();
}
}
}

11
lib/core/utils/pre_cache_image.dart

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
Future<void> preCacheImages() async {
await Future.wait(
MyAssets.images.map(
(assetPath) => precacheImage(AssetImage(assetPath), MyContext.get),
),
);
}

4
lib/core/utils/storage_path.dart

@ -3,6 +3,10 @@ import 'dart:io';
import 'package:path_provider/path_provider.dart';
class StoragePath {
static const StoragePath _i = StoragePath._internal();
const StoragePath._internal();
factory StoragePath() => _i;
static Directory documentDir = Directory('');
static Future<void> getDocumentDir() async {

4
lib/core/widgets/animations/fade_anim.dart

@ -19,8 +19,8 @@ class _FadeAnimState extends State<FadeAnim>
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(seconds: 500),
duration: const Duration(milliseconds: 500),
reverseDuration: const Duration(seconds: 500),
);
_animation = Tween<double>(
begin: 0,

39
lib/core/widgets/animations/fade_anim_controller.dart

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
class FadeAnimController extends StatefulWidget {
const FadeAnimController({
super.key,
required this.child,
required this.controller,
});
final Widget child;
final AnimationController controller;
@override
State<FadeAnimController> createState() => _FadeAnimControllerState();
}
class _FadeAnimControllerState extends State<FadeAnimController>
with SingleTickerProviderStateMixin {
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(parent: widget.controller, curve: Curves.linear));
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.controller,
child: widget.child,
builder: (context, child) =>
FadeTransition(opacity: _animation, child: child),
);
}
}

59
lib/core/widgets/animations/fade_anim_delayed.dart

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
class FadeAnimDelayed extends StatefulWidget {
const FadeAnimDelayed({
super.key,
required this.child,
required this.duration,
});
final Widget child;
final Duration duration;
@override
State<FadeAnimDelayed> createState() => _FadeAnimDelayedState();
}
class _FadeAnimDelayedState extends State<FadeAnimDelayed>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
reverseDuration: const Duration(seconds: 500),
);
_animation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
startAnim();
}
Future<void> startAnim() async {
await Future.delayed(widget.duration, () {
_controller.forward();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: widget.child,
builder: (context, child) =>
FadeTransition(opacity: _animation, child: child),
);
}
}

38
lib/core/widgets/animations/globe_animation.dart

@ -4,10 +4,9 @@ import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
class GlobeAnimation extends StatefulWidget {
const GlobeAnimation({super.key, required this.child, this.state = true});
const GlobeAnimation({super.key, required this.child});
final Widget child;
final bool state;
@override
State<GlobeAnimation> createState() => _GlobeAnimationState();
@ -25,29 +24,24 @@ class _GlobeAnimationState extends State<GlobeAnimation>
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
reverseDuration: Duration(seconds: 1),
duration: const Duration(hours: 1),
reverseDuration: const Duration(hours: 1),
);
_animation = Tween<double>(
begin: 1,
end: 1.05,
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
}
@override
void didUpdateWidget(covariant GlobeAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.state) {
_controller.repeat(reverse: true);
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_gradient == null) {
if (!mounted) return;
setState(() {
_gradient = RadialGradient(
colors: [
Color(0XFFDFCD00),
Color(0XFFDFCD00).withValues(alpha: 0.35),
Color(0XFFDFCD00).withValues(alpha: 0),
const Color(0XFFDFCD00),
const Color(0XFFDFCD00).withValues(alpha: 0.35),
const Color(0XFFDFCD00).withValues(alpha: 0),
],
center: Alignment.center,
);
@ -58,22 +52,6 @@ class _GlobeAnimationState extends State<GlobeAnimation>
});
}
});
} else {
_timer?.cancel();
_timer = null;
_controller.stop();
if (!mounted) return;
setState(() {
_gradient = RadialGradient(
colors: [
Color(0XFFDFCD00).withValues(alpha: 0),
Color(0XFFDFCD00).withValues(alpha: 0),
Color(0XFFDFCD00).withValues(alpha: 0),
],
center: Alignment.center,
);
});
}
}
@override
@ -94,7 +72,7 @@ class _GlobeAnimationState extends State<GlobeAnimation>
alignment: Alignment.center,
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
padding: EdgeInsets.all(MySpaces.s0),
padding: const EdgeInsets.all(MySpaces.s0),
decoration: BoxDecoration(gradient: _gradient),
child: child,
),

2
lib/core/widgets/animations/rotation_anim.dart

@ -19,7 +19,7 @@ class _RotationAnimState extends State<RotationAnim>
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 20),
duration: const Duration(seconds: 20),
);
_animation = Tween<double>(
begin: 0,

4
lib/core/widgets/animations/scale_anim.dart

@ -20,8 +20,8 @@ class _ScaleAnimState extends State<ScaleAnim>
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 200),
reverseDuration: Duration(milliseconds: 200),
duration: const Duration(milliseconds: 200),
reverseDuration: const Duration(milliseconds: 200),
);
_animation = Tween<double>(
begin: 0,

4
lib/core/widgets/animations/ship_anim.dart

@ -22,8 +22,8 @@ class _ShipAnimState extends State<ShipAnim>
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 15),
reverseDuration: Duration(seconds: 15),
duration: const Duration(seconds: 15),
reverseDuration: const Duration(seconds: 15),
)..repeat();
}

2
lib/core/widgets/animations/slide_anim.dart

@ -19,7 +19,7 @@ class SlideAnim extends StatefulWidget {
class _SlideAnimState extends State<SlideAnim>
with SingleTickerProviderStateMixin {
late Animation<Offset> _animation;
final List<Offset> offsetList = [
final List<Offset> offsetList = const [
Offset(-2, -2),
Offset(2, -2),
Offset(-2, 2),

6
lib/core/widgets/animations/slide_down_fade.dart

@ -25,8 +25,8 @@ class _SlideDownFadeState extends State<SlideDownFade>
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
duration: const Duration(milliseconds: 500),
reverseDuration: const Duration(milliseconds: 500),
);
_fadeAnim = Tween<double>(
@ -35,7 +35,7 @@ class _SlideDownFadeState extends State<SlideDownFade>
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));
_slideAnim = Tween<Offset>(
begin: Offset(0, -0.1),
begin: const Offset(0, -0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));
startAnim();

6
lib/core/widgets/animations/slide_up_fade.dart

@ -25,8 +25,8 @@ class _SlideUpFadeState extends State<SlideUpFade>
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
duration: const Duration(milliseconds: 500),
reverseDuration: const Duration(milliseconds: 500),
);
_fadeAnim = Tween<double>(
@ -35,7 +35,7 @@ class _SlideUpFadeState extends State<SlideUpFade>
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));
_slideAnim = Tween<Offset>(
begin: Offset(0, 0.1),
begin: const Offset(0, 0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn));
startAnim();

30
lib/core/widgets/answer_box/answer_box.dart

@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:gif/gif.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_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/my_showcase_widget.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerBox extends StatefulWidget {
@ -15,9 +14,9 @@ class AnswerBox extends StatefulWidget {
required this.answer,
required this.correctAnswer,
required this.index,
required this.globalKey,
this.onTap,
this.onNotifTap,
this.autostart = Autostart.no,
});
final AnswerEntity answer;
@ -25,7 +24,7 @@ class AnswerBox extends StatefulWidget {
final void Function(bool isCorrect, int correctAnswer)? onTap;
final int index;
final Function(AnswerEntity answer)? onNotifTap;
final GlobalKey globalKey;
final Autostart autostart;
@override
State<AnswerBox> createState() => _AnswerBoxState();
@ -53,14 +52,14 @@ class _AnswerBoxState extends State<AnswerBox> {
}
: null,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
alignment: Alignment.topCenter,
children: [
AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
autostart: widget.autostart,
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
@ -68,16 +67,20 @@ class _AnswerBoxState extends State<AnswerBox> {
Positioned(
left: 0,
right: 0,
bottom: -60,
bottom: 0,
child: AnswerTextBox(text: widget.answer.title ?? ''),
),
PositionedDirectional(
top: setSize(context: context, mobile: MySpaces.s12, tablet: MySpaces.s20),
end: setSize(context: context, mobile: MySpaces.s8, tablet: MySpaces.s20),
child: MyShowcaseWidget(
globalKey: widget.globalKey,
type: ShowcaseTooltipType.bottom,
description: context.translate.showcase_notif,
top: setSize(
context: context,
mobile: MySpaces.s12,
tablet: MySpaces.s20,
),
end: setSize(
context: context,
mobile: MySpaces.s8,
tablet: MySpaces.s20,
),
child: GestureDetector(
onTap: () {
widget.onNotifTap?.call(widget.answer);
@ -88,7 +91,6 @@ class _AnswerBoxState extends State<AnswerBox> {
),
),
),
),
],
),
),

6
lib/core/widgets/answer_box/answer_box_show.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gif/gif.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/set_platform_size.dart';
@ -26,19 +27,20 @@ class AnswerBoxShow extends StatelessWidget {
child: Material(
type: MaterialType.transparency,
child: Stack(
alignment: Alignment.center,
alignment: Alignment.topCenter,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: false,
index: index,
image: answer.image ?? '',
autostart: Autostart.loop,
correctAnswer: 0,
),
Positioned(
left: 0,
right: 0,
bottom: -MySpaces.s40,
bottom: -10,
child: AnswerTextBox(text: answer.title ?? ''),
),
PositionedDirectional(

111
lib/core/widgets/answer_box/answer_box_showcase.dart

@ -0,0 +1,111 @@
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_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/my_showcase_widget.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerBoxShowCase extends StatefulWidget {
const AnswerBoxShowCase({
super.key,
required this.answer,
required this.correctAnswer,
required this.index,
this.globalKey,
this.answerGlobalKey,
this.onTap,
this.onNotifTap,
});
final AnswerEntity answer;
final int correctAnswer;
final void Function(bool isCorrect, int correctAnswer)? onTap;
final int index;
final Function(AnswerEntity answer)? onNotifTap;
final GlobalKey? globalKey;
final GlobalKey? answerGlobalKey;
@override
State<AnswerBoxShowCase> createState() => _AnswerBoxShowCaseState();
}
class _AnswerBoxShowCaseState extends State<AnswerBoxShowCase> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Hero(
tag: 'Hero_answer_${widget.answer.id}',
child: Material(
type: MaterialType.transparency,
child: GestureDetector(
onTap: !selected
? () {
setState(() {
selected = true;
});
widget.onTap?.call(
widget.index == widget.correctAnswer,
widget.correctAnswer,
);
}
: null,
child: Stack(
alignment: Alignment.topCenter,
children: [
MyShowcaseWidget(
globalKey: widget.answerGlobalKey,
description: context.translate.showcase_answer,
child: AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AnswerTextBox(text: widget.answer.title ?? ''),
),
PositionedDirectional(
top: setSize(
context: context,
mobile: MySpaces.s12,
tablet: MySpaces.s20,
),
end: setSize(
context: context,
mobile: MySpaces.s8,
tablet: MySpaces.s20,
),
child: MyShowcaseWidget(
globalKey: widget.globalKey,
type: ShowcaseTooltipType.bottom,
description: context.translate.showcase_notif,
child: GestureDetector(
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
child: MyImage(
image: MyAssets.iconNotif,
size: setSize(context: context, tablet: 50),
),
),
),
),
],
),
),
),
);
}
}

118
lib/core/widgets/answer_box/styles/image_box.dart

@ -0,0 +1,118 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
class ImageBox extends StatelessWidget {
const ImageBox({
super.key,
required this.image,
});
final String image;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _CustomShapePainter(),
child: ClipPath(
clipper: _CustomShapeClipper(),
child: Stack(
alignment: Alignment.center,
children: [
AspectRatio(
aspectRatio: 1,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 150),
reverseDuration: const Duration(milliseconds: 150),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
child: Image(
width: context.widthScreen,
height: context.heightScreen,
image: FileImage(File(image)),
fit: BoxFit.cover,
),
transitionBuilder: (child, animation) =>
FadeTransition(
opacity: animation,
child: child,
),
),
),
],
),
),
);
}
}
class _CustomShapePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint strokePaint = Paint()
..color = const Color(0XFFF2F7FF)
..style = PaintingStyle.stroke
..strokeWidth = 4;
final Path path = _CustomShapeClipper().getClip(size);
canvas.drawPath(path, strokePaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return this != oldDelegate;
}
}
class _CustomShapeClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
// Original SVG dimensions to calculate the scaling factors.
const double originalWidth = 193.0;
const double originalHeight = 189.0;
// Scaling factors to make the path responsive.
final double scaleX = size.width / originalWidth;
final double scaleY = size.height / originalHeight;
// The path is defined using the scaled coordinates from the SVG.
final Path path = Path()
..moveTo(148.483 * scaleX, 4.10254 * scaleY)
..cubicTo(131.624 * scaleX, 1.93333 * scaleY, 111.221 * scaleX, 1.00169 * scaleY, 91.2451 * scaleX, 1.2666 * scaleY)
..cubicTo(71.2667 * scaleX, 1.53156 * scaleY, 51.7626 * scaleX, 2.99274 * scaleY, 36.6973 * scaleX, 5.59668 * scaleY)
..cubicTo(29.1597 * scaleX, 6.8995 * scaleY, 22.7796 * scaleX, 8.48114 * scaleY, 18.0205 * scaleX, 10.3203 * scaleY)
..cubicTo(15.641 * scaleX, 11.2399 * scaleY, 13.7026 * scaleX, 12.2101 * scaleY, 12.2383 * scaleX, 13.2188 * scaleY)
..cubicTo(10.7653 * scaleX, 14.2333 * scaleY, 9.84633 * scaleX, 15.2359 * scaleY, 9.3916 * scaleX, 16.1904 * scaleY)
..cubicTo(8.252 * scaleX, 18.5828 * scaleY, 7.18153 * scaleX, 22.466 * scaleY, 6.2207 * scaleX, 27.5654 * scaleY)
..cubicTo(5.26481 * scaleX, 32.6387 * scaleY, 4.43215 * scaleX, 38.8273 * scaleY, 3.73535 * scaleX, 45.7744 * scaleY)
..cubicTo(2.34189 * scaleX, 59.6675 * scaleY, 1.49647 * scaleX, 76.5363 * scaleY, 1.27832 * scaleX, 93.4678 * scaleY)
..cubicTo(1.06017 * scaleX, 110.4 * scaleY, 1.47057 * scaleX, 127.372 * scaleY, 2.58301 * scaleX, 141.473 * scaleY)
..cubicTo(3.13928 * scaleX, 148.524 * scaleY, 3.86921 * scaleX, 154.841 * scaleY, 4.78125 * scaleX, 160.068 * scaleY)
..cubicTo(5.69748 * scaleX, 165.32 * scaleY, 6.78334 * scaleX, 169.385 * scaleY, 8.01367 * scaleX, 171.984 * scaleY)
..cubicTo(8.53417 * scaleX, 173.084 * scaleY, 9.59654 * scaleX, 174.216 * scaleY, 11.2891 * scaleX, 175.343 * scaleY)
..cubicTo(12.9722 * scaleX, 176.463 * scaleY, 15.1988 * scaleX, 177.524 * scaleY, 17.9219 * scaleX, 178.515 * scaleY)
..cubicTo(23.3679 * scaleX, 180.496 * scaleY, 30.6491 * scaleX, 182.138 * scaleY, 39.1807 * scaleX, 183.437 * scaleY)
..cubicTo(56.2336 * scaleX, 186.032 * scaleY, 78.0934 * scaleX, 187.222 * scaleY, 99.8242 * scaleX, 187.064 * scaleY)
..cubicTo(121.556 * scaleX, 186.906 * scaleY, 143.101 * scaleX, 185.4 * scaleY, 159.525 * scaleX, 182.622 * scaleY)
..cubicTo(167.745 * scaleX, 181.232 * scaleY, 174.627 * scaleX, 179.531 * scaleY, 179.594 * scaleX, 177.548 * scaleY)
..cubicTo(182.079 * scaleX, 176.556 * scaleY, 184.034 * scaleX, 175.512 * scaleY, 185.429 * scaleX, 174.437 * scaleY)
..cubicTo(186.83 * scaleX, 173.355 * scaleY, 187.568 * scaleX, 172.319 * scaleY, 187.812 * scaleX, 171.361 * scaleY)
..lineTo(187.812 * scaleX, 171.361 * scaleY) // In SVG, this was H (horizontal line), equivalent to lineTo in Flutter
..cubicTo(189.156 * scaleX, 166.074 * scaleY, 190.148 * scaleX, 155.525 * scaleY, 190.773 * scaleX, 142.157 * scaleY)
..cubicTo(191.396 * scaleX, 128.832 * scaleY, 191.651 * scaleX, 112.822 * scaleY, 191.552 * scaleX, 96.6875 * scaleY)
..cubicTo(191.453 * scaleX, 80.5539 * scaleY, 191.001 * scaleX, 64.3091 * scaleY, 190.213 * scaleX, 50.5156 * scaleY)
..cubicTo(189.423 * scaleX, 36.6928 * scaleY, 188.299 * scaleX, 25.4153 * scaleY, 186.876 * scaleX, 19.167 * scaleY)
..cubicTo(186.404 * scaleX, 17.0929 * scaleY, 185.566 * scaleX, 15.3424 * scaleY, 184.087 * scaleX, 14.1582 * scaleY)
..cubicTo(181.343 * scaleX, 11.9613 * scaleY, 176.72 * scaleX, 9.98089 * scaleY, 170.561 * scaleX, 8.27539 * scaleY)
..cubicTo(164.434 * scaleX, 6.579 * scaleY, 156.914 * scaleX, 5.18731 * scaleY, 148.483 * scaleX, 4.10254 * scaleY)
..close(); // Closes the path to form a complete shape.
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return this != oldClipper;
}
}

46
lib/core/widgets/answer_box/styles/picture_box.dart

@ -1,10 +1,12 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:gif/gif.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
@ -16,6 +18,8 @@ class AnswerPictureBox extends StatelessWidget {
required this.index,
required this.correctAnswer,
this.onTap,
this.autostart = Autostart.no,
this.showIndex = true,
});
final bool selected;
@ -23,6 +27,8 @@ class AnswerPictureBox extends StatelessWidget {
final int index;
final int correctAnswer;
final VoidCallback? onTap;
final Autostart autostart;
final bool showIndex;
@override
Widget build(BuildContext context) {
@ -33,22 +39,32 @@ class AnswerPictureBox extends StatelessWidget {
child: Stack(
alignment: Alignment.center,
children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 150),
reverseDuration: Duration(milliseconds: 150),
AspectRatio(
aspectRatio: 1,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 150),
reverseDuration: const Duration(milliseconds: 150),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
child: selected && (index != correctAnswer) ?
Image.file(
key: Key('1'),
File(image),
Gif(
key: const Key('1'),
width: context.widthScreen,
height: context.heightScreen,
image: FileImage(File(image)),
fps: 25,
autostart: autostart,
fit: BoxFit.cover,
color: MyColors.black,
colorBlendMode: BlendMode.color,
) :
Image.file(
key: Key('2'),
File(image),
Gif(
key: const Key('2'),
width: context.widthScreen,
height: context.heightScreen,
image: FileImage(File(image)),
fps: 25,
autostart: autostart,
fit: BoxFit.cover,
),
transitionBuilder: (child, animation) =>
@ -57,12 +73,14 @@ class AnswerPictureBox extends StatelessWidget {
child: child,
),
),
),
if (showIndex)
PositionedDirectional(
top: 0,
child: Container(
width: MySpaces.s34,
alignment: Alignment.center,
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: Color(0XFFF2F7FF),
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(10),
@ -71,7 +89,7 @@ class AnswerPictureBox extends StatelessWidget {
child: Text(
'$index',
style: MYTextStyle.titr1.copyWith(
color: Color(0XFF9B85D8),
color: const Color(0XFF9B85D8),
),
),
),
@ -96,7 +114,7 @@ class _CustomShapePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint strokePaint = Paint()
..color = Color(0XFFF2F7FF)
..color = const Color(0XFFF2F7FF)
..style = PaintingStyle.stroke
..strokeWidth = 4;
@ -114,8 +132,8 @@ class _CustomShapeClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
// Original SVG dimensions to calculate the scaling factors.
final double originalWidth = 193.0;
final double originalHeight = 189.0;
const double originalWidth = 193.0;
const double originalHeight = 189.0;
// Scaling factors to make the path responsive.
final double scaleX = size.width / originalWidth;

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

@ -13,9 +13,9 @@ class AnswerTextBox extends StatelessWidget {
clipper: WavyBannerClipper(),
child: Container(
height: 90,
padding: EdgeInsets.symmetric(horizontal: MySpaces.s10),
padding: const EdgeInsets.symmetric(horizontal: MySpaces.s10),
alignment: Alignment.center,
decoration: BoxDecoration(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
@ -29,7 +29,7 @@ class AnswerTextBox extends StatelessWidget {
text,
textAlign: TextAlign.center,
style: MYTextStyle.matn2.copyWith(
color: Color(0XFF322386),
color: const Color(0XFF322386),
height: 1.2,
),
maxLines: 5,

2
lib/core/widgets/button/my_blue_button.dart

@ -41,7 +41,7 @@ class MyBlueButton extends StatelessWidget {
child: Text(
title ?? '',
style: MYTextStyle.button1.copyWith(
color: Color(0XFF1D6EFF),
color: const Color(0XFF1D6EFF),
),
),
),

2
lib/core/widgets/button/my_white_button.dart

@ -40,7 +40,7 @@ class MyWhiteButton extends StatelessWidget {
child: Text(
title ?? '',
style: MYTextStyle.button1.copyWith(
color: Color(0XFFD93D16),
color: const Color(0XFFD93D16),
fontSize: setSize(context: context, tablet: 60),
),
),

2
lib/core/widgets/button/my_yellow_button.dart

@ -41,7 +41,7 @@ class MyYellowButton extends StatelessWidget {
child: Text(
title ?? '',
style: MYTextStyle.button1.copyWith(
color: Color(0XFFD93D16),
color: const Color(0XFFD93D16),
fontSize: setSize(context: context, tablet: 60),
),
),

60
lib/core/widgets/confetti/my_confetti.dart

@ -1,60 +0,0 @@
import 'dart:math';
import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
class MyConfetti extends StatelessWidget {
const MyConfetti({super.key, required this.controller});
final ConfettiController controller;
@override
Widget build(BuildContext context) {
return ConfettiWidget(
confettiController: controller,
shouldLoop: false,
blastDirectionality: BlastDirectionality.explosive,
blastDirection: pi / 2,
gravity: 1,
emissionFrequency: 0.5,
numberOfParticles: 15,
particleDrag: 0.1,
colors: const [
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.blue,
Colors.purple,
Colors.pink,
Colors.cyan,
],
createParticlePath: (size) {
double degToRad(double deg) => deg * (pi / 180.0);
const numberOfPoints = 5;
final halfWidth = size.width / 2;
final externalRadius = halfWidth;
final internalRadius = halfWidth / 2.5;
final degreesPerStep = degToRad(360 / numberOfPoints);
final halfDegreesPerStep = degreesPerStep / 2;
final path = Path();
final fullAngle = degToRad(360);
path.moveTo(size.width, halfWidth);
for (double step = 0; step < fullAngle; step += degreesPerStep) {
path.lineTo(
halfWidth + externalRadius * cos(step),
halfWidth + externalRadius * sin(step),
);
path.lineTo(
halfWidth + internalRadius * cos(step + halfDegreesPerStep),
halfWidth + internalRadius * sin(step + halfDegreesPerStep),
);
}
path.close();
return path;
},
);
}
}

17
lib/core/widgets/dialog/about_us_dialog.dart

@ -1,5 +1,6 @@
import 'dart:ui';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
@ -14,7 +15,7 @@ import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.dart';
Future<void> showAboutUsDialog({required BuildContext context}) async {
await showDialog(
context: context,
builder: (context) => AboutUsDialog(),
builder: (context) => const AboutUsDialog(),
barrierColor: MyColors.purple.withValues(alpha: 0.82),
useSafeArea: false,
);
@ -48,19 +49,23 @@ class AboutUsDialog extends StatelessWidget {
Text(
context.translate.about_us,
style: MYTextStyle.titr3.copyWith(
color: Color(0XFF322386),
color: const Color(0XFF322386),
),
),
Text(
Expanded(
child: AutoSizeText(
context.translate.about_us_desc,
minFontSize: 12,
maxFontSize: 20,
style: MYTextStyle.matn1.copyWith(
color: Color(0XFF494178),
color: const Color(0XFF494178),
),
),
MyImage(
),
const MyImage(
image: MyAssets.newHorizon,
),
MyImage(
const MyImage(
image: MyAssets.khadijeLogo,
size: 100,
),

25
lib/core/widgets/dialog/exit_dialog.dart

@ -6,22 +6,25 @@ import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/dialog/styles/dialog_background.dart';
import 'package:hadi_hoda_flutter/core/widgets/dialog/styles/dialog_button.dart';
Future<void> showExitDialog({required BuildContext context}) async {
Future<void> showExitDialog({required BuildContext context, bool? backHome}) async {
await showDialog(
context: context,
builder: (context) => ExitDialog(),
builder: (context) => ExitDialog(backHome: backHome),
barrierColor: MyColors.purple.withValues(alpha: 0.82),
useSafeArea: false,
);
}
class ExitDialog extends StatelessWidget {
const ExitDialog({super.key});
const ExitDialog({super.key, this.backHome});
final bool? backHome;
@override
Widget build(BuildContext context) {
@ -41,11 +44,11 @@ class ExitDialog extends StatelessWidget {
children: [
Text(
context.translate.want_to_exit,
style: MYTextStyle.titr0.copyWith(color: Color(0XFF322386)),
style: MYTextStyle.titr0.copyWith(color: const Color(0XFF322386)),
),
Text(
context.translate.exit_dialog_desc,
style: MYTextStyle.titr3.copyWith(color: Color(0XFF6272A9)),
style: MYTextStyle.titr3.copyWith(color: const Color(0XFF6272A9)),
textAlign: TextAlign.center,
),
Row(
@ -57,12 +60,12 @@ class ExitDialog extends StatelessWidget {
context.pop();
},
height: 72,
color: Color(0XFFC0BDD3),
color: const Color(0XFFC0BDD3),
child: Text(
context.translate.cancel,
style: MYTextStyle.button2.copyWith(
shadows: [
BoxShadow(
const BoxShadow(
color: Color(0XFF9895AE),
offset: Offset(0, 3.32),
),
@ -74,15 +77,19 @@ class ExitDialog extends StatelessWidget {
Expanded(
child: DialogButton(
onTap: () {
if(backHome ?? false){
context.goNamed(Routes.homePage);
} else {
SystemNavigator.pop();
}
},
height: 72,
color: Color(0XFFD42427),
color: const Color(0XFFD42427),
child: Text(
context.translate.exit,
style: MYTextStyle.button2.copyWith(
shadows: [
BoxShadow(
const BoxShadow(
color: Color(0XFFC82020),
offset: Offset(0, 3.32),
),

8
lib/core/widgets/dialog/hadith_dialog.dart

@ -53,7 +53,7 @@ class HadithDialog extends StatelessWidget {
if(hadith.isNotEmpty){
return ListView.separated(
itemCount: hadith.length,
separatorBuilder: (context, index) => Divider(
separatorBuilder: (context, index) => const Divider(
height: 40,
thickness: 1,
endIndent: MySpaces.s20,
@ -64,13 +64,13 @@ class HadithDialog extends StatelessWidget {
TextSpan(
text: '${hadith[index].narratorName ?? ''}:\n',
style: MYTextStyle.titr1.copyWith(
color: Color(0XFF494178),
color: const Color(0XFF494178),
),
children: [
TextSpan(
text: hadith[index].hadithText,
style: MYTextStyle.matn1.copyWith(
color: Color(0XFF494178),
color: const Color(0XFF494178),
),
),
],
@ -82,7 +82,7 @@ class HadithDialog extends StatelessWidget {
child: Text(
context.translate.no_hadith,
style: MYTextStyle.titr4.copyWith(
color: Color(0XFF494178),
color: const Color(0XFF494178),
),
),
);

386
lib/core/widgets/dialog/reward_dialog.dart

@ -0,0 +1,386 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_animations.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.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/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/dialog/styles/dialog_background.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/prize_entity.dart';
import 'package:lottie/lottie.dart';
Future<void> showRewardDialog({
required BuildContext context,
required PrizeEntity prize,
}) async {
await showDialog(
context: context,
builder: (context) => RewardDialog(prize: prize),
barrierColor: MyColors.purple.withValues(alpha: 0.82),
useSafeArea: false,
);
}
class RewardDialog extends StatelessWidget {
const RewardDialog({super.key, required this.prize});
final PrizeEntity prize;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyColors.transparent,
body: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal:
setSize(context: context, mobile: 18, tablet: 120) ?? 0,
),
child: Stack(
alignment: Alignment.center,
children: [
Lottie.asset(
MyAnimations.confetti,
height: context.heightScreen,
fit: BoxFit.cover,
),
Stack(
clipBehavior: Clip.none,
children: [
DialogBackground(
child: Column(
spacing: 34,
children: [
Text(
context.translate.reward,
style: MYTextStyle.titr0.copyWith(
color: const Color(0XFF322386),
fontSize: 22,
),
),
Text(
prize.title ?? '',
style: MYTextStyle.titr0.copyWith(
color: const Color(0XFF322386),
fontSize: 22,
),
),
CustomPaint(
painter: _CustomShapePainter(),
child: ClipPath(
clipper: _CustomShapeClipper(),
child: GestureDetector(
onTap: () {
context.pushNamed(
Routes.videoPage,
extra: prize.animationURL as String,
);
},
child: Stack(
alignment: Alignment.center,
children: [
Image.network(
prize.imageURL ?? '',
loadingBuilder: (context, child, loadingProgress) => SizedBox(
height: 300,
width: 300,
child: child,
),
fit: BoxFit.cover,
),
const MyImage(
image: MyAssets.iconPlayVideo,
),
],
),
),
),
),
],
),
),
PositionedDirectional(
end: setSize(context: context, mobile: 30, tablet: 40),
top: -12,
child: GestureDetector(
onTap: context.pop,
behavior: HitTestBehavior.opaque,
child: MyImage(
image: MyAssets.closeBtn,
size: setSize(context: context, mobile: 40, tablet: 60),
),
),
),
],
),
],
),
),
),
),
);
}
}
class _CustomShapePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint strokePaint = Paint()
..color = const Color(0XFFF2F7FF)
..style = PaintingStyle.stroke
..strokeWidth = 4;
final Path path = _CustomShapeClipper().getClip(size);
canvas.drawPath(path, strokePaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return this != oldDelegate;
}
}
class _CustomShapeClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
// Original SVG dimensions to calculate the scaling factors.
const double originalWidth = 193.0;
const double originalHeight = 189.0;
// Scaling factors to make the path responsive.
final double scaleX = size.width / originalWidth;
final double scaleY = size.height / originalHeight;
// The path is defined using the scaled coordinates from the SVG.
final Path path = Path()
..moveTo(148.483 * scaleX, 4.10254 * scaleY)
..cubicTo(
131.624 * scaleX,
1.93333 * scaleY,
111.221 * scaleX,
1.00169 * scaleY,
91.2451 * scaleX,
1.2666 * scaleY,
)
..cubicTo(
71.2667 * scaleX,
1.53156 * scaleY,
51.7626 * scaleX,
2.99274 * scaleY,
36.6973 * scaleX,
5.59668 * scaleY,
)
..cubicTo(
29.1597 * scaleX,
6.8995 * scaleY,
22.7796 * scaleX,
8.48114 * scaleY,
18.0205 * scaleX,
10.3203 * scaleY,
)
..cubicTo(
15.641 * scaleX,
11.2399 * scaleY,
13.7026 * scaleX,
12.2101 * scaleY,
12.2383 * scaleX,
13.2188 * scaleY,
)
..cubicTo(
10.7653 * scaleX,
14.2333 * scaleY,
9.84633 * scaleX,
15.2359 * scaleY,
9.3916 * scaleX,
16.1904 * scaleY,
)
..cubicTo(
8.252 * scaleX,
18.5828 * scaleY,
7.18153 * scaleX,
22.466 * scaleY,
6.2207 * scaleX,
27.5654 * scaleY,
)
..cubicTo(
5.26481 * scaleX,
32.6387 * scaleY,
4.43215 * scaleX,
38.8273 * scaleY,
3.73535 * scaleX,
45.7744 * scaleY,
)
..cubicTo(
2.34189 * scaleX,
59.6675 * scaleY,
1.49647 * scaleX,
76.5363 * scaleY,
1.27832 * scaleX,
93.4678 * scaleY,
)
..cubicTo(
1.06017 * scaleX,
110.4 * scaleY,
1.47057 * scaleX,
127.372 * scaleY,
2.58301 * scaleX,
141.473 * scaleY,
)
..cubicTo(
3.13928 * scaleX,
148.524 * scaleY,
3.86921 * scaleX,
154.841 * scaleY,
4.78125 * scaleX,
160.068 * scaleY,
)
..cubicTo(
5.69748 * scaleX,
165.32 * scaleY,
6.78334 * scaleX,
169.385 * scaleY,
8.01367 * scaleX,
171.984 * scaleY,
)
..cubicTo(
8.53417 * scaleX,
173.084 * scaleY,
9.59654 * scaleX,
174.216 * scaleY,
11.2891 * scaleX,
175.343 * scaleY,
)
..cubicTo(
12.9722 * scaleX,
176.463 * scaleY,
15.1988 * scaleX,
177.524 * scaleY,
17.9219 * scaleX,
178.515 * scaleY,
)
..cubicTo(
23.3679 * scaleX,
180.496 * scaleY,
30.6491 * scaleX,
182.138 * scaleY,
39.1807 * scaleX,
183.437 * scaleY,
)
..cubicTo(
56.2336 * scaleX,
186.032 * scaleY,
78.0934 * scaleX,
187.222 * scaleY,
99.8242 * scaleX,
187.064 * scaleY,
)
..cubicTo(
121.556 * scaleX,
186.906 * scaleY,
143.101 * scaleX,
185.4 * scaleY,
159.525 * scaleX,
182.622 * scaleY,
)
..cubicTo(
167.745 * scaleX,
181.232 * scaleY,
174.627 * scaleX,
179.531 * scaleY,
179.594 * scaleX,
177.548 * scaleY,
)
..cubicTo(
182.079 * scaleX,
176.556 * scaleY,
184.034 * scaleX,
175.512 * scaleY,
185.429 * scaleX,
174.437 * scaleY,
)
..cubicTo(
186.83 * scaleX,
173.355 * scaleY,
187.568 * scaleX,
172.319 * scaleY,
187.812 * scaleX,
171.361 * scaleY,
)
..lineTo(
187.812 * scaleX,
171.361 * scaleY,
) // In SVG, this was H (horizontal line), equivalent to lineTo in Flutter
..cubicTo(
189.156 * scaleX,
166.074 * scaleY,
190.148 * scaleX,
155.525 * scaleY,
190.773 * scaleX,
142.157 * scaleY,
)
..cubicTo(
191.396 * scaleX,
128.832 * scaleY,
191.651 * scaleX,
112.822 * scaleY,
191.552 * scaleX,
96.6875 * scaleY,
)
..cubicTo(
191.453 * scaleX,
80.5539 * scaleY,
191.001 * scaleX,
64.3091 * scaleY,
190.213 * scaleX,
50.5156 * scaleY,
)
..cubicTo(
189.423 * scaleX,
36.6928 * scaleY,
188.299 * scaleX,
25.4153 * scaleY,
186.876 * scaleX,
19.167 * scaleY,
)
..cubicTo(
186.404 * scaleX,
17.0929 * scaleY,
185.566 * scaleX,
15.3424 * scaleY,
184.087 * scaleX,
14.1582 * scaleY,
)
..cubicTo(
181.343 * scaleX,
11.9613 * scaleY,
176.72 * scaleX,
9.98089 * scaleY,
170.561 * scaleX,
8.27539 * scaleY,
)
..cubicTo(
164.434 * scaleX,
6.579 * scaleY,
156.914 * scaleX,
5.18731 * scaleY,
148.483 * scaleX,
4.10254 * scaleY,
)
..close(); // Closes the path to form a complete shape.
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return this != oldClipper;
}
}

6
lib/core/widgets/error/error_state.dart

@ -17,19 +17,19 @@ class ErrorState extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
Spacer(),
const Spacer(),
Text(
context.translate.lost_connection,
style: MYTextStyle.button1,
),
MyImage(image: MyAssets.error),
const MyImage(image: MyAssets.error),
MySpaces.s40.gapHeight,
Text(
context.translate.connected_to_internet,
textAlign: TextAlign.center,
style: MYTextStyle.matn3,
),
Spacer(),
const Spacer(),
MyBlueButton(
title: context.translate.retry,
onTap: onTap,

5
lib/core/widgets/pop_scope/my_pop_scope.dart

@ -2,16 +2,17 @@ import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/core/widgets/dialog/exit_dialog.dart';
class MyPopScope extends StatelessWidget {
const MyPopScope({super.key, required this.child});
const MyPopScope({super.key, required this.child, this.backHome});
final Widget child;
final bool? backHome;
void onPopInvokedWithResult(
bool didPop,
dynamic result,
BuildContext context,
) {
showExitDialog(context: context);
showExitDialog(context: context, backHome: backHome);
}
@override

20
lib/core/widgets/showcase/my_showcase_widget.dart

@ -20,7 +20,7 @@ enum ShowcaseTooltipType {
crossAxisAlignment: CrossAxisAlignment.center,
spacing: MySpaces.s4,
children: [
MyImage(
const MyImage(
image: MyAssets.handPoint,
size: 50,
),
@ -44,7 +44,7 @@ enum ShowcaseTooltipType {
Transform.flip(
flipY: true,
flipX: true,
child: MyImage(
child: const MyImage(
image: MyAssets.handPoint,
size: 50,
),
@ -63,7 +63,7 @@ enum ShowcaseTooltipType {
),
Transform.rotate(
angle: 2.5,
child: MyImage(
child: const MyImage(
image: MyAssets.handPoint,
size: 50,
),
@ -82,13 +82,13 @@ enum ShowcaseTooltipType {
class MyShowcaseWidget extends StatelessWidget {
const MyShowcaseWidget({
super.key,
required this.globalKey,
required this.child,
this.globalKey,
this.description,
this.type = ShowcaseTooltipType.bottom,
});
final GlobalKey globalKey;
final GlobalKey? globalKey;
final String? description;
final Widget child;
final ShowcaseTooltipType type;
@ -96,10 +96,10 @@ class MyShowcaseWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Showcase(
key: globalKey,
key: globalKey ?? GlobalKey(),
disableBarrierInteraction: false,
targetShapeBorder: CircleBorder(),
overlayColor: Color(0XFF0F0041),
targetShapeBorder: const CircleBorder(),
overlayColor: const Color(0XFF0F0041),
overlayOpacity: 0.82,
/// ToolTip
tooltipPadding: EdgeInsets.zero,
@ -108,7 +108,7 @@ class MyShowcaseWidget extends StatelessWidget {
targetTooltipGap: 0,
toolTipSlideEndDistance: MySpaces.s6,
toolTipMargin: 0,
tooltipActionConfig: TooltipActionConfig(
tooltipActionConfig: const TooltipActionConfig(
gapBetweenContentAndAction: 0,
),
tooltipActions: [
@ -128,7 +128,7 @@ class MyShowcaseWidget extends StatelessWidget {
Expanded(
child: MyInkwell(
onTap: () {
ShowcaseView.get().unregister();
ShowcaseView.get().dismiss();
},
splashColor: MyColors.transparent,
highlightColor: MyColors.transparent,

76
lib/core/widgets/video/my_video_player.dart

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/services/audio_service.dart';
import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
import 'package:hadi_hoda_flutter/init_bindings.dart';
import 'package:pod_player/pod_player.dart';
class MyVideoPlayer extends StatefulWidget {
const MyVideoPlayer({super.key, required this.videoURL});
final String? videoURL;
@override
State<MyVideoPlayer> createState() => _MyVideoPlayerState();
}
class _MyVideoPlayerState extends State<MyVideoPlayer> {
late final PodPlayerController _controller;
final AudioService _mainAudioService = locator(
instanceName: MyConstants.mainAudioService,
);
final AudioService _effectAudioService = locator(
instanceName: MyConstants.effectAudioService,
);
@override
void initState() {
super.initState();
_mainAudioService.stop();
_effectAudioService.stop();
_controller = PodPlayerController(
podPlayerConfig: const PodPlayerConfig(
autoPlay: false,
isLooping: false,
wakelockEnabled: true,
),
playVideoFrom: PlayVideoFrom.network(widget.videoURL ?? ''),
)..initialise();
}
@override
void dispose() {
_controller.dispose();
_mainAudioService.play();
_effectAudioService.play();
MyDevice.setPortrait();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyColors.black,
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: MyColors.transparent,
foregroundColor: MyColors.white,
),
body: PodVideoPlayer(
controller: _controller,
matchVideoAspectRatioToFrame: true,
matchFrameAspectRatioToVideo: true,
videoAspectRatio: _controller.videoPlayerValue?.aspectRatio ?? 16 / 9,
podProgressBarConfig: const PodProgressBarConfig(),
onToggleFullScreen: (isFullScreen) async {
if (isFullScreen) {
await MyDevice.setAllOrientations();
} else {
await MyDevice.setPortrait();
}
},
),
);
}
}

22
lib/features/download/data/datasource/download_datasource.dart

@ -10,8 +10,8 @@ import 'package:hadi_hoda_flutter/core/response/base_response.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/utils/storage_path.dart';
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.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';
import 'package:hadi_hoda_flutter/features/level/data/model/node_model.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart';
import 'package:hive/hive.dart';
@ -90,11 +90,11 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
'lang': selectedLanguage,
},
onReceive: (count, total) {
streamController.add(DownloadEntity(
count: count / 1,
total: total / 1,
percent: (count / total) * 100,
));
// streamController.add(DownloadEntity(
// count: count / 1,
// total: total / 1,
// percent: (count / total) * 100,
// ));
},
).then((value) async {
await LocalStorage.saveData(
@ -146,11 +146,11 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
path: MyApi.levels,
queryParameters: {'lang': selectedLanguage},
);
final List<LevelEntity> levels = BaseResponse.getDataList<LevelEntity>(
response?['result'],
(json) => LevelModel.fromJson(json),
final List<NodeEntity> levels = BaseResponse.getDataList<NodeEntity>(
response?['path'],
(json) => NodeModel.fromJson(json),
);
await data.add(TotalDataEntity(code: selectedLanguage, levels: levels));
await data.add(TotalDataEntity(code: selectedLanguage, nodes: levels));
}
}

6
lib/features/download/presentation/bloc/download_bloc.dart

@ -8,6 +8,7 @@ import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
import 'package:hadi_hoda_flutter/core/status/base_status.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
import 'package:hadi_hoda_flutter/core/utils/pre_cache_image.dart';
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart';
import 'package:hadi_hoda_flutter/features/download/domain/usecases/get_audios_usecase.dart';
import 'package:hadi_hoda_flutter/features/download/domain/usecases/get_images_usecase.dart';
@ -25,6 +26,7 @@ class DownloadBloc extends Bloc<DownloadEvent, DownloadState> {
this._saveLevelsUseCase,
)
: super(const DownloadState()) {
preCacheImages();
on<GetImagesEvent>(_getImagesEvent);
on<GetAudiosEvent>(_getAudiosEvent);
on<SaveLevelsEvent>(_saveLevelsEvent);
@ -38,7 +40,7 @@ class DownloadBloc extends Bloc<DownloadEvent, DownloadState> {
final LoadingStreamUseCase _loadingStreamUseCase;
/// ------------Variables------------
Stream<DownloadEntity> loadingStream = Stream.empty();
Stream<DownloadEntity> loadingStream = const Stream.empty();
/// ------------Controllers------------
@ -49,7 +51,7 @@ class DownloadBloc extends Bloc<DownloadEvent, DownloadState> {
GetImagesEvent event,
Emitter<DownloadState> emit,
) async {
emit(state.copyWith(getFilesStatus: BaseInit()));
emit(state.copyWith(getFilesStatus: const BaseInit()));
await _getImagesUseCase(NoParams()).then((value) {
value.fold(
(data) {

8
lib/features/download/presentation/ui/download_page.dart

@ -25,7 +25,7 @@ class DownloadPage extends StatelessWidget {
height: context.heightScreen,
width: context.widthScreen,
decoration: BoxDecoration(
gradient: LinearGradient(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
@ -34,7 +34,7 @@ class DownloadPage extends StatelessWidget {
],
),
image: DecorationImage(
image: AssetImage(MyAssets.pattern),
image: const AssetImage(MyAssets.pattern),
scale: 3,
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
@ -74,7 +74,7 @@ class DownloadPage extends StatelessWidget {
}
Widget _image() {
return Stack(
return const Stack(
children: [
MyImage(
image: MyAssets.hadiHoda,
@ -101,7 +101,7 @@ class DownloadPage extends StatelessWidget {
style: MYTextStyle.titr0,
),
StreamBuilder<DownloadEntity>(
initialData: DownloadEntity(),
initialData: const DownloadEntity(),
stream: context.read<DownloadBloc>().loadingStream,
builder: (context, snapshot) => Text(
'${context.translate.downloading_data} (${snapshot.data?.count.toMB ?? 0.0}mb / ${snapshot.data?.total.toMB ?? 0.0}mb)',

10
lib/features/download/presentation/ui/widgets/download_loading_widget.dart

@ -19,7 +19,7 @@ class DownloadLoadingWidget extends StatelessWidget {
child: Container(
width: 300,
height: 60,
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
vertical: MySpaces.s4,
horizontal: MySpaces.s2,
),
@ -34,7 +34,7 @@ class DownloadLoadingWidget extends StatelessWidget {
),
),
child: StreamBuilder<DownloadEntity>(
initialData: DownloadEntity(),
initialData: const DownloadEntity(),
stream: loadingStream,
builder: (context, snapshot) {
return Row(
@ -49,12 +49,12 @@ class DownloadLoadingWidget extends StatelessWidget {
end: 260 - ((snapshot.data?.percent ?? 0) * 260 / 100),
),
decoration: BoxDecoration(
color: Color(0xFF1F59BD).withValues(alpha: 0.25),
color: const Color(0xFF1F59BD).withValues(alpha: 0.25),
),
child: ClipPath(
clipper: BubbleClip(),
child: Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
gradient: RadialGradient(
radius: 2,
colors: [
@ -74,7 +74,7 @@ class DownloadLoadingWidget extends StatelessWidget {
child: Text(
'${snapshot.data?.percent?.toInt() ?? 0}%',
style: MYTextStyle.titr4.copyWith(
color: Color(0XFF6E83A8),
color: const Color(0XFF6E83A8),
),
),
),

33
lib/features/guider/data/datasource/guider_datasource.dart

@ -0,0 +1,33 @@
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart';
import 'package:hive/hive.dart';
abstract class IGuiderDatasource {
Future<LevelEntity> getLevel();
}
/// Local
class GuiderDatasourceImpl implements IGuiderDatasource {
const GuiderDatasourceImpl();
@override
Future<LevelEntity> getLevel() async {
try {
final String selectedLanguage =
LocalStorage.readData(key: MyConstants.selectLanguage) ??
MyConstants.defaultLanguage;
final Box<TotalDataEntity> levelBox = Hive.box(MyConstants.levelBox);
final TotalDataEntity findData = levelBox.values.singleWhere(
(e) => e.code == selectedLanguage,
orElse: () => TotalDataEntity(),
);
final LevelEntity? findLevel = findData.nodes?.first.level;
return findLevel ?? LevelEntity();
} catch (e) {
throw MyException(errorMessage: '$e');
}
}
}

28
lib/features/guider/data/repository_impl/guider_repository_impl.dart

@ -0,0 +1,28 @@
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/guider/data/datasource/guider_datasource.dart';
import 'package:hadi_hoda_flutter/features/guider/domain/repository/guider_repository.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
class GuiderRepositoryImpl implements IGuiderRepository {
final IGuiderDatasource datasource;
const GuiderRepositoryImpl(this.datasource);
@override
Future<DataState<LevelEntity, MyException>> getLevel() async {
try {
final LevelEntity response = await datasource.getLevel();
return DataState.success(response);
} on MyException catch (e) {
return DataState.error(e);
} catch (e) {
if (kDebugMode) {
rethrow;
} else {
return DataState.error(MyException(errorMessage: '$e'));
}
}
}
}

7
lib/features/guider/domain/repository/guider_repository.dart

@ -0,0 +1,7 @@
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/domain/entity/level_entity.dart';
abstract class IGuiderRepository {
Future<DataState<LevelEntity, MyException>> getLevel();
}

17
lib/features/guider/domain/usecases/get_first_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/no_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/guider/domain/repository/guider_repository.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
class GetFirstLevelUseCase implements UseCase<LevelEntity, NoParams> {
final IGuiderRepository repository;
const GetFirstLevelUseCase(this.repository);
@override
Future<DataState<LevelEntity, MyException>> call(NoParams params) {
return repository.getLevel();
}
}

131
lib/features/guider/presentation/bloc/guider_bloc.dart

@ -0,0 +1,131 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/params/no_params.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
import 'package:hadi_hoda_flutter/core/status/base_status.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
import 'package:hadi_hoda_flutter/features/guider/domain/usecases/get_first_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_event.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_state.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
import 'package:showcaseview/showcaseview.dart';
class GuiderBloc extends Bloc<GuiderEvent, GuiderState> {
/// ------------constructor------------
GuiderBloc(this._getFirstLevelUseCase) : super(const GuiderState()) {
registerShowCase();
on<GetFirstLevelEvent>(_getFirstLevelEvent);
}
@override
Future<void> close() {
unRegisterShowCase();
animationController.dispose();
return super.close();
}
/// ------------UseCases------------
final GetFirstLevelUseCase _getFirstLevelUseCase;
/// ------------Variables------------
final Map<String, GlobalKey> showCaseKey = {
'answer_key_0': GlobalKey(),
'answer_key_1': GlobalKey(),
'answer_key_2': GlobalKey(),
'answer_key_3': GlobalKey(),
'notif_key_0': GlobalKey(),
'notif_key_1': GlobalKey(),
'notif_key_2': GlobalKey(),
'notif_key_3': GlobalKey(),
'stepper_key': GlobalKey(),
'hadith_key': GlobalKey(),
'guide_key': GlobalKey(),
};
String? id;
/// ------------Controllers------------
late final AnimationController animationController;
/// ------------Functions------------
void registerShowCase() {
try {
ShowcaseView.register(
onStart: (showcaseIndex, key) {
LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'true');
},
onDismiss: (onDismiss) {
MyContext.get.goNamed(
Routes.questionPage,
pathParameters: {'id': id ?? ''},
);
},
onFinish: () {
MyContext.get.goNamed(
Routes.questionPage,
pathParameters: {'id': id ?? ''},
);
},
);
} catch (_) {}
}
void unRegisterShowCase() {
try {
ShowcaseView.get().unregister();
} catch (_) {}
}
void startShowcase() {
if (LocalStorage.readData(key: MyConstants.firstShowcase) != 'true') {
try {
ShowcaseView.get().startShowCase([
showCaseKey['answer_key_1']!,
showCaseKey['notif_key_0']!,
showCaseKey['stepper_key']!,
showCaseKey['hadith_key']!,
showCaseKey['guide_key']!,
]);
} catch (_) {}
}
}
/// ------------Event Calls------------
FutureOr<void> _getFirstLevelEvent(
GetFirstLevelEvent event,
Emitter<GuiderState> emit,
) async {
id = event.id;
await _getFirstLevelUseCase(NoParams()).then(
(value) => value.fold((data) async {
final LevelEntity level = LevelEntity(
id: data.id,
order: data.order,
title: data.title,
questions: [
...?data.questions,
QuestionEntity(order: (data.questions?.length ?? 0) + 1),
],
);
emit(
state.copyWith(
getQuestionStatus: const BaseComplete(''),
levelEntity: level,
currentQuestion: data.questions?.first,
),
);
await Future.delayed(const Duration(milliseconds: 300), () {
animationController.forward().then((value) {
startShowcase();
});
});
}, (error) {}),
);
}
}

8
lib/features/guider/presentation/bloc/guider_event.dart

@ -0,0 +1,8 @@
sealed class GuiderEvent {
const GuiderEvent();
}
class GetFirstLevelEvent extends GuiderEvent {
final String? id;
const GetFirstLevelEvent({this.id});
}

27
lib/features/guider/presentation/bloc/guider_state.dart

@ -0,0 +1,27 @@
import 'package:hadi_hoda_flutter/core/status/base_status.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
class GuiderState {
final BaseStatus getQuestionStatus;
final LevelEntity? levelEntity;
final QuestionEntity? currentQuestion;
const GuiderState({
this.getQuestionStatus = const BaseInit(),
this.levelEntity,
this.currentQuestion,
});
GuiderState copyWith({
BaseStatus? getQuestionStatus,
LevelEntity? levelEntity,
QuestionEntity? currentQuestion,
}) {
return GuiderState(
getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus,
levelEntity: levelEntity ?? this.levelEntity,
currentQuestion: currentQuestion ?? this.currentQuestion,
);
}
}

248
lib/features/guider/presentation/ui/guider_page.dart

@ -0,0 +1,248 @@
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_audios.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/gap.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/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/fade_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_down_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box_showcase.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/pop_scope/my_pop_scope.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/my_showcase_widget.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_bloc.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_state.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/glassy_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_title.dart';
class GuiderPage extends StatefulWidget {
const GuiderPage({super.key});
@override
State<GuiderPage> createState() => _GuiderPageState();
}
class _GuiderPageState extends State<GuiderPage> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
context
.read<GuiderBloc>()
.animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
reverseDuration: const Duration(milliseconds: 500),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: MyPopScope(
backHome: true,
child: Directionality(
textDirection: TextDirection.ltr,
child: Container(
height: context.heightScreen,
width: context.widthScreen,
padding: EdgeInsets.symmetric(
horizontal:
setSize(
context: context,
mobile: MySpaces.s16,
tablet: MySpaces.s30,
) ??
0,
),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0XFF6930DA), Color(0XFF263AA1)],
),
image: DecorationImage(
image: const AssetImage(MyAssets.pattern),
scale: 3,
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
Colors.black.withValues(alpha: 0.3),
BlendMode.srcIn,
),
),
),
child: Column(
children: [
setPlatform<double>(
android: MySpaces.s20,
iOS: 50,
)?.gapHeight ??
const SizedBox.shrink(),
_topButtons(context),
MySpaces.s10.gapHeight,
Expanded(
child: Column(
children: [
_stepper(context),
_titles(context),
MySpaces.s20.gapHeight,
Expanded(child: _answers(context)),
_bottom(context),
],
),
),
setPlatform<double>(android: MySpaces.s20)?.gapHeight ??
const SizedBox.shrink(),
],
),
),
),
),
);
}
Widget _topButtons(BuildContext context) {
return SlideDownFade(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GlassyButton(
image: MyAssets.home,
audio: MyAudios.back,
onTap: () {},
),
BlocBuilder<GuiderBloc, GuiderState>(
builder: (context, state) =>
QuestionTitle(
step: state.levelEntity?.order,
currentQuestion: state.currentQuestion?.order,
questionLength: state.levelEntity?.questions?.length,
),
),
GlassyButton(image: MyAssets.music, onTap: () {}),
],
),
);
}
Widget _stepper(BuildContext context) {
return FadeAnim(
child: MyShowcaseWidget(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['stepper_key']!,
description: context.translate.showcase_stepper,
child: const QuestionStepper(length: 4, currentStep: 1),
),
);
}
Widget _titles(BuildContext context) {
return BlocBuilder<GuiderBloc, GuiderState>(
builder: (context, state) =>
FadeAnim(
child: Text(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
maxLines: 3,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
offset: const Offset(0, 2),
color: MyColors.black.withValues(alpha: 0.25),
),
],
),
),
),
);
}
Widget _answers(BuildContext context) {
return BlocBuilder<GuiderBloc, GuiderState>(
builder: (context, state) =>
GridView.builder(
itemCount: state.currentQuestion?.answers?.length ?? 0,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
clipBehavior: Clip.none,
padding: EdgeInsets.symmetric(
horizontal: setSize(context: context, tablet: 70) ?? 0,
),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: MySpaces.s20,
mainAxisSpacing: 20,
childAspectRatio: 0.75,
),
itemBuilder: (context, index) =>
state.currentQuestion?.answers?[index].imageId == null
? const SizedBox.shrink()
: SlideAnim(
controller: context.read<GuiderBloc>().animationController,
index: index,
child: AnswerBoxShowCase(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['notif_key_$index']!,
answerGlobalKey: context
.read<GuiderBloc>()
.showCaseKey['answer_key_$index']!,
index: state.currentQuestion?.answers?[index].order ?? 1,
answer:
state.currentQuestion?.answers?[index] ?? AnswerEntity(),
correctAnswer: 0,
onNotifTap: (AnswerEntity answer) {},
onTap: (isCorrect, correctAnswer) {},
),
),
),
);
}
Widget _bottom(BuildContext context) {
return SlideUpFade(
child: SizedBox(
width: context.widthScreen,
child: Stack(
alignment: AlignmentDirectional.centerStart,
children: [
MyShowcaseWidget(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['guide_key']!,
description: context.translate.showcase_guide,
type: ShowcaseTooltipType.top,
child: MyImage(
image: MyAssets.globe,
fit: BoxFit.cover,
size: setSize(context: context, tablet: 120),
),
),
PositionedDirectional(
end: 0,
child: MyShowcaseWidget(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['hadith_key']!,
type: ShowcaseTooltipType.topLeft,
description: context.translate.showcase_hadith,
child: GlassyButton(image: MyAssets.leaf, onTap: () {}),
),
),
],
),
),
);
}
}

60
lib/features/home/presentation/bloc/home_bloc.dart

@ -1,13 +1,17 @@
import 'dart:async';
import 'dart:io';
import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_audios.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
import 'package:hadi_hoda_flutter/core/services/audio_service.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
import 'package:hadi_hoda_flutter/core/utils/storage_path.dart';
import 'package:hadi_hoda_flutter/core/widgets/dialog/about_us_dialog.dart';
import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_event.dart';
import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_state.dart';
@ -16,12 +20,12 @@ import 'package:hive/hive.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
/// ------------constructor------------
HomeBloc(
this._mainAudioService,
this._effectAudioService,
) : super(const HomeState()) {
HomeBloc(this._mainAudioService, this._effectAudioService)
: super(const HomeState()) {
volumeStream = _mainAudioService.volumeStream();
playMusic();
preCacheQuestionGifs();
preCacheAnswerGifs();
on<GetHomeEvent>(_getHomeEvent);
}
@ -35,25 +39,27 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
final AudioService _effectAudioService;
/// ------------Functions------------
void goToLevelPage(BuildContext context){
final String? selectedLanguage = LocalStorage.readData(key: MyConstants.selectLanguage);
void goToLevelPage(BuildContext context) {
final String? selectedLanguage = LocalStorage.readData(
key: MyConstants.selectLanguage,
);
final Box<TotalDataEntity> dataBox = Hive.box(MyConstants.levelBox);
final TotalDataEntity findData = dataBox.values.singleWhere(
(e) => e.code == selectedLanguage,
orElse: () => TotalDataEntity(),
);
if (findData.levels?.isNotEmpty ?? false) {
if (findData.nodes?.isNotEmpty ?? false) {
context.goNamed(Routes.levelPage);
} else {
context.goNamed(Routes.downloadPage);
}
}
void goToLanguagePage(BuildContext context){
void goToLanguagePage(BuildContext context) {
context.pushNamed(Routes.languagePage);
}
void showAboutUs(BuildContext context){
void showAboutUs(BuildContext context) {
showAboutUsDialog(context: context);
}
@ -72,6 +78,42 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
await _mainAudioService.play();
}
Future<void> preCacheQuestionGifs() async {
try {
final Directory directory = Directory(
'${StoragePath.documentDir}/question_image',
);
final List<FileSystemEntity> files = directory.listSync();
await Future.wait(
files.map(
(file) => precacheImage(FileImage(File(file.path)), MyContext.get),
),
);
} catch (e) {
if (kDebugMode) {
print('$e');
}
}
}
Future<void> preCacheAnswerGifs() async {
try {
final Directory directory = Directory(
'${StoragePath.documentDir}/answer_image',
);
final List<FileSystemEntity> files = directory.listSync();
await Future.wait(
files.map(
(file) => precacheImage(FileImage(File(file.path)), MyContext.get),
),
);
} catch (e) {
if (kDebugMode) {
print('$e');
}
}
}
/// ------------Api Calls------------
FutureOr<void> _getHomeEvent(event, emit) async {}
}

8
lib/features/home/presentation/ui/home_page.dart

@ -21,7 +21,7 @@ class HomePage extends StatelessWidget {
return Scaffold(
body: MyPopScope(
child: DecoratedBox(
decoration: BoxDecoration(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(MyAssets.backgroundHome),
fit: BoxFit.cover,
@ -47,7 +47,7 @@ class HomePage extends StatelessWidget {
top: setPlatform(android: MySpaces.s36, iOS: 50),
end: MySpaces.s16,
child: SlideDownFade(
delay: Duration(milliseconds: 200),
delay: const Duration(milliseconds: 200),
child: StreamBuilder<double>(
initialData: 1,
stream: context.read<HomeBloc>().volumeStream,
@ -67,7 +67,7 @@ class HomePage extends StatelessWidget {
Positioned _image(BuildContext context) {
return Positioned(
top: setSize(context: context, mobile: 0.1.h, tablet: 0.15.h),
child: Stack(
child: const Stack(
children: [
MyImage(
image: MyAssets.hadiHoda,
@ -91,7 +91,7 @@ class HomePage extends StatelessWidget {
right: MySpaces.s16,
child: SafeArea(
child: SlideUpFade(
delay: Duration(milliseconds: 200),
delay: const Duration(milliseconds: 200),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,

105
lib/features/intro/presentation/bloc/intro_bloc.dart

@ -14,17 +14,31 @@ import 'package:hadi_hoda_flutter/features/intro/presentation/ui/screens/intro_2
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/screens/intro_3_screen.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/screens/intro_4_screen.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/screens/intro_5_screen.dart';
import 'package:pod_player/pod_player.dart';
class IntroBloc extends Bloc<IntroEvent, IntroState> {
/// ------------constructor------------
IntroBloc() : super(const IntroState()){
on<ChangeIntroEvent>(_changeIntroEvent);
IntroBloc() : super(const IntroState()) {
initVideos();
listenController();
on<InitVideosEvent>(_initVideosEvent);
on<ChangeVideosEvent>(_changeVideosEvent);
}
@override
Future<void> close() {
podController1.dispose();
podController2.dispose();
podController3.dispose();
podController4.dispose();
podController5.dispose();
return super.close();
}
/// ------------UseCases------------
/// ------------Variables------------
final List<Widget> intros = [
final List<Widget> intros = const [
Intro1Screen(key: Key('0')),
Intro2Screen(key: Key('1')),
Intro3Screen(key: Key('2')),
@ -33,21 +47,92 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
];
/// ------------Controllers------------
final PodPlayerController podController1 = PodPlayerController(
playVideoFrom: PlayVideoFrom.asset('assets/videos/intro_1.mp4'),
);
final PodPlayerController podController2 = PodPlayerController(
playVideoFrom: PlayVideoFrom.asset('assets/videos/intro_2.mp4'),
);
final PodPlayerController podController3 = PodPlayerController(
playVideoFrom: PlayVideoFrom.asset('assets/videos/intro_3.mp4'),
);
final PodPlayerController podController4 = PodPlayerController(
playVideoFrom: PlayVideoFrom.asset('assets/videos/intro_4.mp4'),
);
final PodPlayerController podController5 = PodPlayerController(
playVideoFrom: PlayVideoFrom.asset('assets/videos/intro_5.mp4'),
);
/// ------------Functions------------
Future<void> goToLevelPage() async {
Future<void> goToHomePage() async {
await LocalStorage.saveData(key: MyConstants.firstIntro, value: 'true');
if (MyContext.get.mounted) {
MyContext.get.goNamed(Routes.levelPage);
MyContext.get.goNamed(Routes.homePage);
}
}
void listenController() {
// A helper function to check for video completion
void onVideoEnd(PodPlayerController controller, int controllerIndex) {
final position = controller.videoPlayerValue?.position;
final duration = controller.videoPlayerValue?.duration;
if (position != null && duration != null && position >= duration) {
// Only trigger the change if the completed video is the current one
if (state.currentIntro == controllerIndex) {
add(ChangeVideosEvent());
}
}
}
podController1.addListener(() => onVideoEnd(podController1, 0));
podController2.addListener(() => onVideoEnd(podController2, 1));
podController3.addListener(() => onVideoEnd(podController3, 2));
podController4.addListener(() => onVideoEnd(podController4, 3));
podController5.addListener(() => onVideoEnd(podController5, 4));
}
/// ------------Api Calls------------
FutureOr<void> _changeIntroEvent(ChangeIntroEvent event, Emitter emit) async {
if (state.currentIntro < intros.length - 1) {
emit(state.copyWith(currentIntro: state.currentIntro + 1));
} else {
goToLevelPage();
FutureOr<void> _changeVideosEvent(ChangeVideosEvent event, Emitter emit) async {
switch (state.currentIntro) {
case 0:
podController2.play();
emit(state.copyWith(currentIntro: 1));
break;
case 1:
podController3.play();
emit(state.copyWith(currentIntro: 2));
break;
case 2:
podController4.play();
emit(state.copyWith(currentIntro: 3));
break;
case 3:
podController5.play();
emit(state.copyWith(currentIntro: 4));
break;
case 4:
goToHomePage();
break;
}
}
Future<void> initVideos() async {
await Future.wait([
podController1.initialise(),
podController2.initialise(),
podController3.initialise(),
podController4.initialise(),
podController5.initialise(),
]);
podController1.play();
add(InitVideosEvent());
}
FutureOr<void> _initVideosEvent(
InitVideosEvent event,
Emitter<IntroState> emit,
) async {
emit(state.copyWith(currentIntro: 0));
}
}

4
lib/features/intro/presentation/bloc/intro_event.dart

@ -2,4 +2,6 @@ sealed class IntroEvent {
const IntroEvent();
}
class ChangeIntroEvent extends IntroEvent {}
class InitVideosEvent extends IntroEvent {}
class ChangeVideosEvent extends IntroEvent {}

8
lib/features/intro/presentation/ui/intro_page.dart

@ -16,7 +16,7 @@ class IntroPage extends StatelessWidget {
return Scaffold(
body: SizedBox.expand(
child: GestureDetector(
onTap: () => context.read<IntroBloc>().add(ChangeIntroEvent()),
onTap: () => context.read<IntroBloc>().add(ChangeVideosEvent()),
child: Stack(
children: [
_mainScreen(),
@ -31,8 +31,8 @@ class IntroPage extends StatelessWidget {
BlocBuilder<IntroBloc, IntroState> _mainScreen() {
return BlocBuilder<IntroBloc, IntroState>(
builder: (context, state) => AnimatedSwitcher(
duration: Duration(milliseconds: 200),
reverseDuration: Duration(milliseconds: 200),
duration: const Duration(milliseconds: 200),
reverseDuration: const Duration(milliseconds: 200),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
child: context.read<IntroBloc>().intros[state.currentIntro],
@ -47,7 +47,7 @@ class IntroPage extends StatelessWidget {
start: MySpaces.s30,
bottom: MySpaces.s16,
child: TextButton(
onPressed: () => context.read<IntroBloc>().goToLevelPage(),
onPressed: () => context.read<IntroBloc>().goToHomePage(),
style: TextButton.styleFrom(
foregroundColor: MyColors.white.withValues(alpha: 0.7),
),

54
lib/features/intro/presentation/ui/screens/intro_1_screen.dart

@ -1,54 +1,24 @@
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_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/widgets/bubble_chat_widget.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart';
import 'package:pod_player/pod_player.dart';
class Intro1Screen extends StatelessWidget {
const Intro1Screen({super.key});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: MyImage(image: MyAssets.intro_1, fit: BoxFit.cover),
return SizedBox.expand(
child: PodVideoPlayer(
controller: context.read<IntroBloc>().podController1,
overlayBuilder: (options) => const SizedBox.shrink(),
alwaysShowProgressBar: false,
videoAspectRatio: context.widthScreen / context.heightScreen,
frameAspectRatio: context.widthScreen / context.heightScreen,
backgroundColor: const Color(0XFF00154C),
onLoading: (context) => const SizedBox.shrink(),
),
PositionedDirectional(
end: setSize<double>(
context: context,
mobile: MySpaces.s10,
tablet: 0.15.w,
),
top: setSize<double>(
context: context,
mobile: 0.15.h,
tablet: 0.25.h,
),
width: 290,
child: SlideUpFade(
delay: Duration(milliseconds: 300),
child: BubbleChatWidget(
text: context.translate.intro_1_1,
flip: true,
),
),
),
PositionedDirectional(
start: setSize(context: context, mobile: MySpaces.s30, tablet: 0.2.w),
top: setSize(context: context, mobile: 0.3.h, tablet: 0.35.h),
width: 250,
child: SlideUpFade(
delay: Duration(milliseconds: 800),
child: BubbleChatWidget(text: context.translate.intro_1_2),
),
),
],
);
}
}

38
lib/features/intro/presentation/ui/screens/intro_2_screen.dart

@ -1,37 +1,25 @@
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_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/widgets/bubble_chat_widget.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart';
import 'package:pod_player/pod_player.dart';
class Intro2Screen extends StatelessWidget {
const Intro2Screen({super.key});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: Transform.flip(
flipX: true,
child: MyImage(image: MyAssets.intro_2, fit: BoxFit.cover),
return SizedBox.expand(
child: PodVideoPlayer(
controller: context.read<IntroBloc>().podController2,
overlayBuilder: (options) => const SizedBox.shrink(),
alwaysShowProgressBar: false,
videoAspectRatio: context.widthScreen / context.heightScreen,
frameAspectRatio: context.widthScreen / context.heightScreen,
backgroundColor: MyColors.transparent,
onLoading: (context) => const SizedBox.shrink(),
),
),
PositionedDirectional(
end: setSize(context: context, mobile: MySpaces.s40, tablet: 0.3.w),
top: setSize(context: context, mobile: 0.17.h, tablet: 0.23.h),
width: 250,
child: SlideUpFade(
delay: Duration(milliseconds: 300),
child: BubbleChatWidget(text: context.translate.intro_2),
),
),
],
);
}
}

38
lib/features/intro/presentation/ui/screens/intro_3_screen.dart

@ -1,37 +1,25 @@
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_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/widgets/bubble_chat_widget.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart';
import 'package:pod_player/pod_player.dart';
class Intro3Screen extends StatelessWidget {
const Intro3Screen({super.key});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: MyImage(image: MyAssets.intro_3, fit: BoxFit.cover),
return SizedBox.expand(
child: PodVideoPlayer(
controller: context.read<IntroBloc>().podController3,
overlayBuilder: (options) => const SizedBox.shrink(),
alwaysShowProgressBar: false,
videoAspectRatio: context.widthScreen / context.heightScreen,
frameAspectRatio: context.widthScreen / context.heightScreen,
backgroundColor: MyColors.transparent,
onLoading: (context) => const SizedBox.shrink(),
),
PositionedDirectional(
start: setSize(context: context, mobile: MySpaces.s30),
top: setSize(context: context, mobile: 180, tablet: 0.25.h),
width: 270,
child: SlideUpFade(
delay: Duration(milliseconds: 300),
child: BubbleChatWidget(
text: context.translate.intro_3,
flip: true,
),
),
),
],
);
}
}

37
lib/features/intro/presentation/ui/screens/intro_4_screen.dart

@ -1,36 +1,25 @@
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_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/widgets/bubble_chat_widget.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart';
import 'package:pod_player/pod_player.dart';
class Intro4Screen extends StatelessWidget {
const Intro4Screen({super.key});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: MyImage(image: MyAssets.intro_4, fit: BoxFit.cover),
return SizedBox.expand(
child: PodVideoPlayer(
controller: context.read<IntroBloc>().podController4,
overlayBuilder: (options) => const SizedBox.shrink(),
alwaysShowProgressBar: false,
videoAspectRatio: context.widthScreen / context.heightScreen,
frameAspectRatio: context.widthScreen / context.heightScreen,
backgroundColor: MyColors.transparent,
onLoading: (context) => const SizedBox.shrink(),
),
PositionedDirectional(
start: setSize(context: context, mobile: MySpaces.s10, tablet: 0.2.w),
bottom: setSize(context: context, mobile: 0.4.h, tablet: 0.4.h),
width: 237,
child: SlideUpFade(
delay: Duration(milliseconds: 300),
child: BubbleChatWidget(
text: context.translate.intro_4,
),
),
),
],
);
}
}

35
lib/features/intro/presentation/ui/screens/intro_5_screen.dart

@ -1,34 +1,25 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/ui/widgets/bubble_chat_widget.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart';
import 'package:pod_player/pod_player.dart';
class Intro5Screen extends StatelessWidget {
const Intro5Screen({super.key});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: MyImage(image: MyAssets.intro_5, fit: BoxFit.cover),
return SizedBox.expand(
child: PodVideoPlayer(
controller: context.read<IntroBloc>().podController5,
overlayBuilder: (options) => const SizedBox.shrink(),
alwaysShowProgressBar: false,
videoAspectRatio: context.widthScreen / context.heightScreen,
frameAspectRatio: context.widthScreen / context.heightScreen,
backgroundColor: MyColors.transparent,
onLoading: (context) => const SizedBox.shrink(),
),
PositionedDirectional(
top: setSize(context: context, mobile: 0.37.h, tablet: 0.42.h),
width: 200,
child: SlideUpFade(
delay: Duration(milliseconds: 300),
child: BubbleChatWidget(
text: context.translate.intro_5,
),
),
),
],
);
}
}

12
lib/features/intro/presentation/ui/widgets/bubble_chat_widget.dart

@ -21,15 +21,15 @@ class BubbleChatWidget extends StatelessWidget {
clipper: _ShapeClipper(flip: flip),
child: Container(
width: width,
padding: EdgeInsets.only(
padding: const EdgeInsets.only(
left: MySpaces.s18,
right: MySpaces.s18,
top: MySpaces.s16,
bottom: MySpaces.s28,
),
decoration: BoxDecoration(
color: Color(0XFFF5E8D7).withValues(alpha: 0.5),
borderRadius: BorderRadius.all(Radius.circular(20)),
color: const Color(0XFFF5E8D7).withValues(alpha: 0.5),
borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border.all(
width: 1,
color: MyColors.white.withValues(alpha: 0.5),
@ -55,9 +55,9 @@ class _ShapeClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
var path = Path();
double w = size.width;
double h = size.height;
final Path path = Path();
final double w = size.width;
final double h = size.height;
// The drawing logic remains exactly the same
path.moveTo(w * 0.92, 0);

4
lib/features/language/presentation/bloc/language_bloc.dart

@ -49,9 +49,9 @@ class LanguageBloc extends Bloc<LanguageEvent, LanguageState> {
value: state.selectedLang.code ?? MyConstants.defaultLanguage,
),
]);
AppBloc appBloc = locator();
final AppBloc appBloc = locator();
appBloc.add(
ChangeLocaleEvent(state.selectedLang.locale ?? Locale('en', 'US')));
ChangeLocaleEvent(state.selectedLang.locale ?? const Locale('en', 'US')));
if (Directory(
'${StoragePath.documentDir.path}/${state.selectedLang

23
lib/features/language/presentation/ui/language_page.dart

@ -1,3 +1,4 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
@ -25,13 +26,13 @@ class LanguagePage extends StatelessWidget {
height: context.heightScreen,
width: context.widthScreen,
decoration: BoxDecoration(
gradient: LinearGradient(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0XFF00154C), Color(0XFF150532)],
),
image: DecorationImage(
image: AssetImage(MyAssets.pattern),
image: const AssetImage(MyAssets.pattern),
scale: 3,
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
@ -42,8 +43,8 @@ class LanguagePage extends StatelessWidget {
),
child: Padding(
padding: EdgeInsets.only(
left: setSize(context: context, mobile: 60, tablet: 0.3.w) ?? 0,
right: setSize(context: context, mobile: 60, tablet: 0.3.w) ?? 0,
left: setSize(context: context, mobile: 50, tablet: 0.3.w) ?? 0,
right: setSize(context: context, mobile: 50, tablet: 0.3.w) ?? 0,
bottom: MySpaces.s40,
top: 100,
),
@ -60,10 +61,16 @@ class LanguagePage extends StatelessWidget {
spacing: MySpaces.s10,
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyImage(image: MyAssets.lang, size: 28),
Text(
const MyImage(image: MyAssets.lang, size: 28),
Expanded(
child: AutoSizeText(
context.translate.select_language,
style: MYTextStyle.titr0.copyWith(color: Color(0XFF847AC4)),
minFontSize: 12,
maxFontSize: 20,
maxLines: 1,
textAlign: TextAlign.center,
style: MYTextStyle.titr0.copyWith(color: const Color(0XFF847AC4)),
),
),
],
);
@ -101,7 +108,7 @@ class LanguagePage extends StatelessWidget {
Widget _btn(BuildContext context) {
return MyBlueButton(
onTap: () => context.read<LanguageBloc>().add(SaveLevelsEvent()),
onTap: () => context.read<LanguageBloc>().add(const SaveLevelsEvent()),
title: context.translate.select,
);
}

14
lib/features/language/presentation/ui/widgets/language_widget.dart

@ -19,7 +19,7 @@ class LanguageWidget extends StatelessWidget {
onTap: onTap,
title: Text(title ?? ''),
titleTextStyle: MYTextStyle.titr1,
contentPadding: EdgeInsets.symmetric(
contentPadding: const EdgeInsets.symmetric(
vertical: MySpaces.s12,
horizontal: 30,
),
@ -31,20 +31,20 @@ class LanguageWidget extends StatelessWidget {
? Container(
height: 17,
width: 17,
padding: EdgeInsets.all(3),
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(width: 1, color: Color(0XFF3CFF3C)),
gradient: LinearGradient(
border: Border.all(width: 1, color: const Color(0XFF3CFF3C)),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0XFF48D336), Color(0XFF2D7C23)],
),
),
child: MyImage(image: MyAssets.doneRounded),
child: const MyImage(image: MyAssets.doneRounded),
)
: SizedBox(height: 17, width: 17),
shape: RoundedRectangleBorder(
: const SizedBox(height: 17, width: 17),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
selectedTileColor: MyColors.white.withValues(alpha: 0.2),

10
lib/features/level/data/datasource/level_datasource.dart

@ -2,12 +2,12 @@ import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
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/local_storage.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart';
import 'package:hive/hive.dart';
abstract class ILevelDatasource {
Future<List<LevelEntity>> getLevels({required LevelParams params});
Future<List<NodeEntity>> getLevels({required LevelParams params});
}
/// Local
@ -15,7 +15,7 @@ class LocalLevelDatasourceImpl implements ILevelDatasource {
const LocalLevelDatasourceImpl();
@override
Future<List<LevelEntity>> getLevels({required LevelParams params}) async {
Future<List<NodeEntity>> getLevels({required LevelParams params}) async {
try {
final String selectedLanguage = LocalStorage.readData(
key: MyConstants.selectLanguage) ?? MyConstants.defaultLanguage;
@ -24,9 +24,9 @@ class LocalLevelDatasourceImpl implements ILevelDatasource {
(e) => e.code == selectedLanguage,
orElse: () => TotalDataEntity(),
);
return findData.levels ?? [];
return findData.nodes ?? [];
} catch (_) {
throw MyException(errorMessage: 'Operation Failed');
throw const MyException(errorMessage: 'Operation Failed');
}
}
}

25
lib/features/level/data/model/node_model.dart

@ -0,0 +1,25 @@
import 'package:hadi_hoda_flutter/features/level/data/model/level_model.dart';
import 'package:hadi_hoda_flutter/features/level/data/model/prize_model.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
class NodeModel extends NodeEntity {
NodeModel({super.nodeType, super.level, super.prize});
factory NodeModel.fromJson(Map<String, dynamic> json) {
return NodeModel(
nodeType: json['node_type'] == null
? null
: NodeType.fromJson[json['node_type']],
level: json['node_type'] == null
? null
: json['node_type'] == 'level'
? LevelModel.fromJson(json['data'])
: null,
prize: json['node_type'] == null
? null
: json['node_type'] == 'prize'
? PrizeModel.fromJson(json['data'])
: null,
);
}
}

21
lib/features/level/data/model/prize_model.dart

@ -0,0 +1,21 @@
import 'package:hadi_hoda_flutter/features/level/domain/entity/prize_entity.dart';
class PrizeModel extends PrizeEntity {
PrizeModel({
super.id,
super.afterLevel,
super.title,
super.imageURL,
super.animationURL,
});
factory PrizeModel.fromJson(Map<String, dynamic> json) {
return PrizeModel(
id: json['id'],
afterLevel: json['after_level'],
title: json['title'],
imageURL: json['image_url'],
animationURL: json['animation_url'],
);
}
}

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

@ -3,7 +3,7 @@ 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/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/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart';
class LevelRepositoryImpl implements ILevelRepository {
@ -12,11 +12,11 @@ class LevelRepositoryImpl implements ILevelRepository {
const LevelRepositoryImpl(this.datasource);
@override
Future<DataState<List<LevelEntity>, MyException>> getLevels({
Future<DataState<List<NodeEntity>, MyException>> getLevels({
required LevelParams params,
}) async {
try {
final List<LevelEntity> response = await datasource.getLevels(
final List<NodeEntity> response = await datasource.getLevels(
params: params,
);
return DataState.success(response);

30
lib/features/level/domain/entity/node_entity.dart

@ -0,0 +1,30 @@
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/prize_entity.dart';
import 'package:hive/hive.dart';
part 'node_entity.g.dart';
@HiveType(typeId: 8)
enum NodeType {
@HiveField(0)
level,
@HiveField(1)
prize;
static Map<String, NodeType> get fromJson => {
'level': NodeType.level,
'prize': NodeType.prize,
};
}
@HiveType(typeId: 7)
class NodeEntity extends HiveObject {
@HiveField(0)
NodeType? nodeType;
@HiveField(1)
LevelEntity? level;
@HiveField(2)
PrizeEntity? prize;
NodeEntity({this.nodeType, this.level, this.prize});
}

86
lib/features/level/domain/entity/node_entity.g.dart

@ -0,0 +1,86 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'node_entity.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class NodeEntityAdapter extends TypeAdapter<NodeEntity> {
@override
final int typeId = 7;
@override
NodeEntity read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return NodeEntity(
nodeType: fields[0] as NodeType?,
level: fields[1] as LevelEntity?,
prize: fields[2] as PrizeEntity?,
);
}
@override
void write(BinaryWriter writer, NodeEntity obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.nodeType)
..writeByte(1)
..write(obj.level)
..writeByte(2)
..write(obj.prize);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NodeEntityAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class NodeTypeAdapter extends TypeAdapter<NodeType> {
@override
final int typeId = 8;
@override
NodeType read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return NodeType.level;
case 1:
return NodeType.prize;
default:
return NodeType.level;
}
}
@override
void write(BinaryWriter writer, NodeType obj) {
switch (obj) {
case NodeType.level:
writer.writeByte(0);
break;
case NodeType.prize:
writer.writeByte(1);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NodeTypeAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

25
lib/features/level/domain/entity/prize_entity.dart

@ -0,0 +1,25 @@
import 'package:hive/hive.dart';
part 'prize_entity.g.dart';
@HiveType(typeId: 6)
class PrizeEntity extends HiveObject {
@HiveField(0)
int? id;
@HiveField(1)
int? afterLevel;
@HiveField(2)
String? title;
@HiveField(3)
String? imageURL;
@HiveField(4)
String? animationURL;
PrizeEntity({
this.id,
this.afterLevel,
this.title,
this.imageURL,
this.animationURL,
});
}

53
lib/features/level/domain/entity/prize_entity.g.dart

@ -0,0 +1,53 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'prize_entity.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class PrizeEntityAdapter extends TypeAdapter<PrizeEntity> {
@override
final int typeId = 6;
@override
PrizeEntity read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return PrizeEntity(
id: fields[0] as int?,
afterLevel: fields[1] as int?,
title: fields[2] as String?,
imageURL: fields[3] as String?,
animationURL: fields[4] as String?,
);
}
@override
void write(BinaryWriter writer, PrizeEntity obj) {
writer
..writeByte(5)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.afterLevel)
..writeByte(2)
..write(obj.title)
..writeByte(3)
..write(obj.imageURL)
..writeByte(4)
..write(obj.animationURL);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PrizeEntityAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

6
lib/features/level/domain/entity/total_data_entity.dart

@ -1,4 +1,4 @@
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
import 'package:hive/hive.dart';
part 'total_data_entity.g.dart';
@ -8,10 +8,10 @@ class TotalDataEntity extends HiveObject{
@HiveField(0)
String? code;
@HiveField(1)
List<LevelEntity>? levels;
List<NodeEntity>? nodes;
TotalDataEntity({
this.code,
this.levels,
this.nodes,
});
}

4
lib/features/level/domain/entity/total_data_entity.g.dart

@ -18,7 +18,7 @@ class TotalDataEntityAdapter extends TypeAdapter<TotalDataEntity> {
};
return TotalDataEntity(
code: fields[0] as String?,
levels: (fields[1] as List?)?.cast<LevelEntity>(),
nodes: (fields[1] as List?)?.cast<NodeEntity>(),
);
}
@ -29,7 +29,7 @@ class TotalDataEntityAdapter extends TypeAdapter<TotalDataEntity> {
..writeByte(0)
..write(obj.code)
..writeByte(1)
..write(obj.levels);
..write(obj.nodes);
}
@override

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

@ -1,8 +1,10 @@
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';
import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
abstract class ILevelRepository {
Future<DataState<List<LevelEntity>, MyException>> getLevels({required LevelParams params});
Future<DataState<List<NodeEntity>, MyException>> getLevels({
required LevelParams params,
});
}

6
lib/features/level/domain/usecases/get_levels_usecase.dart

@ -2,16 +2,16 @@ 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/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart';
class GetLevelsUseCase implements UseCase<List<LevelEntity>, LevelParams> {
class GetLevelsUseCase implements UseCase<List<NodeEntity>, LevelParams> {
final ILevelRepository repository;
const GetLevelsUseCase(this.repository);
@override
Future<DataState<List<LevelEntity>, MyException>> call(LevelParams params) {
Future<DataState<List<NodeEntity>, MyException>> call(LevelParams params) {
return repository.getLevels(params: params);
}
}

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

@ -13,12 +13,15 @@ import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/dialog/reward_dialog.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/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/prize_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_levels_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';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/level_widget.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/node_widget.dart';
class LevelBloc extends Bloc<LevelEvent, LevelState> {
/// ------------constructor------------
@ -174,7 +177,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
];
final List<LevelEntity> levelList = [];
final List<NodeEntity> nodeList = [];
late final Stream<double> volumeStream;
@ -221,6 +224,14 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
}
}
bool getReward(int index) {
final int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1',
);
return currentLevel > index;
}
Future<void> changeMute() async {
await Future.wait([
_mainAudioService.changeMute(),
@ -229,13 +240,21 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
}
int get diamonds {
int currentLevel = int.parse(
final int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1',
);
return currentLevel - 1;
}
/// ------------Api Calls------------
void showReward({
required BuildContext context,
required PrizeEntity prize,
}) {
showRewardDialog(context: context, prize: prize);
}
/// ------------Event Calls------------
FutureOr<void> _getLevelListEvent(GetLevelListEvent event,
Emitter<LevelState> emit) async {
final int currentLevel = int.parse(
@ -244,11 +263,11 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
await _getLeveslUseCase(LevelParams()).then((value) {
value.fold(
(data) async {
levelList.addAll(data);
nodeList.addAll(data);
try {
emit(state.copyWith(
getLevelStatus: const BaseComplete(''),
chooseLevel: data.singleWhere((e) => e.order == currentLevel),
chooseLevel: data.singleWhere((e) => e.level?.order == currentLevel).level,
));
} catch (e) {
emit(state.copyWith(

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

@ -1,5 +1,5 @@
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/level_widget.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/node_widget.dart';
sealed class LevelEvent {
const LevelEvent();

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

@ -16,7 +16,7 @@ import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_event.d
import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_state.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/diamond_level.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/level_path.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/level_widget.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/node_widget.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/play_button.dart';
class LevelPage extends StatelessWidget {
@ -28,6 +28,7 @@ class LevelPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: MyPopScope(
backHome: true,
child: Stack(
alignment: Alignment.center,
children: [
@ -193,7 +194,7 @@ class LevelPage extends StatelessWidget {
child: Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
const Positioned.fill(
child: LevelPath(),
),
Positioned.fill(child: _levelLocation(context)),
@ -208,7 +209,7 @@ class LevelPage extends StatelessWidget {
clipBehavior: Clip.none,
children: [
...List.generate(
context.read<LevelBloc>().levelList.length,
context.read<LevelBloc>().nodeList.length,
(index) => Positioned(
top: context.read<LevelBloc>().locationList[index].top,
bottom: context.read<LevelBloc>().locationList[index].bottom,
@ -217,10 +218,17 @@ class LevelPage extends StatelessWidget {
child: BlocBuilder<LevelBloc, LevelState>(
buildWhen: (previous, current) =>
previous.chooseLevel?.id != current.chooseLevel?.id,
builder: (context, state) => LevelWidget(
builder: (context, state) => NodeWidget(
chooseLevel: state.chooseLevel,
level: context.read<LevelBloc>().levelList[index],
type: context.read<LevelBloc>().getLevelType(index + 1),
node: context.read<LevelBloc>().nodeList[index],
type: context.read<LevelBloc>().getLevelType,
getReward: context.read<LevelBloc>().getReward,
onRewardPressed: (prize) {
context.read<LevelBloc>().showReward(
context: context,
prize: prize,
);
},
onTap: (LevelEntity level, LevelType type) {
context.read<LevelBloc>().add(
ChooseLevelEvent(level, type),
@ -236,7 +244,7 @@ class LevelPage extends StatelessWidget {
}
Widget _ship(BuildContext context) {
return ShipAnim(
return const ShipAnim(
child: MyImage(image: MyAssets.ship),
);
}
@ -271,7 +279,7 @@ class LevelPage extends StatelessWidget {
size: setSize(context: context, tablet: 80),
),
),
Spacer(),
const Spacer(),
DiamondLevel(
diamonds: context.read<LevelBloc>().diamonds,
),
@ -292,7 +300,7 @@ class LevelPage extends StatelessWidget {
}
Widget _background(BuildContext context) {
return MyImage(
return const MyImage(
image: MyAssets.mapBackground,
fit: BoxFit.cover,
);

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

@ -10,7 +10,7 @@ class BottomPath extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _Path(),
painter: const _Path(),
size: Size(
context.widthScreen * 0.76,
context.heightScreen * 0.64,

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save