completed main logic #17

Merged
amirreza.chegini merged 1 commits from fix/question into develop 13 hours ago
  1. 26
      assets/images/button_3.svg
  2. BIN
      assets/images/diamond_big.png
  3. BIN
      assets/images/happy_persons.png
  4. BIN
      assets/images/shiny.png
  5. BIN
      assets/images/ship.png
  6. 5
      lib/common_ui/resources/my_assets.dart
  7. 10
      lib/common_ui/resources/my_text_style.dart
  8. 2
      lib/core/routers/my_routes.dart
  9. 7
      lib/core/widgets/answer_box/answer_box.dart
  10. 62
      lib/core/widgets/confetti/my_confetti.dart
  11. 1
      lib/features/home/presentation/bloc/home_bloc.dart
  12. 9
      lib/features/level/presentation/bloc/level_bloc.dart
  13. 8
      lib/features/level/presentation/ui/level_page.dart
  14. 26
      lib/features/question/data/datasource/question_datasource.dart
  15. 18
      lib/features/question/data/repository_impl/question_repository_impl.dart
  16. 5
      lib/features/question/domain/entity/answer_entity.dart
  17. 10
      lib/features/question/domain/entity/question_entity.dart
  18. 8
      lib/features/question/domain/entity/question_entity.g.dart
  19. 1
      lib/features/question/domain/repository/question_repository.dart
  20. 17
      lib/features/question/domain/usecases/get_next_level_usecase.dart
  21. 89
      lib/features/question/presentation/bloc/question_bloc.dart
  22. 8
      lib/features/question/presentation/bloc/question_event.dart
  23. 4
      lib/features/question/presentation/bloc/question_state.dart
  24. 137
      lib/features/question/presentation/ui/question_page.dart
  25. 164
      lib/features/question/presentation/ui/screens/diamond_screen.dart
  26. 141
      lib/features/question/presentation/ui/screens/question_screen.dart
  27. 3
      lib/features/question/presentation/ui/widgets/left_blob.dart
  28. 6
      lib/features/question/presentation/ui/widgets/question_stepper.dart
  29. 3
      lib/features/question/presentation/ui/widgets/right_blob.dart
  30. 2
      lib/init_bindings.dart
  31. 8
      lib/l10n/app_en.arb
  32. 36
      lib/l10n/app_localizations.dart
  33. 18
      lib/l10n/app_localizations_en.dart
  34. 8
      pubspec.lock
  35. 1
      pubspec.yaml

26
assets/images/button_3.svg

@ -0,0 +1,26 @@
<svg width="198" height="94" viewBox="0 0 198 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1_4072)">
<path d="M188.461 75.7215C188.278 75.8967 188.081 76.0595 187.873 76.2088C171.323 88.5762 27.7109 88.1393 7.66767 77.0322C6.79486 76.6133 6.06848 75.9786 5.5714 75.2006C-1.50808 61.7579 2.33508 21.1271 13.5887 12.3389C25.6882 2.92896 170.11 -1.33912 185.041 10.6586C196.055 19.4131 201.278 63.4214 188.461 75.7215Z" fill="url(#paint0_linear_1_4072)"/>
</g>
<path d="M5.62633 72.529C12.4668 85.5516 174.357 86.5934 188.461 73.0667C201.333 60.7665 196.055 16.7415 185.096 7.97009C170.165 -3.99397 25.7431 0.257303 13.662 9.65043C2.38999 18.4218 -1.45315 59.0526 5.62633 72.529Z" fill="url(#paint1_linear_1_4072)"/>
<defs>
<filter id="filter0_d_1_4072" x="0.369676" y="3.31946" width="197.233" height="89.8485" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="6.11371"/>
<feGaussianBlur stdDeviation="0.815162"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_4072"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_4072" result="shape"/>
</filter>
<linearGradient id="paint0_linear_1_4072" x1="99.0206" y1="85.4339" x2="99.0206" y2="3.31544" gradientUnits="userSpaceOnUse">
<stop stop-color="#8B9AD1"/>
<stop offset="1" stop-color="#CEE0FF"/>
</linearGradient>
<linearGradient id="paint1_linear_1_4072" x1="99.0203" y1="82.7623" x2="99.0203" y2="0.643783" gradientUnits="userSpaceOnUse">
<stop stop-color="#CADCFF"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
</defs>
</svg>

BIN
assets/images/diamond_big.png

After

Width: 244  |  Height: 186  |  Size: 39 KiB

BIN
assets/images/happy_persons.png

After

Width: 150  |  Height: 125  |  Size: 35 KiB

BIN
assets/images/shiny.png

After

Width: 441  |  Height: 759  |  Size: 274 KiB

BIN
assets/images/ship.png

After

Width: 156  |  Height: 117  |  Size: 15 KiB

5
lib/common_ui/resources/my_assets.dart

@ -10,6 +10,7 @@ class MyAssets {
static const String musicOn = 'assets/images/music_on.svg';
static const String button = 'assets/images/button.svg';
static const String button2 = 'assets/images/button_2.svg';
static const String button3 = 'assets/images/button_3.svg';
static const String theme = 'assets/images/theme.svg';
static const String facebook = 'assets/images/facebook.svg';
static const String whatsapp = 'assets/images/whatsapp.svg';
@ -40,4 +41,8 @@ class MyAssets {
static const String lang = 'assets/images/lang.svg';
static const String error = 'assets/images/error.png';
static const String leaf = 'assets/images/leaf.png';
static const String happyPersons = 'assets/images/happy_persons.png';
static const String diamondBig = 'assets/images/diamond_big.png';
static const String ship = 'assets/images/ship.png';
static const String shiny = 'assets/images/shiny.png';
}

10
lib/common_ui/resources/my_text_style.dart

@ -23,6 +23,11 @@ class DinoKids {
fontSize: 35,
fontWeight: FontWeight.w400,
);
static const TextStyle regular40 = TextStyle(
fontFamily: fontFamily,
fontSize: 40,
fontWeight: FontWeight.w400,
);
static const TextStyle regular45 = TextStyle(
fontFamily: fontFamily,
fontSize: 45,
@ -87,4 +92,9 @@ class Marhey {
fontSize: 14,
fontWeight: FontWeight.w700,
);
static const TextStyle bold26 = TextStyle(
fontFamily: fontFamily,
fontSize: 26,
fontWeight: FontWeight.w700,
);
}

2
lib/core/routers/my_routes.dart

@ -80,7 +80,7 @@ GoRouter get appPages => GoRouter(
path: '${Routes.questionPage}/:id',
builder: (context, state) => BlocProvider(
create: (context) =>
QuestionBloc(locator())
QuestionBloc(locator(), locator())
..add(GetLevelEvent(state.pathParameters['id'])),
child: const QuestionPage(),
),

7
lib/core/widgets/answer_box/answer_box.dart

@ -15,7 +15,7 @@ class AnswerBox extends StatefulWidget {
final AnswerEntity answer;
final int correctAnswer;
final VoidCallback? onTap;
final void Function(bool isCorrect)? onTap;
final int index;
@override
@ -26,7 +26,6 @@ class _AnswerBoxState extends State<AnswerBox> {
bool selected = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
@ -34,9 +33,7 @@ class _AnswerBoxState extends State<AnswerBox> {
setState(() {
selected = true;
});
if (selected && (widget.index == widget.correctAnswer)) {
widget.onTap?.call();
}
widget.onTap?.call(widget.index == widget.correctAnswer);
} : null,
child: SizedBox(
child: Stack(

62
lib/core/widgets/confetti/my_confetti.dart

@ -0,0 +1,62 @@
import 'dart:math';
import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
class MyConfetti extends StatelessWidget {
const MyConfetti({super.key, required this.controller});
final ConfettiController controller;
@override
Widget build(BuildContext context) {
return ConfettiWidget(
confettiController: controller,
shouldLoop: false,
blastDirectionality: BlastDirectionality.explosive,
blastDirection: pi / 2,
// down
gravity: 0.2,
// let pieces fall
emissionFrequency: 0.5,
numberOfParticles: 15,
particleDrag: 0.1,
colors: const [
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.blue,
Colors.purple,
Colors.pink,
Colors.cyan,
],
createParticlePath: (size) {
double degToRad(double deg) => deg * (pi / 180.0);
const numberOfPoints = 5;
final halfWidth = size.width / 2;
final externalRadius = halfWidth;
final internalRadius = halfWidth / 2.5;
final degreesPerStep = degToRad(360 / numberOfPoints);
final halfDegreesPerStep = degreesPerStep / 2;
final path = Path();
final fullAngle = degToRad(360);
path.moveTo(size.width, halfWidth);
for (double step = 0; step < fullAngle; step += degreesPerStep) {
path.lineTo(
halfWidth + externalRadius * cos(step),
halfWidth + externalRadius * sin(step),
);
path.lineTo(
halfWidth + internalRadius * cos(step + halfDegreesPerStep),
halfWidth + internalRadius * sin(step + halfDegreesPerStep),
);
}
path.close();
return path;
},
);
}
}

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

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:go_router/go_router.dart';

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

@ -70,7 +70,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
/// ------------Functions------------
void goToQuestionPage(BuildContext context, LevelEntity level){
context.pushNamed(
context.pushReplacementNamed(
Routes.questionPage,
pathParameters: {
'id': '${level.id}',
@ -109,10 +109,17 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
if(data.length > 8){
top12LevelList.addAll(data.sublist(8, data.length));
}
try {
emit(state.copyWith(
getLevelStatus: const BaseComplete(''),
chooseLevel: data.singleWhere((e) => e.order == currentLevel),
));
} catch (e) {
emit(state.copyWith(
getLevelStatus: const BaseComplete(''),
chooseLevel: LevelEntity(),
));
}
add(StartScrollEvent());
},
(error) {},

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

@ -43,8 +43,11 @@ class LevelPage extends StatelessWidget {
buildWhen: (previous, current) =>
previous.chooseLevel?.id != current.chooseLevel?.id,
builder: (context, state) {
if (state.chooseLevel?.id != null) {
return Positioned(
bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s10,
bottom: MediaQuery
.viewPaddingOf(context)
.bottom + MySpaces.s10,
right: MySpaces.s16,
left: MySpaces.s16,
child: HintLevelWidget(
@ -53,6 +56,9 @@ class LevelPage extends StatelessWidget {
context.read<LevelBloc>().goToQuestionPage(context, level),
),
);
} else {
return SizedBox.shrink();
}
}
);
}

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

@ -8,6 +8,7 @@ import 'package:hive/hive.dart';
abstract class IQuestionDatasource {
Future<LevelEntity> getLevel({required QuestionParams params});
Future<LevelEntity> getNextLevel({required QuestionParams params});
}
/// Local
@ -33,4 +34,29 @@ class QuestionDatasourceImpl implements IQuestionDatasource {
throw MyException(errorMessage: '$e');
}
}
@override
Future<LevelEntity> getNextLevel({required QuestionParams params}) async {
try {
final String selectedLanguage = LocalStorage.readData(
key: MyConstants.selectLanguage) ?? 'fa';
final int index = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1');
final Box<TotalDataEntity> levelBox = Hive.box(MyConstants.levelBox);
final TotalDataEntity findData = levelBox.values.singleWhere(
(e) => e.code == selectedLanguage,
orElse: () => TotalDataEntity(),
);
if(index > (findData.levels?.length ?? 0)){
throw MyException();
}
final LevelEntity? findLevel = findData.levels?.singleWhere(
(e) => e.order == index,
orElse: () => LevelEntity(),
);
return findLevel ?? LevelEntity();
} catch (e) {
throw MyException(errorMessage: '$e');
}
}
}

18
lib/features/question/data/repository_impl/question_repository_impl.dart

@ -1,6 +1,6 @@
import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:flutter/foundation.dart';
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:hadi_hoda_flutter/core/utils/data_state.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/question/data/datasource/question_datasource.dart';
@ -26,4 +26,20 @@ class QuestionRepositoryImpl implements IQuestionRepository {
}
}
}
@override
Future<DataState<LevelEntity, MyException>> getNextLevel({required QuestionParams params}) async {
try {
final LevelEntity response = await datasource.getNextLevel(params: params);
return DataState.success(response);
} on MyException catch (e) {
return DataState.error(e);
} catch (e) {
if (kDebugMode) {
rethrow;
} else {
return DataState.error(MyException(errorMessage: '$e'));
}
}
}
}

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

@ -1,5 +1,3 @@
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:hive/hive.dart';
@ -32,7 +30,6 @@ class AnswerEntity extends HiveObject {
this.isActive,
this.image,
}){
image =
'${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/files/images/${imageInfo?.filename ?? ''}';
image = '${StoragePath.documentDir.path}/images/${imageInfo?.filename}';
}
}

10
lib/features/question/domain/entity/question_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/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart';
import 'package:hive/hive.dart';
@ -22,6 +25,8 @@ class QuestionEntity extends HiveObject {
bool? isActive;
@HiveField(7)
List<AnswerEntity>? answers;
@HiveField(8)
String? audio;
QuestionEntity({
this.id,
@ -32,5 +37,8 @@ class QuestionEntity extends HiveObject {
this.correctAnswer,
this.isActive,
this.answers,
});
}){
audio =
'${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/audio/${audioInfo?.filename}';
}
}

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

@ -25,13 +25,13 @@ class QuestionEntityAdapter extends TypeAdapter<QuestionEntity> {
correctAnswer: fields[5] as int?,
isActive: fields[6] as bool?,
answers: (fields[7] as List?)?.cast<AnswerEntity>(),
);
)..audio = fields[8] as String?;
}
@override
void write(BinaryWriter writer, QuestionEntity obj) {
writer
..writeByte(8)
..writeByte(9)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@ -47,7 +47,9 @@ class QuestionEntityAdapter extends TypeAdapter<QuestionEntity> {
..writeByte(6)
..write(obj.isActive)
..writeByte(7)
..write(obj.answers);
..write(obj.answers)
..writeByte(8)
..write(obj.audio);
}
@override

1
lib/features/question/domain/repository/question_repository.dart

@ -5,4 +5,5 @@ import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart
abstract class IQuestionRepository {
Future<DataState<LevelEntity, MyException>> getLevel({required QuestionParams params});
Future<DataState<LevelEntity, MyException>> getNextLevel({required QuestionParams params});
}

17
lib/features/question/domain/usecases/get_next_level_usecase.dart

@ -0,0 +1,17 @@
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:hadi_hoda_flutter/core/usecase/usecase.dart';
import 'package:hadi_hoda_flutter/core/utils/data_state.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/repository/question_repository.dart';
class GetNextLevelUseCase implements UseCase<LevelEntity, QuestionParams> {
final IQuestionRepository repository;
const GetNextLevelUseCase(this.repository);
@override
Future<DataState<LevelEntity, MyException>> call(QuestionParams params) {
return repository.getNextLevel(params: params);
}
}

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

@ -1,11 +1,20 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:confetti/confetti.dart';
import 'package:flutter/cupertino.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
import 'package:hadi_hoda_flutter/core/status/base_status.dart';
import 'package:hadi_hoda_flutter/core/utils/context_provider.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/widgets/hadith_dialog/hadith_dialog.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.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_next_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_state.dart';
import 'package:showcaseview/showcaseview.dart';
@ -14,13 +23,22 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
/// ------------constructor------------
QuestionBloc(
this._getLevelUseCase,
) : super(const QuestionState()) {
this._getNextLevelUseCase,
) : super(QuestionState()) {
on<GetLevelEvent>(_getLevelEvent);
on<ChangeQuestionEvent>(_changeQuestionEvent);
on<ChooseAnswerEvent>(_chooseAnswerEvent);
on<GetNextLevelEvent>(_getNextLevelEvent);
}
@override
Future<void> close() {
confettiController.dispose();
return super.close();
}
/// ------------UseCases------------
final GetLevelUseCase _getLevelUseCase;
final GetNextLevelUseCase _getNextLevelUseCase;
/// ------------Variables------------
final List<GlobalKey> keys = [
@ -32,6 +50,9 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
/// ------------Controllers------------
final ConfettiController confettiController = ConfettiController(
duration: Duration(seconds: 1),
);
/// ------------Functions------------
void startShowCase({required BuildContext context}) {
@ -42,15 +63,28 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
showHadithDialog(context: context);
}
void goToLevelPage({required BuildContext context}) {
context.pushReplacement(Routes.levelPage);
}
/// ------------Event Calls------------
FutureOr<void> _getLevelEvent(GetLevelEvent event, Emitter<QuestionState> emit) async {
await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then(
(value) {
value.fold(
(data) {
final LevelEntity level = LevelEntity(
id: data.id,
order: data.order,
title: data.title,
questions: [
...?data.questions,
QuestionEntity(order: (data.questions?.length ?? 0) + 1)
],
);
emit(state.copyWith(
getQuestionStatus: BaseComplete(''),
levelEntity: data,
levelEntity: level,
currentQuestion: data.questions?.first,
));
},
@ -62,17 +96,50 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
);
}
FutureOr<void> _changeQuestionEvent(
ChangeQuestionEvent event,
Emitter<QuestionState> emit,
) async {
await Future.delayed(Duration(seconds: 1), () {
FutureOr<void> _chooseAnswerEvent(ChooseAnswerEvent event,
Emitter<QuestionState> emit,) async {
emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer));
if (event.chooseCorrectAnswer) {
confettiController.play();
await Future.delayed(Duration(seconds: 2), () async {
final QuestionEntity? findPreQuestion = state.currentQuestion;
final int findPreIndex = (findPreQuestion?.order ?? 1) - 1;
final int newIndex = findPreIndex + 1;
final int findIndex = (findPreQuestion?.order ?? 1);
emit(
state.copyWith(currentQuestion: state.levelEntity?.questions?[newIndex]),
state.copyWith(
currentQuestion: state.levelEntity?.questions?[findIndex],
),
);
if (state.currentQuestion?.order ==
state.levelEntity?.questions?.length) {
int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1');
++currentLevel;
await LocalStorage.saveData(
key: MyConstants.currentLevel,
value: '$currentLevel',
);
}
});
}
}
FutureOr<void> _getNextLevelEvent(GetNextLevelEvent event,
Emitter<QuestionState> emit) async {
await _getNextLevelUseCase(QuestionParams()).then((value) =>
value.fold(
(data) {
ContextProvider.context.pushReplacementNamed(
Routes.questionPage,
pathParameters: {
'id': '${data.id}'
},
);
},
(error) {
goToLevelPage(context: ContextProvider.context);
},
),
);
}
}

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

@ -7,5 +7,11 @@ class GetLevelEvent extends QuestionEvent {
const GetLevelEvent(this.id);
}
class ChangeQuestionEvent extends QuestionEvent {
class ChooseAnswerEvent extends QuestionEvent {
final bool chooseCorrectAnswer;
const ChooseAnswerEvent(this.chooseCorrectAnswer);
}
class GetNextLevelEvent extends QuestionEvent {
const GetNextLevelEvent();
}

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

@ -6,22 +6,26 @@ class QuestionState {
final BaseStatus getQuestionStatus;
final LevelEntity? levelEntity;
final QuestionEntity? currentQuestion;
final bool? correctAnswer;
const QuestionState({
this.getQuestionStatus = const BaseInit(),
this.levelEntity,
this.currentQuestion,
this.correctAnswer,
});
QuestionState copyWith({
BaseStatus? getQuestionStatus,
LevelEntity? levelEntity,
QuestionEntity? currentQuestion,
bool? correctAnswer,
}) {
return QuestionState(
getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus,
levelEntity: levelEntity ?? this.levelEntity,
currentQuestion: currentQuestion ?? this.currentQuestion,
correctAnswer: correctAnswer,
);
}
}

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

@ -5,20 +5,15 @@ 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/gap.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/widgets/answer_box/answer_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/question_showcase.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/core/widgets/confetti/my_confetti.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.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/ui/screens/diamond_screen.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/screens/question_screen.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/glassy_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/left_blob.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/refresh_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/right_blob.dart';
import 'package:showcaseview/showcaseview.dart';
class QuestionPage extends StatelessWidget {
@ -53,14 +48,28 @@ class QuestionPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: MySpaces.s16),
child: Column(
children: [
MyConfetti(
controller: context.read<QuestionBloc>().confettiController,
),
MySpaces.s4.gapHeight,
_topButtons(context),
MySpaces.s10.gapHeight,
_stepper(),
_titles(),
MySpaces.s14.gapHeight,
_answers(),
_bottomDetail(context),
Expanded(
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
(previous.currentQuestion?.order !=
current.currentQuestion?.order),
builder: (context, state) {
if (state.currentQuestion?.order ==
state.levelEntity?.questions?.length) {
return DiamondScreen();
} else {
return QuestionScreen();
}
},
),
),
],
),
),
@ -77,7 +86,10 @@ class QuestionPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GlassyButton(image: MyAssets.home, onTap: () {}),
GlassyButton(
image: MyAssets.home,
onTap: () => context.read<QuestionBloc>().goToLevelPage(context: context),
),
Spacer(),
BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text(
@ -106,103 +118,4 @@ class QuestionPage extends StatelessWidget {
),
);
}
Column _titles() {
return Column(
spacing: MySpaces.s4,
children: [
BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text(
'${context.translate.question} ${state.currentQuestion?.order ?? 1} / ${state.levelEntity?.questions?.length ?? 0}',
style: Marhey.medium12.copyWith(
color: MyColors.white.withValues(alpha: 0.5),
shadows: [
Shadow(
offset: Offset(0, 1),
blurRadius: 1,
color: Color(0xFF000000).withValues(alpha: 0.25),
),
],
),
),
),
BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
style: Marhey.semiBold22.copyWith(
color: MyColors.white,
shadows: [
Shadow(
offset: Offset(0, 1),
blurRadius: 1,
color: Color(0xFF000000).withValues(alpha: 0.25),
),
],
),
),
),
],
);
}
Expanded _answers() {
return Expanded(
child: GridView.builder(
itemCount: 4,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: MySpaces.s20,
mainAxisSpacing: 50,
),
itemBuilder: (context, index) => QuestionShowcase(
globalKey: context.read<QuestionBloc>().keys[index],
description: context.translate.tap_to_select,
child: BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => AnswerBox(
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()),
),
),
),
),
);
}
Widget _bottomDetail(BuildContext context) {
return SizedBox(
width: context.widthScreen,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
PositionedDirectional(
start: 0,
top: -10,
child: LeftBlob(),
),
Padding(
padding: const EdgeInsetsDirectional.only(end: 60),
child: MyImage(image: MyAssets.persons),
),
PositionedDirectional(
start: 210,
top: -20,
child: RightBlob(),
),
PositionedDirectional(
end: 0,
bottom: 10,
child: RefreshButton(
onTap: () {},
),
),
],
),
);
}
}

164
lib/features/question/presentation/ui/screens/diamond_screen.dart

@ -0,0 +1,164 @@
import 'dart:ui';
import 'package:flutter/material.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_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/gap.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/widgets/button/enum/button_type.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/my_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart';
class DiamondScreen extends StatelessWidget {
const DiamondScreen({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
children: [
_title(context),
80.0.gapHeight,
Column(
children: [
_diamonds(context),
_mainText(context),
],
),
Spacer(),
Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
_ship(context),
_btns(context),
],
),
],
);
}
Text _title(BuildContext context) {
return Text(
context.translate.you_win,
style: Marhey.semiBold22.copyWith(color: MyColors.white),
);
}
SizedBox _diamonds(BuildContext context) {
return SizedBox(
width: context.widthScreen,
height: context.heightScreen / 3,
child: Stack(
alignment: Alignment.center,
children: [
PositionedDirectional(
start: 20,
top: 0,
child: SizedBox(
height: 50,
width: 50,
child: Transform.rotate(
angle: -0.5,
child: Stack(
children: [
MyImage(image: MyAssets.diamondBig),
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
child: SizedBox(width: 50, height: 50),
),
),
],
),
),
),
),
PositionedDirectional(
end: 0,
top: 30,
child: Transform.rotate(
angle: 0.5,
child: Stack(
children: [
MyImage(image: MyAssets.diamondBig, size: 60),
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
child: SizedBox(width: 100, height: 100),
),
),
],
),
),
),
Positioned(top: 100, child: MyImage(image: MyAssets.diamondBig)),
],
),
);
}
Widget _mainText(BuildContext context){
return ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [MyColors.white, Color(0XFF63D4F9)],
).createShader(bounds),
child: Text(
context.translate.you_got_diamond,
style: Marhey
.bold26, // The color here will be overridden by the ShaderMask
),
);
}
PositionedDirectional _ship(BuildContext context) {
return PositionedDirectional(
end: context.widthScreen / 10,
top: -80,
child: MyImage(image: MyAssets.ship),
);
}
Row _btns(BuildContext context) {
return Row(
children: [
Expanded(
child: InkWell(
onTap: () => context.read<QuestionBloc>().goToLevelPage(context: context),
child: Stack(
alignment: Alignment.center,
children: [
MyImage(image: MyAssets.button3, size: 84),
Positioned(
top: 10,
child: Text(
context.translate.view_map,
style: DinoKids.regular35.copyWith(
color: Color(0XFFD93D16),
),
),
),
],
),
),
),
Expanded(
child: MyButton(
onTap: () => context.read<QuestionBloc>().add(GetNextLevelEvent()),
title: context.translate.go_next,
type: ButtonType.type2,
),
),
],
);
}
}

141
lib/features/question/presentation/ui/screens/question_screen.dart

@ -0,0 +1,141 @@
import 'package:flutter/material.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_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/gap.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/widgets/answer_box/answer_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/question_showcase.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.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/ui/widgets/left_blob.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/refresh_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/right_blob.dart';
class QuestionScreen extends StatelessWidget {
const QuestionScreen({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
_titles(),
MySpaces.s14.gapHeight,
_answers(),
_bottomDetail(context),
],
);
}
Column _titles() {
return Column(
spacing: MySpaces.s4,
children: [
BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text(
'${context.translate.question} ${state.currentQuestion?.order ?? 1} / ${(state.levelEntity?.questions?.length ?? 0) - 1}',
style: Marhey.medium12.copyWith(
color: MyColors.white.withValues(alpha: 0.5),
shadows: [
Shadow(
offset: Offset(0, 1),
blurRadius: 1,
color: Color(0xFF000000).withValues(alpha: 0.25),
),
],
),
),
),
BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Text(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
style: Marhey.semiBold22.copyWith(
color: MyColors.white,
shadows: [
Shadow(
offset: Offset(0, 1),
blurRadius: 1,
color: Color(0xFF000000).withValues(alpha: 0.25),
),
],
),
),
),
],
);
}
Expanded _answers() {
return Expanded(
child: GridView.builder(
itemCount: 4,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: MySpaces.s20,
mainAxisSpacing: 50,
),
itemBuilder: (context, index) => QuestionShowcase(
globalKey: context.read<QuestionBloc>().keys[index],
description: context.translate.tap_to_select,
child: BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => AnswerBox(
key: Key('${state.currentQuestion?.id}'),
index: state.currentQuestion?.answers?[index].order ?? 1,
answer: state.currentQuestion?.answers?[index] ?? AnswerEntity(),
correctAnswer: state.currentQuestion?.correctAnswer ?? 0,
onTap: (isCorrect) =>
context.read<QuestionBloc>().add(
ChooseAnswerEvent(isCorrect),
),
),
),
),
),
);
}
Widget _bottomDetail(BuildContext context) {
return Row(
children: [
Spacer(),
BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => Stack(
clipBehavior: Clip.none,
children: [
if(state.correctAnswer == false)
PositionedDirectional(
start: -100,
top: -10,
child: LeftBlob(),
),
MyImage(
image: state.correctAnswer == true
? MyAssets.happyPersons
: MyAssets.persons,
fit: BoxFit.contain,
size: 110,
),
if(state.correctAnswer == false)
PositionedDirectional(
top: -30,
end: -90,
child: RightBlob(),
),
],
),
),
Spacer(),
RefreshButton(
onTap: () {},
),
],
);
}
}

3
lib/features/question/presentation/ui/widgets/left_blob.dart

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.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_localization.dart';
class LeftBlob extends StatelessWidget {
const LeftBlob({super.key});
@ -13,7 +14,7 @@ class LeftBlob extends StatelessWidget {
children: [
MyImage(image: MyAssets.bubbleChatLeft),
Text(
'Your answer\nwas not correct.',
context.translate.wrong_answer,
textAlign: TextAlign.center,
style: Marhey.medium12.copyWith(
color: Color(0XFFB5AEEE),

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

@ -4,7 +4,7 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/core/utils/my_image.dart';
class QuestionStepper extends StatelessWidget {
const QuestionStepper({super.key, this.length = 0, this.currentStep = 0});
const QuestionStepper({super.key, required this.length ,required this.currentStep});
final int length;
final int currentStep;
@ -32,9 +32,9 @@ class QuestionStepper extends StatelessWidget {
padding: EdgeInsets.all(0),
enableStepTapping: false,
steps: List.generate(
length + 1,
length,
(index) => EasyStep(
customStep: index == length
customStep: index == length - 1
? MyImage(image: MyAssets.diamond, size: 50)
: ClipPath(
clipper: _StepperClipper(),

3
lib/features/question/presentation/ui/widgets/right_blob.dart

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.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_localization.dart';
class RightBlob extends StatelessWidget {
const RightBlob({super.key});
@ -13,7 +14,7 @@ class RightBlob extends StatelessWidget {
children: [
MyImage(image: MyAssets.bubbleChatRight),
Text(
'Be more\ncareful.',
context.translate.be_cureful,
textAlign: TextAlign.center,
style: Marhey.medium12.copyWith(
color: Color(0XFFB5AEEE),

2
lib/init_bindings.dart

@ -28,6 +28,7 @@ import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.da
import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart';
import 'package:hadi_hoda_flutter/features/question/domain/repository/question_repository.dart';
import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_next_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/sample/data/datasource/sample_datasource.dart';
import 'package:hadi_hoda_flutter/features/sample/data/repository_impl/sample_repository_impl.dart';
import 'package:hadi_hoda_flutter/features/sample/domain/repository/sample_repository.dart';
@ -67,6 +68,7 @@ void initBindings() {
locator.registerLazySingleton<IQuestionDatasource>(() => QuestionDatasourceImpl());
locator.registerLazySingleton<IQuestionRepository>(() => QuestionRepositoryImpl(locator()));
locator.registerLazySingleton<GetLevelUseCase>(() => GetLevelUseCase(locator()));
locator.registerLazySingleton<GetNextLevelUseCase>(() => GetNextLevelUseCase(locator()));
/// Level Feature
locator.registerLazySingleton<ILevelDatasource>(() => LocalLevelDatasourceImpl());

8
lib/l10n/app_en.arb

@ -11,5 +11,11 @@
"connected_to_internet": "You must be connected to the internet to download the initial game data.",
"start": "Start",
"step": "Step",
"question": "Question"
"question": "Question",
"be_cureful": "Be more\ncareful.",
"wrong_answer": "Your answer\nwas not correct.",
"you_got_diamond": "You got the diamond",
"view_map": "View Map",
"go_next": "Go Next",
"you_win": "You Win!"
}

36
lib/l10n/app_localizations.dart

@ -171,6 +171,42 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Question'**
String get question;
/// No description provided for @be_cureful.
///
/// In en, this message translates to:
/// **'Be more\ncareful.'**
String get be_cureful;
/// No description provided for @wrong_answer.
///
/// In en, this message translates to:
/// **'Your answer\nwas not correct.'**
String get wrong_answer;
/// No description provided for @you_got_diamond.
///
/// In en, this message translates to:
/// **'You got the diamond'**
String get you_got_diamond;
/// No description provided for @view_map.
///
/// In en, this message translates to:
/// **'View Map'**
String get view_map;
/// No description provided for @go_next.
///
/// In en, this message translates to:
/// **'Go Next'**
String get go_next;
/// No description provided for @you_win.
///
/// In en, this message translates to:
/// **'You Win!'**
String get you_win;
}
class _AppLocalizationsDelegate

18
lib/l10n/app_localizations_en.dart

@ -48,4 +48,22 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get question => 'Question';
@override
String get be_cureful => 'Be more\ncareful.';
@override
String get wrong_answer => 'Your answer\nwas not correct.';
@override
String get you_got_diamond => 'You got the diamond';
@override
String get view_map => 'View Map';
@override
String get go_next => 'Go Next';
@override
String get you_win => 'You Win!';
}

8
pubspec.lock

@ -161,6 +161,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
confetti:
dependency: "direct main"
description:
name: confetti
sha256: "79376a99648efbc3f23582f5784ced0fe239922bd1a0fb41f582051eba750751"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
convert:
dependency: transitive
description:

1
pubspec.yaml

@ -8,6 +8,7 @@ environment:
dependencies:
bloc: ^9.0.0
confetti: ^0.8.0
dio: ^5.9.0
easy_stepper: ^0.8.5+1
equatable: ^2.0.7

Loading…
Cancel
Save