Browse Source

fix: changes

pull/42/head
AmirrezaChegini 1 week ago
parent
commit
c64a465bb3
  1. 1
      assets/animations/Celebration.json
  2. 4
      lib/core/constants/my_api.dart
  3. 6
      lib/core/constants/my_constants.dart
  4. 2
      lib/core/middlewares/my_middlewares.dart
  5. 2
      lib/core/routers/hero_dialog_route.dart
  6. 21
      lib/core/services/audio_service.dart
  7. 102
      lib/core/widgets/animations/globe_animation.dart
  8. 26
      lib/core/widgets/animations/slide_anim.dart
  9. 2
      lib/core/widgets/showcase/my_showcase_widget.dart
  10. 2
      lib/features/download/presentation/bloc/download_bloc.dart
  11. 2
      lib/features/home/presentation/bloc/home_bloc.dart
  12. 6
      lib/features/level/data/model/level_model.dart
  13. 4
      lib/features/level/domain/entity/level_entity.dart
  14. 7
      lib/features/level/domain/entity/level_entity.g.dart
  15. 32
      lib/features/level/presentation/ui/level_page.dart
  16. 20
      lib/features/question/data/model/question_model.dart
  17. 2
      lib/features/question/domain/entity/answer_entity.dart
  18. 53
      lib/features/question/domain/entity/asdasd.json
  19. 29
      lib/features/question/domain/entity/question_entity.dart
  20. 39
      lib/features/question/domain/entity/question_entity.g.dart
  21. 60
      lib/features/question/presentation/bloc/question_bloc.dart
  22. 4
      lib/features/question/presentation/bloc/question_state.dart
  23. 18
      lib/features/question/presentation/ui/screens/answer_screen.dart
  24. 87
      lib/features/question/presentation/ui/screens/question_screen.dart
  25. 9
      lib/features/sample/presentation/ui/sample_page.dart
  26. 8
      pubspec.lock
  27. 3
      pubspec.yaml

1
assets/animations/Celebration.json
File diff suppressed because it is too large
View File

4
lib/core/constants/my_api.dart

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

6
lib/core/constants/my_constants.dart

@ -28,9 +28,9 @@ class MyConstants {
static const String defaultLanguage = 'en';
static const List<LanguageEntity> languages = [
LanguageEntity(title: 'English (English)', code: 'en', locale: Locale('en','US')),
LanguageEntity(title: 'French (Français)', code: 'fr', locale: Locale('fr','FR')),
LanguageEntity(title: 'Russian (Русский)', code: 'ru', locale: Locale('ru','RU')),
LanguageEntity(title: 'Turkish (Türkçe)', code: 'tr', locale: Locale('tr','TR')),
// LanguageEntity(title: 'French (Français)', code: 'fr', locale: Locale('fr','FR')),
// LanguageEntity(title: 'Russian (Русский)', code: 'ru', locale: Locale('ru','RU')),
// LanguageEntity(title: 'Turkish (Türkçe)', code: 'tr', locale: Locale('tr','TR')),
LanguageEntity(title: 'Arabic (العربية)', code: 'ar', locale: Locale('ar','AE')),
];
}

2
lib/core/middlewares/my_middlewares.dart

@ -25,7 +25,7 @@ class MyMiddlewares {
final String? firstIntro = LocalStorage.readData(
key: MyConstants.firstIntro);
if (firstIntro == 'true') {
return Routes.levelPage;
return Routes.homePage;
} else {
return null;
}

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 => false;
bool get barrierDismissible => true;
@override
Duration get transitionDuration => const Duration(seconds: 1); // Adjust as needed

21
lib/core/services/audio_service.dart

@ -1,8 +1,11 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:just_audio/just_audio.dart';
class AudioService {
final AudioPlayer _player = AudioPlayer();
final StreamController<bool> _streamController = StreamController.broadcast();
final double? volume;
AudioService({this.volume}) {
@ -93,14 +96,18 @@ class AudioService {
}
}
Stream<double> volumeStream() async* {
try {
yield* _player.volumeStream;
} catch (e) {
if (kDebugMode) {
print('$e');
Stream<double> volumeStream() => _player.volumeStream;
Stream<bool> playingStream() async* {
_player.processingStateStream.listen((event) {
if (event == ProcessingState.ready) {
_streamController.add(true);
}
}
if (event == ProcessingState.completed) {
_streamController.add(false);
}
});
yield* _streamController.stream;
}
Future<void> setLoopMode({bool isLoop = false}) async {

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

@ -0,0 +1,102 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
class GlobeAnimation extends StatefulWidget {
const GlobeAnimation({super.key, required this.child, this.state = true});
final Widget child;
final bool state;
@override
State<GlobeAnimation> createState() => _GlobeAnimationState();
}
class _GlobeAnimationState extends State<GlobeAnimation>
with SingleTickerProviderStateMixin {
Timer? _timer;
Gradient? _gradient;
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
reverseDuration: Duration(seconds: 1),
);
_animation = Tween<double>(
begin: 1,
end: 1.05,
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
}
@override
void didUpdateWidget(covariant GlobeAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.state) {
_controller.repeat(reverse: true);
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
if (_gradient == null) {
setState(() {
_gradient = RadialGradient(
colors: [
Color(0XFFDFCD00),
Color(0XFFDFCD00).withValues(alpha: 0.35),
Color(0XFFDFCD00).withValues(alpha: 0),
],
center: Alignment.center,
);
});
} else {
setState(() {
_gradient = null;
});
}
});
} else {
_timer?.cancel();
_timer = null;
_controller.stop();
setState(() {
_gradient = RadialGradient(
colors: [
Color(0XFFDFCD00).withValues(alpha: 0),
Color(0XFFDFCD00).withValues(alpha: 0),
Color(0XFFDFCD00).withValues(alpha: 0),
],
center: Alignment.center,
);
});
}
}
@override
void dispose() {
_timer?.cancel();
_timer = null;
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: widget.child,
builder: (context, child) => ScaleTransition(
scale: _animation,
alignment: Alignment.center,
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
padding: EdgeInsets.all(MySpaces.s0),
decoration: BoxDecoration(gradient: _gradient),
child: child,
),
),
);
}
}

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

@ -5,10 +5,12 @@ class SlideAnim extends StatefulWidget {
super.key,
required this.child,
required this.index,
required this.controller,
});
final Widget child;
final int index;
final AnimationController controller;
@override
State<SlideAnim> createState() => _SlideAnimState();
@ -16,7 +18,6 @@ class SlideAnim extends StatefulWidget {
class _SlideAnimState extends State<SlideAnim>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _animation;
final List<Offset> offsetList = [
Offset(-2, -2),
@ -28,34 +29,19 @@ class _SlideAnimState extends State<SlideAnim>
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
);
_animation = Tween<Offset>(
begin: offsetList[widget.index],
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
).animate(CurvedAnimation(parent: widget.controller, curve: Curves.linear));
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
animation: widget.controller,
child: widget.child,
builder: (context, child) => SlideTransition(
position: _animation,
child: child,
),
builder: (context, child) =>
SlideTransition(position: _animation, child: child),
);
}
}

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

@ -97,7 +97,7 @@ class MyShowcaseWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Showcase(
key: globalKey,
disableBarrierInteraction: true,
disableBarrierInteraction: false,
targetShapeBorder: CircleBorder(),
overlayColor: Color(0XFF0F0041),
overlayOpacity: 0.82,

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

@ -87,7 +87,7 @@ class DownloadBloc extends Bloc<DownloadEvent, DownloadState> {
(data) async {
await LocalStorage.saveData(key: MyConstants.firstDownload, value: 'true');
if(MyContext.get.mounted){
MyContext.get.goNamed(Routes.homePage);
MyContext.get.goNamed(Routes.introPage);
}
},
(error) {

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

@ -43,7 +43,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
orElse: () => TotalDataEntity(),
);
if (findData.levels?.isNotEmpty ?? false) {
context.goNamed(Routes.introPage);
context.goNamed(Routes.levelPage);
} else {
context.goNamed(Routes.downloadPage);
}

6
lib/features/level/data/model/level_model.dart

@ -1,7 +1,5 @@
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/question/data/model/hadith_model.dart';
import 'package:hadi_hoda_flutter/features/question/data/model/question_model.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/hadith_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
class LevelModel extends LevelEntity {
@ -10,7 +8,6 @@ class LevelModel extends LevelEntity {
super.order,
super.title,
super.questions,
super.hadith,
});
factory LevelModel.fromJson(Map<String, dynamic> json) {
@ -21,9 +18,6 @@ class LevelModel extends LevelEntity {
questions: json['questions']
?.map<QuestionEntity>((e) => QuestionModel.fromJson(e))
.toList(),
hadith: json['hadiths']
?.map<HadithEntity>((e) => HadithModel.fromJson(e))
.toList(),
);
}
}

4
lib/features/level/domain/entity/level_entity.dart

@ -1,4 +1,3 @@
import 'package:hadi_hoda_flutter/features/question/domain/entity/hadith_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
import 'package:hive/hive.dart';
@ -14,14 +13,11 @@ class LevelEntity extends HiveObject {
String? title;
@HiveField(3)
List<QuestionEntity>? questions;
@HiveField(4)
List<HadithEntity>? hadith;
LevelEntity({
this.id,
this.order,
this.title,
this.questions,
this.hadith,
});
}

7
lib/features/level/domain/entity/level_entity.g.dart

@ -21,14 +21,13 @@ class LevelEntityAdapter extends TypeAdapter<LevelEntity> {
order: fields[1] as int?,
title: fields[2] as String?,
questions: (fields[3] as List?)?.cast<QuestionEntity>(),
hadith: (fields[4] as List?)?.cast<HadithEntity>(),
);
}
@override
void write(BinaryWriter writer, LevelEntity obj) {
writer
..writeByte(5)
..writeByte(4)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@ -36,9 +35,7 @@ class LevelEntityAdapter extends TypeAdapter<LevelEntity> {
..writeByte(2)
..write(obj.title)
..writeByte(3)
..write(obj.questions)
..writeByte(4)
..write(obj.hadith);
..write(obj.questions);
}
@override

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

@ -6,6 +6,7 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.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/rotation_planet.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/ship_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.dart';
import 'package:hadi_hoda_flutter/core/widgets/pop_scope/my_pop_scope.dart';
@ -43,7 +44,17 @@ class LevelPage extends StatelessWidget {
),
),
_topButtons(context),
_playButton(context)
Positioned(
bottom: 0,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
_ship(context),
_playButton(context),
],
),
)
],
),
),
@ -161,7 +172,7 @@ class LevelPage extends StatelessWidget {
),
),
Positioned(
bottom: setSize(context: context, mobile: 1.7.h, tablet: 2.9.h),
bottom: setSize(context: context, mobile: 1.63.h, tablet: 2.9.h),
left: setSize(context: context, mobile: 80, tablet: 0.2.w),
child: MyImage(
image: MyAssets.planetFinal,
@ -224,18 +235,21 @@ class LevelPage extends StatelessWidget {
);
}
Widget _ship(BuildContext context) {
return ShipAnim(
child: MyImage(image: MyAssets.ship),
);
}
Widget _playButton(BuildContext context) {
return BlocBuilder<LevelBloc, LevelState>(
buildWhen: (previous, current) =>
previous.chooseLevel?.id != current.chooseLevel?.id,
builder: (context, state) {
return Positioned(
bottom: MySpaces.s20,
child: PlayButton(
level: state.chooseLevel ?? LevelEntity(),
onTap: (level) =>
context.read<LevelBloc>().goToQuestionPage(context, level),
),
return PlayButton(
level: state.chooseLevel ?? LevelEntity(),
onTap: (level) =>
context.read<LevelBloc>().goToQuestionPage(context, level),
);
}
);

20
lib/features/question/data/model/question_model.dart

@ -1,6 +1,8 @@
import 'package:hadi_hoda_flutter/features/question/data/model/answer_model.dart';
import 'package:hadi_hoda_flutter/features/question/data/model/file_model.dart';
import 'package:hadi_hoda_flutter/features/question/data/model/hadith_model.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/hadith_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
class QuestionModel extends QuestionEntity {
@ -13,6 +15,12 @@ class QuestionModel extends QuestionEntity {
super.correctAnswer,
super.isActive,
super.answers,
super.correctAnswerText,
super.correctAnswerAudioId,
super.correctAnswerAudioInfo,
super.hadiths,
super.imageId,
super.imageInfo,
});
factory QuestionModel.fromJson(Map<String, dynamic> json) {
@ -29,6 +37,18 @@ class QuestionModel extends QuestionEntity {
answers: json['answers']
?.map<AnswerEntity>((e) => AnswerModel.fromJson(e))
.toList(),
correctAnswerAudioId: json['correct_answer_audio_id'],
correctAnswerText: json['correct_answer_text'],
correctAnswerAudioInfo: json['correct_answer_audio_info'] == null
? null
: FileModel.fromJson(json['correct_answer_audio_info']),
hadiths: json['hadiths']
?.map<HadithEntity>((e) => HadithModel.fromJson(e))
.toList(),
imageId: json['image_id'],
imageInfo: json['image_info'] == null
? null
: FileModel.fromJson(json['image_info']),
);
}
}

2
lib/features/question/domain/entity/answer_entity.dart

@ -39,7 +39,7 @@ class AnswerEntity extends HiveObject {
this.audioID,
this.audioInfo,
}){
image = '${StoragePath.documentDir.path}/images/${imageInfo?.filename}';
image = '${StoragePath.documentDir.path}/answer_image/${imageInfo?.filename}';
audio = '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/answer_audio/${audioInfo?.filename}';
}
}

53
lib/features/question/domain/entity/asdasd.json

@ -0,0 +1,53 @@
{
"result": [
{
"id": 73,
"order": 1,
"title": "Stage One",
"questions": [
{
"id": 594,
"title": "Hadi and Morteza are playing soccer in the yard. Hadi kicks the ball hard, and it goes over the neighbor's wall. Hadi gets a ladder to climb up, but Morteza says, \"Wait! We should ask for permission first.\" Hadi climbs up anyway to get the ball. Suddenly, he sees the neighbor looking right at him! What should Hadi do?",
"audio_id": "31775a6d74",
"audio_info": {
"filename": "level_01_question_01_en.mp3",
"size": 352802,
"extension": ".mp3"
},
"order": 1,
"correct_answer": 3,
"correct_answer_text": "That is correct! Great job!",
"correct_answer_audio_id": "9d89be8fd6",
"correct_answer_audio_info": {
"filename": "level_01_question_01_correct_en.mp3",
"size": 31391,
"extension": ".mp3"
},
"hadiths": [],
"is_active": true,
"answers": [
{
"id": 2335,
"title": "Tell Morteza, \"Come up here! Look, the neighbor is eating chicken!\"",
"image_id": "7eb7047f81",
"image_info": {
"filename": "level_01_question_01_answer_01_image.jpg",
"size": 190516,
"extension": ".jpg"
},
"audio_id": "9a3c564d47",
"audio_info": {
"filename": "level_01_question_01_answer_01_en.mp3",
"size": 74859,
"extension": ".mp3"
},
"order": 1,
"is_active": true
}
]
}
]
}
],
"levels_count": 5
}

29
lib/features/question/domain/entity/question_entity.dart

@ -3,6 +3,7 @@ import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/utils/storage_path.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/hadith_entity.dart';
import 'package:hive/hive.dart';
part 'question_entity.g.dart';
@ -22,11 +23,27 @@ class QuestionEntity extends HiveObject {
@HiveField(5)
int? correctAnswer;
@HiveField(6)
bool? isActive;
String? correctAnswerText;
@HiveField(7)
List<AnswerEntity>? answers;
String? correctAnswerAudioId;
@HiveField(8)
FileEntity? correctAnswerAudioInfo;
@HiveField(9)
List<HadithEntity>? hadiths;
@HiveField(10)
bool? isActive;
@HiveField(11)
List<AnswerEntity>? answers;
@HiveField(12)
String? audio;
@HiveField(13)
String? correctAudio;
@HiveField(14)
String? imageId;
@HiveField(15)
FileEntity? imageInfo;
@HiveField(16)
String? image;
QuestionEntity({
this.id,
@ -37,7 +54,15 @@ class QuestionEntity extends HiveObject {
this.correctAnswer,
this.isActive,
this.answers,
this.hadiths,
this.correctAnswerAudioId,
this.correctAnswerText,
this.correctAnswerAudioInfo,
this.imageId,
this.imageInfo,
}){
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}';
}
}

39
lib/features/question/domain/entity/question_entity.g.dart

@ -23,15 +23,24 @@ class QuestionEntityAdapter extends TypeAdapter<QuestionEntity> {
audioInfo: fields[3] as FileEntity?,
order: fields[4] as int?,
correctAnswer: fields[5] as int?,
isActive: fields[6] as bool?,
answers: (fields[7] as List?)?.cast<AnswerEntity>(),
)..audio = fields[8] as String?;
isActive: fields[10] as bool?,
answers: (fields[11] as List?)?.cast<AnswerEntity>(),
hadiths: (fields[9] as List?)?.cast<HadithEntity>(),
correctAnswerAudioId: fields[7] as String?,
correctAnswerText: fields[6] as String?,
correctAnswerAudioInfo: fields[8] as FileEntity?,
imageId: fields[14] as String?,
imageInfo: fields[15] as FileEntity?,
)
..audio = fields[12] as String?
..correctAudio = fields[13] as String?
..image = fields[16] as String?;
}
@override
void write(BinaryWriter writer, QuestionEntity obj) {
writer
..writeByte(9)
..writeByte(17)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@ -45,11 +54,27 @@ class QuestionEntityAdapter extends TypeAdapter<QuestionEntity> {
..writeByte(5)
..write(obj.correctAnswer)
..writeByte(6)
..write(obj.isActive)
..write(obj.correctAnswerText)
..writeByte(7)
..write(obj.answers)
..write(obj.correctAnswerAudioId)
..writeByte(8)
..write(obj.audio);
..write(obj.correctAnswerAudioInfo)
..writeByte(9)
..write(obj.hadiths)
..writeByte(10)
..write(obj.isActive)
..writeByte(11)
..write(obj.answers)
..writeByte(12)
..write(obj.audio)
..writeByte(13)
..write(obj.correctAudio)
..writeByte(14)
..write(obj.imageId)
..writeByte(15)
..write(obj.imageInfo)
..writeByte(16)
..write(obj.image);
}
@override

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

@ -32,6 +32,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
this._effectAudioService,
) : super(QuestionState()) {
volumeStream = _mainAudioService.volumeStream();
playingStream = _mainAudioService.playingStream();
stopMusic();
registerShowCase();
on<GetLevelEvent>(_getLevelEvent);
@ -41,6 +42,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
@override
Future<void> close() {
unRegisterShowCase();
animationController.dispose();
return super.close();
}
@ -63,10 +65,12 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
'guide_key': GlobalKey(),
};
late final Stream<double> volumeStream;
late final Stream<bool> playingStream;
/// ------------Controllers------------
final AudioService _mainAudioService;
final AudioService _effectAudioService;
late final AnimationController animationController;
/// ------------Functions------------
void registerShowCase() {
@ -103,7 +107,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
void showHadith({required BuildContext context}) {
showHadithDialog(
context: context,
hadith: state.levelEntity?.hadith ?? [],
hadith: state.currentQuestion?.hadiths ?? [],
);
}
@ -127,11 +131,6 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
]);
}
Future<void> playCorrectAudio() async {
await _effectAudioService.setAudio(assetPath: MyAudios.rightAnswer);
await _effectAudioService.play();
}
Future<void> playWrongAudio() async {
await _effectAudioService.setAudio(assetPath: MyAudios.incorrectAnswer);
await _effectAudioService.play();
@ -145,7 +144,6 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
Future<void> playQuestionAudio() async {
await _mainAudioService.setAudio(filePath: state.currentQuestion?.audio);
await _mainAudioService.play();
await showQueueAnswer();
}
Future<void> changeMute() async {
@ -155,27 +153,28 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
]);
}
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 {
// 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,
required AnswerEntity answerEntity,
String? correctAudio,
bool showConfetti = false,
bool autoClose = false,
}) async {
@ -183,6 +182,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
HeroDialogRoute(
builder: (dialogContext) {
return AnswerScreen(
correctAudio: correctAudio,
answerEntity: answerEntity,
showConfetti: showConfetti,
autoClose: autoClose,
@ -217,7 +217,6 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
id: data.id,
order: data.order,
title: data.title,
hadith: data.hadith,
questions: [
...?data.questions,
QuestionEntity(order: (data.questions?.length ?? 0) + 1)
@ -227,9 +226,12 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
getQuestionStatus: BaseComplete(''),
levelEntity: level,
currentQuestion: data.questions?.first,
showAnswers: true
));
await playQuestionAudio();
startShowcase();
await playQuestionAudio();
animationController.forward().then((value) {
startShowcase();
});
},
(error) {
emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage)));
@ -244,9 +246,10 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer));
if (event.chooseCorrectAnswer) {
playCorrectAudio();
animationController.reverse();
await showAnswerDialog(
context: MyContext.get,
correctAudio: state.currentQuestion?.correctAudio,
answerEntity: state.currentQuestion?.answers?.singleWhere((e) =>
e.order == event.correctAnswer) ?? AnswerEntity(),
showConfetti: true,
@ -273,6 +276,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
}
} else {
await playQuestionAudio();
animationController.forward();
}
});
} else {

4
lib/features/question/presentation/bloc/question_state.dart

@ -7,12 +7,14 @@ class QuestionState {
final LevelEntity? levelEntity;
final QuestionEntity? currentQuestion;
final bool? correctAnswer;
final bool showAnswers;
const QuestionState({
this.getQuestionStatus = const BaseInit(),
this.levelEntity,
this.currentQuestion,
this.correctAnswer,
this.showAnswers = false,
});
QuestionState copyWith({
@ -20,12 +22,14 @@ class QuestionState {
LevelEntity? levelEntity,
QuestionEntity? currentQuestion,
bool? correctAnswer,
bool? showAnswers,
}) {
return QuestionState(
getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus,
levelEntity: levelEntity ?? this.levelEntity,
currentQuestion: currentQuestion ?? this.currentQuestion,
correctAnswer: correctAnswer,
showAnswers: showAnswers ?? this.showAnswers,
);
}
}

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

@ -21,9 +21,11 @@ class AnswerScreen extends StatefulWidget {
required this.answerEntity,
required this.showConfetti,
required this.autoClose,
this.correctAudio,
});
final AnswerEntity answerEntity;
final String? correctAudio;
final bool showConfetti;
final bool autoClose;
@ -45,11 +47,10 @@ class _AnswerScreenState extends State<AnswerScreen> {
Future<void> initWidget() async {
if (widget.showConfetti) {
await Future.delayed(Duration(seconds: 3), () {
if (MyContext.get.mounted && MyContext.get.canPop()) {
MyContext.get.pop();
}
});
await playCorrectAudio();
if (MyContext.get.mounted && MyContext.get.canPop()) {
MyContext.get.pop();
}
} else {
await playAudio();
if (widget.autoClose) {
@ -60,6 +61,11 @@ class _AnswerScreenState extends State<AnswerScreen> {
}
}
Future<void> playCorrectAudio() async {
await audioService.setAudio(filePath: widget.correctAudio);
await audioService.play();
}
Future<void> playAudio() async {
await audioService.setAudio(filePath: widget.answerEntity.audio);
await audioService.play();
@ -85,7 +91,7 @@ class _AnswerScreenState extends State<AnswerScreen> {
),
),
Positioned(
bottom: setPlatform<double>(android: MySpaces.s30),
bottom: setPlatform<double>(android: MySpaces.s30, iOS: MySpaces.s12),
child: TextButton(
onPressed: () {
context.pop();

87
lib/features/question/presentation/ui/screens/question_screen.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';
@ -9,6 +10,7 @@ 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/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';
@ -21,9 +23,25 @@ import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_s
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/glassy_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart';
class QuestionScreen extends StatelessWidget {
class QuestionScreen extends StatefulWidget {
const QuestionScreen({super.key});
@override
State<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
context.read<QuestionBloc>().animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
);
}
@override
Widget build(BuildContext context) {
return Column(
@ -55,39 +73,39 @@ class QuestionScreen extends StatelessWidget {
}
Widget _titles(BuildContext context) {
return Expanded(
flex: setSize(context: context, mobile: 15, tablet: 5) ?? 1,
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) =>
FadeAnim(
child: Text(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
maxLines: 3,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
offset: Offset(0, 2),
color: MyColors.black.withValues(alpha: 0.25),
),
],
),
return BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) =>
FadeAnim(
child: AutoSizeText(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
maxLines: 4,
maxFontSize: 20,
minFontSize: 16,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
offset: Offset(0, 2),
color: MyColors.black.withValues(alpha: 0.25),
),
],
),
),
),
),
);
}
Expanded _answers(BuildContext context) {
return Expanded(
flex: setSize(context: context, mobile: 85, tablet: 95) ?? 1,
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,
),
@ -101,6 +119,7 @@ class QuestionScreen extends StatelessWidget {
? 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']!,
@ -141,23 +160,17 @@ class QuestionScreen extends StatelessWidget {
globalKey: context.read<QuestionBloc>().showCaseKey['guide_key']!,
description: context.translate.showcase_guide,
type: ShowcaseTooltipType.top,
child: Container(
padding: EdgeInsets.all(MySpaces.s4),
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
Color(0XFFDFCD00),
Color(0XFFDFCD00).withValues(alpha: 0.35),
Color(0XFFDFCD00).withValues(alpha: 0),
],
center: Alignment.center,
child: StreamBuilder<bool>(
initialData: false,
stream: context.read<QuestionBloc>().playingStream,
builder: (context, snapshot) => GlobeAnimation(
state: snapshot.data ?? false,
child: MyImage(
image: MyAssets.globe,
fit: BoxFit.cover,
size: setSize(context: context, tablet: 120),
),
),
child: MyImage(
image: MyAssets.globe,
fit: BoxFit.cover,
size: setSize(context: context, tablet: 120),
),
),
),
PositionedDirectional(

9
lib/features/sample/presentation/ui/sample_page.dart

@ -1,10 +1,17 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class SamplePage extends StatelessWidget {
const SamplePage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold();
return Scaffold(
body: Center(
child: Lottie.asset(
'assets/animations/Celebration.json',
),
),
);
}
}

8
pubspec.lock

@ -49,6 +49,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.2"
auto_size_text:
dependency: "direct main"
description:
name: auto_size_text
sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
bloc:
dependency: "direct main"
description:

3
pubspec.yaml

@ -1,12 +1,13 @@
name: hadi_hoda_flutter
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.4+1
version: 0.1.5+1
environment:
sdk: ^3.9.2
dependencies:
auto_size_text: ^3.0.0
bloc: ^9.0.0
confetti: ^0.8.0
dio: ^5.9.0

Loading…
Cancel
Save