diff --git a/assets/svg/check.svg b/assets/svg/check.svg
new file mode 100644
index 0000000..9b0d0d5
--- /dev/null
+++ b/assets/svg/check.svg
@@ -0,0 +1,32 @@
+
diff --git a/assets/videos/globe_state_after_wrong.gif b/assets/videos/globe_state_after_wrong.gif
index 60070d6..cc7614b 100644
Binary files a/assets/videos/globe_state_after_wrong.gif and b/assets/videos/globe_state_after_wrong.gif differ
diff --git a/assets/videos/globe_state_normal.gif b/assets/videos/globe_state_normal.gif
index 2b22c94..f2e6cae 100644
Binary files a/assets/videos/globe_state_normal.gif and b/assets/videos/globe_state_normal.gif differ
diff --git a/assets/videos/globe_state_speaking.gif b/assets/videos/globe_state_speaking.gif
index 49b3f20..5e3f3c5 100644
Binary files a/assets/videos/globe_state_speaking.gif and b/assets/videos/globe_state_speaking.gif differ
diff --git a/assets/videos/globe_state_success.gif b/assets/videos/globe_state_success.gif
index 4c41ce3..6ed1fe3 100644
Binary files a/assets/videos/globe_state_success.gif and b/assets/videos/globe_state_success.gif differ
diff --git a/assets/videos/globe_state_wrong.gif b/assets/videos/globe_state_wrong.gif
index 845abf5..1d0cb72 100644
Binary files a/assets/videos/globe_state_wrong.gif and b/assets/videos/globe_state_wrong.gif differ
diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart
index 8e16d4c..e43ed0a 100644
--- a/lib/common_ui/resources/my_assets.dart
+++ b/lib/common_ui/resources/my_assets.dart
@@ -73,6 +73,7 @@ class MyAssets {
static const String iconPlay = 'assets/svg/icon_play.svg.vec';
static const String iconNotif = 'assets/svg/icon_notif.svg.vec';
static const String iconPlayVideo = 'assets/svg/icon_play_video.svg.vec';
+ static const String check = 'assets/svg/check.svg.vec';
static final List images = [
backgroundHome,
diff --git a/lib/core/constants/my_constants.dart b/lib/core/constants/my_constants.dart
index 41d2335..a8813e7 100644
--- a/lib/core/constants/my_constants.dart
+++ b/lib/core/constants/my_constants.dart
@@ -30,8 +30,8 @@ class MyConstants {
static const double effectAudioVolume = 0.2;
static const String defaultLanguage = 'en';
static const List languages = [
- LanguageEntity(title: 'English (English)', code: 'en'),
- LanguageEntity(title: 'German (Germany)', code: 'de'),
- LanguageEntity(title: 'Arabic (العربية)', code: 'ar'),
+ LanguageEntity(displayName: 'English (English)', code: 'en', title: ''),
+ LanguageEntity(displayName: 'German (Germany)', code: 'de', title: ''),
+ LanguageEntity(displayName: 'Arabic (العربية)', code: 'ar', title: ''),
];
}
diff --git a/lib/core/widgets/answer_box/answer_box_showcase.dart b/lib/core/widgets/answer_box/answer_box_showcase.dart
index d0d1423..b1bd522 100644
--- a/lib/core/widgets/answer_box/answer_box_showcase.dart
+++ b/lib/core/widgets/answer_box/answer_box_showcase.dart
@@ -89,6 +89,7 @@ class _AnswerBoxShowCaseState extends State {
),
child: MyShowcaseWidget(
globalKey: widget.globalKey,
+ highlightRadiusIncrease: 15,
type: ShowcaseTooltipType.bottom,
description: context.translate.showcase_notif,
child: GestureDetector(
diff --git a/lib/core/widgets/showcase/my_showcase_widget.dart b/lib/core/widgets/showcase/my_showcase_widget.dart
index 4b762f1..0b22112 100644
--- a/lib/core/widgets/showcase/my_showcase_widget.dart
+++ b/lib/core/widgets/showcase/my_showcase_widget.dart
@@ -86,12 +86,14 @@ class MyShowcaseWidget extends StatelessWidget {
this.globalKey,
this.description,
this.type = ShowcaseTooltipType.bottom,
+ this.highlightRadiusIncrease = 0,
});
final GlobalKey? globalKey;
final String? description;
final Widget child;
final ShowcaseTooltipType type;
+ final double highlightRadiusIncrease;
@override
Widget build(BuildContext context) {
@@ -99,6 +101,7 @@ class MyShowcaseWidget extends StatelessWidget {
key: globalKey ?? GlobalKey(),
disableBarrierInteraction: false,
targetShapeBorder: const CircleBorder(),
+ targetPadding: EdgeInsets.all(highlightRadiusIncrease),
overlayColor: const Color(0XFF0F0041),
overlayOpacity: 0.82,
/// ToolTip
diff --git a/lib/features/app/presentation/bloc/app_bloc.dart b/lib/features/app/presentation/bloc/app_bloc.dart
index 1c5c206..eebdb70 100644
--- a/lib/features/app/presentation/bloc/app_bloc.dart
+++ b/lib/features/app/presentation/bloc/app_bloc.dart
@@ -21,7 +21,7 @@ class AppBloc extends Bloc {
// Try to find in constants first, or create a temporary one to get locale
final LanguageEntity findLanguage = MyConstants.languages.firstWhere(
(e) => e.code == selectLanguage,
- orElse: () => LanguageEntity(code: selectLanguage, title: ''),
+ orElse: () => LanguageEntity(code: selectLanguage, title: '', displayName: ''),
);
emit(state.copyWith(locale: findLanguage.locale));
diff --git a/lib/features/download/data/datasource/download_datasource.dart b/lib/features/download/data/datasource/download_datasource.dart
index c125f3c..bb41a2b 100644
--- a/lib/features/download/data/datasource/download_datasource.dart
+++ b/lib/features/download/data/datasource/download_datasource.dart
@@ -68,6 +68,13 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
int _audioCompleted = 0;
int _targetLevel = 0;
+ Future _ensureLevelFolderExists(String levelPath) async {
+ final Directory directory = Directory(levelPath);
+ if (!await directory.exists()) {
+ await directory.create(recursive: true);
+ }
+ }
+
void _emitProgress() {
final int totalTasks = _targetLevel * 2;
final int completedTasks = _imageCompleted + _audioCompleted;
@@ -89,17 +96,7 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
final Directory directory = Directory(levelPath);
- if (!await directory.exists()) {
- return false;
- }
-
- final List files = directory.listSync(recursive: false);
-
- if (files.isEmpty) {
- return false;
- }
-
- return true;
+ return directory.exists();
}
Future _getLevelsImages(int toLevel) async {
@@ -135,6 +132,9 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
if (response is Response && response.statusCode == 204) {
debugPrint("No image content for level $level (204)");
+ await _ensureLevelFolderExists(
+ '${StoragePath.documentDir.path}/$level/',
+ );
_imageCompleted++;
_emitProgress();
return;
@@ -178,17 +178,7 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
final Directory directory = Directory(levelPath);
- if (!await directory.exists()) {
- return false;
- }
-
- final List files = directory.listSync(recursive: false);
-
- if (files.isEmpty) {
- return false;
- }
-
- return true;
+ return directory.exists();
}
Future _getLevelsAudios(int toLevel) async {
@@ -232,6 +222,9 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
if (response is Response && response.statusCode == 204) {
debugPrint("No audio content for level $level (204)");
+ await _ensureLevelFolderExists(
+ '${StoragePath.documentDir.path}/$lang/$level/',
+ );
_audioCompleted++;
_emitProgress();
return;
diff --git a/lib/features/download/presentation/ui/download_page.dart b/lib/features/download/presentation/ui/download_page.dart
index 9ac0b4d..daa3b67 100644
--- a/lib/features/download/presentation/ui/download_page.dart
+++ b/lib/features/download/presentation/ui/download_page.dart
@@ -25,7 +25,7 @@ import 'package:hadi_hoda_flutter/features/download/presentation/ui/widgets/down
class DownloadPage extends StatefulWidget {
final DownloadPageConfig config;
- const DownloadPage({super.key,required this.config});
+ const DownloadPage({super.key, required this.config});
@override
State createState() => _DownloadPageState();
@@ -70,8 +70,8 @@ class _DownloadPageState extends State {
),
child: BlocConsumer(
listener: (context, state) {
- if(state.status is BaseComplete) {
- if(widget.config.redirectTo == Routes.homePage) {
+ if (state.status is BaseComplete) {
+ if (widget.config.redirectTo == Routes.homePage) {
context.goNamed(Routes.homePage);
} else {
context.pushNamed(
@@ -87,7 +87,9 @@ class _DownloadPageState extends State {
return Padding(
padding: EdgeInsets.symmetric(
vertical:
- MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16,
+ MediaQuery
+ .viewPaddingOf(context)
+ .bottom + MySpaces.s16,
horizontal: 60,
),
child: ErrorState(
@@ -130,15 +132,28 @@ class _DownloadPageState extends State {
bottom: 130,
child: Column(
spacing: MySpaces.s6,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- Text(context.translate.please_wait, style: MYTextStyle.titr0),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: Text(context.translate.please_wait,
+ style: MYTextStyle.titr0.copyWith(fontSize: 24),
+ textAlign: TextAlign.center,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis),
+ ),
StreamBuilder(
initialData: DownloadEntity.empty(),
- stream: context.read().loadingStream,
- builder: (context, snapshot) => Text(
- 'Downloading ...${snapshot.data?.downloadedLevels}/${widget.config.downloadToLevel}',
- style: MYTextStyle.matn3,
- ),
+ stream: context
+ .read()
+ .loadingStream,
+ builder: (context, snapshot) =>
+ Text(
+ 'Downloading ...${snapshot.data?.downloadedLevels}/${widget
+ .config.downloadToLevel}',
+ textAlign: TextAlign.center,
+ style: MYTextStyle.matn3,
+ ),
),
],
),
@@ -149,7 +164,9 @@ class _DownloadPageState extends State {
return Positioned(
bottom: MySpaces.s40,
child: DownloadLoadingWidget(
- loadingStream: context.read().loadingStream,
+ loadingStream: context
+ .read()
+ .loadingStream,
),
);
}
diff --git a/lib/features/language/data/model/language_model.dart b/lib/features/language/data/model/language_model.dart
index 3383b39..fa3fc52 100644
--- a/lib/features/language/data/model/language_model.dart
+++ b/lib/features/language/data/model/language_model.dart
@@ -1,12 +1,13 @@
import 'package:hadi_hoda_flutter/features/language/domain/entity/language_entity.dart';
class LanguageModel extends LanguageEntity {
- const LanguageModel({required super.code, required super.title});
+ const LanguageModel({required super.code,required super.displayName, required super.title});
factory LanguageModel.fromJson(Map json) {
return LanguageModel(
code: json['code'] ?? '',
title: json['name'] ?? '', // API returns 'name', we map to 'title'
+ displayName: json['display_name'] ?? '', // API returns 'name', we map to 'title'
);
}
}
diff --git a/lib/features/language/domain/entity/language_entity.dart b/lib/features/language/domain/entity/language_entity.dart
index b5068a1..b43153f 100644
--- a/lib/features/language/domain/entity/language_entity.dart
+++ b/lib/features/language/domain/entity/language_entity.dart
@@ -4,8 +4,9 @@ import 'package:equatable/equatable.dart';
class LanguageEntity extends Equatable {
final String code;
final String title;
+ final String displayName;
- const LanguageEntity({required this.code, required this.title});
+ const LanguageEntity({required this.code, required this.title, required this.displayName});
Locale get locale {
final parts = code.split('_');
diff --git a/lib/features/language/presentation/ui/language_page.dart b/lib/features/language/presentation/ui/language_page.dart
index 768e99f..a934b2a 100644
--- a/lib/features/language/presentation/ui/language_page.dart
+++ b/lib/features/language/presentation/ui/language_page.dart
@@ -133,11 +133,14 @@ class _LanguagePageState extends State {
Center(
child: Container(
width: double.infinity,
- height: itemSize + 4,
+ alignment: Alignment.centerLeft,
+ height: itemSize,
+ padding: const EdgeInsets.symmetric(horizontal: 18),
decoration: BoxDecoration(
- color: Colors.black.withOpacity(0.5),
- borderRadius: BorderRadius.circular(16),
+ color: Colors.white.withValues(alpha: .2),
+ borderRadius: BorderRadius.circular(12),
),
+ child: const MyImage(image: MyAssets.check),
),
),
WheelChooser.choices(
@@ -156,17 +159,17 @@ class _LanguagePageState extends State {
}
},
selectTextStyle: MYTextStyle.titr1.copyWith(
- fontSize: itemSize * 0.4,
+ fontSize: itemSize * 0.34,
color: Colors.white,
),
unSelectTextStyle: MYTextStyle.titr1.copyWith(
- fontSize: itemSize * 0.4,
+ fontSize: itemSize * 0.34,
color: Colors.white.withOpacity(0.5),
),
choices: state.languages.map((lang) {
return WheelChoice(
value: lang,
- title: lang.title,
+ title: lang.displayName,
);
}).toList(),
),
@@ -191,15 +194,13 @@ class _LanguagePageState extends State {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const MyImage(image: MyAssets.lang, size: 28),
- Expanded(
- child: AutoSizeText(
- context.translate.select_language,
- minFontSize: 12,
- maxFontSize: 20,
- maxLines: 1,
- textAlign: TextAlign.center,
- style: MYTextStyle.titr0.copyWith(color: const Color(0XFF847AC4)),
- ),
+ AutoSizeText(
+ context.translate.select_language,
+ minFontSize: 12,
+ maxFontSize: 20,
+ maxLines: 1,
+ textAlign: TextAlign.center,
+ style: MYTextStyle.titr0.copyWith(color: const Color(0XFF847AC4)),
),
],
);
diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart
index 54017d9..851cbb6 100644
--- a/lib/features/level/presentation/bloc/level_bloc.dart
+++ b/lib/features/level/presentation/bloc/level_bloc.dart
@@ -84,7 +84,7 @@ class LevelBloc extends Bloc {
),
LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.33.h, tablet: 0.55.h),
- left: setSize(context: MyContext.get, mobile: 0.65.w, tablet: 0.8.w),
+ left: setSize(context: MyContext.get, mobile: 0.63.w, tablet: 0.8.w),
index: 5,
),
LevelLocation(
diff --git a/lib/features/level/presentation/ui/level_page.dart b/lib/features/level/presentation/ui/level_page.dart
index 462e697..aca3773 100644
--- a/lib/features/level/presentation/ui/level_page.dart
+++ b/lib/features/level/presentation/ui/level_page.dart
@@ -329,22 +329,22 @@ class _LevelPageState extends State {
);
}
- Widget _downloadIndicator() {
- return BlocBuilder(
- buildWhen: (prev, curr) => prev.status != curr.status,
- builder: (context, downloadState) {
- if (downloadState.status is! BaseLoading) {
- return const SizedBox.shrink();
- }
- return Padding(
- padding: const EdgeInsets.only(top: MySpaces.s10),
- child: DownloadLoadingWidget(
- loadingStream: context.read().loadingStream,
- ),
- );
- },
- );
- }
+ // Widget _downloadIndicator() {
+ // return BlocBuilder(
+ // buildWhen: (prev, curr) => prev.status != curr.status,
+ // builder: (context, downloadState) {
+ // if (downloadState.status is! BaseLoading) {
+ // return const SizedBox.shrink();
+ // }
+ // return Padding(
+ // padding: const EdgeInsets.only(top: MySpaces.s10),
+ // child: DownloadLoadingWidget(
+ // loadingStream: context.read().loadingStream,
+ // ),
+ // );
+ // },
+ // );
+ // }
Widget _background(BuildContext context) {
return const MyImage(
diff --git a/lib/features/level/presentation/ui/widgets/play_button.dart b/lib/features/level/presentation/ui/widgets/play_button.dart
index 1a121b3..8200877 100644
--- a/lib/features/level/presentation/ui/widgets/play_button.dart
+++ b/lib/features/level/presentation/ui/widgets/play_button.dart
@@ -15,6 +15,8 @@ class PlayButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final playTitle = context.translate.play;
+ final playTitleLength = playTitle.length;
return MyInkwell(
onTap: () => onTap?.call(level),
child: Stack(
@@ -35,9 +37,11 @@ class PlayButton extends StatelessWidget {
colors: [Color(0XFFF9601F), Color(0XFFD93D16)],
).createShader(bounds),
child: Text(
- context.translate.play,
+ playTitle,
maxLines: 1,
style: MYTextStyle.button1.copyWith(
+ fontSize: playTitleLength > 5 ? 36: null,
+ height: playTitleLength > 5 ? 1.75: null,
shadows: [
BoxShadow(
color: const Color(0XFFFFFFAB).withValues(alpha: 0.40),
diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart
index 738782b..2a9170d 100644
--- a/lib/features/question/presentation/ui/screens/question_screen.dart
+++ b/lib/features/question/presentation/ui/screens/question_screen.dart
@@ -96,17 +96,19 @@ class _QuestionScreenState extends State
duration: const Duration(seconds: 1),
child: FadeAnimController(
controller: context.read().imageAnimationController!,
- child: Column(
- children: [
- _titles(context),
- 20.0.gapHeight,
- BlocBuilder(
- builder: (context, state) => ImageBox(
- key: Key('${state.currentQuestion?.image}'),
- image: state.currentQuestion?.image ?? '',
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ _titles(context),
+ 20.0.gapHeight,
+ BlocBuilder(
+ builder: (context, state) => ImageBox(
+ key: Key('${state.currentQuestion?.image}'),
+ image: state.currentQuestion?.image ?? '',
+ ),
),
- ),
- ],
+ ],
+ ),
),
),
);
@@ -134,7 +136,6 @@ class _QuestionScreenState extends State
textAlign: TextAlign.center,
minFontSize: 16,
maxFontSize: 20,
- maxLines: 8,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
@@ -148,23 +149,34 @@ class _QuestionScreenState extends State
}
Widget _answers(BuildContext context) {
- return ListView(
- controller: context.read().scrollController,
- padding: const EdgeInsets.only(top: 10),
- children: [
- FadeAnimController(
- controller: context.read().answerAnimationController!,
- child: _titles(context),
- ),
- 50.0.gapHeight,
- SizedBox(
- width: context.widthScreen,
- child: BlocBuilder(
- buildWhen: (previous, current) =>
- previous.currentQuestion?.id != current.currentQuestion?.id,
- builder: (context, state) => Column(
- spacing: 30,
- children: [
+ return AnimatedBuilder(
+ animation: context.read().imageAnimationController!,
+ builder: (context, child) => IgnorePointer(
+ ignoring:
+ context
+ .read()
+ .imageAnimationController
+ ?.isForwardOrCompleted ??
+ false,
+ child: child,
+ ),
+ child: ListView(
+ controller: context.read().scrollController,
+ padding: const EdgeInsets.only(top: 10),
+ children: [
+ FadeAnimController(
+ controller: context.read().answerAnimationController!,
+ child: _titles(context),
+ ),
+ 50.0.gapHeight,
+ SizedBox(
+ width: context.widthScreen,
+ child: BlocBuilder(
+ buildWhen: (previous, current) =>
+ previous.currentQuestion?.id != current.currentQuestion?.id,
+ builder: (context, state) => Column(
+ spacing: 30,
+ children: [
Row(
key: Key('${state.currentQuestion?.id}answer0'),
spacing: 20,
@@ -440,11 +452,12 @@ class _QuestionScreenState extends State
),
],
),
- ],
+ ],
+ ),
),
),
- ),
- ],
+ ],
+ ),
);
}