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 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 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 String defaultLanguage = 'en';
static const List<LanguageEntity> languages = [ static const List<LanguageEntity> languages = [
LanguageEntity(title: 'English (English)', code: 'en', locale: Locale('en','US')), 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')), 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( final String? firstIntro = LocalStorage.readData(
key: MyConstants.firstIntro); key: MyConstants.firstIntro);
if (firstIntro == 'true') { if (firstIntro == 'true') {
return Routes.levelPage;
return Routes.homePage;
} else { } else {
return null; return null;
} }

2
lib/core/routers/hero_dialog_route.dart

@ -16,7 +16,7 @@ class HeroDialogRoute<T> extends PageRoute<T> {
bool get fullscreenDialog => false; bool get fullscreenDialog => false;
@override @override
bool get barrierDismissible => false;
bool get barrierDismissible => true;
@override @override
Duration get transitionDuration => const Duration(seconds: 1); // Adjust as needed 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:flutter/foundation.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
class AudioService { class AudioService {
final AudioPlayer _player = AudioPlayer(); final AudioPlayer _player = AudioPlayer();
final StreamController<bool> _streamController = StreamController.broadcast();
final double? volume; final double? volume;
AudioService({this.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 { 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, super.key,
required this.child, required this.child,
required this.index, required this.index,
required this.controller,
}); });
final Widget child; final Widget child;
final int index; final int index;
final AnimationController controller;
@override @override
State<SlideAnim> createState() => _SlideAnimState(); State<SlideAnim> createState() => _SlideAnimState();
@ -16,7 +18,6 @@ class SlideAnim extends StatefulWidget {
class _SlideAnimState extends State<SlideAnim> class _SlideAnimState extends State<SlideAnim>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _animation; late Animation<Offset> _animation;
final List<Offset> offsetList = [ final List<Offset> offsetList = [
Offset(-2, -2), Offset(-2, -2),
@ -28,34 +29,19 @@ class _SlideAnimState extends State<SlideAnim>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
);
_animation = Tween<Offset>( _animation = Tween<Offset>(
begin: offsetList[widget.index], begin: offsetList[widget.index],
end: Offset.zero, 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedBuilder( return AnimatedBuilder(
animation: _controller,
animation: widget.controller,
child: widget.child, 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) { Widget build(BuildContext context) {
return Showcase( return Showcase(
key: globalKey, key: globalKey,
disableBarrierInteraction: true,
disableBarrierInteraction: false,
targetShapeBorder: CircleBorder(), targetShapeBorder: CircleBorder(),
overlayColor: Color(0XFF0F0041), overlayColor: Color(0XFF0F0041),
overlayOpacity: 0.82, overlayOpacity: 0.82,

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

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

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

@ -43,7 +43,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
orElse: () => TotalDataEntity(), orElse: () => TotalDataEntity(),
); );
if (findData.levels?.isNotEmpty ?? false) { if (findData.levels?.isNotEmpty ?? false) {
context.goNamed(Routes.introPage);
context.goNamed(Routes.levelPage);
} else { } else {
context.goNamed(Routes.downloadPage); 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/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/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'; import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
class LevelModel extends LevelEntity { class LevelModel extends LevelEntity {
@ -10,7 +8,6 @@ class LevelModel extends LevelEntity {
super.order, super.order,
super.title, super.title,
super.questions, super.questions,
super.hadith,
}); });
factory LevelModel.fromJson(Map<String, dynamic> json) { factory LevelModel.fromJson(Map<String, dynamic> json) {
@ -21,9 +18,6 @@ class LevelModel extends LevelEntity {
questions: json['questions'] questions: json['questions']
?.map<QuestionEntity>((e) => QuestionModel.fromJson(e)) ?.map<QuestionEntity>((e) => QuestionModel.fromJson(e))
.toList(), .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:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -14,14 +13,11 @@ class LevelEntity extends HiveObject {
String? title; String? title;
@HiveField(3) @HiveField(3)
List<QuestionEntity>? questions; List<QuestionEntity>? questions;
@HiveField(4)
List<HadithEntity>? hadith;
LevelEntity({ LevelEntity({
this.id, this.id,
this.order, this.order,
this.title, this.title,
this.questions, 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?, order: fields[1] as int?,
title: fields[2] as String?, title: fields[2] as String?,
questions: (fields[3] as List?)?.cast<QuestionEntity>(), questions: (fields[3] as List?)?.cast<QuestionEntity>(),
hadith: (fields[4] as List?)?.cast<HadithEntity>(),
); );
} }
@override @override
void write(BinaryWriter writer, LevelEntity obj) { void write(BinaryWriter writer, LevelEntity obj) {
writer writer
..writeByte(5)
..writeByte(4)
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
@ -36,9 +35,7 @@ class LevelEntityAdapter extends TypeAdapter<LevelEntity> {
..writeByte(2) ..writeByte(2)
..write(obj.title) ..write(obj.title)
..writeByte(3) ..writeByte(3)
..write(obj.questions)
..writeByte(4)
..write(obj.hadith);
..write(obj.questions);
} }
@override @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/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_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/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/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.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'; import 'package:hadi_hoda_flutter/core/widgets/pop_scope/my_pop_scope.dart';
@ -43,7 +44,17 @@ class LevelPage extends StatelessWidget {
), ),
), ),
_topButtons(context), _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( 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), left: setSize(context: context, mobile: 80, tablet: 0.2.w),
child: MyImage( child: MyImage(
image: MyAssets.planetFinal, 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) { Widget _playButton(BuildContext context) {
return BlocBuilder<LevelBloc, LevelState>( return BlocBuilder<LevelBloc, LevelState>(
buildWhen: (previous, current) => buildWhen: (previous, current) =>
previous.chooseLevel?.id != current.chooseLevel?.id, previous.chooseLevel?.id != current.chooseLevel?.id,
builder: (context, state) { 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/answer_model.dart';
import 'package:hadi_hoda_flutter/features/question/data/model/file_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/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'; import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
class QuestionModel extends QuestionEntity { class QuestionModel extends QuestionEntity {
@ -13,6 +15,12 @@ class QuestionModel extends QuestionEntity {
super.correctAnswer, super.correctAnswer,
super.isActive, super.isActive,
super.answers, super.answers,
super.correctAnswerText,
super.correctAnswerAudioId,
super.correctAnswerAudioInfo,
super.hadiths,
super.imageId,
super.imageInfo,
}); });
factory QuestionModel.fromJson(Map<String, dynamic> json) { factory QuestionModel.fromJson(Map<String, dynamic> json) {
@ -29,6 +37,18 @@ class QuestionModel extends QuestionEntity {
answers: json['answers'] answers: json['answers']
?.map<AnswerEntity>((e) => AnswerModel.fromJson(e)) ?.map<AnswerEntity>((e) => AnswerModel.fromJson(e))
.toList(), .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.audioID,
this.audioInfo, 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}'; 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/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/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/file_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'; import 'package:hive/hive.dart';
part 'question_entity.g.dart'; part 'question_entity.g.dart';
@ -22,11 +23,27 @@ class QuestionEntity extends HiveObject {
@HiveField(5) @HiveField(5)
int? correctAnswer; int? correctAnswer;
@HiveField(6) @HiveField(6)
bool? isActive;
String? correctAnswerText;
@HiveField(7) @HiveField(7)
List<AnswerEntity>? answers;
String? correctAnswerAudioId;
@HiveField(8) @HiveField(8)
FileEntity? correctAnswerAudioInfo;
@HiveField(9)
List<HadithEntity>? hadiths;
@HiveField(10)
bool? isActive;
@HiveField(11)
List<AnswerEntity>? answers;
@HiveField(12)
String? audio; String? audio;
@HiveField(13)
String? correctAudio;
@HiveField(14)
String? imageId;
@HiveField(15)
FileEntity? imageInfo;
@HiveField(16)
String? image;
QuestionEntity({ QuestionEntity({
this.id, this.id,
@ -37,7 +54,15 @@ class QuestionEntity extends HiveObject {
this.correctAnswer, this.correctAnswer,
this.isActive, this.isActive,
this.answers, 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}'; 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?, audioInfo: fields[3] as FileEntity?,
order: fields[4] as int?, order: fields[4] as int?,
correctAnswer: fields[5] 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 @override
void write(BinaryWriter writer, QuestionEntity obj) { void write(BinaryWriter writer, QuestionEntity obj) {
writer writer
..writeByte(9)
..writeByte(17)
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
@ -45,11 +54,27 @@ class QuestionEntityAdapter extends TypeAdapter<QuestionEntity> {
..writeByte(5) ..writeByte(5)
..write(obj.correctAnswer) ..write(obj.correctAnswer)
..writeByte(6) ..writeByte(6)
..write(obj.isActive)
..write(obj.correctAnswerText)
..writeByte(7) ..writeByte(7)
..write(obj.answers)
..write(obj.correctAnswerAudioId)
..writeByte(8) ..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 @override

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

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

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

@ -7,12 +7,14 @@ class QuestionState {
final LevelEntity? levelEntity; final LevelEntity? levelEntity;
final QuestionEntity? currentQuestion; final QuestionEntity? currentQuestion;
final bool? correctAnswer; final bool? correctAnswer;
final bool showAnswers;
const QuestionState({ const QuestionState({
this.getQuestionStatus = const BaseInit(), this.getQuestionStatus = const BaseInit(),
this.levelEntity, this.levelEntity,
this.currentQuestion, this.currentQuestion,
this.correctAnswer, this.correctAnswer,
this.showAnswers = false,
}); });
QuestionState copyWith({ QuestionState copyWith({
@ -20,12 +22,14 @@ class QuestionState {
LevelEntity? levelEntity, LevelEntity? levelEntity,
QuestionEntity? currentQuestion, QuestionEntity? currentQuestion,
bool? correctAnswer, bool? correctAnswer,
bool? showAnswers,
}) { }) {
return QuestionState( return QuestionState(
getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus, getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus,
levelEntity: levelEntity ?? this.levelEntity, levelEntity: levelEntity ?? this.levelEntity,
currentQuestion: currentQuestion ?? this.currentQuestion, currentQuestion: currentQuestion ?? this.currentQuestion,
correctAnswer: correctAnswer, 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.answerEntity,
required this.showConfetti, required this.showConfetti,
required this.autoClose, required this.autoClose,
this.correctAudio,
}); });
final AnswerEntity answerEntity; final AnswerEntity answerEntity;
final String? correctAudio;
final bool showConfetti; final bool showConfetti;
final bool autoClose; final bool autoClose;
@ -45,11 +47,10 @@ class _AnswerScreenState extends State<AnswerScreen> {
Future<void> initWidget() async { Future<void> initWidget() async {
if (widget.showConfetti) { 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 { } else {
await playAudio(); await playAudio();
if (widget.autoClose) { 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 { Future<void> playAudio() async {
await audioService.setAudio(filePath: widget.answerEntity.audio); await audioService.setAudio(filePath: widget.answerEntity.audio);
await audioService.play(); await audioService.play();
@ -85,7 +91,7 @@ class _AnswerScreenState extends State<AnswerScreen> {
), ),
), ),
Positioned( Positioned(
bottom: setPlatform<double>(android: MySpaces.s30),
bottom: setPlatform<double>(android: MySpaces.s30, iOS: MySpaces.s12),
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
context.pop(); 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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_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/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_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.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_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart'; import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box.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/glassy_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart';
class QuestionScreen extends StatelessWidget {
class QuestionScreen extends StatefulWidget {
const QuestionScreen({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
@ -55,39 +73,39 @@ class QuestionScreen extends StatelessWidget {
} }
Widget _titles(BuildContext context) { 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) { Expanded _answers(BuildContext context) {
return Expanded( return Expanded(
flex: setSize(context: context, mobile: 85, tablet: 95) ?? 1,
child: BlocBuilder<QuestionBloc, QuestionState>( child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) => buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id, previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => GridView.builder( builder: (context, state) => GridView.builder(
itemCount: state.currentQuestion?.answers?.length ?? 0, itemCount: state.currentQuestion?.answers?.length ?? 0,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: setSize(context: context, tablet: 70) ?? 0, horizontal: setSize(context: context, tablet: 70) ?? 0,
), ),
@ -101,6 +119,7 @@ class QuestionScreen extends StatelessWidget {
? SizedBox.shrink() ? SizedBox.shrink()
: SlideAnim( : SlideAnim(
key: Key('${state.currentQuestion?.id}'), key: Key('${state.currentQuestion?.id}'),
controller: context.read<QuestionBloc>().animationController,
index: index, index: index,
child: MyShowcaseWidget( child: MyShowcaseWidget(
globalKey: context.read<QuestionBloc>().showCaseKey['answer_key_$index']!, globalKey: context.read<QuestionBloc>().showCaseKey['answer_key_$index']!,
@ -141,23 +160,17 @@ class QuestionScreen extends StatelessWidget {
globalKey: context.read<QuestionBloc>().showCaseKey['guide_key']!, globalKey: context.read<QuestionBloc>().showCaseKey['guide_key']!,
description: context.translate.showcase_guide, description: context.translate.showcase_guide,
type: ShowcaseTooltipType.top, 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( PositionedDirectional(

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

@ -1,10 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class SamplePage extends StatelessWidget { class SamplePage extends StatelessWidget {
const SamplePage({super.key}); const SamplePage({super.key});
@override @override
Widget build(BuildContext context) { 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" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.2" 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: bloc:
dependency: "direct main" dependency: "direct main"
description: description:

3
pubspec.yaml

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

Loading…
Cancel
Save