diff --git a/.gitignore b/.gitignore index 3820a95..5bf1f3b 100644 --- a/.gitignore +++ b/.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 diff --git a/.metadata b/.metadata deleted file mode 100644 index 3d40ea9..0000000 --- a/.metadata +++ /dev/null @@ -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' diff --git a/README.md b/README.md index 93f5d5c..f88065b 100644 --- a/README.md +++ b/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 diff --git a/analysis_options.yaml b/analysis_options.yaml index f9b3034..31e4275 100644 --- a/analysis_options.yaml +++ b/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 \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 50bc02b..8e05a08 100644 --- a/android/app/build.gradle.kts +++ b/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") } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index da9f7d3..7ceae5e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,8 @@ + android:icon="@mipmap/ic_launcher" + android:enableOnBackInvokedCallback="true"> + + + + + + + + + diff --git a/assets/videos/intro_1.mp4 b/assets/videos/intro_1.mp4 new file mode 100644 index 0000000..d4e82b4 Binary files /dev/null and b/assets/videos/intro_1.mp4 differ diff --git a/assets/videos/intro_2.mp4 b/assets/videos/intro_2.mp4 new file mode 100644 index 0000000..37676a5 Binary files /dev/null and b/assets/videos/intro_2.mp4 differ diff --git a/assets/videos/intro_3.mp4 b/assets/videos/intro_3.mp4 new file mode 100644 index 0000000..56f10d9 Binary files /dev/null and b/assets/videos/intro_3.mp4 differ diff --git a/assets/videos/intro_4.mp4 b/assets/videos/intro_4.mp4 new file mode 100644 index 0000000..1b7df80 Binary files /dev/null and b/assets/videos/intro_4.mp4 differ diff --git a/assets/videos/intro_5.mp4 b/assets/videos/intro_5.mp4 new file mode 100644 index 0000000..c456f63 Binary files /dev/null and b/assets/videos/intro_5.mp4 differ diff --git a/compile_svg.zsh b/compile_svg.zsh new file mode 100755 index 0000000..702da25 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/ios/Podfile b/ios/Podfile index 620e46e..6649374 100644 --- a/ios/Podfile +++ b/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' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index edf03be..8e2b452 100644 --- a/ios/Podfile.lock +++ b/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 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 4d55364..3291dc0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,11 @@ + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart index 2153575..8e16d4c 100644 --- a/lib/common_ui/resources/my_assets.dart +++ b/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 images = [ backgroundHome, @@ -103,5 +107,8 @@ class MyAssets { satellite, planetFinal, behindDiamond, + gift, + giftDisable, + giftBackground, ]; } \ No newline at end of file diff --git a/lib/common_ui/theme/my_theme.dart b/lib/common_ui/theme/my_theme.dart index d59c6b9..a4f6ad8 100644 --- a/lib/common_ui/theme/my_theme.dart +++ b/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, + ); } \ No newline at end of file diff --git a/lib/core/constants/my_api.dart b/lib/core/constants/my_api.dart index 0e24eaa..a8aebd5 100644 --- a/lib/core/constants/my_api.dart +++ b/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/'; } diff --git a/lib/core/constants/my_constants.dart b/lib/core/constants/my_constants.dart index b7a2826..59c91ec 100644 --- a/lib/core/constants/my_constants.dart +++ b/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 languages = [ LanguageEntity(title: 'English (English)', code: 'en', locale: Locale('en','US')), diff --git a/lib/core/middlewares/my_middlewares.dart b/lib/core/middlewares/my_middlewares.dart index 09b7f82..0778906 100644 --- a/lib/core/middlewares/my_middlewares.dart +++ b/lib/core/middlewares/my_middlewares.dart @@ -30,4 +30,14 @@ class MyMiddlewares { return null; } } + + static FutureOr 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']}'; + } + } } diff --git a/lib/core/routers/hero_dialog_route.dart b/lib/core/routers/hero_dialog_route.dart index 7b53b6b..b191512 100644 --- a/lib/core/routers/hero_dialog_route.dart +++ b/lib/core/routers/hero_dialog_route.dart @@ -16,7 +16,7 @@ class HeroDialogRoute extends PageRoute { 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 extends PageRoute { 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 animation, Animation secondaryAnimation) { diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index 2b604c5..07b7cd7 100644 --- a/lib/core/routers/my_routes.dart +++ b/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), + ), ], ); \ No newline at end of file diff --git a/lib/core/services/audio_service.dart b/lib/core/services/audio_service.dart index 4fc6f9e..2feb262 100644 --- a/lib/core/services/audio_service.dart +++ b/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 _streamController = StreamController.broadcast(); - final double? volume; + final double volume; - AudioService({this.volume}) { - _player.setVolume(volume ?? 1); + AudioService({required this.volume}){ + setVolume(volume: volume); } Future setAudio({String? filePath, String? assetPath}) async { @@ -72,10 +72,10 @@ class AudioService { } } - Future changeMute() async { + Future 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 volumeStream() => _player.volumeStream; + double get audioVolume => _player.volume; + Stream playingStream() async* { _player.processingStateStream.listen((event) { if (event == ProcessingState.ready) { diff --git a/lib/core/utils/app_life_cycle.dart b/lib/core/utils/app_life_cycle.dart new file mode 100644 index 0000000..79f1fec --- /dev/null +++ b/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(); + } + } +} diff --git a/lib/core/utils/pre_cache_image.dart b/lib/core/utils/pre_cache_image.dart new file mode 100644 index 0000000..3651b51 --- /dev/null +++ b/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 preCacheImages() async { + await Future.wait( + MyAssets.images.map( + (assetPath) => precacheImage(AssetImage(assetPath), MyContext.get), + ), + ); +} diff --git a/lib/core/utils/storage_path.dart b/lib/core/utils/storage_path.dart index 56a6746..01e9543 100644 --- a/lib/core/utils/storage_path.dart +++ b/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 getDocumentDir() async { diff --git a/lib/core/widgets/animations/fade_anim.dart b/lib/core/widgets/animations/fade_anim.dart index 0a5e40b..2db0e92 100644 --- a/lib/core/widgets/animations/fade_anim.dart +++ b/lib/core/widgets/animations/fade_anim.dart @@ -19,8 +19,8 @@ class _FadeAnimState extends State 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( begin: 0, diff --git a/lib/core/widgets/animations/fade_anim_controller.dart b/lib/core/widgets/animations/fade_anim_controller.dart new file mode 100644 index 0000000..2965c32 --- /dev/null +++ b/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 createState() => _FadeAnimControllerState(); +} + +class _FadeAnimControllerState extends State + with SingleTickerProviderStateMixin { + late Animation _animation; + + @override + void initState() { + super.initState(); + _animation = Tween( + 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), + ); + } +} diff --git a/lib/core/widgets/animations/fade_anim_delayed.dart b/lib/core/widgets/animations/fade_anim_delayed.dart new file mode 100644 index 0000000..ea9f655 --- /dev/null +++ b/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 createState() => _FadeAnimDelayedState(); +} + +class _FadeAnimDelayedState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + reverseDuration: const Duration(seconds: 500), + ); + _animation = Tween( + begin: 0, + end: 1, + ).animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); + + startAnim(); + } + + Future 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), + ); + } +} diff --git a/lib/core/widgets/animations/globe_animation.dart b/lib/core/widgets/animations/globe_animation.dart index 4911704..6585db9 100644 --- a/lib/core/widgets/animations/globe_animation.dart +++ b/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 createState() => _GlobeAnimationState(); @@ -25,55 +24,34 @@ class _GlobeAnimationState extends State 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( 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) { - if (_gradient == null) { - if (!mounted) return; - setState(() { - _gradient = RadialGradient( - colors: [ - Color(0XFFDFCD00), - Color(0XFFDFCD00).withValues(alpha: 0.35), - Color(0XFFDFCD00).withValues(alpha: 0), - ], - center: Alignment.center, - ); - }); - } else { - setState(() { - _gradient = null; - }); - } - }); - } 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, - ); - }); - } + _controller.repeat(reverse: true); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_gradient == null) { + if (!mounted) return; + setState(() { + _gradient = RadialGradient( + colors: [ + const Color(0XFFDFCD00), + const Color(0XFFDFCD00).withValues(alpha: 0.35), + const Color(0XFFDFCD00).withValues(alpha: 0), + ], + center: Alignment.center, + ); + }); + } else { + setState(() { + _gradient = null; + }); + } + }); } @override @@ -94,7 +72,7 @@ class _GlobeAnimationState extends State 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, ), diff --git a/lib/core/widgets/animations/rotation_anim.dart b/lib/core/widgets/animations/rotation_anim.dart index ea4d9e9..b588fdd 100644 --- a/lib/core/widgets/animations/rotation_anim.dart +++ b/lib/core/widgets/animations/rotation_anim.dart @@ -19,7 +19,7 @@ class _RotationAnimState extends State super.initState(); _controller = AnimationController( vsync: this, - duration: Duration(seconds: 20), + duration: const Duration(seconds: 20), ); _animation = Tween( begin: 0, diff --git a/lib/core/widgets/animations/scale_anim.dart b/lib/core/widgets/animations/scale_anim.dart index e76c247..214be38 100644 --- a/lib/core/widgets/animations/scale_anim.dart +++ b/lib/core/widgets/animations/scale_anim.dart @@ -20,8 +20,8 @@ class _ScaleAnimState extends State 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( begin: 0, diff --git a/lib/core/widgets/animations/ship_anim.dart b/lib/core/widgets/animations/ship_anim.dart index 217ca2d..ddcdd3e 100644 --- a/lib/core/widgets/animations/ship_anim.dart +++ b/lib/core/widgets/animations/ship_anim.dart @@ -22,8 +22,8 @@ class _ShipAnimState extends State 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(); } diff --git a/lib/core/widgets/animations/slide_anim.dart b/lib/core/widgets/animations/slide_anim.dart index 14c02c9..e62368d 100644 --- a/lib/core/widgets/animations/slide_anim.dart +++ b/lib/core/widgets/animations/slide_anim.dart @@ -19,7 +19,7 @@ class SlideAnim extends StatefulWidget { class _SlideAnimState extends State with SingleTickerProviderStateMixin { late Animation _animation; - final List offsetList = [ + final List offsetList = const [ Offset(-2, -2), Offset(2, -2), Offset(-2, 2), diff --git a/lib/core/widgets/animations/slide_down_fade.dart b/lib/core/widgets/animations/slide_down_fade.dart index e035c65..0ef73a7 100644 --- a/lib/core/widgets/animations/slide_down_fade.dart +++ b/lib/core/widgets/animations/slide_down_fade.dart @@ -25,8 +25,8 @@ class _SlideDownFadeState extends State 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( @@ -35,7 +35,7 @@ class _SlideDownFadeState extends State ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); _slideAnim = Tween( - begin: Offset(0, -0.1), + begin: const Offset(0, -0.1), end: Offset.zero, ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); startAnim(); diff --git a/lib/core/widgets/animations/slide_up_fade.dart b/lib/core/widgets/animations/slide_up_fade.dart index 5b9dda4..8df3866 100644 --- a/lib/core/widgets/animations/slide_up_fade.dart +++ b/lib/core/widgets/animations/slide_up_fade.dart @@ -25,8 +25,8 @@ class _SlideUpFadeState extends State 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( @@ -35,7 +35,7 @@ class _SlideUpFadeState extends State ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); _slideAnim = Tween( - begin: Offset(0, 0.1), + begin: const Offset(0, 0.1), end: Offset.zero, ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); startAnim(); diff --git a/lib/core/widgets/answer_box/answer_box.dart b/lib/core/widgets/answer_box/answer_box.dart index b01d027..a9b672a 100644 --- a/lib/core/widgets/answer_box/answer_box.dart +++ b/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 createState() => _AnswerBoxState(); @@ -53,14 +52,14 @@ class _AnswerBoxState extends State { } : 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,24 +67,27 @@ class _AnswerBoxState extends State { 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, - child: GestureDetector( - onTap: () { - widget.onNotifTap?.call(widget.answer); - }, - child: MyImage( - image: MyAssets.iconNotif, - size: setSize(context: context, tablet: 50), - ), + 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); + }, + child: MyImage( + image: MyAssets.iconNotif, + size: setSize(context: context, tablet: 50), ), ), ), diff --git a/lib/core/widgets/answer_box/answer_box_show.dart b/lib/core/widgets/answer_box/answer_box_show.dart index 726c799..75d7bea 100644 --- a/lib/core/widgets/answer_box/answer_box_show.dart +++ b/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( diff --git a/lib/core/widgets/answer_box/answer_box_showcase.dart b/lib/core/widgets/answer_box/answer_box_showcase.dart new file mode 100644 index 0000000..d0d1423 --- /dev/null +++ b/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 createState() => _AnswerBoxShowCaseState(); +} + +class _AnswerBoxShowCaseState extends State { + 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), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/core/widgets/answer_box/styles/image_box.dart b/lib/core/widgets/answer_box/styles/image_box.dart new file mode 100644 index 0000000..3a6589e --- /dev/null +++ b/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 { + @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 oldClipper) { + return this != oldClipper; + } +} \ No newline at end of file diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index f1f2a56..64915a9 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/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,49 +39,61 @@ class AnswerPictureBox extends StatelessWidget { child: Stack( alignment: Alignment.center, children: [ - AnimatedSwitcher( - duration: Duration(milliseconds: 150), - reverseDuration: Duration(milliseconds: 150), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - child: selected && (index != correctAnswer) ? - Image.file( - key: Key('1'), - File(image), - fit: BoxFit.cover, - color: MyColors.black, - colorBlendMode: BlendMode.color, - ) : - Image.file( - key: Key('2'), - File(image), - fit: BoxFit.cover, + 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) ? + 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, + ) : + 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) => + FadeTransition( + opacity: animation, + child: child, + ), ), - transitionBuilder: (child, animation) => - FadeTransition( - opacity: animation, - child: child, - ), ), - PositionedDirectional( - top: 0, - child: Container( - width: MySpaces.s34, - alignment: Alignment.center, - decoration: BoxDecoration( - color: Color(0XFFF2F7FF), - borderRadius: BorderRadius.vertical( - bottom: Radius.circular(10), + if (showIndex) + PositionedDirectional( + top: 0, + child: Container( + width: MySpaces.s34, + alignment: Alignment.center, + decoration: const BoxDecoration( + color: Color(0XFFF2F7FF), + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(10), + ), ), - ), - child: Text( - '$index', - style: MYTextStyle.titr1.copyWith( - color: Color(0XFF9B85D8), + child: Text( + '$index', + style: MYTextStyle.titr1.copyWith( + color: const Color(0XFF9B85D8), + ), ), ), ), - ), if(selected) PositionedDirectional( top: setSize(context: context, mobile: MySpaces.s8, tablet: MySpaces.s20), @@ -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 { @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; diff --git a/lib/core/widgets/answer_box/styles/text_box.dart b/lib/core/widgets/answer_box/styles/text_box.dart index 343e1c6..c6f82ec 100644 --- a/lib/core/widgets/answer_box/styles/text_box.dart +++ b/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, diff --git a/lib/core/widgets/button/my_blue_button.dart b/lib/core/widgets/button/my_blue_button.dart index b90f8b1..7082d11 100644 --- a/lib/core/widgets/button/my_blue_button.dart +++ b/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), ), ), ), diff --git a/lib/core/widgets/button/my_white_button.dart b/lib/core/widgets/button/my_white_button.dart index e88b951..62f9b61 100644 --- a/lib/core/widgets/button/my_white_button.dart +++ b/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), ), ), diff --git a/lib/core/widgets/button/my_yellow_button.dart b/lib/core/widgets/button/my_yellow_button.dart index 33fc12e..d9d746c 100644 --- a/lib/core/widgets/button/my_yellow_button.dart +++ b/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), ), ), diff --git a/lib/core/widgets/confetti/my_confetti.dart b/lib/core/widgets/confetti/my_confetti.dart deleted file mode 100644 index a5f0486..0000000 --- a/lib/core/widgets/confetti/my_confetti.dart +++ /dev/null @@ -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; - }, - ); - } -} diff --git a/lib/core/widgets/dialog/about_us_dialog.dart b/lib/core/widgets/dialog/about_us_dialog.dart index 4992978..9760fcf 100644 --- a/lib/core/widgets/dialog/about_us_dialog.dart +++ b/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 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( - context.translate.about_us_desc, - style: MYTextStyle.matn1.copyWith( - color: Color(0XFF494178), + Expanded( + child: AutoSizeText( + context.translate.about_us_desc, + minFontSize: 12, + maxFontSize: 20, + style: MYTextStyle.matn1.copyWith( + color: const Color(0XFF494178), + ), ), ), - MyImage( + const MyImage( image: MyAssets.newHorizon, ), - MyImage( + const MyImage( image: MyAssets.khadijeLogo, size: 100, ), diff --git a/lib/core/widgets/dialog/exit_dialog.dart b/lib/core/widgets/dialog/exit_dialog.dart index 9d4a7bf..db15902 100644 --- a/lib/core/widgets/dialog/exit_dialog.dart +++ b/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 showExitDialog({required BuildContext context}) async { +Future 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: () { - SystemNavigator.pop(); + 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), ), diff --git a/lib/core/widgets/dialog/hadith_dialog.dart b/lib/core/widgets/dialog/hadith_dialog.dart index 0191fce..b10e197 100644 --- a/lib/core/widgets/dialog/hadith_dialog.dart +++ b/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), ), ), ); diff --git a/lib/core/widgets/dialog/reward_dialog.dart b/lib/core/widgets/dialog/reward_dialog.dart new file mode 100644 index 0000000..7d25500 --- /dev/null +++ b/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 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 { + @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 oldClipper) { + return this != oldClipper; + } +} diff --git a/lib/core/widgets/error/error_state.dart b/lib/core/widgets/error/error_state.dart index 2d6244f..23d2c4c 100644 --- a/lib/core/widgets/error/error_state.dart +++ b/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, diff --git a/lib/core/widgets/pop_scope/my_pop_scope.dart b/lib/core/widgets/pop_scope/my_pop_scope.dart index 96b98da..8340b4b 100644 --- a/lib/core/widgets/pop_scope/my_pop_scope.dart +++ b/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 diff --git a/lib/core/widgets/showcase/my_showcase_widget.dart b/lib/core/widgets/showcase/my_showcase_widget.dart index afd7062..4b762f1 100644 --- a/lib/core/widgets/showcase/my_showcase_widget.dart +++ b/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, diff --git a/lib/core/widgets/video/my_video_player.dart b/lib/core/widgets/video/my_video_player.dart new file mode 100644 index 0000000..365898a --- /dev/null +++ b/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 createState() => _MyVideoPlayerState(); +} + +class _MyVideoPlayerState extends State { + 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(); + } + }, + ), + ); + } +} diff --git a/lib/features/download/data/datasource/download_datasource.dart b/lib/features/download/data/datasource/download_datasource.dart index 587574d..1b452fb 100644 --- a/lib/features/download/data/datasource/download_datasource.dart +++ b/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 levels = BaseResponse.getDataList( - response?['result'], - (json) => LevelModel.fromJson(json), + final List levels = BaseResponse.getDataList( + response?['path'], + (json) => NodeModel.fromJson(json), ); - await data.add(TotalDataEntity(code: selectedLanguage, levels: levels)); + await data.add(TotalDataEntity(code: selectedLanguage, nodes: levels)); } } diff --git a/lib/features/download/presentation/bloc/download_bloc.dart b/lib/features/download/presentation/bloc/download_bloc.dart index 4c94b16..af7064e 100644 --- a/lib/features/download/presentation/bloc/download_bloc.dart +++ b/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 { this._saveLevelsUseCase, ) : super(const DownloadState()) { + preCacheImages(); on(_getImagesEvent); on(_getAudiosEvent); on(_saveLevelsEvent); @@ -38,7 +40,7 @@ class DownloadBloc extends Bloc { final LoadingStreamUseCase _loadingStreamUseCase; /// ------------Variables------------ - Stream loadingStream = Stream.empty(); + Stream loadingStream = const Stream.empty(); /// ------------Controllers------------ @@ -49,7 +51,7 @@ class DownloadBloc extends Bloc { GetImagesEvent event, Emitter emit, ) async { - emit(state.copyWith(getFilesStatus: BaseInit())); + emit(state.copyWith(getFilesStatus: const BaseInit())); await _getImagesUseCase(NoParams()).then((value) { value.fold( (data) { diff --git a/lib/features/download/presentation/ui/download_page.dart b/lib/features/download/presentation/ui/download_page.dart index 7efeedd..eee353d 100644 --- a/lib/features/download/presentation/ui/download_page.dart +++ b/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( - initialData: DownloadEntity(), + initialData: const DownloadEntity(), stream: context.read().loadingStream, builder: (context, snapshot) => Text( '${context.translate.downloading_data} (${snapshot.data?.count.toMB ?? 0.0}mb / ${snapshot.data?.total.toMB ?? 0.0}mb)', diff --git a/lib/features/download/presentation/ui/widgets/download_loading_widget.dart b/lib/features/download/presentation/ui/widgets/download_loading_widget.dart index 933eb13..c9cd0be 100644 --- a/lib/features/download/presentation/ui/widgets/download_loading_widget.dart +++ b/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( - 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), ), ), ), diff --git a/lib/features/guider/data/datasource/guider_datasource.dart b/lib/features/guider/data/datasource/guider_datasource.dart new file mode 100644 index 0000000..6816cf5 --- /dev/null +++ b/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 getLevel(); +} + +/// Local +class GuiderDatasourceImpl implements IGuiderDatasource { + const GuiderDatasourceImpl(); + + @override + Future getLevel() async { + try { + final String selectedLanguage = + LocalStorage.readData(key: MyConstants.selectLanguage) ?? + MyConstants.defaultLanguage; + final Box 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'); + } + } +} diff --git a/lib/features/guider/data/repository_impl/guider_repository_impl.dart b/lib/features/guider/data/repository_impl/guider_repository_impl.dart new file mode 100644 index 0000000..941b6fd --- /dev/null +++ b/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> 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')); + } + } + } +} diff --git a/lib/features/guider/domain/repository/guider_repository.dart b/lib/features/guider/domain/repository/guider_repository.dart new file mode 100644 index 0000000..575a9bd --- /dev/null +++ b/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> getLevel(); +} diff --git a/lib/features/guider/domain/usecases/get_first_level_usecase.dart b/lib/features/guider/domain/usecases/get_first_level_usecase.dart new file mode 100644 index 0000000..77d3eef --- /dev/null +++ b/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 { + final IGuiderRepository repository; + + const GetFirstLevelUseCase(this.repository); + + @override + Future> call(NoParams params) { + return repository.getLevel(); + } +} diff --git a/lib/features/guider/presentation/bloc/guider_bloc.dart b/lib/features/guider/presentation/bloc/guider_bloc.dart new file mode 100644 index 0000000..be6db95 --- /dev/null +++ b/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 { + /// ------------constructor------------ + GuiderBloc(this._getFirstLevelUseCase) : super(const GuiderState()) { + registerShowCase(); + on(_getFirstLevelEvent); + } + + @override + Future close() { + unRegisterShowCase(); + animationController.dispose(); + return super.close(); + } + + /// ------------UseCases------------ + final GetFirstLevelUseCase _getFirstLevelUseCase; + + /// ------------Variables------------ + final Map 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 _getFirstLevelEvent( + GetFirstLevelEvent event, + Emitter 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) {}), + ); + } +} diff --git a/lib/features/guider/presentation/bloc/guider_event.dart b/lib/features/guider/presentation/bloc/guider_event.dart new file mode 100644 index 0000000..0f2c910 --- /dev/null +++ b/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}); +} diff --git a/lib/features/guider/presentation/bloc/guider_state.dart b/lib/features/guider/presentation/bloc/guider_state.dart new file mode 100644 index 0000000..2f399c5 --- /dev/null +++ b/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, + ); + } +} diff --git a/lib/features/guider/presentation/ui/guider_page.dart b/lib/features/guider/presentation/ui/guider_page.dart new file mode 100644 index 0000000..b3ddd63 --- /dev/null +++ b/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 createState() => _GuiderPageState(); +} + +class _GuiderPageState extends State with TickerProviderStateMixin { + + @override + void initState() { + super.initState(); + context + .read() + .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( + 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(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( + 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() + .showCaseKey['stepper_key']!, + description: context.translate.showcase_stepper, + child: const QuestionStepper(length: 4, currentStep: 1), + ), + ); + } + + Widget _titles(BuildContext context) { + return BlocBuilder( + 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( + 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().animationController, + index: index, + child: AnswerBoxShowCase( + globalKey: context + .read() + .showCaseKey['notif_key_$index']!, + answerGlobalKey: context + .read() + .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() + .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() + .showCaseKey['hadith_key']!, + type: ShowcaseTooltipType.topLeft, + description: context.translate.showcase_hadith, + child: GlassyButton(image: MyAssets.leaf, onTap: () {}), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/home/presentation/bloc/home_bloc.dart b/lib/features/home/presentation/bloc/home_bloc.dart index 706dfa9..05cd7ff 100644 --- a/lib/features/home/presentation/bloc/home_bloc.dart +++ b/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 { /// ------------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); } @@ -35,25 +39,27 @@ class HomeBloc extends Bloc { 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 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 { await _mainAudioService.play(); } + Future preCacheQuestionGifs() async { + try { + final Directory directory = Directory( + '${StoragePath.documentDir}/question_image', + ); + final List files = directory.listSync(); + await Future.wait( + files.map( + (file) => precacheImage(FileImage(File(file.path)), MyContext.get), + ), + ); + } catch (e) { + if (kDebugMode) { + print('$e'); + } + } + } + + Future preCacheAnswerGifs() async { + try { + final Directory directory = Directory( + '${StoragePath.documentDir}/answer_image', + ); + final List 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 _getHomeEvent(event, emit) async {} } diff --git a/lib/features/home/presentation/ui/home_page.dart b/lib/features/home/presentation/ui/home_page.dart index 467afec..431473f 100644 --- a/lib/features/home/presentation/ui/home_page.dart +++ b/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( initialData: 1, stream: context.read().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, diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index 986da04..ae5acdc 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/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 { /// ------------constructor------------ - IntroBloc() : super(const IntroState()){ - on(_changeIntroEvent); + IntroBloc() : super(const IntroState()) { + initVideos(); + listenController(); + on(_initVideosEvent); + on(_changeVideosEvent); + } + + @override + Future close() { + podController1.dispose(); + podController2.dispose(); + podController3.dispose(); + podController4.dispose(); + podController5.dispose(); + return super.close(); } /// ------------UseCases------------ /// ------------Variables------------ - final List intros = [ + final List intros = const [ Intro1Screen(key: Key('0')), Intro2Screen(key: Key('1')), Intro3Screen(key: Key('2')), @@ -33,21 +47,92 @@ class IntroBloc extends Bloc { ]; /// ------------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 goToLevelPage() async { + Future 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 _changeIntroEvent(ChangeIntroEvent event, Emitter emit) async { - if (state.currentIntro < intros.length - 1) { - emit(state.copyWith(currentIntro: state.currentIntro + 1)); - } else { - goToLevelPage(); + FutureOr _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 initVideos() async { + await Future.wait([ + podController1.initialise(), + podController2.initialise(), + podController3.initialise(), + podController4.initialise(), + podController5.initialise(), + ]); + podController1.play(); + add(InitVideosEvent()); + } + + FutureOr _initVideosEvent( + InitVideosEvent event, + Emitter emit, + ) async { + emit(state.copyWith(currentIntro: 0)); + } } diff --git a/lib/features/intro/presentation/bloc/intro_event.dart b/lib/features/intro/presentation/bloc/intro_event.dart index 5a38075..5577c28 100644 --- a/lib/features/intro/presentation/bloc/intro_event.dart +++ b/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 {} diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index 2c20d18..5663b63 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/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().add(ChangeIntroEvent()), + onTap: () => context.read().add(ChangeVideosEvent()), child: Stack( children: [ _mainScreen(), @@ -31,8 +31,8 @@ class IntroPage extends StatelessWidget { BlocBuilder _mainScreen() { return BlocBuilder( 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().intros[state.currentIntro], @@ -47,7 +47,7 @@ class IntroPage extends StatelessWidget { start: MySpaces.s30, bottom: MySpaces.s16, child: TextButton( - onPressed: () => context.read().goToLevelPage(), + onPressed: () => context.read().goToHomePage(), style: TextButton.styleFrom( foregroundColor: MyColors.white.withValues(alpha: 0.7), ), diff --git a/lib/features/intro/presentation/ui/screens/intro_1_screen.dart b/lib/features/intro/presentation/ui/screens/intro_1_screen.dart index 7e76806..ae31cd1 100644 --- a/lib/features/intro/presentation/ui/screens/intro_1_screen.dart +++ b/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), - ), - PositionedDirectional( - end: setSize( - context: context, - mobile: MySpaces.s10, - tablet: 0.15.w, - ), - top: setSize( - 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), - ), - ), - ], + return SizedBox.expand( + child: PodVideoPlayer( + controller: context.read().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(), + ), ); } } diff --git a/lib/features/intro/presentation/ui/screens/intro_2_screen.dart b/lib/features/intro/presentation/ui/screens/intro_2_screen.dart index 47ed928..36e110b 100644 --- a/lib/features/intro/presentation/ui/screens/intro_2_screen.dart +++ b/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), - ), - ), - 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), - ), - ), - ], + return SizedBox.expand( + child: PodVideoPlayer( + controller: context.read().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(), + ), ); } } diff --git a/lib/features/intro/presentation/ui/screens/intro_3_screen.dart b/lib/features/intro/presentation/ui/screens/intro_3_screen.dart index 3386651..135b381 100644 --- a/lib/features/intro/presentation/ui/screens/intro_3_screen.dart +++ b/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), - ), - 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, - ), - ), - ), - ], + return SizedBox.expand( + child: PodVideoPlayer( + controller: context.read().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(), + ), ); } } diff --git a/lib/features/intro/presentation/ui/screens/intro_4_screen.dart b/lib/features/intro/presentation/ui/screens/intro_4_screen.dart index e7a1562..61209d7 100644 --- a/lib/features/intro/presentation/ui/screens/intro_4_screen.dart +++ b/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), - ), - 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, - ), - ), - ), - ], + return SizedBox.expand( + child: PodVideoPlayer( + controller: context.read().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(), + ), ); } } diff --git a/lib/features/intro/presentation/ui/screens/intro_5_screen.dart b/lib/features/intro/presentation/ui/screens/intro_5_screen.dart index 7228d8f..57a61b8 100644 --- a/lib/features/intro/presentation/ui/screens/intro_5_screen.dart +++ b/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), - ), - 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, - ), - ), - ), - ], + return SizedBox.expand( + child: PodVideoPlayer( + controller: context.read().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(), + ), ); } } diff --git a/lib/features/intro/presentation/ui/widgets/bubble_chat_widget.dart b/lib/features/intro/presentation/ui/widgets/bubble_chat_widget.dart index 729d6be..e29ed0c 100644 --- a/lib/features/intro/presentation/ui/widgets/bubble_chat_widget.dart +++ b/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 { @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); diff --git a/lib/features/language/presentation/bloc/language_bloc.dart b/lib/features/language/presentation/bloc/language_bloc.dart index c6a616f..bf9e56a 100644 --- a/lib/features/language/presentation/bloc/language_bloc.dart +++ b/lib/features/language/presentation/bloc/language_bloc.dart @@ -49,9 +49,9 @@ class LanguageBloc extends Bloc { 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 diff --git a/lib/features/language/presentation/ui/language_page.dart b/lib/features/language/presentation/ui/language_page.dart index 9165123..189bc48 100644 --- a/lib/features/language/presentation/ui/language_page.dart +++ b/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( - context.translate.select_language, - style: MYTextStyle.titr0.copyWith(color: Color(0XFF847AC4)), + const MyImage(image: MyAssets.lang, size: 28), + Expanded( + child: AutoSizeText( + context.translate.select_language, + 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().add(SaveLevelsEvent()), + onTap: () => context.read().add(const SaveLevelsEvent()), title: context.translate.select, ); } diff --git a/lib/features/language/presentation/ui/widgets/language_widget.dart b/lib/features/language/presentation/ui/widgets/language_widget.dart index 1725b68..fc2cd43 100644 --- a/lib/features/language/presentation/ui/widgets/language_widget.dart +++ b/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), diff --git a/lib/features/level/data/datasource/level_datasource.dart b/lib/features/level/data/datasource/level_datasource.dart index 64cdcf9..e102bf5 100644 --- a/lib/features/level/data/datasource/level_datasource.dart +++ b/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> getLevels({required LevelParams params}); + Future> getLevels({required LevelParams params}); } /// Local @@ -15,7 +15,7 @@ class LocalLevelDatasourceImpl implements ILevelDatasource { const LocalLevelDatasourceImpl(); @override - Future> getLevels({required LevelParams params}) async { + Future> 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'); } } } \ No newline at end of file diff --git a/lib/features/level/data/model/node_model.dart b/lib/features/level/data/model/node_model.dart new file mode 100644 index 0000000..d476f17 --- /dev/null +++ b/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 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, + ); + } +} diff --git a/lib/features/level/data/model/prize_model.dart b/lib/features/level/data/model/prize_model.dart new file mode 100644 index 0000000..7655a37 --- /dev/null +++ b/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 json) { + return PrizeModel( + id: json['id'], + afterLevel: json['after_level'], + title: json['title'], + imageURL: json['image_url'], + animationURL: json['animation_url'], + ); + } +} diff --git a/lib/features/level/data/repository_impl/level_repository_impl.dart b/lib/features/level/data/repository_impl/level_repository_impl.dart index 07d9366..51fdb4f 100644 --- a/lib/features/level/data/repository_impl/level_repository_impl.dart +++ b/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, MyException>> getLevels({ + Future, MyException>> getLevels({ required LevelParams params, }) async { try { - final List response = await datasource.getLevels( + final List response = await datasource.getLevels( params: params, ); return DataState.success(response); diff --git a/lib/features/level/domain/entity/node_entity.dart b/lib/features/level/domain/entity/node_entity.dart new file mode 100644 index 0000000..6c9fa84 --- /dev/null +++ b/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 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}); +} diff --git a/lib/features/level/domain/entity/node_entity.g.dart b/lib/features/level/domain/entity/node_entity.g.dart new file mode 100644 index 0000000..41204de --- /dev/null +++ b/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 { + @override + final int typeId = 7; + + @override + NodeEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + 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 { + @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; +} diff --git a/lib/features/level/domain/entity/prize_entity.dart b/lib/features/level/domain/entity/prize_entity.dart new file mode 100644 index 0000000..7af133b --- /dev/null +++ b/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, + }); +} diff --git a/lib/features/level/domain/entity/prize_entity.g.dart b/lib/features/level/domain/entity/prize_entity.g.dart new file mode 100644 index 0000000..5ab0f95 --- /dev/null +++ b/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 { + @override + final int typeId = 6; + + @override + PrizeEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + 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; +} diff --git a/lib/features/level/domain/entity/total_data_entity.dart b/lib/features/level/domain/entity/total_data_entity.dart index 1428455..4b0c5b8 100644 --- a/lib/features/level/domain/entity/total_data_entity.dart +++ b/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? levels; + List? nodes; TotalDataEntity({ this.code, - this.levels, + this.nodes, }); } \ No newline at end of file diff --git a/lib/features/level/domain/entity/total_data_entity.g.dart b/lib/features/level/domain/entity/total_data_entity.g.dart index 947076d..9512c61 100644 --- a/lib/features/level/domain/entity/total_data_entity.g.dart +++ b/lib/features/level/domain/entity/total_data_entity.g.dart @@ -18,7 +18,7 @@ class TotalDataEntityAdapter extends TypeAdapter { }; return TotalDataEntity( code: fields[0] as String?, - levels: (fields[1] as List?)?.cast(), + nodes: (fields[1] as List?)?.cast(), ); } @@ -29,7 +29,7 @@ class TotalDataEntityAdapter extends TypeAdapter { ..writeByte(0) ..write(obj.code) ..writeByte(1) - ..write(obj.levels); + ..write(obj.nodes); } @override diff --git a/lib/features/level/domain/repository/level_repository.dart b/lib/features/level/domain/repository/level_repository.dart index 672d6f4..fd29b6b 100644 --- a/lib/features/level/domain/repository/level_repository.dart +++ b/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, MyException>> getLevels({required LevelParams params}); + Future, MyException>> getLevels({ + required LevelParams params, + }); } diff --git a/lib/features/level/domain/usecases/get_levels_usecase.dart b/lib/features/level/domain/usecases/get_levels_usecase.dart index 3f12106..fe555c0 100644 --- a/lib/features/level/domain/usecases/get_levels_usecase.dart +++ b/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, LevelParams> { +class GetLevelsUseCase implements UseCase, LevelParams> { final ILevelRepository repository; const GetLevelsUseCase(this.repository); @override - Future, MyException>> call(LevelParams params) { + Future, MyException>> call(LevelParams params) { return repository.getLevels(params: params); } } diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart index 981cc3e..11e9ac7 100644 --- a/lib/features/level/presentation/bloc/level_bloc.dart +++ b/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 { /// ------------constructor------------ @@ -174,7 +177,7 @@ class LevelBloc extends Bloc { ]; - final List levelList = []; + final List nodeList = []; late final Stream volumeStream; @@ -221,6 +224,14 @@ class LevelBloc extends Bloc { } } + bool getReward(int index) { + final int currentLevel = int.parse( + LocalStorage.readData(key: MyConstants.currentLevel) ?? '1', + ); + + return currentLevel > index; + } + Future changeMute() async { await Future.wait([ _mainAudioService.changeMute(), @@ -229,13 +240,21 @@ class LevelBloc extends Bloc { } 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 _getLevelListEvent(GetLevelListEvent event, Emitter emit) async { final int currentLevel = int.parse( @@ -244,11 +263,11 @@ class LevelBloc extends Bloc { 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( diff --git a/lib/features/level/presentation/bloc/level_event.dart b/lib/features/level/presentation/bloc/level_event.dart index 818ed6f..b5824a6 100644 --- a/lib/features/level/presentation/bloc/level_event.dart +++ b/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(); diff --git a/lib/features/level/presentation/ui/level_page.dart b/lib/features/level/presentation/ui/level_page.dart index 2fb4ce7..3dfcdc0 100644 --- a/lib/features/level/presentation/ui/level_page.dart +++ b/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().levelList.length, + context.read().nodeList.length, (index) => Positioned( top: context.read().locationList[index].top, bottom: context.read().locationList[index].bottom, @@ -217,10 +218,17 @@ class LevelPage extends StatelessWidget { child: BlocBuilder( buildWhen: (previous, current) => previous.chooseLevel?.id != current.chooseLevel?.id, - builder: (context, state) => LevelWidget( + builder: (context, state) => NodeWidget( chooseLevel: state.chooseLevel, - level: context.read().levelList[index], - type: context.read().getLevelType(index + 1), + node: context.read().nodeList[index], + type: context.read().getLevelType, + getReward: context.read().getReward, + onRewardPressed: (prize) { + context.read().showReward( + context: context, + prize: prize, + ); + }, onTap: (LevelEntity level, LevelType type) { context.read().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().diamonds, ), @@ -292,7 +300,7 @@ class LevelPage extends StatelessWidget { } Widget _background(BuildContext context) { - return MyImage( + return const MyImage( image: MyAssets.mapBackground, fit: BoxFit.cover, ); diff --git a/lib/features/level/presentation/ui/widgets/bottom_path.dart b/lib/features/level/presentation/ui/widgets/bottom_path.dart index 0d5e1b3..66fb96f 100644 --- a/lib/features/level/presentation/ui/widgets/bottom_path.dart +++ b/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, diff --git a/lib/features/level/presentation/ui/widgets/diamond_level.dart b/lib/features/level/presentation/ui/widgets/diamond_level.dart index 4170dc3..8caea48 100644 --- a/lib/features/level/presentation/ui/widgets/diamond_level.dart +++ b/lib/features/level/presentation/ui/widgets/diamond_level.dart @@ -32,7 +32,7 @@ class DiamondLevel extends StatelessWidget { MySpaces.s16.gapWidth, ShaderMask( blendMode: BlendMode.modulate, - shaderCallback: (bounds) => LinearGradient( + shaderCallback: (bounds) => const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0XFF4BA5EA), Color(0XFF0C4EE9)], @@ -44,7 +44,7 @@ class DiamondLevel extends StatelessWidget { shadows: [ BoxShadow( color: MyColors.black.withValues(alpha: 0.25), - offset: Offset(0, 1.43), + offset: const Offset(0, 1.43), blurRadius: 1.43, ), ], diff --git a/lib/features/level/presentation/ui/widgets/level_widget.dart b/lib/features/level/presentation/ui/widgets/level_widget.dart deleted file mode 100644 index f39fc81..0000000 --- a/lib/features/level/presentation/ui/widgets/level_widget.dart +++ /dev/null @@ -1,121 +0,0 @@ -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_colors.dart'; -import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; -import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart'; -import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart'; -import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; - -enum LevelType { - unFinished, - finished, - current; - - static Map get image => { - LevelType.unFinished: MyAssets.level, - LevelType.finished: MyAssets.finishedLevel, - LevelType.current: MyAssets.currentLevel, - }; - - static Map get textShadowColor => { - LevelType.unFinished: Color(0XFF5B5B5B), - LevelType.finished: Color(0XFF096D7B), - LevelType.current: Color(0XFF91500D), - }; - - static Map get textColor => { - LevelType.unFinished: Color(0XFFEDEDED), - LevelType.finished: Color(0XFFFFF2D0), - LevelType.current: Color(0XFFFFF2D0), - }; -} - -class LevelWidget extends StatelessWidget { - const LevelWidget({ - super.key, - required this.level, - required this.type, - required this.chooseLevel, - this.onTap, - }); - - final LevelType type; - final LevelEntity level; - final LevelEntity? chooseLevel; - final Function(LevelEntity level, LevelType type)? onTap; - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => onTap?.call(level, type), - child: Stack( - alignment: Alignment.topCenter, - clipBehavior: Clip.none, - children: [ - MyImage( - image: LevelType.image[type] ?? MyAssets.level, - fit: BoxFit.cover, - size: setSize(context: context, tablet: 70, mobile: 44), - ), - ShaderMask( - blendMode: BlendMode.modulate, - shaderCallback: (bounds) => LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color(0XFFFFFFFF), - LevelType.textColor[type] ?? MyColors.white, - ], - ).createShader(bounds), - child: Text( - '${level.order ?? 0}', - maxLines: 1, - style: MYTextStyle.button1.copyWith( - fontSize: setSize(context: context, mobile: 24, tablet: 34), - shadows: [ - BoxShadow( - color: LevelType.textShadowColor[type] ?? MyColors.white, - offset: Offset(0, 2.97), - ) - ], - ), - ), - ), - if(level.id == chooseLevel?.id) - Positioned( - top: setSize(context: context, mobile: -20, tablet: -30), - child: MyImage( - image: MyAssets.location, - size: setSize(context: context,mobile: 26, tablet: 40), - ), - ), - if(type == LevelType.finished) - Positioned( - bottom: 0, - child: Container( - height: setSize(context: context, mobile: 17, tablet: 24), - width: setSize(context: context, mobile: 17, tablet: 24), - padding: EdgeInsets.all(3), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - width: 1, - color: Color(0XFF3CFF3C), - ), - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color(0XFF48D336), - Color(0XFF2D7C23), - ], - ), - ), - child: MyImage(image: MyAssets.doneRounded), - ), - ), - ], - ), - ); - } -} diff --git a/lib/features/level/presentation/ui/widgets/node_widget.dart b/lib/features/level/presentation/ui/widgets/node_widget.dart new file mode 100644 index 0000000..93cb885 --- /dev/null +++ b/lib/features/level/presentation/ui/widgets/node_widget.dart @@ -0,0 +1,161 @@ +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_colors.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; +import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart'; +import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart'; +import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.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/prize_entity.dart'; + +enum LevelType { + unFinished, + finished, + current; + + static Map get image => { + LevelType.unFinished: MyAssets.level, + LevelType.finished: MyAssets.finishedLevel, + LevelType.current: MyAssets.currentLevel, + }; + + static Map get textShadowColor => { + LevelType.unFinished: const Color(0XFF5B5B5B), + LevelType.finished: const Color(0XFF096D7B), + LevelType.current: const Color(0XFF91500D), + }; + + static Map get textColor => { + LevelType.unFinished: const Color(0XFFEDEDED), + LevelType.finished: const Color(0XFFFFF2D0), + LevelType.current: const Color(0XFFFFF2D0), + }; +} + +class NodeWidget extends StatelessWidget { + const NodeWidget({ + super.key, + required this.node, + required this.getReward, + required this.type, + required this.chooseLevel, + this.onTap, + this.onRewardPressed, + }); + + final LevelType Function(int index) type; + final bool Function(int index) getReward; + final NodeEntity node; + final LevelEntity? chooseLevel; + final Function(LevelEntity level, LevelType type)? onTap; + final void Function(PrizeEntity prize)? onRewardPressed; + + @override + Widget build(BuildContext context) { + return Builder( + builder: (context) { + if (node.nodeType == NodeType.prize) { + return MyInkwell( + onTap: () { + if (getReward(node.prize?.afterLevel ?? 1)) { + onRewardPressed?.call(node.prize ?? PrizeEntity()); + } + }, + child: Stack( + alignment: Alignment.center, + children: [ + if (getReward(node.prize?.afterLevel ?? 1)) ...{ + const MyImage(image: MyAssets.giftBackground, size: 70), + const MyImage(image: MyAssets.gift, size: 50), + } else ...{ + const MyImage(image: MyAssets.giftDisable, size: 50), + }, + ], + ), + ); + } else { + return InkWell( + onTap: () => onTap?.call( + node.level ?? LevelEntity(), + type(node.level?.order ?? 1), + ), + child: Stack( + alignment: Alignment.topCenter, + clipBehavior: Clip.none, + children: [ + MyImage( + image: + LevelType.image[type(node.level?.order ?? 1)] ?? + MyAssets.level, + fit: BoxFit.cover, + size: setSize(context: context, tablet: 70, mobile: 44), + ), + ShaderMask( + blendMode: BlendMode.modulate, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + const Color(0XFFFFFFFF), + LevelType.textColor[type(node.level?.order ?? 1)] ?? + MyColors.white, + ], + ).createShader(bounds), + child: Text( + '${node.level?.order ?? 0}', + maxLines: 1, + style: MYTextStyle.button1.copyWith( + fontSize: setSize( + context: context, + mobile: 24, + tablet: 34, + ), + shadows: [ + BoxShadow( + color: + LevelType.textShadowColor[type( + node.level?.order ?? 1, + )] ?? + MyColors.white, + offset: const Offset(0, 2.97), + ), + ], + ), + ), + ), + if (node.level?.id == chooseLevel?.id) + Positioned( + top: setSize(context: context, mobile: -20, tablet: -30), + child: MyImage( + image: MyAssets.location, + size: setSize(context: context, mobile: 26, tablet: 40), + ), + ), + if (type(node.level?.order ?? 1) == LevelType.finished) + Positioned( + bottom: 0, + child: Container( + height: setSize(context: context, mobile: 17, tablet: 24), + width: setSize(context: context, mobile: 17, tablet: 24), + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(width: 1, color: const Color(0XFF3CFF3C)), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFF48D336), Color(0XFF2D7C23)], + ), + ), + child: const MyImage(image: MyAssets.doneRounded), + ), + ), + ], + ), + ); + } + }, + ); + } +} diff --git a/lib/features/level/presentation/ui/widgets/play_button.dart b/lib/features/level/presentation/ui/widgets/play_button.dart index bd41792..1a121b3 100644 --- a/lib/features/level/presentation/ui/widgets/play_button.dart +++ b/lib/features/level/presentation/ui/widgets/play_button.dart @@ -20,16 +20,16 @@ class PlayButton extends StatelessWidget { child: Stack( alignment: Alignment.center, children: [ - MyImage(image: MyAssets.button2), + const MyImage(image: MyAssets.button2), Positioned( top: MySpaces.s2, child: Row( spacing: MySpaces.s4, children: [ - MyImage(image: MyAssets.iconPlay), + const MyImage(image: MyAssets.iconPlay), ShaderMask( blendMode: BlendMode.modulate, - shaderCallback: (bounds) => LinearGradient( + shaderCallback: (bounds) => const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0XFFF9601F), Color(0XFFD93D16)], @@ -40,8 +40,8 @@ class PlayButton extends StatelessWidget { style: MYTextStyle.button1.copyWith( shadows: [ BoxShadow( - color: Color(0XFFFFFFAB).withValues(alpha: 0.40), - offset: Offset(0, 2.61), + color: const Color(0XFFFFFFAB).withValues(alpha: 0.40), + offset: const Offset(0, 2.61), ), ], ), @@ -54,7 +54,7 @@ class PlayButton extends StatelessWidget { bottom: MySpaces.s20, child: Text( '${context.translate.step} ${level.order ?? 0}', - style: MYTextStyle.matn3.copyWith(color: Color(0XFFD8490B)), + style: MYTextStyle.matn3.copyWith(color: const Color(0XFFD8490B)), ), ), ], diff --git a/lib/features/question/data/datasource/question_datasource.dart b/lib/features/question/data/datasource/question_datasource.dart index 831f844..ed5d2dc 100644 --- a/lib/features/question/data/datasource/question_datasource.dart +++ b/lib/features/question/data/datasource/question_datasource.dart @@ -3,6 +3,7 @@ import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/params/question_params.dart'; import 'package:hadi_hoda_flutter/core/utils/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'; @@ -25,11 +26,11 @@ class QuestionDatasourceImpl implements IQuestionDatasource { (e) => e.code == selectedLanguage, orElse: () => TotalDataEntity(), ); - final LevelEntity? findLevel = findData.levels?.singleWhere( - (e) => e.id == params.id, - orElse: () => LevelEntity(), + final NodeEntity? findLevel = findData.nodes?.singleWhere( + (e) => e.level?.id == params.id, + orElse: () => NodeEntity(), ); - return findLevel ?? LevelEntity(); + return findLevel?.level ?? LevelEntity(); } catch (e) { throw MyException(errorMessage: '$e'); } @@ -47,14 +48,17 @@ class QuestionDatasourceImpl implements IQuestionDatasource { (e) => e.code == selectedLanguage, orElse: () => TotalDataEntity(), ); - if(index > (findData.levels?.length ?? 0)){ - throw MyException(); + final List? levelList = findData.nodes?.where( + (e) => e.nodeType == NodeType.level, + ).toList(); + if(index > (levelList?.length ?? 0)){ + throw const MyException(); } - final LevelEntity? findLevel = findData.levels?.singleWhere( - (e) => e.order == index, - orElse: () => LevelEntity(), + final NodeEntity? findLevel = findData.nodes?.singleWhere( + (e) => e.level?.order == index, + orElse: () => NodeEntity(), ); - return findLevel ?? LevelEntity(); + return findLevel?.level ?? LevelEntity(); } catch (e) { throw MyException(errorMessage: '$e'); } diff --git a/lib/features/question/domain/entity/question_entity.dart b/lib/features/question/domain/entity/question_entity.dart index 4effad3..98bb07a 100644 --- a/lib/features/question/domain/entity/question_entity.dart +++ b/lib/features/question/domain/entity/question_entity.dart @@ -63,6 +63,6 @@ class QuestionEntity extends HiveObject { }){ audio = '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/question_audio/${audioInfo?.filename}'; correctAudio = '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/correct_answer_audio/${correctAnswerAudioInfo?.filename}'; - image = '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/question_image/${imageInfo?.filename}'; + image = '${StoragePath.documentDir.path}/question_image/${imageInfo?.filename}'; } } diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index a832e3f..ced5212 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -21,7 +21,6 @@ import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_next_lev import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/screens/answer_screen.dart'; -import 'package:showcaseview/showcaseview.dart'; class QuestionBloc extends Bloc { /// ------------constructor------------ @@ -30,19 +29,22 @@ class QuestionBloc extends Bloc { this._getNextLevelUseCase, this._mainAudioService, this._effectAudioService, - ) : super(QuestionState()) { + ) : super(const QuestionState()) { volumeStream = _mainAudioService.volumeStream(); - playingStream = _mainAudioService.playingStream(); - stopMusic(); - registerShowCase(); + initAudios(); on(_getLevelEvent); on(_chooseAnswerEvent); } @override Future close() { - unRegisterShowCase(); - animationController.dispose(); + if (_mainAudioService.audioVolume != 0) { + _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); + } + _backgroundAudioService.dispose(); + answerAnimationController?.dispose(); + imageAnimationController?.dispose(); + scrollController.dispose(); return super.close(); } @@ -64,45 +66,26 @@ class QuestionBloc extends Bloc { 'hadith_key': GlobalKey(), 'guide_key': GlobalKey(), }; - late final Stream volumeStream; - late final Stream playingStream; + Stream? volumeStream; + bool showAnswerSequence = true; /// ------------Controllers------------ final AudioService _mainAudioService; + final AudioService _backgroundAudioService = AudioService(volume: 0); final AudioService _effectAudioService; - late final AnimationController animationController; + AnimationController? answerAnimationController; + AnimationController? imageAnimationController; + final ScrollController scrollController = ScrollController(); /// ------------Functions------------ - void registerShowCase() { - try { - ShowcaseView.register( - onStart: (showcaseIndex, key) { - LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'true'); - }, - ); - } 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 (_) {} - } - } + // void startScrollTitle({Duration? audioDuration}) { + // if (audioDuration == null || audioDuration == Duration.zero) return; + // titleController.animateTo( + // titleController.position.maxScrollExtent, + // duration: audioDuration, + // curve: Curves.linear + // ); + // } void showHadith({required BuildContext context}) { showHadithDialog( @@ -120,15 +103,24 @@ class QuestionBloc extends Bloc { } Future playDiamondAudio() async { - await _effectAudioService.setAudio(assetPath: MyAudios.diamondEnd); - await _effectAudioService.play(); + await _mainAudioService.setAudio(assetPath: MyAudios.diamondEnd); + await _mainAudioService.play(); } - Future stopMusic() async { + Future initAudios() async { await Future.wait([ _mainAudioService.stop(), _mainAudioService.setLoopMode(isLoop: false), + _backgroundAudioService.setAudio(assetPath: MyAudios.question), + _backgroundAudioService.setLoopMode(isLoop: true), ]); + if (_mainAudioService.audioVolume != 0) { + await Future.wait([ + _mainAudioService.setVolume(volume: MyConstants.questionAudioVolume), + _backgroundAudioService.setVolume(volume: 0.1), + ]); + } + await _backgroundAudioService.play(); } Future playWrongAudio() async { @@ -148,28 +140,30 @@ class QuestionBloc extends Bloc { Future changeMute() async { await Future.wait([ - _mainAudioService.changeMute(), + _mainAudioService.changeMute(newVolume: MyConstants.questionAudioVolume), + _backgroundAudioService.changeMute(newVolume: 0.1), _effectAudioService.changeMute(), ]); } - // Future showQueueAnswer() async { - // final List answers = state.currentQuestion?.answers ?? []; - // if (answers.isNotEmpty) { - // answers.removeWhere((e) => e.imageId == null); - // } - // for (final answer in answers) { - // await Future.delayed(const Duration(milliseconds: 500), () async { - // if (MyContext.get.mounted) { - // await showAnswerDialog( - // context: MyContext.get, - // answerEntity: answer, - // autoClose: true, - // ); - // } - // }); - // } - // } + Future showQueueAnswer() async { + if(!showAnswerSequence) return; + final List answers = state.currentQuestion?.answers ?? []; + if (answers.isNotEmpty) { + answers.removeWhere((e) => e.imageId == null); + } + for (final answer in answers) { + await Future.delayed(const Duration(milliseconds: 500), () async { + if (MyContext.get.mounted) { + await showAnswerDialog( + context: MyContext.get, + answerEntity: answer, + autoClose: true, + ); + } + }); + } + } Future showAnswerDialog({ required BuildContext context, @@ -193,20 +187,32 @@ class QuestionBloc extends Bloc { } Future getNextLevelEvent({required BuildContext context}) async { - await _getNextLevelUseCase(QuestionParams()).then((value) => value.fold( - (data) { - context.pushNamed( - Routes.questionPage, - pathParameters: {'id': '${data.id}'}, - ); - }, - (error) { + await _getNextLevelUseCase(QuestionParams()).then((value) => + value.fold((data) { + add(GetLevelEvent('${data.id}', context)); + }, (error) { goToLevelPage(context: MyContext.get); }, - ), + ), ); } + void showingAnswerSequence({required bool show}){ + showAnswerSequence = show; + } + + Future pausePlaying() async { + await _mainAudioService.pause(); + } + + Future pauseBackgroundPlaying() async { + await _backgroundAudioService.pause(); + } + + Future playBackgroundPlaying() async { + await _backgroundAudioService.play(); + } + /// ------------Event Calls------------ FutureOr _getLevelEvent(GetLevelEvent event, Emitter emit) async { await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then( @@ -223,14 +229,17 @@ class QuestionBloc extends Bloc { ], ); emit(state.copyWith( - getQuestionStatus: BaseComplete(''), + getQuestionStatus: const BaseComplete(''), levelEntity: level, currentQuestion: data.questions?.first, - showAnswers: true + showAnswers: false, + correctAnswer: false, )); + imageAnimationController?.forward(); await playQuestionAudio(); - animationController.forward().then((value) { - startShowcase(); + imageAnimationController?.reverse(); + answerAnimationController?.forward().then((value) { + showQueueAnswer(); }); }, (error) { @@ -246,7 +255,6 @@ class QuestionBloc extends Bloc { emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer)); if (event.chooseCorrectAnswer) { - animationController.reverse(); await showAnswerDialog( context: MyContext.get, correctAudio: state.currentQuestion?.correctAudio, @@ -254,7 +262,8 @@ class QuestionBloc extends Bloc { e.order == event.correctAnswer) ?? AnswerEntity(), showConfetti: true, ); - await Future.delayed(Duration(seconds: 1), () async { + answerAnimationController?.reverse(); + await Future.delayed(const Duration(seconds: 1), () async { final QuestionEntity? findPreQuestion = state.currentQuestion; final int findIndex = (findPreQuestion?.order ?? 1); emit( @@ -275,8 +284,15 @@ class QuestionBloc extends Bloc { ); } } else { - await playQuestionAudio(); - animationController.forward(); + showingAnswerSequence(show: true); + await Future.delayed(const Duration(seconds: 1)); + imageAnimationController?.forward(); + scrollController.jumpTo(0); + await playQuestionAudio(); + imageAnimationController?.reverse(); + answerAnimationController?.forward().then((value) { + showQueueAnswer(); + }); } }); } else { diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index 2feda58..f4ce8e5 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -22,6 +22,7 @@ class QuestionPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: MyPopScope( + backHome: true, child: Directionality( textDirection: TextDirection.ltr, child: Container( @@ -33,13 +34,13 @@ class QuestionPage extends StatelessWidget { tablet: MySpaces.s30) ?? 0, ), decoration: BoxDecoration( - gradient: LinearGradient( + gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0XFF6930DA), Color(0XFF263AA1)], ), image: DecorationImage( - image: AssetImage(MyAssets.pattern), + image: const AssetImage(MyAssets.pattern), scale: 3, repeat: ImageRepeat.repeat, colorFilter: ColorFilter.mode( @@ -50,7 +51,7 @@ class QuestionPage extends StatelessWidget { ), child: Column( children: [ - setPlatform(android: MySpaces.s20, iOS: 50)?.gapHeight ?? SizedBox.shrink(), + setPlatform(android: MySpaces.s20, iOS: 50)?.gapHeight ?? const SizedBox.shrink(), _topButtons(context), MySpaces.s10.gapHeight, Expanded( @@ -61,14 +62,14 @@ class QuestionPage extends StatelessWidget { builder: (context, state) { if (state.currentQuestion?.order == state.levelEntity?.questions?.length) { - return DiamondScreen(); + return const DiamondScreen(); } else { - return QuestionScreen(); + return const QuestionScreen(); } }, ), ), - setPlatform(android: MySpaces.s20,)?.gapHeight ?? SizedBox.shrink(), + setPlatform(android: MySpaces.s20,)?.gapHeight ?? const SizedBox.shrink(), ], ), ), diff --git a/lib/features/question/presentation/ui/screens/answer_screen.dart b/lib/features/question/presentation/ui/screens/answer_screen.dart index a1ac4c9..46e2066 100644 --- a/lib/features/question/presentation/ui/screens/answer_screen.dart +++ b/lib/features/question/presentation/ui/screens/answer_screen.dart @@ -71,6 +71,11 @@ class _AnswerScreenState extends State { await audioService.play(); } + @override + void dispose() { + super.dispose(); + } + @override Widget build(BuildContext context) { return Stack( @@ -90,11 +95,21 @@ class _AnswerScreenState extends State { ), ), ), + if (widget.showConfetti) ...{ + Lottie.asset( + MyAnimations.confetti, + height: context.heightScreen, + fit: BoxFit.cover, + ), + }, Positioned( bottom: setPlatform(android: MySpaces.s30, iOS: MySpaces.s12), child: TextButton( - onPressed: () { - context.pop(); + onPressed: () async { + await audioService.pause(); + if (context.mounted) { + context.pop(); + } }, style: TextButton.styleFrom( foregroundColor: MyColors.white.withValues(alpha: 0.7), @@ -105,13 +120,6 @@ class _AnswerScreenState extends State { ), ), ), - if (widget.showConfetti) ...{ - Lottie.asset( - MyAnimations.confetti, - height: context.heightScreen, - fit: BoxFit.cover, - ), - }, ], ); } diff --git a/lib/features/question/presentation/ui/screens/diamond_screen.dart b/lib/features/question/presentation/ui/screens/diamond_screen.dart index 2a236b7..8c8ac35 100644 --- a/lib/features/question/presentation/ui/screens/diamond_screen.dart +++ b/lib/features/question/presentation/ui/screens/diamond_screen.dart @@ -41,7 +41,7 @@ class DiamondScreen extends StatelessWidget { Lottie.asset( MyAnimations.lightPurple, ), - MyImage( + const MyImage( image: MyAssets.diamondBig, ), Positioned( @@ -58,7 +58,7 @@ class DiamondScreen extends StatelessWidget { style: MYTextStyle.titr0, ), ShaderMask( - shaderCallback: (bounds) => LinearGradient( + shaderCallback: (bounds) => const LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ @@ -72,7 +72,7 @@ class DiamondScreen extends StatelessWidget { shadows: [ BoxShadow( color: MyColors.black.withValues(alpha: 0.25), - offset: Offset(0, 1.22), + offset: const Offset(0, 1.22), blurRadius: 0.82, ), ], @@ -150,7 +150,7 @@ class DiamondScreen extends StatelessWidget { Widget _ship(BuildContext context) { - return ShipAnim( + return const ShipAnim( child: MyImage(image: MyAssets.ship), ); } diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart index 1d6b9da..dd7104b 100644 --- a/lib/features/question/presentation/ui/screens/question_screen.dart +++ b/lib/features/question/presentation/ui/screens/question_screen.dart @@ -3,19 +3,22 @@ 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_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/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/utils/gap.dart'; +import 'package:hadi_hoda_flutter/core/utils/local_storage.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/fade_anim_controller.dart'; +import 'package:hadi_hoda_flutter/core/widgets/animations/fade_anim_delayed.dart'; import 'package:hadi_hoda_flutter/core/widgets/animations/globe_animation.dart'; import 'package:hadi_hoda_flutter/core/widgets/animations/slide_anim.dart'; import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box.dart'; +import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/image_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'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart'; @@ -30,16 +33,43 @@ class QuestionScreen extends StatefulWidget { State createState() => _QuestionScreenState(); } -class _QuestionScreenState extends State with SingleTickerProviderStateMixin { - +class _QuestionScreenState extends State + with TickerProviderStateMixin, WidgetsBindingObserver { @override void initState() { super.initState(); - context.read().animationController = AnimationController( + WidgetsBinding.instance.addObserver(this); + context.read().answerAnimationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + reverseDuration: const Duration(milliseconds: 500), + ); + + context.read().imageAnimationController = AnimationController( vsync: this, - duration: Duration(milliseconds: 500), - reverseDuration: Duration(milliseconds: 500), + duration: const Duration(milliseconds: 500), + reverseDuration: const Duration(milliseconds: 500), ); + if (LocalStorage.readData(key: MyConstants.firstShowcase) == 'true') { + context.read().imageAnimationController?.forward(); + } + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.paused || + state == AppLifecycleState.inactive) { + context.read().pauseBackgroundPlaying(); + } else if (state == AppLifecycleState.resumed) { + context.read().playBackgroundPlaying(); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); } @override @@ -47,26 +77,48 @@ class _QuestionScreenState extends State with SingleTickerProvid return Column( children: [ _stepper(), - _titles(context), - MySpaces.s20.gapHeight, - _answers(context), + Expanded( + child: Stack( + children: [ + _questionImage(context), + _answers(context), + ], + ), + ), _bottom(context), ], ); } + Widget _questionImage(BuildContext context) { + return FadeAnimDelayed( + duration: const Duration(seconds: 1), + child: FadeAnimController( + controller: context.read().imageAnimationController!, + child: Column( + children: [ + _titles(context), + 20.0.gapHeight, + BlocBuilder( + builder: (context, state) => ImageBox( + key: Key('${state.currentQuestion?.image}'), + image: state.currentQuestion?.image ?? '', + ), + ), + ], + ), + ), + ); + } + Widget _stepper() { return BlocBuilder( buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, - builder: (context, state) => MyShowcaseWidget( - globalKey: context.read().showCaseKey['stepper_key']!, - description: context.translate.showcase_stepper, - child: FadeAnim( - child: QuestionStepper( - length: state.levelEntity?.questions?.length ?? 0, - currentStep: state.currentQuestion?.order ?? 1, - ), + previous.currentQuestion?.id != current.currentQuestion?.id, + builder: (context, state) => FadeAnim( + child: QuestionStepper( + length: state.levelEntity?.questions?.length ?? 0, + currentStep: state.currentQuestion?.order ?? 1, ), ), ); @@ -75,77 +127,251 @@ class _QuestionScreenState extends State with SingleTickerProvid Widget _titles(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, - builder: (context, state) => - FadeAnim( - child: AutoSizeText( - state.currentQuestion?.title ?? '', - textAlign: TextAlign.center, - maxLines: 4, - maxFontSize: 20, - minFontSize: 16, - style: MYTextStyle.titr1.copyWith( - shadows: [ - BoxShadow( - offset: Offset(0, 2), - color: MyColors.black.withValues(alpha: 0.25), - ), - ], - ), + previous.currentQuestion?.id != current.currentQuestion?.id, + builder: (context, state) => AutoSizeText( + state.currentQuestion?.title ?? '', + textAlign: TextAlign.center, + minFontSize: 16, + maxFontSize: 20, + maxLines: 8, + style: MYTextStyle.titr1.copyWith( + shadows: [ + BoxShadow( + offset: const Offset(0, 2), + color: MyColors.black.withValues(alpha: 0.25), ), - ), + ], + ), + ), ); } - Expanded _answers(BuildContext context) { - return Expanded( - child: BlocBuilder( - buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, - builder: (context, state) => GridView.builder( - itemCount: state.currentQuestion?.answers?.length ?? 0, - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - padding: EdgeInsets.symmetric( - horizontal: setSize(context: context, tablet: 70) ?? 0, - ), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: MySpaces.s20, - mainAxisSpacing: 80, - ), - itemBuilder: (context, index) => - state.currentQuestion?.answers?[index].imageId == null - ? SizedBox.shrink() - : SlideAnim( - key: Key('${state.currentQuestion?.id}'), - controller: context.read().animationController, - index: index, - child: MyShowcaseWidget( - globalKey: context.read().showCaseKey['answer_key_$index']!, - description: context.translate.showcase_answer, - child: AnswerBox( - globalKey: context.read().showCaseKey['notif_key_$index']!, - index: state.currentQuestion?.answers?[index].order ?? 1, - answer: - state.currentQuestion?.answers?[index] ?? - AnswerEntity(), - correctAnswer: state.currentQuestion?.correctAnswer ?? 0, - onNotifTap: (AnswerEntity answer) { - context.read().showAnswerDialog( - context: context, - answerEntity: answer, - ); + Widget _answers(BuildContext context) { + return ListView( + controller: context.read().scrollController, + padding: const EdgeInsets.only(top: 10), + children: [ + FadeAnimController( + controller: context.read().answerAnimationController!, + child: _titles(context), + ), + 50.0.gapHeight, + SizedBox( + width: context.widthScreen, + child: BlocBuilder( + buildWhen: (previous, current) => + previous.currentQuestion?.id != current.currentQuestion?.id, + builder: (context, state) => Column( + spacing: 30, + children: [ + Row( + key: Key('${state.currentQuestion?.id}answer0'), + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Builder( + key: Key('${state.currentQuestion?.id}0'), + builder: (context) { + if (state.currentQuestion?.answers?[0].imageId == + null) { + return const SizedBox.shrink(); + } else { + return SizedBox( + width: 180, + height: 250, + child: SlideAnim( + controller: context + .read() + .answerAnimationController!, + index: 0, + child: AnswerBox( + index: + state.currentQuestion?.answers?[0].order ?? + 1, + answer: + state.currentQuestion?.answers?[0] ?? + AnswerEntity(), + correctAnswer: + state.currentQuestion?.correctAnswer ?? 0, + onNotifTap: (AnswerEntity answer) { + context.read().showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent( + isCorrect, + correctAnswer, + context, + ), + ), + ), + ), + ); + } }, - onTap: (isCorrect, correctAnswer) => - context.read().add( - ChooseAnswerEvent(isCorrect, correctAnswer, context), - ), ), - ), + Builder( + key: Key('${state.currentQuestion?.id}1'), + builder: (context) { + if (state.currentQuestion?.answers?[1].imageId == + null) { + return const SizedBox.shrink(); + } else { + return SizedBox( + width: 180, + height: 250, + child: SlideAnim( + controller: context + .read() + .answerAnimationController!, + index: 1, + child: AnswerBox( + index: + state.currentQuestion?.answers?[1].order ?? + 1, + answer: + state.currentQuestion?.answers?[1] ?? + AnswerEntity(), + correctAnswer: + state.currentQuestion?.correctAnswer ?? 0, + onNotifTap: (AnswerEntity answer) { + context.read().showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent( + isCorrect, + correctAnswer, + context, + ), + ), + ), + ), + ); + } + }, + ), + ], ), + Row( + key: Key('${state.currentQuestion?.id}answer1'), + mainAxisAlignment: + (state.currentQuestion?.answers?.length ?? 0) > 3 + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.center, + children: [ + if ((state.currentQuestion?.answers?.length ?? 0) > 2) + Builder( + key: Key('${state.currentQuestion?.id}2'), + builder: (context) { + if (state.currentQuestion?.answers?[2].imageId == + null) { + return const SizedBox.shrink(); + } else { + return SizedBox( + width: 180, + height: 250, + child: SlideAnim( + controller: context + .read() + .answerAnimationController!, + index: 2, + child: AnswerBox( + index: + state + .currentQuestion + ?.answers?[2] + .order ?? + 1, + answer: + state.currentQuestion?.answers?[2] ?? + AnswerEntity(), + correctAnswer: + state.currentQuestion?.correctAnswer ?? 0, + onNotifTap: (AnswerEntity answer) { + context + .read() + .showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent( + isCorrect, + correctAnswer, + context, + ), + ), + ), + ), + ); + } + }, + ), + if ((state.currentQuestion?.answers?.length ?? 0) > 3) + Builder( + key: Key('${state.currentQuestion?.id}1'), + builder: (context) { + if (state.currentQuestion?.answers?[3].imageId == + null) { + return const SizedBox.shrink(); + } else { + return SizedBox( + width: 180, + height: 250, + child: SlideAnim( + controller: context + .read() + .answerAnimationController!, + index: 3, + child: AnswerBox( + index: + state + .currentQuestion + ?.answers?[3] + .order ?? + 1, + answer: + state.currentQuestion?.answers?[3] ?? + AnswerEntity(), + correctAnswer: + state.currentQuestion?.correctAnswer ?? 0, + onNotifTap: (AnswerEntity answer) { + context + .read() + .showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent( + isCorrect, + correctAnswer, + context, + ), + ), + ), + ), + ); + } + }, + ), + ], + ), + ], + ), + ), ), - ), + ], ); } @@ -156,34 +382,45 @@ class _QuestionScreenState extends State with SingleTickerProvid child: Stack( alignment: AlignmentDirectional.centerStart, children: [ - MyShowcaseWidget( - globalKey: context.read().showCaseKey['guide_key']!, - description: context.translate.showcase_guide, - type: ShowcaseTooltipType.top, - child: StreamBuilder( - initialData: false, - stream: context.read().playingStream, - builder: (context, snapshot) => GlobeAnimation( - state: snapshot.data ?? false, - child: MyImage( - image: MyAssets.globe, - fit: BoxFit.cover, - size: setSize(context: context, tablet: 120), + GlobeAnimation( + child: MyImage( + image: MyAssets.globe, + fit: BoxFit.cover, + size: setSize(context: context, tablet: 120), + ), + ), + Positioned( + left: 90, + right: 90, + child: FadeAnimController( + controller: context.read().imageAnimationController!, + child: TextButton( + onPressed: () async { + if(context.read().imageAnimationController?.isForwardOrCompleted ?? false){ + context.read().imageAnimationController?.reverse(); + context.read().answerAnimationController?.forward(); + context.read().showingAnswerSequence(show: false); + context.read().pausePlaying(); + } + }, + style: TextButton.styleFrom( + foregroundColor: MyColors.white.withValues(alpha: 0.7), ), - ), + child: FittedBox( + child: Text( + context.translate.skip, + style: MYTextStyle.button2, + ), + ), + ) ), ), PositionedDirectional( end: 0, - child: MyShowcaseWidget( - globalKey: context.read().showCaseKey['hadith_key']!, - type: ShowcaseTooltipType.topLeft, - description: context.translate.showcase_hadith, - child: GlassyButton( - image: MyAssets.leaf, - onTap: () => - context.read().showHadith(context: context), - ), + child: GlassyButton( + image: MyAssets.leaf, + onTap: () => + context.read().showHadith(context: context), ), ), ], diff --git a/lib/features/question/presentation/ui/widgets/glassy_button.dart b/lib/features/question/presentation/ui/widgets/glassy_button.dart index 2e9cc1c..4befc3c 100644 --- a/lib/features/question/presentation/ui/widgets/glassy_button.dart +++ b/lib/features/question/presentation/ui/widgets/glassy_button.dart @@ -25,7 +25,7 @@ class GlassyButton extends StatelessWidget { end: Alignment.bottomCenter, colors: [ Colors.white.withValues(alpha: 0.3), - Color(0XFF6930DA).withValues(alpha: 0.1), + const Color(0XFF6930DA).withValues(alpha: 0.1), ], ), border: Border.all(color: Colors.white.withValues(alpha: 0.3)), @@ -33,7 +33,7 @@ class GlassyButton extends StatelessWidget { child: MyInkwell( onTap: onTap, audio: audio, - borderRadius: BorderRadius.all(Radius.circular(100)), + borderRadius: const BorderRadius.all(Radius.circular(100)), child: Padding( padding: EdgeInsets.all( setSize(context: context, mobile: MySpaces.s12, tablet: MySpaces.s20) ?? 0, diff --git a/lib/features/question/presentation/ui/widgets/question_stepper.dart b/lib/features/question/presentation/ui/widgets/question_stepper.dart index d7408b4..f763ca4 100644 --- a/lib/features/question/presentation/ui/widgets/question_stepper.dart +++ b/lib/features/question/presentation/ui/widgets/question_stepper.dart @@ -15,7 +15,7 @@ class QuestionStepper extends StatelessWidget { height: 70, child: EasyStepper( activeStep: currentStep - 1, - lineStyle: LineStyle( + lineStyle: const LineStyle( lineLength: 20, lineType: LineType.normal, defaultLineColor: Color(0XFFDFDDF6), @@ -29,36 +29,36 @@ class QuestionStepper extends StatelessWidget { showLoadingAnimation: false, stepRadius: 18, showStepBorder: false, - padding: EdgeInsets.all(0), + padding: const EdgeInsets.all(0), enableStepTapping: false, steps: List.generate( length, (index) => EasyStep( customStep: index == length - 1 - ? MyImage(image: MyAssets.diamond, size: 50) + ? const MyImage(image: MyAssets.diamond, size: 50) : ClipPath( clipper: _StepperClipper(), child: Container( height: 32, width: 32, - padding: EdgeInsets.all(4), - decoration: BoxDecoration( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( color: Color(0XFFDFDDF6), shape: BoxShape.circle, ), child: ClipPath( clipper: _StepperClipper(), child: Container( - padding: EdgeInsets.all(6), + padding: const EdgeInsets.all(6), decoration: BoxDecoration( shape: BoxShape.circle, color: index < currentStep - 1 - ? Color(0XFF21B738) + ? const Color(0XFF21B738) : index == currentStep - 1 - ? Color(0XFF847AC4) + ? const Color(0XFF847AC4) : Colors.transparent, ), - child: index < currentStep - 1 ? MyImage(image: MyAssets.done) : null, + child: index < currentStep - 1 ? const MyImage(image: MyAssets.done) : null, ), ), ), diff --git a/lib/features/question/presentation/ui/widgets/question_title.dart b/lib/features/question/presentation/ui/widgets/question_title.dart index 4d4305e..0a2681a 100644 --- a/lib/features/question/presentation/ui/widgets/question_title.dart +++ b/lib/features/question/presentation/ui/widgets/question_title.dart @@ -23,19 +23,20 @@ class QuestionTitle extends StatelessWidget { '${context.translate.step} ${step ?? 0}', style: MYTextStyle.titr3, ), - Text( - '${context.translate.question} ${currentQuestion ?? 0}/${(questionLength ?? 0) - 1}', - style: MYTextStyle.matn3.copyWith( - color: MyColors.white.withValues(alpha: 0.5), - shadows: [ - BoxShadow( - color: MyColors.black.withValues(alpha: 0.25), - blurRadius: 0.82, - offset: Offset(0, 1.22), - ), - ], + if ((currentQuestion ?? 0) < (questionLength ?? 0)) + Text( + '${context.translate.question} ${currentQuestion ?? 0}/${(questionLength ?? 0) - 1}', + style: MYTextStyle.matn3.copyWith( + color: MyColors.white.withValues(alpha: 0.5), + shadows: [ + BoxShadow( + color: MyColors.black.withValues(alpha: 0.25), + blurRadius: 0.82, + offset: const Offset(0, 1.22), + ), + ], + ), ), - ), ], ); } diff --git a/lib/features/sample/presentation/ui/sample_page.dart b/lib/features/sample/presentation/ui/sample_page.dart index 5378b9f..0292338 100644 --- a/lib/features/sample/presentation/ui/sample_page.dart +++ b/lib/features/sample/presentation/ui/sample_page.dart @@ -1,16 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:lottie/lottie.dart'; class SamplePage extends StatelessWidget { const SamplePage({super.key}); @override Widget build(BuildContext context) { - return Scaffold( + return const Scaffold( body: Center( - child: Lottie.asset( - 'assets/animations/Celebration.json', - ), + child: Text('Sample Page'), ), ); } diff --git a/lib/features/splash/presentation/bloc/splash_bloc.dart b/lib/features/splash/presentation/bloc/splash_bloc.dart index 5fc686b..266f31f 100644 --- a/lib/features/splash/presentation/bloc/splash_bloc.dart +++ b/lib/features/splash/presentation/bloc/splash_bloc.dart @@ -1,8 +1,8 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; -import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; +import 'package:hadi_hoda_flutter/core/utils/pre_cache_image.dart'; import 'package:hadi_hoda_flutter/features/splash/presentation/bloc/splash_event.dart'; import 'package:hadi_hoda_flutter/features/splash/presentation/bloc/splash_state.dart'; @@ -17,21 +17,11 @@ class SplashBloc extends Bloc { /// ------------Controllers------------ /// ------------Functions------------ - Future _precacheAllImages(BuildContext context) async { - await Future.wait( - MyAssets.images.map( - (assetPath) => precacheImage(AssetImage(assetPath), context), - ), - ); - } Future goToHomePage(BuildContext context) async { - if (context.mounted) { - await _precacheAllImages(context); - } - + await preCacheImages(); await Future.delayed( - Duration(seconds: 2), + const Duration(seconds: 2), () { if (context.mounted) { context.goNamed(Routes.homePage); diff --git a/lib/features/splash/presentation/ui/splash_page.dart b/lib/features/splash/presentation/ui/splash_page.dart index 7036d27..79ea4a3 100644 --- a/lib/features/splash/presentation/ui/splash_page.dart +++ b/lib/features/splash/presentation/ui/splash_page.dart @@ -32,7 +32,7 @@ class _SplashPageState extends State { height: context.heightScreen, width: context.widthScreen, decoration: BoxDecoration( - gradient: LinearGradient( + gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ @@ -41,7 +41,7 @@ class _SplashPageState extends State { ], ), image: DecorationImage( - image: AssetImage(MyAssets.pattern), + image: const AssetImage(MyAssets.pattern), scale: 3, repeat: ImageRepeat.repeat, colorFilter: ColorFilter.mode( @@ -62,7 +62,7 @@ class _SplashPageState extends State { } Widget _image() { - return Stack( + return const Stack( children: [ MyImage( image: MyAssets.hadiHoda, diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index 2690c2f..cf4e6e5 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -13,9 +13,15 @@ import 'package:hadi_hoda_flutter/features/download/domain/usecases/get_audios_u import 'package:hadi_hoda_flutter/features/download/domain/usecases/get_images_usecase.dart'; import 'package:hadi_hoda_flutter/features/download/domain/usecases/loading_stream_usecase.dart'; import 'package:hadi_hoda_flutter/features/download/domain/usecases/save_levels_usecase.dart'; +import 'package:hadi_hoda_flutter/features/guider/data/datasource/guider_datasource.dart'; +import 'package:hadi_hoda_flutter/features/guider/data/repository_impl/guider_repository_impl.dart'; +import 'package:hadi_hoda_flutter/features/guider/domain/repository/guider_repository.dart'; +import 'package:hadi_hoda_flutter/features/guider/domain/usecases/get_first_level_usecase.dart'; import 'package:hadi_hoda_flutter/features/level/data/datasource/level_datasource.dart'; import 'package:hadi_hoda_flutter/features/level/data/repository_impl/level_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/level/domain/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/prize_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart'; import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_levels_usecase.dart'; @@ -41,11 +47,11 @@ void initBindings() { /// Classes locator.registerSingleton(HttpRequestImpl()); locator.registerSingleton( - AudioService(volume: MyConstants.mainAudioVolume), + AudioService(volume: MyConstants.musicAudioVolume), instanceName: MyConstants.mainAudioService, ); locator.registerSingleton( - AudioService(), + AudioService(volume: MyConstants.effectAudioVolume), instanceName: MyConstants.effectAudioService, ); locator.registerSingleton(AppBloc()); @@ -63,14 +69,19 @@ void initBindings() { locator.registerLazySingleton(() => SaveLevelsUseCase(locator())); locator.registerLazySingleton(() => LoadingStreamUseCase(locator())); + /// Guider Feature + locator.registerLazySingleton(() => const GuiderDatasourceImpl()); + locator.registerLazySingleton(() => GuiderRepositoryImpl(locator())); + locator.registerLazySingleton(() => GetFirstLevelUseCase(locator())); + /// Question Feature - locator.registerLazySingleton(() => QuestionDatasourceImpl()); + locator.registerLazySingleton(() => const QuestionDatasourceImpl()); locator.registerLazySingleton(() => QuestionRepositoryImpl(locator())); locator.registerLazySingleton(() => GetLevelUseCase(locator())); locator.registerLazySingleton(() => GetNextLevelUseCase(locator())); /// Level Feature - locator.registerLazySingleton(() => LocalLevelDatasourceImpl()); + locator.registerLazySingleton(() => const LocalLevelDatasourceImpl()); locator.registerLazySingleton(() => LevelRepositoryImpl(locator())); locator.registerLazySingleton(() => GetLevelsUseCase(locator())); } @@ -83,7 +94,10 @@ Future initDataBase() async { ..registerAdapter(AnswerEntityAdapter()) ..registerAdapter(QuestionEntityAdapter()) ..registerAdapter(HadithEntityAdapter()) + ..registerAdapter(PrizeEntityAdapter()) ..registerAdapter(LevelEntityAdapter()) + ..registerAdapter(NodeTypeAdapter()) + ..registerAdapter(NodeEntityAdapter()) ..registerAdapter(TotalDataEntityAdapter()); await Hive.openBox(MyConstants.levelBox); diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index eac3c2f..6999f41 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -35,5 +35,6 @@ "showcase_notif": "سيقوم الراوي بقراءة\nالإجابات والخيارات لك.", "showcase_stepper": "هنا سترى الأسئلة الخاصة\nبهذه المرحلة للوصول\nإلى الماسة.", "showcase_hadith": "اطّلع على المصادر والأحاديث\nلهذا السؤال", - "showcase_guide": "هذا دليل سيساعدك\nفي رحلتك." + "showcase_guide": "هذا دليل سيساعدك\nفي رحلتك.", + "reward": "جائزة" } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb new file mode 100644 index 0000000..156cbbd --- /dev/null +++ b/lib/l10n/app_de.arb @@ -0,0 +1,40 @@ +{ + "@@locale": "de", + "about_us": "Über uns", + "about_us_desc": "Rive kombiniert ein interaktives Design-Tool, ein neues zustandsbasiertes Grafikformat, eine leichte plattformübergreifende Laufzeitumgebung und einen blitzschnellen Vektoren-Renderer. \nDiese durchgängige Pipeline erweckt Benutzeroberflächen durch Bewegung zum Leben. Sie bietet Designern und Entwicklern die Werkzeuge, um zu gestalten und zu bauen.", + "select_language": "Sprache auswählen", + "select": "Auswählen", + "please_wait": "Bitte einen Moment warten...", + "downloading_data": "Initiale Daten werden heruntergeladen", + "lost_connection": "Verbindung verloren!", + "retry": "Erneut versuchen", + "connected_to_internet": "Du musst mit dem Internet verbunden sein, um die Anfangsdaten des Spiels herunterzuladen.", + "start": "Start", + "step": "Schritt", + "question": "Frage", + "be_cureful": "Sei etwas\nvorsichtiger.", + "wrong_answer": "Deine Antwort\nwar nicht korrekt.", + "you_got_diamond": "Du hast den Diamanten erhalten", + "map": "Karte", + "next": "Weiter", + "you_win": "Du hast gewonnen!", + "skip": "Überspringen", + "intro_1_1": "Das Abendessen ist fertig! Komm schnell und wasch dir schön die Hände!", + "intro_1_2": "Mama! Unsere Hände sind gar nicht so schmutzig! Wir wischen sie einfach mit einem Taschentuch ab!", + "intro_2": "Die Reinheit des Glaubens...\nSie bedeutet, dass Sauberkeit ein Zeichen des Glaubens ist!", + "intro_3": "Diese guten Taten machen unsere Seelen stark und schön!", + "intro_4": "Möchtest du in den Verheißenen Garten reisen?", + "intro_5": "Jaaaa...\nWir sind bereit!", + "want_to_exit": "Möchtest du beenden?", + "exit_dialog_desc": "Komm zurück, Held!\nDas Abenteuer ist noch nicht vorbei.", + "cancel": "Abbrechen", + "exit": "Beenden", + "play": "SPIELEN", + "no_hadith": "Für diese Frage gibt es keine Hadith.", + "showcase_answer": "Tippe auf die richtige Option,\num sie auszuwählen.", + "showcase_notif": "Der Sprecher wird\ndir die Antwortmöglichkeiten\nvorlesen.", + "showcase_stepper": "Hier siehst du die\nFragen für diese\nStufe, um den\nDiamanten zu erreichen.", + "showcase_hadith": "Quellen und\nHadithe zu dieser\nFrage ansehen.", + "showcase_guide": "Dies ist eine Anleitung,\ndie dir hilft.", + "reward": "belohnen" +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cdb4691..e4b3a06 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -35,5 +35,6 @@ "showcase_notif": "The announcer will\nread the answers to\nthe options to you.", "showcase_stepper": "Here you will see the\nquestions for this\nstage to reach the\ndiamond.", "showcase_hadith": "View sources and\nhadiths for this\nquestion", - "showcase_guide": "This is a guide that will\nhelp you." + "showcase_guide": "This is a guide that will\nhelp you.", + "reward": "Reward" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 8c008a6..5ba2ce2 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -35,5 +35,6 @@ "showcase_notif": "Le narrateur lira\nles réponses des\noptions pour toi.", "showcase_stepper": "Ici, tu verras les\nquestions de cette\nétape pour atteindre\nle diamant.", "showcase_hadith": "Consulte les sources et\nles hadiths pour cette\nquestion", - "showcase_guide": "Ceci est un guide\nqui t’aidera." + "showcase_guide": "Ceci est un guide\nqui t’aidera.", + "reward": "récompense" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 6b4956f..68eec8f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -6,6 +6,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart' as intl; import 'app_localizations_ar.dart'; +import 'app_localizations_de.dart'; import 'app_localizations_en.dart'; import 'app_localizations_fr.dart'; import 'app_localizations_ru.dart'; @@ -98,6 +99,7 @@ abstract class AppLocalizations { /// A list of this localizations delegate's supported locales. static const List supportedLocales = [ Locale('ar'), + Locale('de'), Locale('en'), Locale('fr'), Locale('ru'), @@ -319,6 +321,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'This is a guide that will\nhelp you.'** String get showcase_guide; + + /// No description provided for @reward. + /// + /// In en, this message translates to: + /// **'Reward'** + String get reward; } class _AppLocalizationsDelegate @@ -331,8 +339,14 @@ class _AppLocalizationsDelegate } @override - bool isSupported(Locale locale) => - ['ar', 'en', 'fr', 'ru', 'tr'].contains(locale.languageCode); + bool isSupported(Locale locale) => [ + 'ar', + 'de', + 'en', + 'fr', + 'ru', + 'tr', + ].contains(locale.languageCode); @override bool shouldReload(_AppLocalizationsDelegate old) => false; @@ -343,6 +357,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) { switch (locale.languageCode) { case 'ar': return AppLocalizationsAr(); + case 'de': + return AppLocalizationsDe(); case 'en': return AppLocalizationsEn(); case 'fr': diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index fdbe46b..3c5f345 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -119,4 +119,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get showcase_guide => 'هذا دليل سيساعدك\nفي رحلتك.'; + + @override + String get reward => 'جائزة'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart new file mode 100644 index 0000000..49f7a1f --- /dev/null +++ b/lib/l10n/app_localizations_de.dart @@ -0,0 +1,132 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for German (`de`). +class AppLocalizationsDe extends AppLocalizations { + AppLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get about_us => 'Über uns'; + + @override + String get about_us_desc => + 'Rive kombiniert ein interaktives Design-Tool, ein neues zustandsbasiertes Grafikformat, eine leichte plattformübergreifende Laufzeitumgebung und einen blitzschnellen Vektoren-Renderer. \nDiese durchgängige Pipeline erweckt Benutzeroberflächen durch Bewegung zum Leben. Sie bietet Designern und Entwicklern die Werkzeuge, um zu gestalten und zu bauen.'; + + @override + String get select_language => 'Sprache auswählen'; + + @override + String get select => 'Auswählen'; + + @override + String get please_wait => 'Bitte einen Moment warten...'; + + @override + String get downloading_data => 'Initiale Daten werden heruntergeladen'; + + @override + String get lost_connection => 'Verbindung verloren!'; + + @override + String get retry => 'Erneut versuchen'; + + @override + String get connected_to_internet => + 'Du musst mit dem Internet verbunden sein, um die Anfangsdaten des Spiels herunterzuladen.'; + + @override + String get start => 'Start'; + + @override + String get step => 'Schritt'; + + @override + String get question => 'Frage'; + + @override + String get be_cureful => 'Sei etwas\nvorsichtiger.'; + + @override + String get wrong_answer => 'Deine Antwort\nwar nicht korrekt.'; + + @override + String get you_got_diamond => 'Du hast den Diamanten erhalten'; + + @override + String get map => 'Karte'; + + @override + String get next => 'Weiter'; + + @override + String get you_win => 'Du hast gewonnen!'; + + @override + String get skip => 'Überspringen'; + + @override + String get intro_1_1 => + 'Das Abendessen ist fertig! Komm schnell und wasch dir schön die Hände!'; + + @override + String get intro_1_2 => + 'Mama! Unsere Hände sind gar nicht so schmutzig! Wir wischen sie einfach mit einem Taschentuch ab!'; + + @override + String get intro_2 => + 'Die Reinheit des Glaubens...\nSie bedeutet, dass Sauberkeit ein Zeichen des Glaubens ist!'; + + @override + String get intro_3 => + 'Diese guten Taten machen unsere Seelen stark und schön!'; + + @override + String get intro_4 => 'Möchtest du in den Verheißenen Garten reisen?'; + + @override + String get intro_5 => 'Jaaaa...\nWir sind bereit!'; + + @override + String get want_to_exit => 'Möchtest du beenden?'; + + @override + String get exit_dialog_desc => + 'Komm zurück, Held!\nDas Abenteuer ist noch nicht vorbei.'; + + @override + String get cancel => 'Abbrechen'; + + @override + String get exit => 'Beenden'; + + @override + String get play => 'SPIELEN'; + + @override + String get no_hadith => 'Für diese Frage gibt es keine Hadith.'; + + @override + String get showcase_answer => + 'Tippe auf die richtige Option,\num sie auszuwählen.'; + + @override + String get showcase_notif => + 'Der Sprecher wird\ndir die Antwortmöglichkeiten\nvorlesen.'; + + @override + String get showcase_stepper => + 'Hier siehst du die\nFragen für diese\nStufe, um den\nDiamanten zu erreichen.'; + + @override + String get showcase_hadith => + 'Quellen und\nHadithe zu dieser\nFrage ansehen.'; + + @override + String get showcase_guide => 'Dies ist eine Anleitung,\ndie dir hilft.'; + + @override + String get reward => 'belohnen'; +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index baf9d51..991362a 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -123,4 +123,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get showcase_guide => 'This is a guide that will\nhelp you.'; + + @override + String get reward => 'Reward'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index f870a7e..6e6ec23 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -124,4 +124,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get showcase_guide => 'Ceci est un guide\nqui t’aidera.'; + + @override + String get reward => 'récompense'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index dbd16a8..e374e4a 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -124,4 +124,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get showcase_guide => 'Это руководство,\nкоторое поможет тебе.'; + + @override + String get reward => 'награда'; } diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 0ae805a..db16200 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -123,4 +123,7 @@ class AppLocalizationsTr extends AppLocalizations { @override String get showcase_guide => 'Bu, sana yardımcı olacak\nbir rehberdir.'; + + @override + String get reward => 'ödül'; } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index c457456..9460133 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -35,5 +35,6 @@ "showcase_notif": "Диктор прочитает\nтебе все варианты\nответов.", "showcase_stepper": "Здесь ты увидишь\nвопросы этого этапа,\nчтобы получить\nалмаз.", "showcase_hadith": "Просмотри источники и\nхадисы по этому вопросу", - "showcase_guide": "Это руководство,\nкоторое поможет тебе." + "showcase_guide": "Это руководство,\nкоторое поможет тебе.", + "reward": "награда" } diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index e8b78c8..9e4691c 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -35,5 +35,6 @@ "showcase_notif": "Anlatıcı, seçeneklerin\ncevaplarını sana\nokuyacak.", "showcase_stepper": "Burada, elmasa ulaşmak\niçin bu aşamadaki\nsoruları göreceksin.", "showcase_hadith": "Bu soruya ait\nkaynakları ve hadisleri\nincele", - "showcase_guide": "Bu, sana yardımcı olacak\nbir rehberdir." + "showcase_guide": "Bu, sana yardımcı olacak\nbir rehberdir.", + "reward": "ödül" } diff --git a/lib/main.dart b/lib/main.dart index b9bd653..1b11c47 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/common_ui/theme/theme_service.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/utils/app_life_cycle.dart'; import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; import 'package:hadi_hoda_flutter/core/utils/my_device.dart'; import 'package:hadi_hoda_flutter/core/utils/storage_path.dart'; @@ -17,6 +18,7 @@ import 'features/app/presentation/bloc/app_state.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + AppLifeCycleController(); initBindings(); await Future.wait([ LocalStorage.init(), @@ -42,7 +44,7 @@ class MainApp extends StatelessWidget { themeMode: ThemeService.getTheme(), locale: state.locale, supportedLocales: MyConstants.languages.map( - (e) => e.locale ?? Locale('en', 'US'), + (e) => e.locale ?? const Locale('en', 'US'), ), routerConfig: appPages, localizationsDelegates: const [ diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 3609dd0..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,999 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" - url: "https://pub.dev" - source: hosted - version: "67.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" - url: "https://pub.dev" - source: hosted - version: "6.4.1" - archive: - dependency: transitive - description: - name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" - source: hosted - version: "4.0.7" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - audio_session: - dependency: transitive - description: - name: audio_session - sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7" - url: "https://pub.dev" - source: hosted - version: "0.2.2" - auto_size_text: - dependency: "direct main" - description: - name: auto_size_text - sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - bloc: - dependency: "direct main" - description: - name: bloc - sha256: e18b8e7825e9921d67a6d256dba0b6015ece8a577eb0a411845c46a352994d78 - url: "https://pub.dev" - source: hosted - version: "9.0.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - build: - dependency: transitive - description: - name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - build_config: - dependency: transitive - description: - name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - build_daemon: - dependency: transitive - description: - name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" - url: "https://pub.dev" - source: hosted - version: "4.0.4" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - build_runner: - dependency: "direct dev" - description: - name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" - url: "https://pub.dev" - source: hosted - version: "2.4.13" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 - url: "https://pub.dev" - source: hosted - version: "7.3.2" - built_collection: - dependency: transitive - description: - name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d - url: "https://pub.dev" - source: hosted - version: "8.12.0" - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" - source: hosted - version: "2.0.4" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" - url: "https://pub.dev" - source: hosted - version: "4.11.0" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - confetti: - dependency: "direct main" - description: - name: confetti - sha256: "79376a99648efbc3f23582f5784ced0fe239922bd1a0fb41f582051eba750751" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" - url: "https://pub.dev" - source: hosted - version: "2.3.6" - dio: - dependency: "direct main" - description: - name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 - url: "https://pub.dev" - source: hosted - version: "5.9.0" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - easy_stepper: - dependency: "direct main" - description: - name: easy_stepper - sha256: "63f66314a509ec690c8152a41288961fd96ba9e92ef184299f068a5e78bd16ad" - url: "https://pub.dev" - source: hosted - version: "0.8.5+1" - equatable: - dependency: "direct main" - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_archive: - dependency: "direct main" - description: - name: flutter_archive - sha256: "5ca235f304c12bf468979235f400f79846d204169d715939e39197106f5fc970" - url: "https://pub.dev" - source: hosted - version: "6.0.3" - flutter_bloc: - dependency: "direct main" - description: - name: flutter_bloc - sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 - url: "https://pub.dev" - source: hosted - version: "9.1.1" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - get_it: - dependency: "direct main" - description: - name: get_it - sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b - url: "https://pub.dev" - source: hosted - version: "8.2.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: c752e2d08d088bf83742cb05bf83003f3e9d276ff1519b5c92f9d5e60e5ddd23 - url: "https://pub.dev" - source: hosted - version: "16.2.4" - graphs: - dependency: transitive - description: - name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - hive: - dependency: "direct main" - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_generator: - dependency: "direct dev" - description: - name: hive_generator - sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - http: - dependency: transitive - description: - name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 - url: "https://pub.dev" - source: hosted - version: "1.5.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - intl: - dependency: "direct main" - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" - io: - dependency: transitive - description: - name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" - source: hosted - version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - just_audio: - dependency: "direct main" - description: - name: just_audio - sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908" - url: "https://pub.dev" - source: hosted - version: "0.10.5" - just_audio_platform_interface: - dependency: transitive - description: - name: just_audio_platform_interface - sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" - url: "https://pub.dev" - source: hosted - version: "4.6.0" - just_audio_web: - dependency: transitive - description: - name: just_audio_web - sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" - url: "https://pub.dev" - source: hosted - version: "0.4.16" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - lottie: - dependency: "direct main" - description: - name: lottie - sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" - url: "https://pub.dev" - source: hosted - version: "3.3.2" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_drawing: - dependency: "direct main" - description: - name: path_drawing - sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.dev" - source: hosted - version: "1.0.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - path_provider: - dependency: "direct main" - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" - url: "https://pub.dev" - source: hosted - version: "2.2.18" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.dev" - source: hosted - version: "7.0.1" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pool: - dependency: transitive - description: - name: pool - sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.dev" - source: hosted - version: "1.5.2" - posix: - dependency: transitive - description: - name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" - source: hosted - version: "6.0.3" - pretty_dio_logger: - dependency: "direct main" - description: - name: pretty_dio_logger - sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - provider: - dependency: transitive - description: - name: provider - sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.dev" - source: hosted - version: "6.1.5+1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.dev" - source: hosted - version: "2.5.3" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e - url: "https://pub.dev" - source: hosted - version: "2.4.13" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - showcaseview: - dependency: "direct main" - description: - name: showcaseview - sha256: "59e5edf33cbe3afb56edcbf25de553211409a576cbbbdda226c574410a314a71" - url: "https://pub.dev" - source: hosted - version: "5.0.1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - url: "https://pub.dev" - source: hosted - version: "1.3.5" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.dev" - source: hosted - version: "3.4.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.dev" - source: hosted - version: "0.7.6" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - uuid: - dependency: transitive - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vector_graphics: - dependency: "direct main" - description: - name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.dev" - source: hosted - version: "1.1.13" - vector_graphics_compiler: - dependency: "direct dev" - description: - name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" - source: hosted - version: "2.2.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.dev" - source: hosted - version: "15.0.2" - watcher: - dependency: transitive - description: - name: watcher - sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.dev" - source: hosted - version: "3.0.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" - source: hosted - version: "6.6.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" -sdks: - dart: ">=3.9.2 <4.0.0" - flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index fe8d858..833d804 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hadi_hoda_flutter description: "A new Flutter project." publish_to: 'none' -version: 0.1.6+1 +version: 1.0.0+1 environment: sdk: ^3.9.2 @@ -9,7 +9,6 @@ environment: dependencies: auto_size_text: ^3.0.0 bloc: ^9.0.0 - confetti: ^0.8.0 dio: ^5.9.0 easy_stepper: ^0.8.5+1 equatable: ^2.0.7 @@ -20,6 +19,7 @@ dependencies: flutter_localizations: sdk: flutter get_it: ^8.2.0 + gif: ^2.3.0 go_router: ^16.1.0 hive: ^2.2.3 intl: ^0.20.2 @@ -27,6 +27,7 @@ dependencies: lottie: ^3.3.2 path_drawing: ^1.0.1 path_provider: ^2.1.5 + pod_player: ^0.2.2 pretty_dio_logger: ^1.4.0 shared_preferences: ^2.5.3 showcaseview: ^5.0.1 @@ -49,9 +50,8 @@ flutter: - assets/images/ - assets/animations/ - assets/audios/ - - path: assets/svg/ - transformers: - - package: vector_graphics_compiler + - assets/videos/ + - assets/svg/ fonts: