Browse Source

fix: zoom in and zoom out answers

pull/21/head
AmirrezaChegini 3 weeks ago
parent
commit
ee7438a55b
  1. 38
      lib/core/routers/hero_dialog_route.dart
  2. 42
      lib/core/widgets/answer_box/answer_box.dart
  3. 42
      lib/core/widgets/answer_box/answer_box_show.dart
  4. 81
      lib/core/widgets/answer_box/styles/picture_box.dart
  5. 2
      lib/features/level/presentation/bloc/level_bloc.dart
  6. 49
      lib/features/question/presentation/bloc/question_bloc.dart
  7. 3
      lib/features/question/presentation/bloc/question_event.dart
  8. 53
      lib/features/question/presentation/ui/screens/answer_screen.dart
  9. 21
      lib/features/question/presentation/ui/screens/question_screen.dart

38
lib/core/routers/hero_dialog_route.dart

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
class HeroDialogRoute<T> extends PageRoute<T> {
HeroDialogRoute({
required WidgetBuilder builder,
super.settings,
super.fullscreenDialog,
}) : _builder = builder;
final WidgetBuilder _builder;
@override
bool get opaque => false;
@override
bool get fullscreenDialog => false;
@override
bool get barrierDismissible => false;
@override
Duration get transitionDuration => const Duration(milliseconds: 300); // Adjust as needed
@override
bool get maintainState => true;
@override
Color get barrierColor => MyColors.transparent; // Or your desired barrier color
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return _builder(context);
}
@override
String get barrierLabel => '';
}

42
lib/core/widgets/answer_box/answer_box.dart

@ -15,7 +15,7 @@ class AnswerBox extends StatefulWidget {
final AnswerEntity answer; final AnswerEntity answer;
final int correctAnswer; final int correctAnswer;
final void Function(bool isCorrect)? onTap;
final void Function(bool isCorrect, int correctAnswer)? onTap;
final int index; final int index;
@override @override
@ -33,29 +33,27 @@ class _AnswerBoxState extends State<AnswerBox> {
setState(() { setState(() {
selected = true; selected = true;
}); });
widget.onTap?.call(widget.index == widget.correctAnswer);
widget.onTap?.call(widget.index == widget.correctAnswer, widget.correctAnswer);
} : null, } : null,
child: SizedBox(
child: Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
child: Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
),
Positioned(
left: 0,
right: 0,
bottom: -MySpaces.s26,
child: AnswerTextBox(
text: widget.answer.title ?? '',
), ),
Positioned(
left: 0,
right: 0,
bottom: -MySpaces.s26,
child: AnswerTextBox(
text: widget.answer.title ?? '',
),
),
],
),
),
],
), ),
); );
} }

42
lib/core/widgets/answer_box/answer_box_show.dart

@ -0,0 +1,42 @@
import 'package:flutter/material.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/text_box.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerBoxShow extends StatelessWidget {
const AnswerBoxShow({
super.key,
required this.answer,
required this.index,
this.correct,
});
final AnswerEntity answer;
final int index;
final bool? correct;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: correct ?? false,
index: index,
image: answer.image ?? '',
correctAnswer: index,
),
Positioned(
left: 0,
right: 0,
bottom: -MySpaces.s26,
child: AnswerTextBox(
text: answer.title ?? '',
),
),
],
);
}
}

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

@ -1,8 +1,6 @@
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_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';
@ -10,7 +8,7 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.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/features/question/presentation/ui/widgets/black_white_effect.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/black_white_effect.dart';
class AnswerPictureBox extends StatefulWidget {
class AnswerPictureBox extends StatelessWidget {
const AnswerPictureBox({ const AnswerPictureBox({
super.key, super.key,
required this.selected, required this.selected,
@ -24,19 +22,6 @@ class AnswerPictureBox extends StatefulWidget {
final int index; final int index;
final int correctAnswer; 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(
@ -46,47 +31,27 @@ class _AnswerPictureBoxState extends State<AnswerPictureBox> {
clipper: _SvgCustomClipper(), clipper: _SvgCustomClipper(),
child: Stack( child: Stack(
children: [ children: [
FutureBuilder<Uint8List>(
future: _imageFuture,
builder: (context, snapshot) {
return AnimatedCrossFade(
crossFadeState: snapshot.hasData
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: Duration(milliseconds: 300),
firstChild: SizedBox(
Builder(
builder: (context) {
if (selected &&
(index != correctAnswer)) {
return BlackWhiteEffect(
child: Image.file(
File(image),
fit: BoxFit.cover,
height: 170,
width: 170,
),
);
} else {
return Image.file(
File(image),
fit: BoxFit.cover,
height: 170, height: 170,
width: 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,
@ -108,7 +73,7 @@ class _AnswerPictureBoxState extends State<AnswerPictureBox> {
), ),
), ),
child: Text( child: Text(
'${widget.index}',
'$index',
style: Marhey.semiBold17.copyWith( style: Marhey.semiBold17.copyWith(
color: MyColors.white, color: MyColors.white,
), ),
@ -116,12 +81,12 @@ class _AnswerPictureBoxState extends State<AnswerPictureBox> {
), ),
), ),
), ),
if(widget.selected)
if(selected)
PositionedDirectional( PositionedDirectional(
top: MySpaces.s14, top: MySpaces.s14,
end: MySpaces.s12, end: MySpaces.s12,
child: MyImage( child: MyImage(
image: widget.index == widget.correctAnswer ? MyAssets.correct : MyAssets
image: index == correctAnswer ? MyAssets.correct : MyAssets
.wrong, .wrong,
size: MySpaces.s40, size: MySpaces.s40,
), ),

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

@ -149,7 +149,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
if (scrollController.hasClients) { if (scrollController.hasClients) {
if(currentLevel > 14){
if(currentLevel >= 14){
scrollController.animateTo( scrollController.animateTo(
scrollController.position.maxScrollExtent, scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 500), // Note: 500 seconds is very long. duration: const Duration(milliseconds: 500), // Note: 500 seconds is very long.

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

@ -1,11 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:confetti/confetti.dart'; import 'package:confetti/confetti.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
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/params/question_params.dart'; import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:hadi_hoda_flutter/core/routers/hero_dialog_route.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
import 'package:hadi_hoda_flutter/core/services/audio_service.dart'; import 'package:hadi_hoda_flutter/core/services/audio_service.dart';
import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart';
@ -13,11 +14,13 @@ 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/utils/local_storage.dart';
import 'package:hadi_hoda_flutter/core/widgets/hadith_dialog/hadith_dialog.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/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'; 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/domain/usecases/get_next_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_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';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/screens/answer_screen.dart';
import 'package:showcaseview/showcaseview.dart'; import 'package:showcaseview/showcaseview.dart';
class QuestionBloc extends Bloc<QuestionEvent, QuestionState> { class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
@ -51,6 +54,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
GlobalKey(), GlobalKey(),
]; ];
late final Stream<double> volumeStream; late final Stream<double> volumeStream;
bool isPlaying = false;
/// ------------Controllers------------ /// ------------Controllers------------
final AudioService _audioService; final AudioService _audioService;
@ -80,12 +84,41 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
await _audioService.changeMute(); await _audioService.changeMute();
} }
Future<void> showAnswerDialog({
required BuildContext context,
required AnswerEntity answerEntity,
bool? correct,
}) async {
await Navigator.of(context).push(
HeroDialogRoute(
builder: (dialogContext) {
return AnswerScreen(answerEntity: answerEntity, correct: correct);
},
),
);
}
Future<void> playback() async {
if (isPlaying) return;
for (int i = 0; i < 4; i++) {
await Future.delayed(Duration(seconds: 1));
if (ContextProvider.context.mounted) {
await showAnswerDialog(
context: ContextProvider.context,
answerEntity: state.currentQuestion?.answers?[i] ?? AnswerEntity(),
);
isPlaying = true;
}
}
isPlaying = false;
}
/// ------------Event 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) {
value.fold( value.fold(
(data) {
(data) async {
final LevelEntity level = LevelEntity( final LevelEntity level = LevelEntity(
id: data.id, id: data.id,
order: data.order, order: data.order,
@ -100,7 +133,8 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
levelEntity: level, levelEntity: level,
currentQuestion: data.questions?.first, currentQuestion: data.questions?.first,
)); ));
playVoice();
await playVoice();
playback();
}, },
(error) { (error) {
emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage))); emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage)));
@ -116,6 +150,12 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
if (event.chooseCorrectAnswer) { if (event.chooseCorrectAnswer) {
confettiController.play(); confettiController.play();
await showAnswerDialog(
answerEntity: state.currentQuestion?.answers?.singleWhere((e) =>
e.order == event.correctAnswer) ?? AnswerEntity(),
context: ContextProvider.context,
correct: true,
);
await Future.delayed(Duration(seconds: 2), () async { await Future.delayed(Duration(seconds: 2), () async {
final QuestionEntity? findPreQuestion = state.currentQuestion; final QuestionEntity? findPreQuestion = state.currentQuestion;
final int findIndex = (findPreQuestion?.order ?? 1); final int findIndex = (findPreQuestion?.order ?? 1);
@ -137,6 +177,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
} }
} else { } else {
await playVoice(); await playVoice();
playback();
} }
}); });
} }

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

@ -9,7 +9,8 @@ class GetLevelEvent extends QuestionEvent {
class ChooseAnswerEvent extends QuestionEvent { class ChooseAnswerEvent extends QuestionEvent {
final bool chooseCorrectAnswer; final bool chooseCorrectAnswer;
const ChooseAnswerEvent(this.chooseCorrectAnswer);
final int correctAnswer;
const ChooseAnswerEvent(this.chooseCorrectAnswer, this.correctAnswer);
} }
class GetNextLevelEvent extends QuestionEvent { class GetNextLevelEvent extends QuestionEvent {

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

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/core/utils/context_provider.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box_show.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerScreen extends StatefulWidget {
const AnswerScreen({super.key, required this.answerEntity, this.correct});
final AnswerEntity answerEntity;
final bool? correct;
@override
State<AnswerScreen> createState() => _AnswerScreenState();
}
class _AnswerScreenState extends State<AnswerScreen> {
@override
void initState() {
super.initState();
back();
}
Future<void> back() async {
await Future.delayed(Duration(seconds: 2), () {
if (ContextProvider.context.mounted) {
Navigator.pop(ContextProvider.context);
}
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Hero(
tag: 'Hero_answer_${widget.answerEntity.id}',
createRectTween: (begin, end) => MaterialRectArcTween(begin: begin, end: end),
flightShuttleBuilder: (flightContext, animation, flightDirection,
fromHeroContext, toHeroContext) => toHeroContext.widget,
child: Transform.scale(
scale: 2,
child: Material(
type: MaterialType.transparency,
child: AnswerBoxShow(
answer: widget.answerEntity,
index: widget.answerEntity.order ?? 0,
correct: widget.correct,
),
),
),
),
);
}
}

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

@ -90,15 +90,18 @@ class QuestionScreen extends StatelessWidget {
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) => AnswerBox(
builder: (context, state) => Hero(
key: Key('${state.currentQuestion?.id}'), 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),
),
tag: 'Hero_answer_${state.currentQuestion?.answers?[index].id}',
child: AnswerBox(
index: state.currentQuestion?.answers?[index].order ?? 1,
answer: state.currentQuestion?.answers?[index] ?? AnswerEntity(),
correctAnswer: state.currentQuestion?.correctAnswer ?? 0,
onTap: (isCorrect, correctAnswer) =>
context.read<QuestionBloc>().add(
ChooseAnswerEvent(isCorrect, correctAnswer),
),
),
), ),
), ),
), ),
@ -154,7 +157,7 @@ class QuestionScreen extends StatelessWidget {
), ),
Spacer(), Spacer(),
RefreshButton( RefreshButton(
onTap: () {},
onTap: () => context.read<QuestionBloc>().playback(),
), ),
], ],
); );

Loading…
Cancel
Save