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. 6
      lib/core/widgets/answer_box/answer_box.dart
  3. 42
      lib/core/widgets/answer_box/answer_box_show.dart
  4. 57
      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. 11
      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 => '';
}

6
lib/core/widgets/answer_box/answer_box.dart

@ -15,7 +15,7 @@ class AnswerBox extends StatefulWidget {
final AnswerEntity answer;
final int correctAnswer;
final void Function(bool isCorrect)? onTap;
final void Function(bool isCorrect, int correctAnswer)? onTap;
final int index;
@override
@ -33,9 +33,8 @@ class _AnswerBoxState extends State<AnswerBox> {
setState(() {
selected = true;
});
widget.onTap?.call(widget.index == widget.correctAnswer);
widget.onTap?.call(widget.index == widget.correctAnswer, widget.correctAnswer);
} : null,
child: SizedBox(
child: Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
@ -56,7 +55,6 @@ class _AnswerBoxState extends State<AnswerBox> {
),
],
),
),
);
}
}

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 ?? '',
),
),
],
);
}
}

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

@ -1,8 +1,6 @@
import 'dart:io';
import 'package:flutter/cupertino.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_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/features/question/presentation/ui/widgets/black_white_effect.dart';
class AnswerPictureBox extends StatefulWidget {
class AnswerPictureBox extends StatelessWidget {
const AnswerPictureBox({
super.key,
required this.selected,
@ -24,19 +22,6 @@ class AnswerPictureBox extends StatefulWidget {
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
Widget build(BuildContext context) {
return CustomPaint(
@ -46,47 +31,27 @@ class _AnswerPictureBoxState extends State<AnswerPictureBox> {
clipper: _SvgCustomClipper(),
child: Stack(
children: [
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(
builder: (context) {
if (widget.selected &&
(widget.index != widget.correctAnswer)) {
if (selected &&
(index != correctAnswer)) {
return BlackWhiteEffect(
child: Image.memory(
snapshot.data ?? Uint8List(0),
child: Image.file(
File(image),
fit: BoxFit.cover,
height: 170,
width: 170,
),
);
} else {
return Image.memory(
snapshot.data ?? Uint8List(0),
return Image.file(
File(image),
fit: BoxFit.cover,
height: 170,
width: 170,
);
}
},
)
);
}
),
PositionedDirectional(
top: MySpaces.s12,
@ -108,7 +73,7 @@ class _AnswerPictureBoxState extends State<AnswerPictureBox> {
),
),
child: Text(
'${widget.index}',
'$index',
style: Marhey.semiBold17.copyWith(
color: MyColors.white,
),
@ -116,12 +81,12 @@ class _AnswerPictureBoxState extends State<AnswerPictureBox> {
),
),
),
if(widget.selected)
if(selected)
PositionedDirectional(
top: MySpaces.s14,
end: MySpaces.s12,
child: MyImage(
image: widget.index == widget.correctAnswer ? MyAssets.correct : MyAssets
image: index == correctAnswer ? MyAssets.correct : MyAssets
.wrong,
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));
if (scrollController.hasClients) {
if(currentLevel > 14){
if(currentLevel >= 14){
scrollController.animateTo(
scrollController.position.maxScrollExtent,
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 'package:bloc/bloc.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: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/hero_dialog_route.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/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/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/answer_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:hadi_hoda_flutter/features/question/presentation/ui/screens/answer_screen.dart';
import 'package:showcaseview/showcaseview.dart';
class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
@ -51,6 +54,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
GlobalKey(),
];
late final Stream<double> volumeStream;
bool isPlaying = false;
/// ------------Controllers------------
final AudioService _audioService;
@ -80,12 +84,41 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
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------------
FutureOr<void> _getLevelEvent(GetLevelEvent event, Emitter<QuestionState> emit) async {
await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then(
(value) {
value.fold(
(data) {
(data) async {
final LevelEntity level = LevelEntity(
id: data.id,
order: data.order,
@ -100,7 +133,8 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
levelEntity: level,
currentQuestion: data.questions?.first,
));
playVoice();
await playVoice();
playback();
},
(error) {
emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage)));
@ -116,6 +150,12 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
if (event.chooseCorrectAnswer) {
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 {
final QuestionEntity? findPreQuestion = state.currentQuestion;
final int findIndex = (findPreQuestion?.order ?? 1);
@ -137,6 +177,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
}
} else {
await playVoice();
playback();
}
});
}

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

@ -9,7 +9,8 @@ class GetLevelEvent extends QuestionEvent {
class ChooseAnswerEvent extends QuestionEvent {
final bool chooseCorrectAnswer;
const ChooseAnswerEvent(this.chooseCorrectAnswer);
final int correctAnswer;
const ChooseAnswerEvent(this.chooseCorrectAnswer, this.correctAnswer);
}
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,
),
),
),
),
);
}
}

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

@ -90,14 +90,17 @@ class QuestionScreen extends StatelessWidget {
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => AnswerBox(
builder: (context, state) => Hero(
key: Key('${state.currentQuestion?.id}'),
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) =>
onTap: (isCorrect, correctAnswer) =>
context.read<QuestionBloc>().add(
ChooseAnswerEvent(isCorrect),
ChooseAnswerEvent(isCorrect, correctAnswer),
),
),
),
),
@ -154,7 +157,7 @@ class QuestionScreen extends StatelessWidget {
),
Spacer(),
RefreshButton(
onTap: () {},
onTap: () => context.read<QuestionBloc>().playback(),
),
],
);

Loading…
Cancel
Save