feature/question #15

Merged
amirreza.chegini merged 2 commits from feature/question into develop 2 days ago
  1. BIN
      assets/images/leaf.png
  2. 1
      lib/common_ui/resources/my_assets.dart
  3. 5
      lib/common_ui/resources/my_text_style.dart
  4. 2
      lib/core/routers/my_routes.dart
  5. 46
      lib/core/widgets/answer_box/answer_box.dart
  6. 97
      lib/core/widgets/answer_box/styles/picture_box.dart
  7. 80
      lib/core/widgets/hadith_dialog/hadith_dialog.dart
  8. 4
      lib/features/level/data/datasource/level_datasource.dart
  9. 30
      lib/features/level/presentation/bloc/level_bloc.dart
  10. 1
      lib/features/level/presentation/bloc/level_event.dart
  11. 28
      lib/features/level/presentation/ui/level_page.dart
  12. 9
      lib/features/level/presentation/ui/widgets/hint_level_widget.dart
  13. 4
      lib/features/level/presentation/ui/widgets/level_widget.dart
  14. 20
      lib/features/question/data/datasource/question_datasource.dart
  15. 11
      lib/features/question/domain/entity/answer_entity.dart
  16. 7
      lib/features/question/domain/entity/answer_entity.g.dart
  17. 25
      lib/features/question/presentation/bloc/question_bloc.dart
  18. 8
      lib/features/question/presentation/bloc/question_event.dart
  19. 14
      lib/features/question/presentation/bloc/question_state.dart
  20. 34
      lib/features/question/presentation/ui/question_page.dart
  21. 22
      lib/features/question/presentation/ui/widgets/black_white_effect.dart
  22. 14
      lib/features/question/presentation/ui/widgets/question_stepper.dart
  23. 2
      lib/init_bindings.dart
  24. 4
      lib/l10n/app_en.arb
  25. 12
      lib/l10n/app_localizations.dart
  26. 6
      lib/l10n/app_localizations_en.dart

BIN
assets/images/leaf.png

After

Width: 23  |  Height: 24  |  Size: 436 B

1
lib/common_ui/resources/my_assets.dart

@ -39,4 +39,5 @@ class MyAssets {
static const String doneRounded = 'assets/images/done_rounded.svg'; static const String doneRounded = 'assets/images/done_rounded.svg';
static const String lang = 'assets/images/lang.svg'; static const String lang = 'assets/images/lang.svg';
static const String error = 'assets/images/error.png'; static const String error = 'assets/images/error.png';
static const String leaf = 'assets/images/leaf.png';
} }

5
lib/common_ui/resources/my_text_style.dart

@ -43,6 +43,11 @@ class Marhey {
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
); );
static const TextStyle medium14 = TextStyle(
fontFamily: fontFamily,
fontSize: 14,
fontWeight: FontWeight.w500,
);
static const TextStyle medium16 = TextStyle( static const TextStyle medium16 = TextStyle(
fontFamily: fontFamily, fontFamily: fontFamily,
fontSize: 16, fontSize: 16,

2
lib/core/routers/my_routes.dart

@ -60,7 +60,7 @@ GoRouter get appPages => GoRouter(
name: Routes.levelPage, name: Routes.levelPage,
path: Routes.levelPage, path: Routes.levelPage,
builder: (context, state) => BlocProvider( builder: (context, state) => BlocProvider(
create: (context) => LevelBloc(locator())..add(GetLevelListEvent()),
create: (context) => LevelBloc(locator())..add(SetCurrentLevelEvent()),
child: const LevelPage(), child: const LevelPage(),
), ),
), ),

46
lib/core/widgets/answer_box/answer_box.dart

@ -1,38 +1,60 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/core/utils/storage_path.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.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';
class AnswerBox extends StatelessWidget {
const AnswerBox({super.key, required this.answer,this.selected, this.onTap, required this.index});
class AnswerBox extends StatefulWidget {
const AnswerBox({
super.key,
required this.answer,
required this.correctAnswer,
required this.index,
this.onTap,
});
final AnswerEntity answer; final AnswerEntity answer;
final bool? selected;
final Function(AnswerEntity answer)? onTap;
final int correctAnswer;
final VoidCallback? onTap;
final int index; final int index;
@override
State<AnswerBox> createState() => _AnswerBoxState();
}
class _AnswerBoxState extends State<AnswerBox> {
bool selected = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () => onTap?.call(answer),
onTap: !selected ? () {
setState(() {
selected = true;
});
if (selected && (widget.index == widget.correctAnswer)) {
widget.onTap?.call();
}
} : null,
child: SizedBox( child: SizedBox(
child: Stack( child: Stack(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
AnswerPictureBox( AnswerPictureBox(
selected: selected ?? false,
index: index,
image: '${StoragePath.documentDir.path}/data/${answer
.imageId}${answer.imageInfo?.extension ?? '.png'}',
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
), ),
Positioned( Positioned(
left: 0, left: 0,
right: 0, right: 0,
bottom: -36,
bottom: -MySpaces.s26,
child: AnswerTextBox( child: AnswerTextBox(
text: answer.title ?? '',
text: widget.answer.title ?? '',
), ),
), ),
], ],

97
lib/core/widgets/answer_box/styles/picture_box.dart

@ -1,31 +1,92 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.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/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/my_image.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/black_white_effect.dart';
class AnswerPictureBox extends StatelessWidget {
const AnswerPictureBox({super.key, required this.selected, required this.image, required this.index});
class AnswerPictureBox extends StatefulWidget {
const AnswerPictureBox({
super.key,
required this.selected,
required this.image,
required this.index,
required this.correctAnswer,
});
final bool selected; final bool selected;
final String image; final String image;
final int index; final int index;
final int correctAnswer;
@override
State<AnswerPictureBox> createState() => _AnswerPictureBoxState();
}
class _AnswerPictureBoxState extends State<AnswerPictureBox> {
late Future<Uint8List> _imageFuture;
@override
void initState() {
super.initState();
_imageFuture = File(widget.image).readAsBytes();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CustomPaint( return CustomPaint(
size: Size(170, 170), size: Size(170, 170),
foregroundPainter: _SvgCustomPainter(selected),
foregroundPainter: _SvgCustomPainter(false),
child: ClipPath( child: ClipPath(
clipper: _SvgCustomClipper(), clipper: _SvgCustomClipper(),
child: Stack( child: Stack(
children: [ children: [
Image.file(
File(image),
fit: BoxFit.cover,
height: 170,
width: 170,
FutureBuilder<Uint8List>(
future: _imageFuture,
builder: (context, snapshot) {
return AnimatedCrossFade(
crossFadeState: snapshot.hasData
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: Duration(milliseconds: 300),
firstChild: SizedBox(
height: 170,
width: 170,
child: Center(
child: CupertinoActivityIndicator(
color: MyColors.white,
),
),
),
secondChild: Builder(
builder: (context) {
if (widget.selected &&
(widget.index != widget.correctAnswer)) {
return BlackWhiteEffect(
child: Image.memory(
snapshot.data ?? Uint8List(0),
fit: BoxFit.cover,
height: 170,
width: 170,
),
);
} else {
return Image.memory(
snapshot.data ?? Uint8List(0),
fit: BoxFit.cover,
height: 170,
width: 170,
);
}
},
)
);
}
), ),
PositionedDirectional( PositionedDirectional(
top: MySpaces.s12, top: MySpaces.s12,
@ -47,7 +108,7 @@ class AnswerPictureBox extends StatelessWidget {
), ),
), ),
child: Text( child: Text(
'$index',
'${widget.index}',
style: Marhey.semiBold17.copyWith( style: Marhey.semiBold17.copyWith(
color: MyColors.white, color: MyColors.white,
), ),
@ -55,14 +116,16 @@ class AnswerPictureBox extends StatelessWidget {
), ),
), ),
), ),
// PositionedDirectional(
// top: MySpaces.s14,
// end: MySpaces.s12,
// child: MyImage(
// image: MyAssets.correct,
// size: MySpaces.s40,
// ),
// ),
if(widget.selected)
PositionedDirectional(
top: MySpaces.s14,
end: MySpaces.s12,
child: MyImage(
image: widget.index == widget.correctAnswer ? MyAssets.correct : MyAssets
.wrong,
size: MySpaces.s40,
),
),
], ],
), ),
), ),

80
lib/core/widgets/hadith_dialog/hadith_dialog.dart

@ -0,0 +1,80 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/check_platform.dart';
import 'package:hadi_hoda_flutter/core/utils/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/about_us_dialog/styles/background.dart';
Future<void> showHadithDialog({required BuildContext context}) async {
await showDialog(
context: context,
builder: (context) => HadithDialog(),
barrierColor: MyColors.purple.withValues(alpha: 0.82),
useSafeArea: false,
);
}
class HadithDialog extends StatelessWidget {
const HadithDialog({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyColors.transparent,
body: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: checkSize(
context: context,
mobile: 18,
tablet: 120,
) ?? 0,
),
child: Stack(
clipBehavior: Clip.none,
children: [
AboutUSDialogBackground(
child: ListView.separated(
itemCount: 3,
separatorBuilder: (context, index) => Divider(
height: 40,
thickness: 1,
endIndent: MySpaces.s20,
indent: MySpaces.s20,
color: Color(0xFFC2BDE4),
),
itemBuilder: (context, index) => Text(
'Prophet Muhammad (PBUH) said: "Teeth, which are smooth and beautiful, become dirty as a result of chewing food, and gradually the smell of the mouth changes and causes corruption in the nasal organs. When a person brushes her teeth, the corruption disappears and the teeth become clean and pure again."',
style: Marhey.medium14.copyWith(
color: Color(0XFF494178),
),
),
),
),
Positioned(
right: checkSize(context: context, mobile: 30, tablet: 40),
top: -12,
child: GestureDetector(
onTap: context.pop,
behavior: HitTestBehavior.opaque,
child: MyImage(
image: MyAssets.closeBtn,
size: checkSize(context: context, mobile: 40, tablet: 60),
),
),
),
],
),
),
),
),
);
}
}

4
lib/features/level/data/datasource/level_datasource.dart

@ -20,8 +20,8 @@ class LocalLevelDatasourceImpl implements ILevelDatasource {
final String selectedLanguage = LocalStorage.readData( final String selectedLanguage = LocalStorage.readData(
key: MyConstants.selectLanguage); key: MyConstants.selectLanguage);
final Box<TotalDataEntity> levelBox = Hive.box(MyConstants.levelBox); final Box<TotalDataEntity> levelBox = Hive.box(MyConstants.levelBox);
final TotalDataEntity findData = levelBox.values.singleWhere((e) =>
e.code == selectedLanguage,
final TotalDataEntity findData = levelBox.values.singleWhere(
(e) => e.code == selectedLanguage,
orElse: () => TotalDataEntity(), orElse: () => TotalDataEntity(),
); );
return findData.levels ?? []; return findData.levels ?? [];

30
lib/features/level/presentation/bloc/level_bloc.dart

@ -20,6 +20,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
this._getLeveslUseCase, this._getLeveslUseCase,
) : super(const LevelState()) { ) : super(const LevelState()) {
on<GetLevelListEvent>(_getLevelListEvent); on<GetLevelListEvent>(_getLevelListEvent);
on<SetCurrentLevelEvent>(_setCurrentLevelEvent);
on<StartScrollEvent>(_startScrollEvent); on<StartScrollEvent>(_startScrollEvent);
} }
@ -81,10 +82,9 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
} }
LevelType getLevelType(int index) { LevelType getLevelType(int index) {
final int currentLevel = int.parse(LocalStorage
.readData(key: MyConstants.currentLevel)
.isEmpty ? '1' : LocalStorage
.readData(key: MyConstants.currentLevel));
final int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel),
);
if (index < currentLevel) { if (index < currentLevel) {
return LevelType.finished; return LevelType.finished;
@ -98,6 +98,9 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
/// ------------Api Calls------------ /// ------------Api Calls------------
FutureOr<void> _getLevelListEvent(GetLevelListEvent event, FutureOr<void> _getLevelListEvent(GetLevelListEvent event,
Emitter<LevelState> emit) async { Emitter<LevelState> emit) async {
final int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel),
);
await _getLeveslUseCase(LevelParams()).then((value) { await _getLeveslUseCase(LevelParams()).then((value) {
value.fold( value.fold(
(data) async { (data) async {
@ -107,7 +110,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
} }
emit(state.copyWith( emit(state.copyWith(
getLevelStatus: const BaseComplete(''), getLevelStatus: const BaseComplete(''),
chooseLevel: data.first,
chooseLevel: data.singleWhere((e) => e.order == currentLevel),
)); ));
add(StartScrollEvent()); add(StartScrollEvent());
}, },
@ -120,10 +123,9 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
StartScrollEvent event, StartScrollEvent event,
Emitter<LevelState> emit, Emitter<LevelState> emit,
) async { ) async {
final int currentLevel = int.parse(LocalStorage
.readData(key: MyConstants.currentLevel)
.isEmpty ? '1' : LocalStorage
.readData(key: MyConstants.currentLevel));
final int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel),
);
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
if (scrollController.hasClients) { if (scrollController.hasClients) {
@ -142,4 +144,14 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
} }
} }
} }
FutureOr<void> _setCurrentLevelEvent(SetCurrentLevelEvent event,
Emitter<LevelState> emit) async {
final String currentLevel = LocalStorage.readData(
key: MyConstants.currentLevel);
if (currentLevel.isEmpty) {
await LocalStorage.saveData(key: MyConstants.currentLevel, value: '1');
}
add(GetLevelListEvent());
}
} }

1
lib/features/level/presentation/bloc/level_event.dart

@ -6,6 +6,7 @@ sealed class LevelEvent {
class GetLevelListEvent extends LevelEvent {} class GetLevelListEvent extends LevelEvent {}
class StartScrollEvent extends LevelEvent {} class StartScrollEvent extends LevelEvent {}
class SetCurrentLevelEvent extends LevelEvent {}
class ChooseLevelEvent extends LevelEvent { class ChooseLevelEvent extends LevelEvent {
final LevelEntity level; final LevelEntity level;
const ChooseLevelEvent(this.level); const ChooseLevelEvent(this.level);

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

@ -43,22 +43,16 @@ class LevelPage extends StatelessWidget {
buildWhen: (previous, current) => buildWhen: (previous, current) =>
previous.chooseLevel?.id != current.chooseLevel?.id, previous.chooseLevel?.id != current.chooseLevel?.id,
builder: (context, state) { builder: (context, state) {
if (state.chooseLevel != null) {
return Positioned(
bottom: MediaQuery
.viewPaddingOf(context)
.bottom + MySpaces.s10,
right: MySpaces.s16,
left: MySpaces.s16,
child: HintLevelWidget(
level: state.chooseLevel ?? LevelEntity(),
onTap: (level) =>
context.read<LevelBloc>().goToQuestionPage(context, level),
),
);
} else {
return SizedBox.shrink();
}
return Positioned(
bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s10,
right: MySpaces.s16,
left: MySpaces.s16,
child: HintLevelWidget(
level: state.chooseLevel ?? LevelEntity(),
onTap: (level) =>
context.read<LevelBloc>().goToQuestionPage(context, level),
),
);
} }
); );
} }
@ -108,7 +102,6 @@ class LevelPage extends StatelessWidget {
right: context.read<LevelBloc>().topLocationList[index].right, right: context.read<LevelBloc>().topLocationList[index].right,
left: context.read<LevelBloc>().topLocationList[index].left, left: context.read<LevelBloc>().topLocationList[index].left,
child: LevelWidget( child: LevelWidget(
index: context.read<LevelBloc>().topLocationList[index].index ?? 0,
level: context.read<LevelBloc>().top12LevelList[index], level: context.read<LevelBloc>().top12LevelList[index],
type: context.read<LevelBloc>().getLevelType(index + 9), type: context.read<LevelBloc>().getLevelType(index + 9),
onTap: (LevelEntity level) {}, onTap: (LevelEntity level) {},
@ -141,7 +134,6 @@ class LevelPage extends StatelessWidget {
right: context.read<LevelBloc>().bottomLocationList[index].right, right: context.read<LevelBloc>().bottomLocationList[index].right,
left: context.read<LevelBloc>().bottomLocationList[index].left, left: context.read<LevelBloc>().bottomLocationList[index].left,
child: LevelWidget( child: LevelWidget(
index: context.read<LevelBloc>().bottomLocationList[index].index ?? 0,
level: context.read<LevelBloc>().bottom8LevelList[index], level: context.read<LevelBloc>().bottom8LevelList[index],
type: context.read<LevelBloc>().getLevelType(index + 1), type: context.read<LevelBloc>().getLevelType(index + 1),
onTap: (LevelEntity level) {}, onTap: (LevelEntity level) {},

9
lib/features/level/presentation/ui/widgets/hint_level_widget.dart

@ -3,12 +3,17 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.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/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.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/screen_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart'; import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
class HintLevelWidget extends StatelessWidget { class HintLevelWidget extends StatelessWidget {
const HintLevelWidget({super.key, required this.level, this.onTap,});
const HintLevelWidget({
super.key,
required this.level,
this.onTap,
});
final LevelEntity level; final LevelEntity level;
final Function(LevelEntity level)? onTap; final Function(LevelEntity level)? onTap;
@ -36,7 +41,7 @@ class HintLevelWidget extends StatelessWidget {
spacing: MySpaces.s8, spacing: MySpaces.s8,
children: [ children: [
Text( Text(
'Step ${level.order ?? 0}',
'${context.translate.step} ${level.order ?? 0}',
style: Marhey.bold14.copyWith( style: Marhey.bold14.copyWith(
color: Color(0xFFD8490B), color: Color(0xFFD8490B),
), ),

4
lib/features/level/presentation/ui/widgets/level_widget.dart

@ -20,13 +20,11 @@ enum LevelType {
class LevelWidget extends StatelessWidget { class LevelWidget extends StatelessWidget {
const LevelWidget({ const LevelWidget({
super.key, super.key,
required this.index,
required this.level, required this.level,
this.type, this.type,
this.onTap, this.onTap,
}); });
final int index;
final LevelType? type; final LevelType? type;
final LevelEntity level; final LevelEntity level;
final Function(LevelEntity level)? onTap; final Function(LevelEntity level)? onTap;
@ -41,7 +39,7 @@ class LevelWidget extends StatelessWidget {
children: [ children: [
MyImage(image: LevelType.image[type] ?? MyAssets.level, size: 46), MyImage(image: LevelType.image[type] ?? MyAssets.level, size: 46),
Text( Text(
'$index',
'${level.order}',
style: DinoKids.regular26.copyWith( style: DinoKids.regular26.copyWith(
color: MyColors.white, color: MyColors.white,
shadows: [ shadows: [

20
lib/features/question/data/datasource/question_datasource.dart

@ -1,28 +1,34 @@
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/network/http_request.dart';
import 'package:hadi_hoda_flutter/core/params/question_params.dart'; import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
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/level/domain/entity/total_data_entity.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
abstract class IQuestionDatasource { abstract class IQuestionDatasource {
Future<LevelEntity> getLevel({required QuestionParams params}); Future<LevelEntity> getLevel({required QuestionParams params});
} }
/// Local
class QuestionDatasourceImpl implements IQuestionDatasource { class QuestionDatasourceImpl implements IQuestionDatasource {
final IHttpRequest httpRequest;
const QuestionDatasourceImpl(this.httpRequest);
const QuestionDatasourceImpl();
@override @override
Future<LevelEntity> getLevel({required QuestionParams params}) async { Future<LevelEntity> getLevel({required QuestionParams params}) async {
try { try {
final Box<LevelEntity> levelBox = Hive.box(MyConstants.levelBox);
final LevelEntity findLevel = levelBox.values.singleWhere(
final String selectedLanguage = LocalStorage.readData(
key: MyConstants.selectLanguage);
final Box<TotalDataEntity> levelBox = Hive.box(MyConstants.levelBox);
final TotalDataEntity findData = levelBox.values.singleWhere(
(e) => e.code == selectedLanguage,
orElse: () => TotalDataEntity(),
);
final LevelEntity? findLevel = findData.levels?.singleWhere(
(e) => e.id == params.id, (e) => e.id == params.id,
orElse: () => LevelEntity(), orElse: () => LevelEntity(),
); );
return findLevel;
return findLevel ?? LevelEntity();
} catch (e) { } catch (e) {
throw MyException(errorMessage: '$e'); throw MyException(errorMessage: '$e');
} }

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

@ -1,3 +1,6 @@
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
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/file_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -17,6 +20,8 @@ class AnswerEntity extends HiveObject {
int? order; int? order;
@HiveField(5) @HiveField(5)
bool? isActive; bool? isActive;
@HiveField(6)
String? image;
AnswerEntity({ AnswerEntity({
this.id, this.id,
@ -25,5 +30,9 @@ class AnswerEntity extends HiveObject {
this.imageInfo, this.imageInfo,
this.order, this.order,
this.isActive, this.isActive,
});
this.image,
}){
image =
'${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/files/images/${imageInfo?.filename ?? ''}';
}
} }

7
lib/features/question/domain/entity/answer_entity.g.dart

@ -23,13 +23,14 @@ class AnswerEntityAdapter extends TypeAdapter<AnswerEntity> {
imageInfo: fields[3] as FileEntity?, imageInfo: fields[3] as FileEntity?,
order: fields[4] as int?, order: fields[4] as int?,
isActive: fields[5] as bool?, isActive: fields[5] as bool?,
image: fields[6] as String?,
); );
} }
@override @override
void write(BinaryWriter writer, AnswerEntity obj) { void write(BinaryWriter writer, AnswerEntity obj) {
writer writer
..writeByte(6)
..writeByte(7)
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
@ -41,7 +42,9 @@ class AnswerEntityAdapter extends TypeAdapter<AnswerEntity> {
..writeByte(4) ..writeByte(4)
..write(obj.order) ..write(obj.order)
..writeByte(5) ..writeByte(5)
..write(obj.isActive);
..write(obj.isActive)
..writeByte(6)
..write(obj.image);
} }
@override @override

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

@ -3,6 +3,8 @@ import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:hadi_hoda_flutter/core/params/question_params.dart'; import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart';
import 'package:hadi_hoda_flutter/core/widgets/hadith_dialog/hadith_dialog.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_level_usecase.dart'; import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart';
@ -14,7 +16,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
this._getLevelUseCase, this._getLevelUseCase,
) : super(const QuestionState()) { ) : super(const QuestionState()) {
on<GetLevelEvent>(_getLevelEvent); on<GetLevelEvent>(_getLevelEvent);
on<ChooseAnswerEvent>(_chooseAnswerEvent);
on<ChangeQuestionEvent>(_changeQuestionEvent);
} }
/// ------------UseCases------------ /// ------------UseCases------------
@ -36,11 +38,11 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
ShowCaseWidget.of(context).startShowCase([keys[1]]); ShowCaseWidget.of(context).startShowCase([keys[1]]);
} }
FutureOr<void> _chooseAnswerEvent(ChooseAnswerEvent event, Emitter<QuestionState> emit) {
emit(state.copyWith(chooseAnswer: event.answer));
void showHadith({required BuildContext context}) {
showHadithDialog(context: context);
} }
/// ------------Api Calls------------
/// ------------Event Calls------------
FutureOr<void> _getLevelEvent(GetLevelEvent event, Emitter<QuestionState> emit) async { FutureOr<void> _getLevelEvent(GetLevelEvent event, Emitter<QuestionState> emit) async {
await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then( await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then(
(value) { (value) {
@ -49,6 +51,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
emit(state.copyWith( emit(state.copyWith(
getQuestionStatus: BaseComplete(''), getQuestionStatus: BaseComplete(''),
levelEntity: data, levelEntity: data,
currentQuestion: data.questions?.first,
)); ));
}, },
(error) { (error) {
@ -58,4 +61,18 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
}, },
); );
} }
FutureOr<void> _changeQuestionEvent(
ChangeQuestionEvent event,
Emitter<QuestionState> emit,
) async {
await Future.delayed(Duration(seconds: 1), () {
final QuestionEntity? findPreQuestion = state.currentQuestion;
final int findPreIndex = (findPreQuestion?.order ?? 1) - 1;
final int newIndex = findPreIndex + 1;
emit(
state.copyWith(currentQuestion: state.levelEntity?.questions?[newIndex]),
);
});
}
} }

8
lib/features/question/presentation/bloc/question_event.dart

@ -1,17 +1,11 @@
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
sealed class QuestionEvent { sealed class QuestionEvent {
const QuestionEvent(); const QuestionEvent();
} }
class GetLevelEvent extends QuestionEvent { class GetLevelEvent extends QuestionEvent {
final String? id; final String? id;
const GetLevelEvent(this.id); const GetLevelEvent(this.id);
} }
class ChooseAnswerEvent extends QuestionEvent {
final AnswerEntity? answer;
const ChooseAnswerEvent(this.answer);
class ChangeQuestionEvent extends QuestionEvent {
} }

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

@ -1,31 +1,27 @@
import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart';
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/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
class QuestionState { class QuestionState {
final BaseStatus getQuestionStatus; final BaseStatus getQuestionStatus;
final LevelEntity? levelEntity; final LevelEntity? levelEntity;
final int currentStep;
final AnswerEntity? chooseAnswer;
final QuestionEntity? currentQuestion;
const QuestionState({ const QuestionState({
this.getQuestionStatus = const BaseInit(), this.getQuestionStatus = const BaseInit(),
this.levelEntity, this.levelEntity,
this.currentStep = 0,
this.chooseAnswer,
this.currentQuestion,
}); });
QuestionState copyWith({ QuestionState copyWith({
BaseStatus? getQuestionStatus, BaseStatus? getQuestionStatus,
LevelEntity? levelEntity, LevelEntity? levelEntity,
int? currentStep,
AnswerEntity? chooseAnswer,
QuestionEntity? currentQuestion,
}) { }) {
return QuestionState( return QuestionState(
getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus, getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus,
levelEntity: levelEntity ?? this.levelEntity, levelEntity: levelEntity ?? this.levelEntity,
currentStep: currentStep ?? this.currentStep,
chooseAnswer: chooseAnswer ?? this.chooseAnswer,
currentQuestion: currentQuestion ?? this.currentQuestion,
); );
} }
} }

34
lib/features/question/presentation/ui/question_page.dart

@ -54,7 +54,7 @@ class QuestionPage extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
MySpaces.s4.gapHeight, MySpaces.s4.gapHeight,
_topButtons(),
_topButtons(context),
MySpaces.s10.gapHeight, MySpaces.s10.gapHeight,
_stepper(), _stepper(),
_titles(), _titles(),
@ -72,20 +72,27 @@ class QuestionPage extends StatelessWidget {
} }
Widget _topButtons() {
Widget _topButtons(BuildContext context) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
GlassyButton(image: MyAssets.home, onTap: () {}), GlassyButton(image: MyAssets.home, onTap: () {}),
Spacer(),
BlocBuilder<QuestionBloc, QuestionState>( BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text( builder: (context, state) => Text(
state.levelEntity?.title ?? '',
'${context.translate.step} ${state.levelEntity?.order ?? 1}',
style: Marhey.bold14.copyWith( style: Marhey.bold14.copyWith(
color: MyColors.white, color: MyColors.white,
), ),
), ),
), ),
Spacer(),
GlassyButton(
image: MyAssets.leaf,
onTap: () => context.read<QuestionBloc>().showHadith(context: context),
),
MySpaces.s10.gapWidth,
GlassyButton(image: MyAssets.music, onTap: () {}), GlassyButton(image: MyAssets.music, onTap: () {}),
], ],
); );
@ -95,7 +102,7 @@ class QuestionPage extends StatelessWidget {
return BlocBuilder<QuestionBloc, QuestionState>( return BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => QuestionStepper( builder: (context, state) => QuestionStepper(
length: state.levelEntity?.questions?.length ?? 0, length: state.levelEntity?.questions?.length ?? 0,
currentStep: state.currentStep,
currentStep: state.currentQuestion?.order ?? 1,
), ),
); );
} }
@ -107,7 +114,7 @@ class QuestionPage extends StatelessWidget {
children: [ children: [
BlocBuilder<QuestionBloc, QuestionState>( BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text( builder: (context, state) => Text(
'Question ${state.currentStep} / ${state.levelEntity?.questions?.length ?? 0}',
'${context.translate.question} ${state.currentQuestion?.order ?? 1} / ${state.levelEntity?.questions?.length ?? 0}',
style: Marhey.medium12.copyWith( style: Marhey.medium12.copyWith(
color: MyColors.white.withValues(alpha: 0.5), color: MyColors.white.withValues(alpha: 0.5),
shadows: [ shadows: [
@ -122,9 +129,9 @@ class QuestionPage extends StatelessWidget {
), ),
BlocBuilder<QuestionBloc, QuestionState>( BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text( builder: (context, state) => Text(
state.levelEntity?.questions?[state.currentStep].title ?? '',
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Marhey.medium12.copyWith(
style: Marhey.semiBold22.copyWith(
color: MyColors.white, color: MyColors.white,
shadows: [ shadows: [
Shadow( Shadow(
@ -154,12 +161,11 @@ class QuestionPage extends StatelessWidget {
description: context.translate.tap_to_select, description: context.translate.tap_to_select,
child: BlocBuilder<QuestionBloc, QuestionState>( child: BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => AnswerBox( builder: (context, state) => AnswerBox(
index: index + 1,
answer: state.levelEntity?.questions?[state.currentStep]
.answers?[index] ?? AnswerEntity(),
selected: state.levelEntity?.questions?[state.currentStep]
.answers?[index].id == state.chooseAnswer?.id,
onTap: (answer) => context.read<QuestionBloc>().add(ChooseAnswerEvent(answer)),
key: Key('${state.currentQuestion?.id}'),
index: state.currentQuestion?.answers?[index].order ?? 1,
answer: state.currentQuestion?.answers?[index] ?? AnswerEntity(),
correctAnswer: state.currentQuestion?.correctAnswer ?? 0,
onTap: () => context.read<QuestionBloc>().add(ChangeQuestionEvent()),
), ),
), ),
), ),

22
lib/features/question/presentation/ui/widgets/black_white_effect.dart

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
class BlackWhiteEffect extends StatelessWidget {
const BlackWhiteEffect({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: ColorFilter.matrix(
<double>[
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
],
),
child: child,
);
}
}

14
lib/features/question/presentation/ui/widgets/question_stepper.dart

@ -12,9 +12,9 @@ class QuestionStepper extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: 80,
height: 70,
child: EasyStepper( child: EasyStepper(
activeStep: currentStep,
activeStep: currentStep - 1,
lineStyle: LineStyle( lineStyle: LineStyle(
lineLength: 20, lineLength: 20,
lineType: LineType.normal, lineType: LineType.normal,
@ -32,9 +32,9 @@ class QuestionStepper extends StatelessWidget {
padding: EdgeInsets.all(0), padding: EdgeInsets.all(0),
enableStepTapping: false, enableStepTapping: false,
steps: List.generate( steps: List.generate(
length,
length + 1,
(index) => EasyStep( (index) => EasyStep(
customStep: index == length - 1
customStep: index == length
? MyImage(image: MyAssets.diamond, size: 50) ? MyImage(image: MyAssets.diamond, size: 50)
: ClipPath( : ClipPath(
clipper: _StepperClipper(), clipper: _StepperClipper(),
@ -52,13 +52,13 @@ class QuestionStepper extends StatelessWidget {
padding: EdgeInsets.all(6), padding: EdgeInsets.all(6),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: index < currentStep
color: index < currentStep - 1
? Color(0XFF21B738) ? Color(0XFF21B738)
: index == currentStep + 1
: index == currentStep - 1
? Color(0XFF847AC4) ? Color(0XFF847AC4)
: Colors.transparent, : Colors.transparent,
), ),
child: index < currentStep ? MyImage(image: MyAssets.done) : null,
child: index < currentStep - 1 ? MyImage(image: MyAssets.done) : null,
), ),
), ),
), ),

2
lib/init_bindings.dart

@ -53,7 +53,7 @@ void initBindings() {
/// Home Feature /// Home Feature
/// Question Feature /// Question Feature
locator.registerLazySingleton<IQuestionDatasource>(() => QuestionDatasourceImpl(locator()));
locator.registerLazySingleton<IQuestionDatasource>(() => QuestionDatasourceImpl());
locator.registerLazySingleton<IQuestionRepository>(() => QuestionRepositoryImpl(locator())); locator.registerLazySingleton<IQuestionRepository>(() => QuestionRepositoryImpl(locator()));
locator.registerLazySingleton<GetLevelUseCase>(() => GetLevelUseCase(locator())); locator.registerLazySingleton<GetLevelUseCase>(() => GetLevelUseCase(locator()));

4
lib/l10n/app_en.arb

@ -9,5 +9,7 @@
"lost_connection": "Lost connection!", "lost_connection": "Lost connection!",
"try_again": "Try Again", "try_again": "Try Again",
"connected_to_internet": "You must be connected to the internet to download the initial game data.", "connected_to_internet": "You must be connected to the internet to download the initial game data.",
"start": "Start"
"start": "Start",
"step": "Step",
"question": "Question"
} }

12
lib/l10n/app_localizations.dart

@ -159,6 +159,18 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Start'** /// **'Start'**
String get start; String get start;
/// No description provided for @step.
///
/// In en, this message translates to:
/// **'Step'**
String get step;
/// No description provided for @question.
///
/// In en, this message translates to:
/// **'Question'**
String get question;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

6
lib/l10n/app_localizations_en.dart

@ -42,4 +42,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get start => 'Start'; String get start => 'Start';
@override
String get step => 'Step';
@override
String get question => 'Question';
} }
Loading…
Cancel
Save