From d85c7e3b644cce7ec45d8ae9ba18fefe26e06aee Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Tue, 2 Dec 2025 00:06:10 +0330 Subject: [PATCH 1/7] fix: audios in app --- lib/core/constants/my_constants.dart | 4 +++- lib/core/services/audio_service.dart | 9 +++------ .../home/presentation/bloc/home_bloc.dart | 6 ++++-- .../level/presentation/bloc/level_bloc.dart | 5 +++-- .../presentation/bloc/question_bloc.dart | 17 +++++++++++++---- lib/init_bindings.dart | 2 +- 6 files changed, 27 insertions(+), 16 deletions(-) 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/services/audio_service.dart b/lib/core/services/audio_service.dart index 4fc6f9e..f8d6610 100644 --- a/lib/core/services/audio_service.dart +++ b/lib/core/services/audio_service.dart @@ -6,11 +6,8 @@ import 'package:just_audio/just_audio.dart'; class AudioService { final AudioPlayer _player = AudioPlayer(); final StreamController _streamController = StreamController.broadcast(); - final double? volume; - AudioService({this.volume}) { - _player.setVolume(volume ?? 1); - } + AudioService(); Future setAudio({String? filePath, String? assetPath}) async { try { @@ -72,10 +69,10 @@ class AudioService { } } - Future changeMute() async { + Future changeMute({required double volume}) async { try { if (_player.volume == 0) { - await _player.setVolume(volume ?? 1); + await _player.setVolume(volume); } else { await _player.setVolume(0); } diff --git a/lib/features/home/presentation/bloc/home_bloc.dart b/lib/features/home/presentation/bloc/home_bloc.dart index 706dfa9..98a52fc 100644 --- a/lib/features/home/presentation/bloc/home_bloc.dart +++ b/lib/features/home/presentation/bloc/home_bloc.dart @@ -59,15 +59,17 @@ class HomeBloc extends Bloc { Future changeMute() async { await Future.wait([ - _mainAudioService.changeMute(), - _effectAudioService.changeMute(), + _mainAudioService.changeMute(volume: MyConstants.musicAudioVolume), + _effectAudioService.changeMute(volume: MyConstants.effectAudioVolume), ]); } Future playMusic() async { await Future.wait([ _mainAudioService.setAudio(assetPath: MyAudios.home), + _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume), _mainAudioService.setLoopMode(isLoop: true), + _effectAudioService.setVolume(volume: MyConstants.effectAudioVolume), ]); await _mainAudioService.play(); } diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart index 981cc3e..272a65a 100644 --- a/lib/features/level/presentation/bloc/level_bloc.dart +++ b/lib/features/level/presentation/bloc/level_bloc.dart @@ -189,6 +189,7 @@ class LevelBloc extends Bloc { Future playMusic() async { await Future.wait([ _mainAudioService.setAudio(assetPath: MyAudios.question), + _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume), _mainAudioService.setLoopMode(isLoop: true), ]); await _mainAudioService.play(); @@ -223,8 +224,8 @@ class LevelBloc extends Bloc { Future changeMute() async { await Future.wait([ - _mainAudioService.changeMute(), - _effectAudioService.changeMute(), + _mainAudioService.changeMute(volume: MyConstants.musicAudioVolume), + _effectAudioService.changeMute(volume: MyConstants.effectAudioVolume), ]); } diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index a832e3f..c52f32b 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -33,7 +33,7 @@ class QuestionBloc extends Bloc { ) : super(QuestionState()) { volumeStream = _mainAudioService.volumeStream(); playingStream = _mainAudioService.playingStream(); - stopMusic(); + initAudios(); registerShowCase(); on(_getLevelEvent); on(_chooseAnswerEvent); @@ -42,6 +42,8 @@ class QuestionBloc extends Bloc { @override Future close() { unRegisterShowCase(); + _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); + _backgroundAudioService.dispose(); animationController.dispose(); return super.close(); } @@ -69,6 +71,7 @@ class QuestionBloc extends Bloc { /// ------------Controllers------------ final AudioService _mainAudioService; + final AudioService _backgroundAudioService = AudioService(); final AudioService _effectAudioService; late final AnimationController animationController; @@ -124,11 +127,16 @@ class QuestionBloc extends Bloc { await _effectAudioService.play(); } - Future stopMusic() async { + Future initAudios() async { await Future.wait([ _mainAudioService.stop(), + _mainAudioService.setVolume(volume: MyConstants.questionAudioVolume), _mainAudioService.setLoopMode(isLoop: false), + _backgroundAudioService.setAudio(assetPath: MyAudios.question), + _backgroundAudioService.setVolume(volume: 0.1), + _backgroundAudioService.setLoopMode(isLoop: true), ]); + await _backgroundAudioService.play(); } Future playWrongAudio() async { @@ -148,8 +156,9 @@ class QuestionBloc extends Bloc { Future changeMute() async { await Future.wait([ - _mainAudioService.changeMute(), - _effectAudioService.changeMute(), + _mainAudioService.changeMute(volume: MyConstants.questionAudioVolume), + _backgroundAudioService.changeMute(volume: 0.1), + _effectAudioService.changeMute(volume: MyConstants.effectAudioVolume), ]); } diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index 2690c2f..4c09d5a 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -41,7 +41,7 @@ void initBindings() { /// Classes locator.registerSingleton(HttpRequestImpl()); locator.registerSingleton( - AudioService(volume: MyConstants.mainAudioVolume), + AudioService(), instanceName: MyConstants.mainAudioService, ); locator.registerSingleton( From be29096fafd3eac9789a7ae5208786d262a2ad0c Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Tue, 2 Dec 2025 00:57:06 +0330 Subject: [PATCH 2/7] fix: skip audio --- .../widgets/showcase/my_showcase_widget.dart | 2 +- .../presentation/bloc/question_bloc.dart | 20 +++++++++++++++---- .../ui/screens/answer_screen.dart | 20 ++++++++++++------- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/lib/core/widgets/showcase/my_showcase_widget.dart b/lib/core/widgets/showcase/my_showcase_widget.dart index afd7062..75ec9d5 100644 --- a/lib/core/widgets/showcase/my_showcase_widget.dart +++ b/lib/core/widgets/showcase/my_showcase_widget.dart @@ -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/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index c52f32b..764bc91 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -33,6 +33,7 @@ class QuestionBloc extends Bloc { ) : super(QuestionState()) { volumeStream = _mainAudioService.volumeStream(); playingStream = _mainAudioService.playingStream(); + // LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'false'); initAudios(); registerShowCase(); on(_getLevelEvent); @@ -82,6 +83,12 @@ class QuestionBloc extends Bloc { onStart: (showcaseIndex, key) { LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'true'); }, + onDismiss: (onDismiss) async { + await playQuestionAudio(); + }, + onFinish: () async { + await playQuestionAudio(); + }, ); } catch (_) {} } @@ -237,10 +244,15 @@ class QuestionBloc extends Bloc { currentQuestion: data.questions?.first, showAnswers: true )); - await playQuestionAudio(); - animationController.forward().then((value) { - startShowcase(); - }); + if(LocalStorage.readData(key: MyConstants.firstShowcase) != 'true'){ + await Future.delayed(Duration(milliseconds: 500)); + animationController.forward().then((value) { + startShowcase(); + }); + } else { + await playQuestionAudio(); + animationController.forward(); + } }, (error) { emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage))); diff --git a/lib/features/question/presentation/ui/screens/answer_screen.dart b/lib/features/question/presentation/ui/screens/answer_screen.dart index a1ac4c9..3360efd 100644 --- a/lib/features/question/presentation/ui/screens/answer_screen.dart +++ b/lib/features/question/presentation/ui/screens/answer_screen.dart @@ -71,6 +71,12 @@ class _AnswerScreenState extends State { await audioService.play(); } + @override + void dispose() { + audioService.pause(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Stack( @@ -90,6 +96,13 @@ 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( @@ -105,13 +118,6 @@ class _AnswerScreenState extends State { ), ), ), - if (widget.showConfetti) ...{ - Lottie.asset( - MyAnimations.confetti, - height: context.heightScreen, - fit: BoxFit.cover, - ), - }, ], ); } From 39db7d8fd2be7279565d9883088902bc6e6d194d Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Tue, 2 Dec 2025 01:01:29 +0330 Subject: [PATCH 3/7] update --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index fe8d858..bae9346 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: 0.1.7+1 environment: sdk: ^3.9.2 From 151f5e78e003a1d6373b9372a25b53761aae5b68 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Tue, 2 Dec 2025 19:18:21 +0330 Subject: [PATCH 4/7] fix: languages and translate --- lib/core/widgets/dialog/about_us_dialog.dart | 13 +- .../intro/presentation/bloc/intro_bloc.dart | 6 +- .../intro/presentation/ui/intro_page.dart | 2 +- .../presentation/ui/language_page.dart | 13 +- lib/l10n/app_de.arb | 39 ++++++ lib/l10n/app_localizations.dart | 14 +- lib/l10n/app_localizations_de.dart | 129 ++++++++++++++++++ 7 files changed, 203 insertions(+), 13 deletions(-) create mode 100644 lib/l10n/app_de.arb create mode 100644 lib/l10n/app_localizations_de.dart diff --git a/lib/core/widgets/dialog/about_us_dialog.dart b/lib/core/widgets/dialog/about_us_dialog.dart index 4992978..75d8241 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'; @@ -51,10 +52,14 @@ class AboutUsDialog extends StatelessWidget { color: 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: Color(0XFF494178), + ), ), ), MyImage( diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index 986da04..dbc5205 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -35,10 +35,10 @@ class IntroBloc extends Bloc { /// ------------Controllers------------ /// ------------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); } } @@ -47,7 +47,7 @@ class IntroBloc extends Bloc { if (state.currentIntro < intros.length - 1) { emit(state.copyWith(currentIntro: state.currentIntro + 1)); } else { - goToLevelPage(); + goToHomePage(); } } } diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index 2c20d18..87aa98e 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -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/language/presentation/ui/language_page.dart b/lib/features/language/presentation/ui/language_page.dart index 9165123..bdaeba2 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'; @@ -61,9 +62,15 @@ class LanguagePage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ MyImage(image: MyAssets.lang, size: 28), - Text( - context.translate.select_language, - style: MYTextStyle.titr0.copyWith(color: Color(0XFF847AC4)), + Expanded( + child: AutoSizeText( + context.translate.select_language, + minFontSize: 12, + maxFontSize: 20, + maxLines: 1, + textAlign: TextAlign.center, + style: MYTextStyle.titr0.copyWith(color: Color(0XFF847AC4)), + ), ), ], ); diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb new file mode 100644 index 0000000..5e2cf47 --- /dev/null +++ b/lib/l10n/app_de.arb @@ -0,0 +1,39 @@ +{ + "@@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." +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 6b4956f..dce9f70 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'), @@ -331,8 +333,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 +351,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_de.dart b/lib/l10n/app_localizations_de.dart new file mode 100644 index 0000000..bc7873b --- /dev/null +++ b/lib/l10n/app_localizations_de.dart @@ -0,0 +1,129 @@ +// 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.'; +} From c528e2f5d4d0b3f9b573e8e67f13f67343bed13b Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Tue, 2 Dec 2025 19:37:04 +0330 Subject: [PATCH 5/7] fix: audio volume --- lib/core/services/audio_service.dart | 11 +++++++--- .../home/presentation/bloc/home_bloc.dart | 6 ++---- .../level/presentation/bloc/level_bloc.dart | 5 ++--- .../presentation/bloc/question_bloc.dart | 21 ++++++++++++------- lib/init_bindings.dart | 4 ++-- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/core/services/audio_service.dart b/lib/core/services/audio_service.dart index f8d6610..2feb262 100644 --- a/lib/core/services/audio_service.dart +++ b/lib/core/services/audio_service.dart @@ -6,8 +6,11 @@ import 'package:just_audio/just_audio.dart'; class AudioService { final AudioPlayer _player = AudioPlayer(); final StreamController _streamController = StreamController.broadcast(); + final double volume; - AudioService(); + AudioService({required this.volume}){ + setVolume(volume: volume); + } Future setAudio({String? filePath, String? assetPath}) async { try { @@ -69,10 +72,10 @@ class AudioService { } } - Future changeMute({required double volume}) async { + Future changeMute({double? newVolume}) async { try { if (_player.volume == 0) { - await _player.setVolume(volume); + await _player.setVolume(newVolume ?? volume); } else { await _player.setVolume(0); } @@ -95,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/features/home/presentation/bloc/home_bloc.dart b/lib/features/home/presentation/bloc/home_bloc.dart index 98a52fc..706dfa9 100644 --- a/lib/features/home/presentation/bloc/home_bloc.dart +++ b/lib/features/home/presentation/bloc/home_bloc.dart @@ -59,17 +59,15 @@ class HomeBloc extends Bloc { Future changeMute() async { await Future.wait([ - _mainAudioService.changeMute(volume: MyConstants.musicAudioVolume), - _effectAudioService.changeMute(volume: MyConstants.effectAudioVolume), + _mainAudioService.changeMute(), + _effectAudioService.changeMute(), ]); } Future playMusic() async { await Future.wait([ _mainAudioService.setAudio(assetPath: MyAudios.home), - _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume), _mainAudioService.setLoopMode(isLoop: true), - _effectAudioService.setVolume(volume: MyConstants.effectAudioVolume), ]); await _mainAudioService.play(); } diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart index 272a65a..981cc3e 100644 --- a/lib/features/level/presentation/bloc/level_bloc.dart +++ b/lib/features/level/presentation/bloc/level_bloc.dart @@ -189,7 +189,6 @@ class LevelBloc extends Bloc { Future playMusic() async { await Future.wait([ _mainAudioService.setAudio(assetPath: MyAudios.question), - _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume), _mainAudioService.setLoopMode(isLoop: true), ]); await _mainAudioService.play(); @@ -224,8 +223,8 @@ class LevelBloc extends Bloc { Future changeMute() async { await Future.wait([ - _mainAudioService.changeMute(volume: MyConstants.musicAudioVolume), - _effectAudioService.changeMute(volume: MyConstants.effectAudioVolume), + _mainAudioService.changeMute(), + _effectAudioService.changeMute(), ]); } diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index 764bc91..74d14c1 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -33,7 +33,6 @@ class QuestionBloc extends Bloc { ) : super(QuestionState()) { volumeStream = _mainAudioService.volumeStream(); playingStream = _mainAudioService.playingStream(); - // LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'false'); initAudios(); registerShowCase(); on(_getLevelEvent); @@ -43,7 +42,9 @@ class QuestionBloc extends Bloc { @override Future close() { unRegisterShowCase(); - _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); + if (_mainAudioService.audioVolume != 0) { + _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); + } _backgroundAudioService.dispose(); animationController.dispose(); return super.close(); @@ -72,7 +73,7 @@ class QuestionBloc extends Bloc { /// ------------Controllers------------ final AudioService _mainAudioService; - final AudioService _backgroundAudioService = AudioService(); + final AudioService _backgroundAudioService = AudioService(volume: 0); final AudioService _effectAudioService; late final AnimationController animationController; @@ -137,12 +138,16 @@ class QuestionBloc extends Bloc { Future initAudios() async { await Future.wait([ _mainAudioService.stop(), - _mainAudioService.setVolume(volume: MyConstants.questionAudioVolume), _mainAudioService.setLoopMode(isLoop: false), _backgroundAudioService.setAudio(assetPath: MyAudios.question), - _backgroundAudioService.setVolume(volume: 0.1), _backgroundAudioService.setLoopMode(isLoop: true), ]); + if (_mainAudioService.audioVolume != 0) { + await Future.wait([ + _mainAudioService.setVolume(volume: MyConstants.questionAudioVolume), + _backgroundAudioService.setVolume(volume: 0.1), + ]); + } await _backgroundAudioService.play(); } @@ -163,9 +168,9 @@ class QuestionBloc extends Bloc { Future changeMute() async { await Future.wait([ - _mainAudioService.changeMute(volume: MyConstants.questionAudioVolume), - _backgroundAudioService.changeMute(volume: 0.1), - _effectAudioService.changeMute(volume: MyConstants.effectAudioVolume), + _mainAudioService.changeMute(newVolume: MyConstants.questionAudioVolume), + _backgroundAudioService.changeMute(newVolume: 0.1), + _effectAudioService.changeMute(), ]); } diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index 4c09d5a..234c512 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -41,11 +41,11 @@ void initBindings() { /// Classes locator.registerSingleton(HttpRequestImpl()); locator.registerSingleton( - AudioService(), + AudioService(volume: MyConstants.musicAudioVolume), instanceName: MyConstants.mainAudioService, ); locator.registerSingleton( - AudioService(), + AudioService(volume: MyConstants.effectAudioVolume), instanceName: MyConstants.effectAudioService, ); locator.registerSingleton(AppBloc()); From 93ffc91ce2fe9b6c6702a8320f2dd064af42dc3c Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Tue, 2 Dec 2025 22:00:39 +0330 Subject: [PATCH 6/7] changes --- lib/core/routers/hero_dialog_route.dart | 2 +- .../animations/fade_anim_controller.dart | 39 +++++ lib/core/widgets/answer_box/answer_box.dart | 5 +- .../widgets/answer_box/answer_box_show.dart | 4 +- .../answer_box/styles/picture_box.dart | 47 +++--- .../domain/entity/question_entity.dart | 2 +- .../presentation/bloc/question_bloc.dart | 69 +++++--- .../ui/screens/answer_screen.dart | 8 +- .../ui/screens/question_screen.dart | 158 ++++++++++++------ pubspec.yaml | 2 +- 10 files changed, 227 insertions(+), 109 deletions(-) create mode 100644 lib/core/widgets/animations/fade_anim_controller.dart diff --git a/lib/core/routers/hero_dialog_route.dart b/lib/core/routers/hero_dialog_route.dart index 7b53b6b..54a199f 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 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/answer_box/answer_box.dart b/lib/core/widgets/answer_box/answer_box.dart index b01d027..0e85ce9 100644 --- a/lib/core/widgets/answer_box/answer_box.dart +++ b/lib/core/widgets/answer_box/answer_box.dart @@ -53,8 +53,7 @@ class _AnswerBoxState extends State { } : null, child: Stack( - alignment: Alignment.center, - clipBehavior: Clip.none, + alignment: Alignment.topCenter, children: [ AnswerPictureBox( selected: selected, @@ -68,7 +67,7 @@ class _AnswerBoxState extends State { Positioned( left: 0, right: 0, - bottom: -60, + bottom: 0, child: AnswerTextBox(text: widget.answer.title ?? ''), ), PositionedDirectional( diff --git a/lib/core/widgets/answer_box/answer_box_show.dart b/lib/core/widgets/answer_box/answer_box_show.dart index 726c799..b09f4c5 100644 --- a/lib/core/widgets/answer_box/answer_box_show.dart +++ b/lib/core/widgets/answer_box/answer_box_show.dart @@ -26,7 +26,7 @@ class AnswerBoxShow extends StatelessWidget { child: Material( type: MaterialType.transparency, child: Stack( - alignment: Alignment.center, + alignment: Alignment.topCenter, clipBehavior: Clip.none, children: [ AnswerPictureBox( @@ -38,7 +38,7 @@ class AnswerBoxShow extends StatelessWidget { Positioned( left: 0, right: 0, - bottom: -MySpaces.s40, + bottom: -10, child: AnswerTextBox(text: answer.title ?? ''), ), PositionedDirectional( diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index f1f2a56..164bb9e 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -33,29 +33,32 @@ 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: 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, + ), + transitionBuilder: (child, animation) => + FadeTransition( + opacity: animation, + child: child, + ), ), - transitionBuilder: (child, animation) => - FadeTransition( - opacity: animation, - child: child, - ), ), PositionedDirectional( top: 0, 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 74d14c1..a78836a 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -46,7 +46,8 @@ class QuestionBloc extends Bloc { _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); } _backgroundAudioService.dispose(); - animationController.dispose(); + answerAnimationController.dispose(); + imageAnimationController.dispose(); return super.close(); } @@ -70,12 +71,14 @@ class QuestionBloc extends Bloc { }; late final Stream volumeStream; late final Stream playingStream; + bool showAnswerSequence = true; /// ------------Controllers------------ final AudioService _mainAudioService; final AudioService _backgroundAudioService = AudioService(volume: 0); final AudioService _effectAudioService; - late final AnimationController animationController; + late final AnimationController answerAnimationController; + late final AnimationController imageAnimationController; /// ------------Functions------------ void registerShowCase() { @@ -131,8 +134,8 @@ 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 initAudios() async { @@ -174,23 +177,24 @@ class QuestionBloc extends Bloc { ]); } - // 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, @@ -228,6 +232,10 @@ class QuestionBloc extends Bloc { ); } + void showingAnswerSequence({required bool show}){ + showAnswerSequence = show; + } + /// ------------Event Calls------------ FutureOr _getLevelEvent(GetLevelEvent event, Emitter emit) async { await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then( @@ -247,16 +255,18 @@ class QuestionBloc extends Bloc { getQuestionStatus: BaseComplete(''), levelEntity: level, currentQuestion: data.questions?.first, - showAnswers: true )); if(LocalStorage.readData(key: MyConstants.firstShowcase) != 'true'){ await Future.delayed(Duration(milliseconds: 500)); - animationController.forward().then((value) { + answerAnimationController.forward().then((value) { startShowcase(); }); } else { await playQuestionAudio(); - animationController.forward(); + imageAnimationController.reverse(); + answerAnimationController.forward().then((value) { + showQueueAnswer(); + }); } }, (error) { @@ -272,7 +282,7 @@ class QuestionBloc extends Bloc { emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer)); if (event.chooseCorrectAnswer) { - animationController.reverse(); + answerAnimationController.reverse(); await showAnswerDialog( context: MyContext.get, correctAudio: state.currentQuestion?.correctAudio, @@ -301,8 +311,13 @@ class QuestionBloc extends Bloc { ); } } else { - await playQuestionAudio(); - animationController.forward(); + showingAnswerSequence(show: true); + imageAnimationController.forward(); + await playQuestionAudio(); + imageAnimationController.reverse(); + answerAnimationController.forward().then((value) { + showQueueAnswer(); + }); } }); } else { diff --git a/lib/features/question/presentation/ui/screens/answer_screen.dart b/lib/features/question/presentation/ui/screens/answer_screen.dart index 3360efd..46e2066 100644 --- a/lib/features/question/presentation/ui/screens/answer_screen.dart +++ b/lib/features/question/presentation/ui/screens/answer_screen.dart @@ -73,7 +73,6 @@ class _AnswerScreenState extends State { @override void dispose() { - audioService.pause(); super.dispose(); } @@ -106,8 +105,11 @@ class _AnswerScreenState extends State { 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), diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart index 1d6b9da..b9012d4 100644 --- a/lib/features/question/presentation/ui/screens/question_screen.dart +++ b/lib/features/question/presentation/ui/screens/question_screen.dart @@ -5,15 +5,19 @@ 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/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/picture_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'; @@ -30,16 +34,25 @@ class QuestionScreen extends StatefulWidget { State createState() => _QuestionScreenState(); } -class _QuestionScreenState extends State with SingleTickerProviderStateMixin { +class _QuestionScreenState extends State with TickerProviderStateMixin { @override void initState() { super.initState(); - context.read().animationController = AnimationController( + context.read().answerAnimationController = AnimationController( vsync: this, duration: Duration(milliseconds: 500), reverseDuration: Duration(milliseconds: 500), ); + + context.read().imageAnimationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: 500), + reverseDuration: Duration(milliseconds: 500), + ); + if(LocalStorage.readData(key: MyConstants.firstShowcase) == 'true') { + context.read().imageAnimationController.forward(); + } } @override @@ -49,12 +62,39 @@ class _QuestionScreenState extends State with SingleTickerProvid _stepper(), _titles(context), MySpaces.s20.gapHeight, - _answers(context), + Expanded( + child: Stack( + children: [ + _questionImage(context), + _answers(context), + ], + ), + ), _bottom(context), ], ); } + Widget _questionImage(BuildContext context) { + return Column( + children: [ + Spacer(), + FadeAnimController( + controller: context.read().imageAnimationController, + child: BlocBuilder( + builder: (context, state) => AnswerPictureBox( + selected: false, + correctAnswer: 0, + index: 0, + image: state.currentQuestion?.image ?? '', + ), + ), + ), + Spacer(), + ], + ); + } + Widget _stepper() { return BlocBuilder( buildWhen: (previous, current) => @@ -97,54 +137,53 @@ class _QuestionScreenState extends State with SingleTickerProvid ); } - 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, - ); - }, - onTap: (isCorrect, correctAnswer) => - context.read().add( - ChooseAnswerEvent(isCorrect, correctAnswer, context), - ), - ), + Widget _answers(BuildContext context) { + return 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: 20, + mainAxisExtent: 250 + ), + itemBuilder: (context, index) => + state.currentQuestion?.answers?[index].imageId == null + ? SizedBox.shrink() + : SlideAnim( + key: Key('${state.currentQuestion?.id}'), + controller: context.read().answerAnimationController, + 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, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent(isCorrect, correctAnswer, context), + ), ), ), - ), + ), ), ); } @@ -173,6 +212,27 @@ class _QuestionScreenState extends State with SingleTickerProvid ), ), ), + Positioned( + left: 120, + right: 120, + child: FadeAnimController( + controller: context.read().imageAnimationController, + child: TextButton( + onPressed: () async { + context.read().imageAnimationController.reverse(); + context.read().answerAnimationController.forward(); + context.read().showingAnswerSequence(show: false); + }, + style: TextButton.styleFrom( + foregroundColor: MyColors.white.withValues(alpha: 0.7), + ), + child: Text( + context.translate.skip, + style: MYTextStyle.button2 + ), + ), + ), + ), PositionedDirectional( end: 0, child: MyShowcaseWidget( diff --git a/pubspec.yaml b/pubspec.yaml index bae9346..82c75e0 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.7+1 +version: 0.1.8+1 environment: sdk: ^3.9.2 From 3eb3be9f1f79e1230c6c10f0a2d3f2781f2f8cb0 Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Tue, 2 Dec 2025 22:11:01 +0330 Subject: [PATCH 7/7] fix: guider --- lib/core/widgets/answer_box/answer_box.dart | 22 +++++++---- .../ui/screens/question_screen.dart | 39 +++++++++---------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/lib/core/widgets/answer_box/answer_box.dart b/lib/core/widgets/answer_box/answer_box.dart index 0e85ce9..148e81c 100644 --- a/lib/core/widgets/answer_box/answer_box.dart +++ b/lib/core/widgets/answer_box/answer_box.dart @@ -16,6 +16,7 @@ class AnswerBox extends StatefulWidget { required this.correctAnswer, required this.index, required this.globalKey, + required this.answerGlobalKey, this.onTap, this.onNotifTap, }); @@ -26,6 +27,7 @@ class AnswerBox extends StatefulWidget { final int index; final Function(AnswerEntity answer)? onNotifTap; final GlobalKey globalKey; + final GlobalKey answerGlobalKey; @override State createState() => _AnswerBoxState(); @@ -55,14 +57,18 @@ class _AnswerBoxState extends State { child: Stack( alignment: Alignment.topCenter, children: [ - AnswerPictureBox( - selected: selected, - index: widget.index, - image: widget.answer.image ?? '', - correctAnswer: widget.correctAnswer, - onTap: () { - widget.onNotifTap?.call(widget.answer); - }, + 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, diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart index b9012d4..39a4892 100644 --- a/lib/features/question/presentation/ui/screens/question_screen.dart +++ b/lib/features/question/presentation/ui/screens/question_screen.dart @@ -161,27 +161,24 @@ class _QuestionScreenState extends State with TickerProviderStat key: Key('${state.currentQuestion?.id}'), controller: context.read().answerAnimationController, 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, - ); - }, - onTap: (isCorrect, correctAnswer) => - context.read().add( - ChooseAnswerEvent(isCorrect, correctAnswer, context), - ), - ), + child: AnswerBox( + 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: state.currentQuestion?.correctAnswer ?? 0, + onNotifTap: (AnswerEntity answer) { + context.read().showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent(isCorrect, correctAnswer, context), + ), ), ), ),