Browse Source

add: guider page

pull/50/head
AmirrezaChegini 5 days ago
parent
commit
72afb18c34
  1. 10
      lib/core/middlewares/my_middlewares.dart
  2. 15
      lib/core/routers/my_routes.dart
  3. 57
      lib/core/widgets/answer_box/answer_box.dart
  4. 111
      lib/core/widgets/answer_box/answer_box_showcase.dart
  5. 6
      lib/core/widgets/showcase/my_showcase_widget.dart
  6. 33
      lib/features/guider/data/datasource/guider_datasource.dart
  7. 28
      lib/features/guider/data/repository_impl/guider_repository_impl.dart
  8. 7
      lib/features/guider/domain/repository/guider_repository.dart
  9. 17
      lib/features/guider/domain/usecases/get_first_level_usecase.dart
  10. 131
      lib/features/guider/presentation/bloc/guider_bloc.dart
  11. 8
      lib/features/guider/presentation/bloc/guider_event.dart
  12. 27
      lib/features/guider/presentation/bloc/guider_state.dart
  13. 247
      lib/features/guider/presentation/ui/guider_page.dart
  14. 57
      lib/features/question/presentation/bloc/question_bloc.dart
  15. 55
      lib/features/question/presentation/ui/screens/question_screen.dart
  16. 5
      lib/features/sample/presentation/ui/sample_page.dart
  17. 9
      lib/init_bindings.dart

10
lib/core/middlewares/my_middlewares.dart

@ -30,4 +30,14 @@ class MyMiddlewares {
return null;
}
}
static FutureOr<String?> question(BuildContext context, GoRouterState state) {
final String? firstShowCase = LocalStorage.readData(
key: MyConstants.firstShowcase);
if (firstShowCase == 'true') {
return null;
} else {
return '${Routes.guiderPage}/${state.pathParameters['id']}';
}
}
}

15
lib/core/routers/my_routes.dart

@ -6,6 +6,9 @@ import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_bloc.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_event.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/ui/download_page.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_bloc.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_event.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/ui/guider_page.dart';
import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_bloc.dart';
import 'package:hadi_hoda_flutter/features/home/presentation/ui/home_page.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart';
@ -37,6 +40,7 @@ class Routes {
static const String languagePage = '/language_page';
static const String homePage = '/home_page';
static const String questionPage = '/question_page';
static const String guiderPage = '/guider_page';
static const String levelPage = '/level_page';
}
@ -118,9 +122,20 @@ GoRouter _appPages() => GoRouter(
child: const LevelPage(),
),
),
GoRoute(
name: Routes.guiderPage,
path: '${Routes.guiderPage}/:id',
builder: (context, state) => BlocProvider(
create: (context) => GuiderBloc(locator())..add(GetFirstLevelEvent(
id: state.pathParameters['id'],
)),
child: const GuiderPage(),
),
),
GoRoute(
name: Routes.questionPage,
path: '${Routes.questionPage}/:id',
redirect: MyMiddlewares.question,
builder: (context, state) => BlocProvider(
create: (context) =>
QuestionBloc(

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

@ -1,12 +1,10 @@
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_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.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/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/my_showcase_widget.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerBox extends StatefulWidget {
@ -15,8 +13,6 @@ class AnswerBox extends StatefulWidget {
required this.answer,
required this.correctAnswer,
required this.index,
required this.globalKey,
required this.answerGlobalKey,
this.onTap,
this.onNotifTap,
});
@ -26,8 +22,6 @@ class AnswerBox extends StatefulWidget {
final void Function(bool isCorrect, int correctAnswer)? onTap;
final int index;
final Function(AnswerEntity answer)? onNotifTap;
final GlobalKey globalKey;
final GlobalKey answerGlobalKey;
@override
State<AnswerBox> createState() => _AnswerBoxState();
@ -57,18 +51,14 @@ class _AnswerBoxState extends State<AnswerBox> {
child: Stack(
alignment: Alignment.topCenter,
children: [
MyShowcaseWidget(
globalKey: widget.answerGlobalKey,
description: context.translate.showcase_answer,
child: AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
),
AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
),
Positioned(
left: 0,
@ -77,20 +67,23 @@ class _AnswerBoxState extends State<AnswerBox> {
child: AnswerTextBox(text: widget.answer.title ?? ''),
),
PositionedDirectional(
top: setSize(context: context, mobile: MySpaces.s12, tablet: MySpaces.s20),
end: setSize(context: context, mobile: MySpaces.s8, tablet: MySpaces.s20),
child: MyShowcaseWidget(
globalKey: widget.globalKey,
type: ShowcaseTooltipType.bottom,
description: context.translate.showcase_notif,
child: GestureDetector(
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
child: MyImage(
image: MyAssets.iconNotif,
size: setSize(context: context, tablet: 50),
),
top: setSize(
context: context,
mobile: MySpaces.s12,
tablet: MySpaces.s20,
),
end: setSize(
context: context,
mobile: MySpaces.s8,
tablet: MySpaces.s20,
),
child: GestureDetector(
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
child: MyImage(
image: MyAssets.iconNotif,
size: setSize(context: context, tablet: 50),
),
),
),

111
lib/core/widgets/answer_box/answer_box_showcase.dart

@ -0,0 +1,111 @@
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_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.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/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/my_showcase_widget.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerBoxShowCase extends StatefulWidget {
const AnswerBoxShowCase({
super.key,
required this.answer,
required this.correctAnswer,
required this.index,
this.globalKey,
this.answerGlobalKey,
this.onTap,
this.onNotifTap,
});
final AnswerEntity answer;
final int correctAnswer;
final void Function(bool isCorrect, int correctAnswer)? onTap;
final int index;
final Function(AnswerEntity answer)? onNotifTap;
final GlobalKey? globalKey;
final GlobalKey? answerGlobalKey;
@override
State<AnswerBoxShowCase> createState() => _AnswerBoxShowCaseState();
}
class _AnswerBoxShowCaseState extends State<AnswerBoxShowCase> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Hero(
tag: 'Hero_answer_${widget.answer.id}',
child: Material(
type: MaterialType.transparency,
child: GestureDetector(
onTap: !selected
? () {
setState(() {
selected = true;
});
widget.onTap?.call(
widget.index == widget.correctAnswer,
widget.correctAnswer,
);
}
: null,
child: Stack(
alignment: Alignment.topCenter,
children: [
MyShowcaseWidget(
globalKey: widget.answerGlobalKey,
description: context.translate.showcase_answer,
child: AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AnswerTextBox(text: widget.answer.title ?? ''),
),
PositionedDirectional(
top: setSize(
context: context,
mobile: MySpaces.s12,
tablet: MySpaces.s20,
),
end: setSize(
context: context,
mobile: MySpaces.s8,
tablet: MySpaces.s20,
),
child: MyShowcaseWidget(
globalKey: widget.globalKey,
type: ShowcaseTooltipType.bottom,
description: context.translate.showcase_notif,
child: GestureDetector(
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
child: MyImage(
image: MyAssets.iconNotif,
size: setSize(context: context, tablet: 50),
),
),
),
),
],
),
),
),
);
}
}

6
lib/core/widgets/showcase/my_showcase_widget.dart

@ -82,13 +82,13 @@ enum ShowcaseTooltipType {
class MyShowcaseWidget extends StatelessWidget {
const MyShowcaseWidget({
super.key,
required this.globalKey,
required this.child,
this.globalKey,
this.description,
this.type = ShowcaseTooltipType.bottom,
});
final GlobalKey globalKey;
final GlobalKey? globalKey;
final String? description;
final Widget child;
final ShowcaseTooltipType type;
@ -96,7 +96,7 @@ class MyShowcaseWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Showcase(
key: globalKey,
key: globalKey ?? GlobalKey(),
disableBarrierInteraction: false,
targetShapeBorder: CircleBorder(),
overlayColor: Color(0XFF0F0041),

33
lib/features/guider/data/datasource/guider_datasource.dart

@ -0,0 +1,33 @@
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/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/total_data_entity.dart';
import 'package:hive/hive.dart';
abstract class IGuiderDatasource {
Future<LevelEntity> getLevel();
}
/// Local
class GuiderDatasourceImpl implements IGuiderDatasource {
const GuiderDatasourceImpl();
@override
Future<LevelEntity> getLevel() async {
try {
final String selectedLanguage =
LocalStorage.readData(key: MyConstants.selectLanguage) ??
MyConstants.defaultLanguage;
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?.first;
return findLevel ?? LevelEntity();
} catch (e) {
throw MyException(errorMessage: '$e');
}
}
}

28
lib/features/guider/data/repository_impl/guider_repository_impl.dart

@ -0,0 +1,28 @@
import 'package:flutter/foundation.dart';
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/utils/data_state.dart';
import 'package:hadi_hoda_flutter/features/guider/data/datasource/guider_datasource.dart';
import 'package:hadi_hoda_flutter/features/guider/domain/repository/guider_repository.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
class GuiderRepositoryImpl implements IGuiderRepository {
final IGuiderDatasource datasource;
const GuiderRepositoryImpl(this.datasource);
@override
Future<DataState<LevelEntity, MyException>> getLevel() async {
try {
final LevelEntity response = await datasource.getLevel();
return DataState.success(response);
} on MyException catch (e) {
return DataState.error(e);
} catch (e) {
if (kDebugMode) {
rethrow;
} else {
return DataState.error(MyException(errorMessage: '$e'));
}
}
}
}

7
lib/features/guider/domain/repository/guider_repository.dart

@ -0,0 +1,7 @@
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart';
import 'package:hadi_hoda_flutter/core/utils/data_state.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
abstract class IGuiderRepository {
Future<DataState<LevelEntity, MyException>> getLevel();
}

17
lib/features/guider/domain/usecases/get_first_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/no_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/guider/domain/repository/guider_repository.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
class GetFirstLevelUseCase implements UseCase<LevelEntity, NoParams> {
final IGuiderRepository repository;
const GetFirstLevelUseCase(this.repository);
@override
Future<DataState<LevelEntity, MyException>> call(NoParams params) {
return repository.getLevel();
}
}

131
lib/features/guider/presentation/bloc/guider_bloc.dart

@ -0,0 +1,131 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.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/no_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/local_storage.dart';
import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
import 'package:hadi_hoda_flutter/features/guider/domain/usecases/get_first_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_event.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_state.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:showcaseview/showcaseview.dart';
class GuiderBloc extends Bloc<GuiderEvent, GuiderState> {
/// ------------constructor------------
GuiderBloc(this._getFirstLevelUseCase) : super(const GuiderState()) {
registerShowCase();
on<GetFirstLevelEvent>(_getFirstLevelEvent);
}
@override
Future<void> close() {
unRegisterShowCase();
animationController.dispose();
return super.close();
}
/// ------------UseCases------------
final GetFirstLevelUseCase _getFirstLevelUseCase;
/// ------------Variables------------
final Map<String, GlobalKey> showCaseKey = {
'answer_key_0': GlobalKey(),
'answer_key_1': GlobalKey(),
'answer_key_2': GlobalKey(),
'answer_key_3': GlobalKey(),
'notif_key_0': GlobalKey(),
'notif_key_1': GlobalKey(),
'notif_key_2': GlobalKey(),
'notif_key_3': GlobalKey(),
'stepper_key': GlobalKey(),
'hadith_key': GlobalKey(),
'guide_key': GlobalKey(),
};
String? id;
/// ------------Controllers------------
late final AnimationController animationController;
/// ------------Functions------------
void registerShowCase() {
try {
ShowcaseView.register(
onStart: (showcaseIndex, key) {
LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'true');
},
onDismiss: (onDismiss) {
MyContext.get.goNamed(
Routes.questionPage,
pathParameters: {'id': id ?? ''},
);
},
onFinish: () {
MyContext.get.goNamed(
Routes.questionPage,
pathParameters: {'id': id ?? ''},
);
},
);
} catch (_) {}
}
void unRegisterShowCase() {
try {
ShowcaseView.get().unregister();
} catch (_) {}
}
void startShowcase() {
if (LocalStorage.readData(key: MyConstants.firstShowcase) != 'true') {
try {
ShowcaseView.get().startShowCase([
showCaseKey['answer_key_1']!,
showCaseKey['notif_key_0']!,
showCaseKey['stepper_key']!,
showCaseKey['hadith_key']!,
showCaseKey['guide_key']!,
]);
} catch (_) {}
}
}
/// ------------Event Calls------------
FutureOr<void> _getFirstLevelEvent(
GetFirstLevelEvent event,
Emitter<GuiderState> emit,
) async {
id = event.id;
await _getFirstLevelUseCase(NoParams()).then(
(value) => value.fold((data) async {
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: level,
currentQuestion: data.questions?.first,
),
);
await Future.delayed(Duration(milliseconds: 300), () {
animationController.forward().then((value) {
startShowcase();
});
});
}, (error) {}),
);
}
}

8
lib/features/guider/presentation/bloc/guider_event.dart

@ -0,0 +1,8 @@
sealed class GuiderEvent {
const GuiderEvent();
}
class GetFirstLevelEvent extends GuiderEvent {
final String? id;
const GetFirstLevelEvent({this.id});
}

27
lib/features/guider/presentation/bloc/guider_state.dart

@ -0,0 +1,27 @@
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/question/domain/entity/question_entity.dart';
class GuiderState {
final BaseStatus getQuestionStatus;
final LevelEntity? levelEntity;
final QuestionEntity? currentQuestion;
const GuiderState({
this.getQuestionStatus = const BaseInit(),
this.levelEntity,
this.currentQuestion,
});
GuiderState copyWith({
BaseStatus? getQuestionStatus,
LevelEntity? levelEntity,
QuestionEntity? currentQuestion,
}) {
return GuiderState(
getQuestionStatus: getQuestionStatus ?? this.getQuestionStatus,
levelEntity: levelEntity ?? this.levelEntity,
currentQuestion: currentQuestion ?? this.currentQuestion,
);
}
}

247
lib/features/guider/presentation/ui/guider_page.dart

@ -0,0 +1,247 @@
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_audios.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_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/fade_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_down_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box_showcase.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/pop_scope/my_pop_scope.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/my_showcase_widget.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_bloc.dart';
import 'package:hadi_hoda_flutter/features/guider/presentation/bloc/guider_state.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/glassy_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_title.dart';
class GuiderPage extends StatefulWidget {
const GuiderPage({super.key});
@override
State<GuiderPage> createState() => _GuiderPageState();
}
class _GuiderPageState extends State<GuiderPage> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
context
.read<GuiderBloc>()
.animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: MyPopScope(
child: Directionality(
textDirection: TextDirection.ltr,
child: Container(
height: context.heightScreen,
width: context.widthScreen,
padding: EdgeInsets.symmetric(
horizontal:
setSize(
context: context,
mobile: MySpaces.s16,
tablet: MySpaces.s30,
) ??
0,
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0XFF6930DA), Color(0XFF263AA1)],
),
image: DecorationImage(
image: AssetImage(MyAssets.pattern),
scale: 3,
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
Colors.black.withValues(alpha: 0.3),
BlendMode.srcIn,
),
),
),
child: Column(
children: [
setPlatform<double>(
android: MySpaces.s20,
iOS: 50,
)?.gapHeight ??
SizedBox.shrink(),
_topButtons(context),
MySpaces.s10.gapHeight,
Expanded(
child: Column(
children: [
_stepper(context),
_titles(context),
MySpaces.s20.gapHeight,
Expanded(child: _answers(context)),
_bottom(context),
],
),
),
setPlatform<double>(android: MySpaces.s20)?.gapHeight ??
SizedBox.shrink(),
],
),
),
),
),
);
}
Widget _topButtons(BuildContext context) {
return SlideDownFade(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GlassyButton(
image: MyAssets.home,
audio: MyAudios.back,
onTap: () {},
),
BlocBuilder<GuiderBloc, GuiderState>(
builder: (context, state) =>
QuestionTitle(
step: state.levelEntity?.order,
currentQuestion: state.currentQuestion?.order,
questionLength: state.levelEntity?.questions?.length,
),
),
GlassyButton(image: MyAssets.music, onTap: () {}),
],
),
);
}
Widget _stepper(BuildContext context) {
return FadeAnim(
child: MyShowcaseWidget(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['stepper_key']!,
description: context.translate.showcase_stepper,
child: QuestionStepper(length: 4, currentStep: 1),
),
);
}
Widget _titles(BuildContext context) {
return BlocBuilder<GuiderBloc, GuiderState>(
builder: (context, state) =>
FadeAnim(
child: Text(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
maxLines: 3,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
offset: Offset(0, 2),
color: MyColors.black.withValues(alpha: 0.25),
),
],
),
),
),
);
}
Widget _answers(BuildContext context) {
return BlocBuilder<GuiderBloc, GuiderState>(
builder: (context, state) =>
GridView.builder(
itemCount: state.currentQuestion?.answers?.length ?? 0,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
clipBehavior: Clip.none,
padding: EdgeInsets.symmetric(
horizontal: setSize(context: context, tablet: 70) ?? 0,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: MySpaces.s20,
mainAxisSpacing: 20,
childAspectRatio: 0.75,
),
itemBuilder: (context, index) =>
state.currentQuestion?.answers?[index].imageId == null
? SizedBox.shrink()
: SlideAnim(
controller: context.read<GuiderBloc>().animationController,
index: index,
child: AnswerBoxShowCase(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['notif_key_$index']!,
answerGlobalKey: context
.read<GuiderBloc>()
.showCaseKey['answer_key_$index']!,
index: state.currentQuestion?.answers?[index].order ?? 1,
answer:
state.currentQuestion?.answers?[index] ?? AnswerEntity(),
correctAnswer: 0,
onNotifTap: (AnswerEntity answer) {},
onTap: (isCorrect, correctAnswer) {},
),
),
),
);
}
Widget _bottom(BuildContext context) {
return SlideUpFade(
child: SizedBox(
width: context.widthScreen,
child: Stack(
alignment: AlignmentDirectional.centerStart,
children: [
MyShowcaseWidget(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['guide_key']!,
description: context.translate.showcase_guide,
type: ShowcaseTooltipType.top,
child: MyImage(
image: MyAssets.globe,
fit: BoxFit.cover,
size: setSize(context: context, tablet: 120),
),
),
PositionedDirectional(
end: 0,
child: MyShowcaseWidget(
globalKey: context
.read<GuiderBloc>()
.showCaseKey['hadith_key']!,
type: ShowcaseTooltipType.topLeft,
description: context.translate.showcase_hadith,
child: GlassyButton(image: MyAssets.leaf, onTap: () {}),
),
),
],
),
),
);
}
}

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

@ -21,7 +21,6 @@ import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_next_lev
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> {
/// ------------constructor------------
@ -34,14 +33,12 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
volumeStream = _mainAudioService.volumeStream();
playingStream = _mainAudioService.playingStream();
initAudios();
registerShowCase();
on<GetLevelEvent>(_getLevelEvent);
on<ChooseAnswerEvent>(_chooseAnswerEvent);
}
@override
Future<void> close() {
unRegisterShowCase();
if (_mainAudioService.audioVolume != 0) {
_mainAudioService.setVolume(volume: MyConstants.musicAudioVolume);
}
@ -83,43 +80,6 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
final ScrollController titleController = ScrollController();
/// ------------Functions------------
void registerShowCase() {
try {
ShowcaseView.register(
onStart: (showcaseIndex, key) {
LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'true');
},
onDismiss: (onDismiss) async {
await playQuestionAudio();
},
onFinish: () async {
await playQuestionAudio();
},
);
} catch (_) {}
}
void unRegisterShowCase() {
try {
ShowcaseView.get().unregister();
} catch (_) {}
}
void startShowcase() {
if (LocalStorage.readData(key: MyConstants.firstShowcase) != 'true') {
try {
ShowcaseView.get().startShowCase([
showCaseKey['answer_key_1']!,
showCaseKey['notif_key_0']!,
showCaseKey['stepper_key']!,
showCaseKey['hadith_key']!,
showCaseKey['guide_key']!,
]);
} catch (_) {}
}
}
void startScrollTitle({Duration? audioDuration}) {
if (audioDuration == null || audioDuration == Duration.zero) return;
titleController.animateTo(
@ -283,18 +243,11 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
levelEntity: level,
currentQuestion: data.questions?.first,
));
if(LocalStorage.readData(key: MyConstants.firstShowcase) != 'true'){
await Future.delayed(Duration(milliseconds: 500));
answerAnimationController.forward().then((value) {
startShowcase();
});
} else {
await playQuestionAudio();
imageAnimationController.reverse();
answerAnimationController.forward().then((value) {
showQueueAnswer();
});
}
await playQuestionAudio();
imageAnimationController.reverse();
answerAnimationController.forward().then((value) {
showQueueAnswer();
});
},
(error) {
emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage)));

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

@ -18,7 +18,6 @@ import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/my_showcase_widget.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';
@ -115,14 +114,10 @@ class _QuestionScreenState extends State<QuestionScreen> with TickerProviderStat
return BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => MyShowcaseWidget(
globalKey: context.read<QuestionBloc>().showCaseKey['stepper_key']!,
description: context.translate.showcase_stepper,
child: FadeAnim(
child: QuestionStepper(
length: state.levelEntity?.questions?.length ?? 0,
currentStep: state.currentQuestion?.order ?? 1,
),
builder: (context, state) => FadeAnim(
child: QuestionStepper(
length: state.levelEntity?.questions?.length ?? 0,
currentStep: state.currentQuestion?.order ?? 1,
),
),
);
@ -184,12 +179,8 @@ class _QuestionScreenState extends State<QuestionScreen> with TickerProviderStat
controller: context.read<QuestionBloc>().answerAnimationController,
index: index,
child: AnswerBox(
globalKey: context.read<QuestionBloc>().showCaseKey['notif_key_$index']!,
answerGlobalKey: context.read<QuestionBloc>().showCaseKey['answer_key_$index']!,
index: state.currentQuestion?.answers?[index].order ?? 1,
answer:
state.currentQuestion?.answers?[index] ??
AnswerEntity(),
answer: state.currentQuestion?.answers?[index] ?? AnswerEntity(),
correctAnswer: state.currentQuestion?.correctAnswer ?? 0,
onNotifTap: (AnswerEntity answer) {
context.read<QuestionBloc>().showAnswerDialog(
@ -214,20 +205,15 @@ class _QuestionScreenState extends State<QuestionScreen> with TickerProviderStat
child: Stack(
alignment: AlignmentDirectional.centerStart,
children: [
MyShowcaseWidget(
globalKey: context.read<QuestionBloc>().showCaseKey['guide_key']!,
description: context.translate.showcase_guide,
type: ShowcaseTooltipType.top,
child: StreamBuilder<bool>(
initialData: false,
stream: context.read<QuestionBloc>().playingStream,
builder: (context, snapshot) => GlobeAnimation(
state: snapshot.data ?? false,
child: MyImage(
image: MyAssets.globe,
fit: BoxFit.cover,
size: setSize(context: context, tablet: 120),
),
StreamBuilder<bool>(
initialData: false,
stream: context.read<QuestionBloc>().playingStream,
builder: (context, snapshot) => GlobeAnimation(
state: snapshot.data ?? false,
child: MyImage(
image: MyAssets.globe,
fit: BoxFit.cover,
size: setSize(context: context, tablet: 120),
),
),
),
@ -255,15 +241,10 @@ class _QuestionScreenState extends State<QuestionScreen> with TickerProviderStat
),
PositionedDirectional(
end: 0,
child: MyShowcaseWidget(
globalKey: context.read<QuestionBloc>().showCaseKey['hadith_key']!,
type: ShowcaseTooltipType.topLeft,
description: context.translate.showcase_hadith,
child: GlassyButton(
image: MyAssets.leaf,
onTap: () =>
context.read<QuestionBloc>().showHadith(context: context),
),
child: GlassyButton(
image: MyAssets.leaf,
onTap: () =>
context.read<QuestionBloc>().showHadith(context: context),
),
),
],

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

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class SamplePage extends StatelessWidget {
const SamplePage({super.key});
@ -8,9 +7,7 @@ class SamplePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Lottie.asset(
'assets/animations/Celebration.json',
),
child: Text('Sample Page'),
),
);
}

9
lib/init_bindings.dart

@ -13,6 +13,10 @@ import 'package:hadi_hoda_flutter/features/download/domain/usecases/get_audios_u
import 'package:hadi_hoda_flutter/features/download/domain/usecases/get_images_usecase.dart';
import 'package:hadi_hoda_flutter/features/download/domain/usecases/loading_stream_usecase.dart';
import 'package:hadi_hoda_flutter/features/download/domain/usecases/save_levels_usecase.dart';
import 'package:hadi_hoda_flutter/features/guider/data/datasource/guider_datasource.dart';
import 'package:hadi_hoda_flutter/features/guider/data/repository_impl/guider_repository_impl.dart';
import 'package:hadi_hoda_flutter/features/guider/domain/repository/guider_repository.dart';
import 'package:hadi_hoda_flutter/features/guider/domain/usecases/get_first_level_usecase.dart';
import 'package:hadi_hoda_flutter/features/level/data/datasource/level_datasource.dart';
import 'package:hadi_hoda_flutter/features/level/data/repository_impl/level_repository_impl.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
@ -63,6 +67,11 @@ void initBindings() {
locator.registerLazySingleton<SaveLevelsUseCase>(() => SaveLevelsUseCase(locator()));
locator.registerLazySingleton<LoadingStreamUseCase>(() => LoadingStreamUseCase(locator()));
/// Guider Feature
locator.registerLazySingleton<IGuiderDatasource>(() => GuiderDatasourceImpl());
locator.registerLazySingleton<IGuiderRepository>(() => GuiderRepositoryImpl(locator()));
locator.registerLazySingleton<GetFirstLevelUseCase>(() => GetFirstLevelUseCase(locator()));
/// Question Feature
locator.registerLazySingleton<IQuestionDatasource>(() => QuestionDatasourceImpl());
locator.registerLazySingleton<IQuestionRepository>(() => QuestionRepositoryImpl(locator()));

Loading…
Cancel
Save