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/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/widgets/answer_box/answer_box_show.dart b/lib/core/widgets/answer_box/answer_box_show.dart index ee524fc..75d7bea 100644 --- a/lib/core/widgets/answer_box/answer_box_show.dart +++ b/lib/core/widgets/answer_box/answer_box_show.dart @@ -34,7 +34,7 @@ class AnswerBoxShow extends StatelessWidget { selected: false, index: index, image: answer.image ?? '', - autostart: Autostart.once, + autostart: Autostart.loop, correctAnswer: 0, ), Positioned( diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index c7d4689..64915a9 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -52,7 +52,7 @@ class AnswerPictureBox extends StatelessWidget { width: context.widthScreen, height: context.heightScreen, image: FileImage(File(image)), - fps: 30, + fps: 25, autostart: autostart, fit: BoxFit.cover, color: MyColors.black, @@ -63,7 +63,7 @@ class AnswerPictureBox extends StatelessWidget { width: context.widthScreen, height: context.heightScreen, image: FileImage(File(image)), - fps: 30, + fps: 25, autostart: autostart, fit: BoxFit.cover, ), diff --git a/lib/core/widgets/dialog/reward_dialog.dart b/lib/core/widgets/dialog/reward_dialog.dart index 5c5ee5e..7d25500 100644 --- a/lib/core/widgets/dialog/reward_dialog.dart +++ b/lib/core/widgets/dialog/reward_dialog.dart @@ -89,6 +89,11 @@ class RewardDialog extends StatelessWidget { children: [ Image.network( prize.imageURL ?? '', + loadingBuilder: (context, child, loadingProgress) => SizedBox( + height: 300, + width: 300, + child: child, + ), fit: BoxFit.cover, ), const MyImage( diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index 34eb70c..ae5acdc 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -20,6 +20,7 @@ class IntroBloc extends Bloc { /// ------------constructor------------ IntroBloc() : super(const IntroState()) { initVideos(); + listenController(); on(_initVideosEvent); on(_changeVideosEvent); } @@ -70,6 +71,27 @@ class IntroBloc extends Bloc { } } + 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 _changeVideosEvent(ChangeVideosEvent event, Emitter emit) async { switch (state.currentIntro) { diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index 910d465..6271771 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -45,6 +45,7 @@ class QuestionBloc extends Bloc { _backgroundAudioService.dispose(); answerAnimationController.dispose(); imageAnimationController.dispose(); + scrollController.dispose(); return super.close(); } @@ -76,6 +77,7 @@ class QuestionBloc extends Bloc { final AudioService _effectAudioService; late final AnimationController answerAnimationController; late final AnimationController imageAnimationController; + final ScrollController scrollController = ScrollController(); /// ------------Functions------------ // void startScrollTitle({Duration? audioDuration}) { @@ -192,6 +194,7 @@ class QuestionBloc extends Bloc { } Future getNextLevelEvent({required BuildContext context}) async { + _backgroundAudioService.dispose(); await _getNextLevelUseCase(QuestionParams()).then((value) => value.fold( (data) { context.pushNamed( @@ -293,6 +296,7 @@ class QuestionBloc extends Bloc { answerAnimationController.reverse(); await Future.delayed(const Duration(seconds: 1)); imageAnimationController.forward(); + scrollController.jumpTo(0); await playQuestionAudio(); imageAnimationController.reverse(); answerAnimationController.forward().then((value) { diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart index 506bf41..b192a43 100644 --- a/lib/features/question/presentation/ui/screens/question_screen.dart +++ b/lib/features/question/presentation/ui/screens/question_screen.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.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/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/utils/gap.dart'; @@ -91,28 +90,30 @@ class _QuestionScreenState extends State with TickerProviderStat } Widget _questionImage(BuildContext context) { - return Column( - children: [ - const Spacer(), - BlocBuilder( - builder: (context, state) => FadeAnimDelayed( - duration: const Duration(seconds: 1), - child: FadeAnimController( - key: Key('${state.currentQuestion?.image}'), - controller: context.read().imageAnimationController, - child: AnswerPictureBox( + return FadeAnimDelayed( + duration: const Duration(seconds: 1), + child: FadeAnimController( + controller: context.read().imageAnimationController, + child: Column( + children: [ + 10.0.gapHeight, + _titles(context), + const Spacer(), + BlocBuilder( + builder: (context, state) => AnswerPictureBox( + key: Key('${state.currentQuestion?.image}'), selected: false, showIndex: false, correctAnswer: 0, index: 0, image: state.currentQuestion?.image ?? '', - autostart: Autostart.once, + autostart: Autostart.loop, ), ), - ), + const Spacer(), + ], ), - const Spacer(), - ], + ), ); } @@ -132,70 +133,74 @@ class _QuestionScreenState extends State with TickerProviderStat Widget _titles(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, - builder: (context, state) => FadeAnimController( - controller: context.read().answerAnimationController, - child: Text( - state.currentQuestion?.title ?? '', - textAlign: TextAlign.center, - style: MYTextStyle.titr1.copyWith( - shadows: [ - BoxShadow( - offset: const Offset(0, 2), - color: MyColors.black.withValues(alpha: 0.25), - ), - ], + previous.currentQuestion?.id != current.currentQuestion?.id, + builder: (context, state) => + Text( + state.currentQuestion?.title ?? '', + textAlign: TextAlign.center, + style: MYTextStyle.titr1.copyWith( + shadows: [ + BoxShadow( + offset: const Offset(0, 2), + color: MyColors.black.withValues(alpha: 0.25), + ), + ], + ), ), - ), - ), ); } Widget _answers(BuildContext context) { return ListView( + controller: context.read().scrollController, + padding: const EdgeInsets.only(top: 10), children: [ - _titles(context), - 30.0.gapHeight, - BlocBuilder( - buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, - 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( - key: Key('${state.currentQuestion?.id}'), - controller: context.read().answerAnimationController, - index: index, - child: AnswerBox( - 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, - ); - }, - onTap: (isCorrect, correctAnswer) => - context.read().add( - ChooseAnswerEvent(isCorrect, correctAnswer, context), - ), + 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) => + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 20, + children: List.generate( + state.currentQuestion?.answers?.length ?? 0, + (index) => + state.currentQuestion?.answers?[index].imageId == null + ? const SizedBox.shrink() + : SizedBox( + key: Key('${state.currentQuestion?.id}$index'), + width: 180, + height: 250, + child: SlideAnim( + controller: context.read().answerAnimationController, + index: index, + child: AnswerBox( + 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, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent(isCorrect, correctAnswer, context), + ), + ), + ), ), ), + ), ), ), ], @@ -222,8 +227,8 @@ class _QuestionScreenState extends State with TickerProviderStat ), ), Positioned( - left: 120, - right: 120, + left: 90, + right: 90, child: FadeAnimController( controller: context.read().imageAnimationController, child: TextButton( @@ -236,9 +241,11 @@ class _QuestionScreenState extends State with TickerProviderStat style: TextButton.styleFrom( foregroundColor: MyColors.white.withValues(alpha: 0.7), ), - child: Text( - context.translate.skip, - style: MYTextStyle.button2 + child: FittedBox( + child: Text( + context.translate.skip, + style: MYTextStyle.button2 + ), ), ), ), diff --git a/lib/features/question/presentation/ui/widgets/question_title.dart b/lib/features/question/presentation/ui/widgets/question_title.dart index 5262ec9..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: const 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), + ), + ], + ), ), - ), ], ); }