Browse Source

Merge pull request 'fix/changes' (#46) from fix/changes into develop

Reviewed-on: https://git.nwhco.ir/amirreza.chegini/hade_hoda_flutter/pulls/46
pull/47/head
amirreza.chegini 6 days ago
parent
commit
4063bc0a16
  1. 4
      lib/core/constants/my_constants.dart
  2. 2
      lib/core/routers/hero_dialog_route.dart
  3. 12
      lib/core/services/audio_service.dart
  4. 39
      lib/core/widgets/animations/fade_anim_controller.dart
  5. 27
      lib/core/widgets/answer_box/answer_box.dart
  6. 4
      lib/core/widgets/answer_box/answer_box_show.dart
  7. 47
      lib/core/widgets/answer_box/styles/picture_box.dart
  8. 13
      lib/core/widgets/dialog/about_us_dialog.dart
  9. 2
      lib/core/widgets/showcase/my_showcase_widget.dart
  10. 6
      lib/features/intro/presentation/bloc/intro_bloc.dart
  11. 2
      lib/features/intro/presentation/ui/intro_page.dart
  12. 13
      lib/features/language/presentation/ui/language_page.dart
  13. 2
      lib/features/question/domain/entity/question_entity.dart
  14. 105
      lib/features/question/presentation/bloc/question_bloc.dart
  15. 26
      lib/features/question/presentation/ui/screens/answer_screen.dart
  16. 157
      lib/features/question/presentation/ui/screens/question_screen.dart
  17. 4
      lib/init_bindings.dart
  18. 39
      lib/l10n/app_de.arb
  19. 14
      lib/l10n/app_localizations.dart
  20. 129
      lib/l10n/app_localizations_de.dart
  21. 2
      pubspec.yaml

4
lib/core/constants/my_constants.dart

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

2
lib/core/routers/hero_dialog_route.dart

@ -16,7 +16,7 @@ class HeroDialogRoute<T> extends PageRoute<T> {
bool get fullscreenDialog => false;
@override
bool get barrierDismissible => true;
bool get barrierDismissible => false;
@override
Duration get transitionDuration => const Duration(seconds: 1); // Adjust as needed

12
lib/core/services/audio_service.dart

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

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

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

27
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<AnswerBox> createState() => _AnswerBoxState();
@ -53,22 +55,25 @@ class _AnswerBoxState extends State<AnswerBox> {
}
: null,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
alignment: Alignment.topCenter,
children: [
AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
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,
right: 0,
bottom: -60,
bottom: 0,
child: AnswerTextBox(text: widget.answer.title ?? ''),
),
PositionedDirectional(

4
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(

47
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,

13
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(

2
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,

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

@ -35,10 +35,10 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
/// ------------Controllers------------
/// ------------Functions------------
Future<void> goToLevelPage() async {
Future<void> goToHomePage() async {
await LocalStorage.saveData(key: MyConstants.firstIntro, value: 'true');
if (MyContext.get.mounted) {
MyContext.get.goNamed(Routes.levelPage);
MyContext.get.goNamed(Routes.homePage);
}
}
@ -47,7 +47,7 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
if (state.currentIntro < intros.length - 1) {
emit(state.copyWith(currentIntro: state.currentIntro + 1));
} else {
goToLevelPage();
goToHomePage();
}
}
}

2
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<IntroBloc>().goToLevelPage(),
onPressed: () => context.read<IntroBloc>().goToHomePage(),
style: TextButton.styleFrom(
foregroundColor: MyColors.white.withValues(alpha: 0.7),
),

13
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)),
),
),
],
);

2
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}';
}
}

105
lib/features/question/presentation/bloc/question_bloc.dart

@ -33,7 +33,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
) : super(QuestionState()) {
volumeStream = _mainAudioService.volumeStream();
playingStream = _mainAudioService.playingStream();
stopMusic();
initAudios();
registerShowCase();
on<GetLevelEvent>(_getLevelEvent);
on<ChooseAnswerEvent>(_chooseAnswerEvent);
@ -42,7 +42,12 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
@override
Future<void> close() {
unRegisterShowCase();
animationController.dispose();
if (_mainAudioService.audioVolume != 0) {
_mainAudioService.setVolume(volume: MyConstants.musicAudioVolume);
}
_backgroundAudioService.dispose();
answerAnimationController.dispose();
imageAnimationController.dispose();
return super.close();
}
@ -66,11 +71,14 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
};
late final Stream<double> volumeStream;
late final Stream<bool> 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() {
@ -79,6 +87,12 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
onStart: (showcaseIndex, key) {
LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'true');
},
onDismiss: (onDismiss) async {
await playQuestionAudio();
},
onFinish: () async {
await playQuestionAudio();
},
);
} catch (_) {}
}
@ -120,15 +134,24 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
}
Future<void> playDiamondAudio() async {
await _effectAudioService.setAudio(assetPath: MyAudios.diamondEnd);
await _effectAudioService.play();
await _mainAudioService.setAudio(assetPath: MyAudios.diamondEnd);
await _mainAudioService.play();
}
Future<void> stopMusic() async {
Future<void> 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<void> playWrongAudio() async {
@ -148,28 +171,30 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
Future<void> changeMute() async {
await Future.wait([
_mainAudioService.changeMute(),
_mainAudioService.changeMute(newVolume: MyConstants.questionAudioVolume),
_backgroundAudioService.changeMute(newVolume: 0.1),
_effectAudioService.changeMute(),
]);
}
// Future<void> showQueueAnswer() async {
// final List<AnswerEntity> 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<void> showQueueAnswer() async {
if(!showAnswerSequence) return;
final List<AnswerEntity> 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<void> showAnswerDialog({
required BuildContext context,
@ -207,6 +232,10 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
);
}
void showingAnswerSequence({required bool show}){
showAnswerSequence = show;
}
/// ------------Event Calls------------
FutureOr<void> _getLevelEvent(GetLevelEvent event, Emitter<QuestionState> emit) async {
await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then(
@ -226,12 +255,19 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
getQuestionStatus: BaseComplete(''),
levelEntity: level,
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));
answerAnimationController.forward().then((value) {
startShowcase();
});
} else {
await playQuestionAudio();
imageAnimationController.reverse();
answerAnimationController.forward().then((value) {
showQueueAnswer();
});
}
},
(error) {
emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage)));
@ -246,7 +282,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer));
if (event.chooseCorrectAnswer) {
animationController.reverse();
answerAnimationController.reverse();
await showAnswerDialog(
context: MyContext.get,
correctAudio: state.currentQuestion?.correctAudio,
@ -275,8 +311,13 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
);
}
} else {
await playQuestionAudio();
animationController.forward();
showingAnswerSequence(show: true);
imageAnimationController.forward();
await playQuestionAudio();
imageAnimationController.reverse();
answerAnimationController.forward().then((value) {
showQueueAnswer();
});
}
});
} else {

26
lib/features/question/presentation/ui/screens/answer_screen.dart

@ -71,6 +71,11 @@ class _AnswerScreenState extends State<AnswerScreen> {
await audioService.play();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
@ -90,11 +95,21 @@ class _AnswerScreenState extends State<AnswerScreen> {
),
),
),
if (widget.showConfetti) ...{
Lottie.asset(
MyAnimations.confetti,
height: context.heightScreen,
fit: BoxFit.cover,
),
},
Positioned(
bottom: setPlatform<double>(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<AnswerScreen> {
),
),
),
if (widget.showConfetti) ...{
Lottie.asset(
MyAnimations.confetti,
height: context.heightScreen,
fit: BoxFit.cover,
),
},
],
);
}

157
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<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> with SingleTickerProviderStateMixin {
class _QuestionScreenState extends State<QuestionScreen> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
context.read<QuestionBloc>().animationController = AnimationController(
context.read<QuestionBloc>().answerAnimationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
);
context.read<QuestionBloc>().imageAnimationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
);
if(LocalStorage.readData(key: MyConstants.firstShowcase) == 'true') {
context.read<QuestionBloc>().imageAnimationController.forward();
}
}
@override
@ -49,12 +62,39 @@ class _QuestionScreenState extends State<QuestionScreen> 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<QuestionBloc>().imageAnimationController,
child: BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => AnswerPictureBox(
selected: false,
correctAnswer: 0,
index: 0,
image: state.currentQuestion?.image ?? '',
),
),
),
Spacer(),
],
);
}
Widget _stepper() {
return BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
@ -97,54 +137,50 @@ class _QuestionScreenState extends State<QuestionScreen> with SingleTickerProvid
);
}
Expanded _answers(BuildContext context) {
return Expanded(
child: BlocBuilder<QuestionBloc, QuestionState>(
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<QuestionBloc>().animationController,
index: index,
child: MyShowcaseWidget(
globalKey: context.read<QuestionBloc>().showCaseKey['answer_key_$index']!,
description: context.translate.showcase_answer,
child: AnswerBox(
globalKey: context.read<QuestionBloc>().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<QuestionBloc>().showAnswerDialog(
context: context,
answerEntity: answer,
);
},
onTap: (isCorrect, correctAnswer) =>
context.read<QuestionBloc>().add(
ChooseAnswerEvent(isCorrect, correctAnswer, context),
),
),
),
),
Widget _answers(BuildContext context) {
return BlocBuilder<QuestionBloc, QuestionState>(
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<QuestionBloc>().answerAnimationController,
index: index,
child: AnswerBox(
globalKey: context.read<QuestionBloc>().showCaseKey['notif_key_$index']!,
answerGlobalKey: context.read<QuestionBloc>().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<QuestionBloc>().showAnswerDialog(
context: context,
answerEntity: answer,
);
},
onTap: (isCorrect, correctAnswer) =>
context.read<QuestionBloc>().add(
ChooseAnswerEvent(isCorrect, correctAnswer, context),
),
),
),
),
);
}
@ -173,6 +209,27 @@ class _QuestionScreenState extends State<QuestionScreen> with SingleTickerProvid
),
),
),
Positioned(
left: 120,
right: 120,
child: FadeAnimController(
controller: context.read<QuestionBloc>().imageAnimationController,
child: TextButton(
onPressed: () async {
context.read<QuestionBloc>().imageAnimationController.reverse();
context.read<QuestionBloc>().answerAnimationController.forward();
context.read<QuestionBloc>().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(

4
lib/init_bindings.dart

@ -41,11 +41,11 @@ void initBindings() {
/// Classes
locator.registerSingleton<IHttpRequest>(HttpRequestImpl());
locator.registerSingleton<AudioService>(
AudioService(volume: MyConstants.mainAudioVolume),
AudioService(volume: MyConstants.musicAudioVolume),
instanceName: MyConstants.mainAudioService,
);
locator.registerSingleton<AudioService>(
AudioService(),
AudioService(volume: MyConstants.effectAudioVolume),
instanceName: MyConstants.effectAudioService,
);
locator.registerSingleton<AppBloc>(AppBloc());

39
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."
}

14
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<Locale> supportedLocales = <Locale>[
Locale('ar'),
Locale('de'),
Locale('en'),
Locale('fr'),
Locale('ru'),
@ -331,8 +333,14 @@ class _AppLocalizationsDelegate
}
@override
bool isSupported(Locale locale) =>
<String>['ar', 'en', 'fr', 'ru', 'tr'].contains(locale.languageCode);
bool isSupported(Locale locale) => <String>[
'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':

129
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.';
}

2
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.8+1
environment:
sdk: ^3.9.2

Loading…
Cancel
Save