diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 7ceae5e..240e27b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -44,4 +44,10 @@
+
+
+
+
+
+
diff --git a/assets/audios/incorrect_answers/ar_1.mp3 b/assets/audios/incorrect_answers/ar_1.mp3
new file mode 100644
index 0000000..10d653c
Binary files /dev/null and b/assets/audios/incorrect_answers/ar_1.mp3 differ
diff --git a/assets/audios/incorrect_answers/ar_2.mp3 b/assets/audios/incorrect_answers/ar_2.mp3
new file mode 100644
index 0000000..4942400
Binary files /dev/null and b/assets/audios/incorrect_answers/ar_2.mp3 differ
diff --git a/assets/audios/incorrect_answers/ar_3.mp3 b/assets/audios/incorrect_answers/ar_3.mp3
new file mode 100644
index 0000000..589ec8a
Binary files /dev/null and b/assets/audios/incorrect_answers/ar_3.mp3 differ
diff --git a/assets/audios/incorrect_answers/ar_4.mp3 b/assets/audios/incorrect_answers/ar_4.mp3
new file mode 100644
index 0000000..6ec06e8
Binary files /dev/null and b/assets/audios/incorrect_answers/ar_4.mp3 differ
diff --git a/assets/audios/incorrect_answers/ar_5.mp3 b/assets/audios/incorrect_answers/ar_5.mp3
new file mode 100644
index 0000000..619974e
Binary files /dev/null and b/assets/audios/incorrect_answers/ar_5.mp3 differ
diff --git a/assets/audios/incorrect_answers/en_1.mp3 b/assets/audios/incorrect_answers/en_1.mp3
new file mode 100644
index 0000000..c4db2fe
Binary files /dev/null and b/assets/audios/incorrect_answers/en_1.mp3 differ
diff --git a/assets/audios/incorrect_answers/en_2.mp3 b/assets/audios/incorrect_answers/en_2.mp3
new file mode 100644
index 0000000..b2734d5
Binary files /dev/null and b/assets/audios/incorrect_answers/en_2.mp3 differ
diff --git a/assets/audios/incorrect_answers/en_3.mp3 b/assets/audios/incorrect_answers/en_3.mp3
new file mode 100644
index 0000000..c0ad942
Binary files /dev/null and b/assets/audios/incorrect_answers/en_3.mp3 differ
diff --git a/assets/audios/incorrect_answers/en_4.mp3 b/assets/audios/incorrect_answers/en_4.mp3
new file mode 100644
index 0000000..cfcbb48
Binary files /dev/null and b/assets/audios/incorrect_answers/en_4.mp3 differ
diff --git a/assets/audios/incorrect_answers/fa_1.mp3 b/assets/audios/incorrect_answers/fa_1.mp3
new file mode 100644
index 0000000..17ce604
Binary files /dev/null and b/assets/audios/incorrect_answers/fa_1.mp3 differ
diff --git a/assets/audios/incorrect_answers/fa_2.mp3 b/assets/audios/incorrect_answers/fa_2.mp3
new file mode 100644
index 0000000..e760d7e
Binary files /dev/null and b/assets/audios/incorrect_answers/fa_2.mp3 differ
diff --git a/assets/audios/incorrect_answers/fa_3.mp3 b/assets/audios/incorrect_answers/fa_3.mp3
new file mode 100644
index 0000000..dc1a716
Binary files /dev/null and b/assets/audios/incorrect_answers/fa_3.mp3 differ
diff --git a/assets/audios/incorrect_answers/fa_4.mp3 b/assets/audios/incorrect_answers/fa_4.mp3
new file mode 100644
index 0000000..9022a98
Binary files /dev/null and b/assets/audios/incorrect_answers/fa_4.mp3 differ
diff --git a/assets/audios/incorrect_answers/fa_5.mp3 b/assets/audios/incorrect_answers/fa_5.mp3
new file mode 100644
index 0000000..bf88cd8
Binary files /dev/null and b/assets/audios/incorrect_answers/fa_5.mp3 differ
diff --git a/assets/audios/incorrect_answers/fa_6.mp3 b/assets/audios/incorrect_answers/fa_6.mp3
new file mode 100644
index 0000000..0e574b7
Binary files /dev/null and b/assets/audios/incorrect_answers/fa_6.mp3 differ
diff --git a/assets/audios/incorrect_answers/incorrect_answer.mp3 b/assets/audios/incorrect_answers/incorrect_answer.mp3
new file mode 100644
index 0000000..17eabf6
Binary files /dev/null and b/assets/audios/incorrect_answers/incorrect_answer.mp3 differ
diff --git a/assets/fonts/NotoSansArabic.ttf b/assets/fonts/NotoSansArabic.ttf
new file mode 100644
index 0000000..85dd0ae
Binary files /dev/null and b/assets/fonts/NotoSansArabic.ttf differ
diff --git a/assets/svg/button_3.5.svg b/assets/svg/button_3.5.svg
new file mode 100644
index 0000000..799b4a0
--- /dev/null
+++ b/assets/svg/button_3.5.svg
@@ -0,0 +1,26 @@
+
diff --git a/assets/svg/lock.svg b/assets/svg/lock.svg
new file mode 100644
index 0000000..98f5cab
--- /dev/null
+++ b/assets/svg/lock.svg
@@ -0,0 +1,219 @@
+
diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart
index e43ed0a..e0f0d8c 100644
--- a/lib/common_ui/resources/my_assets.dart
+++ b/lib/common_ui/resources/my_assets.dart
@@ -49,6 +49,7 @@ class MyAssets {
static const String button2 = 'assets/svg/button_2.svg.vec';
static const String button2Tablet = 'assets/svg/button_2_tablet.svg.vec';
static const String button3 = 'assets/svg/button_3.svg.vec';
+ static const String button35 = 'assets/svg/button_3.5.svg.vec';
static const String button3Tablet = 'assets/svg/button_3_tablet.svg.vec';
static const String theme = 'assets/svg/theme.svg.vec';
static const String facebook = 'assets/svg/facebook.svg.vec';
@@ -74,6 +75,7 @@ class MyAssets {
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 const String lock = 'assets/svg/lock.svg.vec';
static final List images = [
backgroundHome,
diff --git a/lib/common_ui/resources/my_audios.dart b/lib/common_ui/resources/my_audios.dart
index 5fbe749..0f62dd5 100644
--- a/lib/common_ui/resources/my_audios.dart
+++ b/lib/common_ui/resources/my_audios.dart
@@ -1,6 +1,13 @@
-class MyAudios {
+import 'dart:math';
+
+import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
+import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
+
+class MyAudios {
static const MyAudios _i = MyAudios._internal();
+
const MyAudios._internal();
+
factory MyAudios() => _i;
static const String back = 'assets/audios/back.mp3';
@@ -8,7 +15,41 @@ class MyAudios {
static const String diamondEnd = 'assets/audios/diamond_end.mp3';
static const String diamondIncrease = 'assets/audios/diamond_increase.mp3';
static const String home = 'assets/audios/home.mp3';
- static const String incorrectAnswer = 'assets/audios/incorrect_answer.mp3';
static const String question = 'assets/audios/question.mp3';
static const String rightAnswer = 'assets/audios/right_answer.mp3';
-}
\ No newline at end of file
+
+ static String get incorrectAnswer {
+ final currentLang =
+ LocalStorage.readData(key: MyConstants.selectLanguage) ??
+ MyConstants.defaultLanguage;
+ if(_incorrectAnswers.keys.contains(currentLang)) {
+ final audios = _incorrectAnswers[currentLang];
+ return audios![Random().nextInt(audios.length)];
+ }
+ return 'assets/audios/incorrect_answer.mp3';
+ }
+
+ static const _incorrectAnswers = {
+ 'en': [
+ "assets/audios/incorrect_answers/en_1.mp3",
+ "assets/audios/incorrect_answers/en_2.mp3",
+ "assets/audios/incorrect_answers/en_3.mp3",
+ "assets/audios/incorrect_answers/en_4.mp3",
+ ],
+ 'fa': [
+ "assets/audios/incorrect_answers/fa_1.mp3",
+ "assets/audios/incorrect_answers/fa_2.mp3",
+ "assets/audios/incorrect_answers/fa_3.mp3",
+ "assets/audios/incorrect_answers/fa_4.mp3",
+ "assets/audios/incorrect_answers/fa_5.mp3",
+ "assets/audios/incorrect_answers/fa_6.mp3",
+ ],
+ 'ar': [
+ "assets/audios/incorrect_answers/ar_1.mp3",
+ "assets/audios/incorrect_answers/ar_2.mp3",
+ "assets/audios/incorrect_answers/ar_3.mp3",
+ "assets/audios/incorrect_answers/ar_4.mp3",
+ "assets/audios/incorrect_answers/ar_5.mp3",
+ ],
+ };
+}
diff --git a/lib/common_ui/resources/my_text_style.dart b/lib/common_ui/resources/my_text_style.dart
index 7cd4376..c3da204 100644
--- a/lib/common_ui/resources/my_text_style.dart
+++ b/lib/common_ui/resources/my_text_style.dart
@@ -1,90 +1,137 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
+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/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
class MYTextStyle {
static const MYTextStyle _i = MYTextStyle._internal();
+
const MYTextStyle._internal();
+
factory MYTextStyle() => _i;
static const String dinoKids = 'DinoKids';
+ static const String notoSansArabic = 'NotoSansArabic';
static const String marhey = 'Marhey';
static const String baloo2 = 'Baloo_2';
static const Color textColor = MyColors.white;
- static const TextStyle titr0 = TextStyle(
- fontFamily: marhey,
- fontWeight: FontWeight.w700,
- fontSize: 26,
+ static bool _isArabicOrPersianLanguage() {
+ final selectedLanguage =
+ (LocalStorage.readData(key: MyConstants.selectLanguage) ??
+ MyConstants.defaultLanguage)
+ .toString()
+ .trim()
+ .toLowerCase();
+ final languageCode = selectedLanguage.split(RegExp(r'[_-]')).first;
+ return languageCode == 'ar' || languageCode == 'fa';
+ }
+
+ static String _fontForLanguage(String defaultFont) {
+ return _isArabicOrPersianLanguage() ? notoSansArabic : defaultFont;
+ }
+
+ static FontWeight _fontWeightForLanguage(FontWeight defaultWeight) {
+ if (!_isArabicOrPersianLanguage()) return defaultWeight;
+ return switch (defaultWeight) {
+ FontWeight.w100 => FontWeight.w300,
+ FontWeight.w200 => FontWeight.w400,
+ FontWeight.w300 => FontWeight.w500,
+ FontWeight.w400 => FontWeight.w600,
+ FontWeight.w500 => FontWeight.w700,
+ FontWeight.w600 => FontWeight.w800,
+ FontWeight.w700 => FontWeight.w900,
+ FontWeight.w800 => FontWeight.w900,
+ FontWeight.w900 => FontWeight.w900,
+ _ => defaultWeight,
+ };
+ }
+
+ static TextStyle get titr0 => TextStyle(
+ fontFamily: _fontForLanguage(marhey),
+ fontWeight: _fontWeightForLanguage(FontWeight.w700),
+ fontSize: MyDevice.fontSize(26),
color: textColor,
);
- static const TextStyle titr1 = TextStyle(
- fontFamily: baloo2,
- fontWeight: FontWeight.w800,
- fontSize: 20,
+ static TextStyle get titr1 => TextStyle(
+ fontFamily: _fontForLanguage(baloo2),
+ fontWeight: _fontWeightForLanguage(FontWeight.w800),
+ fontSize: MyDevice.fontSize(20),
color: textColor,
);
- static const TextStyle titr3 = TextStyle(
- fontFamily: marhey,
- fontWeight: FontWeight.w600,
- fontSize: 18,
+ static TextStyle get titr3 => TextStyle(
+ fontFamily: _fontForLanguage(marhey),
+ fontWeight: _fontWeightForLanguage(FontWeight.w600),
+ fontSize: MyDevice.fontSize(18),
color: textColor,
);
- static const TextStyle titr4 = TextStyle(
- fontFamily: baloo2,
- fontWeight: FontWeight.w700,
- fontSize: 14,
+ static TextStyle get titr4 => TextStyle(
+ fontFamily: _fontForLanguage(marhey),
+ fontWeight: _fontWeightForLanguage(FontWeight.w700),
+ fontSize: MyDevice.fontSize(14),
color: textColor,
);
- static const TextStyle matn1 = TextStyle(
- fontFamily: baloo2,
- fontWeight: FontWeight.w600,
- fontSize: 18,
+ static TextStyle get matn1 => TextStyle(
+ fontFamily: _fontForLanguage(baloo2),
+ fontWeight: _fontWeightForLanguage(FontWeight.w600),
+ fontSize: MyDevice.fontSize(18),
color: textColor,
);
- static const TextStyle matn2_3 = TextStyle(
- fontFamily: baloo2,
- fontWeight: FontWeight.w700,
- fontSize: 18,
+ static TextStyle get matn2_3 => TextStyle(
+ fontFamily: _fontForLanguage(baloo2),
+ fontWeight: _fontWeightForLanguage(FontWeight.w700),
+ fontSize: MyDevice.fontSize(18),
color: textColor,
);
- static const TextStyle matn2_2 = TextStyle(
- fontFamily: baloo2,
- fontWeight: FontWeight.w700,
- fontSize: 16,
+ static TextStyle get matn2_2 => TextStyle(
+ fontFamily: _fontForLanguage(baloo2),
+ fontWeight: _fontWeightForLanguage(FontWeight.w700),
+ fontSize: MyDevice.fontSize(16),
color: textColor,
);
- static const TextStyle matn2 = TextStyle(
- fontFamily: baloo2,
- fontWeight: FontWeight.w700,
- fontSize: 14,
+ static TextStyle get matn2 => TextStyle(
+ fontFamily: _fontForLanguage(baloo2),
+ fontWeight: _fontWeightForLanguage(FontWeight.w700),
+ fontSize: MyDevice.fontSize(14),
color: textColor,
);
- static const TextStyle matn3 = TextStyle(
- fontFamily: baloo2,
- fontWeight: FontWeight.w500,
- fontSize: 12,
+ static TextStyle get matn3 => TextStyle(
+ fontFamily: _fontForLanguage(baloo2),
+ fontWeight: _fontWeightForLanguage(FontWeight.w500),
+ fontSize: MyDevice.fontSize(12),
color: textColor,
);
- static const TextStyle button1 = TextStyle(
- fontFamily: dinoKids,
- fontWeight: FontWeight.w400,
- fontSize: 45,
+ static TextStyle get button1 => TextStyle(
+ fontFamily: _fontForLanguage(dinoKids),
+ fontWeight: _fontWeightForLanguage(FontWeight.w400),
+ fontSize: MyDevice.fontSize(45),
color: textColor,
);
- static const TextStyle button2 = TextStyle(
- fontFamily: dinoKids,
- fontWeight: FontWeight.w400,
- fontSize: 30,
+ static TextStyle get button2 => TextStyle(
+ fontFamily: _fontForLanguage(dinoKids),
+ fontWeight: _fontWeightForLanguage(FontWeight.w400),
+ fontSize: MyDevice.fontSize(30),
color: textColor,
);
-}
\ No newline at end of file
+}
diff --git a/lib/core/middlewares/my_middlewares.dart b/lib/core/middlewares/my_middlewares.dart
index 5427f8f..e5be772 100644
--- a/lib/core/middlewares/my_middlewares.dart
+++ b/lib/core/middlewares/my_middlewares.dart
@@ -31,6 +31,16 @@ class MyMiddlewares {
}
}
+ static FutureOr download(BuildContext context, GoRouterState state) {
+ final String? firstIntro = LocalStorage.readData(
+ key: MyConstants.firstIntro);
+ if (firstIntro != 'true') {
+ return Routes.introPage;
+ } else {
+ return null;
+ }
+ }
+
static FutureOr question(BuildContext context, GoRouterState state) {
final String? firstShowCase = LocalStorage.readData(
key: MyConstants.firstShowcase);
diff --git a/lib/core/network/interceptors/logging_interceptor.dart b/lib/core/network/interceptors/logging_interceptor.dart
index 5311e0d..891acfb 100644
--- a/lib/core/network/interceptors/logging_interceptor.dart
+++ b/lib/core/network/interceptors/logging_interceptor.dart
@@ -10,7 +10,8 @@ class LoggingInterceptor {
requestHeader: true,
requestBody: true,
responseBody: true,
- responseHeader: false,
+ responseHeader: true,
+ request: true,
error: true,
compact: true,
maxWidth: 90,
diff --git a/lib/core/params/level_params.dart b/lib/core/params/level_params.dart
index 1e885f8..7709331 100644
--- a/lib/core/params/level_params.dart
+++ b/lib/core/params/level_params.dart
@@ -1,13 +1,16 @@
class LevelParams {
int? id;
+ bool forceRemote;
- LevelParams({this.id});
+ LevelParams({this.id, this.forceRemote = false});
LevelParams copyWith({
int? id,
+ bool? forceRemote,
}) {
return LevelParams(
id: id ?? this.id,
+ forceRemote: forceRemote ?? this.forceRemote,
);
}
}
diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart
index 526e9dc..8888454 100644
--- a/lib/core/routers/my_routes.dart
+++ b/lib/core/routers/my_routes.dart
@@ -74,6 +74,7 @@ GoRouter _appPages() => GoRouter(
GoRoute(
name: Routes.downloadPage,
path: Routes.downloadPage,
+ redirect: MyMiddlewares.download,
pageBuilder: (context, state) {
final config = state.extra as DownloadPageConfig;
return myPageTransition(
@@ -132,7 +133,7 @@ GoRouter _appPages() => GoRouter(
locator(),
locator(instanceName: MyConstants.mainAudioService),
locator(instanceName: MyConstants.effectAudioService),
- locator()
+ locator(),
)..add(SetCurrentLevelEvent()),
child: const LevelPage(),
),
diff --git a/lib/core/services/audio_service.dart b/lib/core/services/audio_service.dart
index 2feb262..09d62a8 100644
--- a/lib/core/services/audio_service.dart
+++ b/lib/core/services/audio_service.dart
@@ -6,6 +6,7 @@ import 'package:just_audio/just_audio.dart';
class AudioService {
final AudioPlayer _player = AudioPlayer();
final StreamController _streamController = StreamController.broadcast();
+ static const double _muteThreshold = 0.001;
final double volume;
AudioService({required this.volume}){
@@ -74,8 +75,9 @@ class AudioService {
Future changeMute({double? newVolume}) async {
try {
- if (_player.volume == 0) {
- await _player.setVolume(newVolume ?? volume);
+ if (isMuted) {
+ final targetVolume = (newVolume ?? volume).clamp(0.0, 1.0).toDouble();
+ await _player.setVolume(targetVolume);
} else {
await _player.setVolume(0);
}
@@ -99,6 +101,7 @@ class AudioService {
Stream volumeStream() => _player.volumeStream;
double get audioVolume => _player.volume;
+ bool get isMuted => _player.volume <= _muteThreshold;
Stream playingStream() async* {
_player.processingStateStream.listen((event) {
diff --git a/lib/core/utils/app_life_cycle.dart b/lib/core/utils/app_life_cycle.dart
index 79f1fec..70a6de3 100644
--- a/lib/core/utils/app_life_cycle.dart
+++ b/lib/core/utils/app_life_cycle.dart
@@ -4,6 +4,8 @@ import 'package:hadi_hoda_flutter/core/services/audio_service.dart';
import 'package:hadi_hoda_flutter/init_bindings.dart';
class AppLifeCycleController extends WidgetsBindingObserver {
+ static bool suppressAutoResumeAudio = false;
+
AppLifeCycleController() {
WidgetsBinding.instance.addObserver(this);
}
@@ -25,6 +27,7 @@ class AppLifeCycleController extends WidgetsBindingObserver {
mainAudio.pause();
effect.pause();
} else if (state == AppLifecycleState.resumed) {
+ if (suppressAutoResumeAudio) return;
mainAudio.play();
effect.play();
}
diff --git a/lib/core/utils/my_device.dart b/lib/core/utils/my_device.dart
index 3bf919a..00feca9 100644
--- a/lib/core/utils/my_device.dart
+++ b/lib/core/utils/my_device.dart
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_context.dart';
class MyDevice {
static const MyDevice _i = MyDevice._internal();
@@ -26,6 +27,23 @@ class MyDevice {
return MediaQuery.sizeOf(context).width >= 1200;
}
+ static double fontSize(
+ double size, {
+ double mobileScale = 1,
+ double tabletScale = 1.25,
+ double desktopScale = 1.35,
+ }) {
+ if (isTablet(MyContext.get)) {
+ return size * tabletScale;
+ }
+
+ if (isDesktop(MyContext.get)) {
+ return size * desktopScale;
+ }
+
+ return size * mobileScale;
+ }
+
/// Get device size
static double? getDeviceWidth(BuildContext context) {
if (isMobile(context)) {
diff --git a/lib/core/utils/screen_size.dart b/lib/core/utils/screen_size.dart
index 689b620..99eec7c 100644
--- a/lib/core/utils/screen_size.dart
+++ b/lib/core/utils/screen_size.dart
@@ -11,3 +11,19 @@ extension AdaptiveSize on double {
double get w => MyContext.get.widthScreen * this;
double get h => MyContext.get.heightScreen * this;
}
+
+extension SizeExtension on num {
+ double get h {
+ final screenHeight = MyContext.get.heightScreen;
+ const designHeight = 812; // your design height (example: iPhone X)
+
+ return (this * screenHeight) / designHeight;
+ }
+
+ double w(BuildContext context) {
+ final screenWidth = MediaQuery.sizeOf(context).width;
+ const designWidth = 375; // your design width
+
+ return (this * screenWidth) / designWidth;
+ }
+}
diff --git a/lib/core/widgets/animations/fade_anim.dart b/lib/core/widgets/animations/fade_anim.dart
index 2db0e92..a91a7be 100644
--- a/lib/core/widgets/animations/fade_anim.dart
+++ b/lib/core/widgets/animations/fade_anim.dart
@@ -19,8 +19,8 @@ class _FadeAnimState extends State
super.initState();
_controller = AnimationController(
vsync: this,
- duration: const Duration(milliseconds: 500),
- reverseDuration: const Duration(seconds: 500),
+ duration: const Duration(milliseconds: 200),
+ reverseDuration: const Duration(seconds: 200),
);
_animation = Tween(
begin: 0,
diff --git a/lib/core/widgets/animations/fade_anim_delayed.dart b/lib/core/widgets/animations/fade_anim_delayed.dart
index ea9f655..28fd9a5 100644
--- a/lib/core/widgets/animations/fade_anim_delayed.dart
+++ b/lib/core/widgets/animations/fade_anim_delayed.dart
@@ -24,8 +24,8 @@ class _FadeAnimDelayedState extends State
super.initState();
_controller = AnimationController(
vsync: this,
- duration: const Duration(milliseconds: 500),
- reverseDuration: const Duration(seconds: 500),
+ duration: const Duration(milliseconds: 200),
+ reverseDuration: const Duration(seconds: 200),
);
_animation = Tween(
begin: 0,
diff --git a/lib/core/widgets/animations/slide_anim.dart b/lib/core/widgets/animations/slide_anim.dart
index e62368d..f6319da 100644
--- a/lib/core/widgets/animations/slide_anim.dart
+++ b/lib/core/widgets/animations/slide_anim.dart
@@ -20,10 +20,10 @@ class _SlideAnimState extends State
with SingleTickerProviderStateMixin {
late Animation _animation;
final List offsetList = const [
- Offset(-2, -2),
- Offset(2, -2),
- Offset(-2, 2),
- Offset(2, 2),
+ Offset(-3, -3),
+ Offset(3, -3),
+ Offset(-3, 3),
+ Offset(3, 3),
];
@override
diff --git a/lib/core/widgets/animations/slide_down_fade.dart b/lib/core/widgets/animations/slide_down_fade.dart
index 0ef73a7..53c7ab2 100644
--- a/lib/core/widgets/animations/slide_down_fade.dart
+++ b/lib/core/widgets/animations/slide_down_fade.dart
@@ -25,8 +25,8 @@ class _SlideDownFadeState extends State
super.initState();
_controller = AnimationController(
vsync: this,
- duration: const Duration(milliseconds: 500),
- reverseDuration: const Duration(milliseconds: 500),
+ duration: const Duration(milliseconds: 200),
+ reverseDuration: const Duration(milliseconds: 200),
);
_fadeAnim = Tween(
diff --git a/lib/core/widgets/animations/slide_up_fade.dart b/lib/core/widgets/animations/slide_up_fade.dart
index 8df3866..30abeb5 100644
--- a/lib/core/widgets/animations/slide_up_fade.dart
+++ b/lib/core/widgets/animations/slide_up_fade.dart
@@ -25,8 +25,8 @@ class _SlideUpFadeState extends State
super.initState();
_controller = AnimationController(
vsync: this,
- duration: const Duration(milliseconds: 500),
- reverseDuration: const Duration(milliseconds: 500),
+ duration: const Duration(milliseconds: 200),
+ reverseDuration: const Duration(milliseconds: 200),
);
_fadeAnim = Tween(
diff --git a/lib/core/widgets/answer_box/answer_box.dart b/lib/core/widgets/answer_box/answer_box.dart
index 7a2a583..7fc8689 100644
--- a/lib/core/widgets/answer_box/answer_box.dart
+++ b/lib/core/widgets/answer_box/answer_box.dart
@@ -70,13 +70,13 @@ class _AnswerBoxState extends State {
bottom: 0,
child: AnswerTextBox(text: widget.answer.title ?? ''),
),
- PositionedDirectional(
+ if(widget.answer.audioInfo?.filename?.isNotEmpty == true)Positioned(
top: setSize(
context: context,
mobile: MySpaces.s12,
tablet: MySpaces.s20,
),
- end: setSize(
+ right: setSize(
context: context,
mobile: MySpaces.s8,
tablet: MySpaces.s20,
diff --git a/lib/core/widgets/answer_box/answer_box_show.dart b/lib/core/widgets/answer_box/answer_box_show.dart
index 75d7bea..658def6 100644
--- a/lib/core/widgets/answer_box/answer_box_show.dart
+++ b/lib/core/widgets/answer_box/answer_box_show.dart
@@ -41,18 +41,29 @@ class AnswerBoxShow extends StatelessWidget {
left: 0,
right: 0,
bottom: -10,
- child: AnswerTextBox(text: answer.title ?? ''),
+ child: AnswerTextBox(
+ text: answer.title ?? '',
+ padding: const EdgeInsetsGeometry.all(14),
+ ),
),
- PositionedDirectional(
+ Positioned(
top: setSize(context: context, mobile: MySpaces.s30, tablet: 60),
- end: setSize(context: context, mobile: MySpaces.s20, tablet: 60),
+ right: setSize(
+ context: context,
+ mobile: MySpaces.s20,
+ tablet: 60,
+ ),
child: GestureDetector(
onTap: () {
onNotifTap?.call(answer);
},
child: MyImage(
image: MyAssets.iconNotif,
- size: setSize(context: context, mobile: MySpaces.s40, tablet: 60),
+ size: setSize(
+ context: context,
+ mobile: MySpaces.s40,
+ tablet: 60,
+ ),
),
),
),
diff --git a/lib/core/widgets/answer_box/styles/text_box.dart b/lib/core/widgets/answer_box/styles/text_box.dart
index c6f82ec..0112890 100644
--- a/lib/core/widgets/answer_box/styles/text_box.dart
+++ b/lib/core/widgets/answer_box/styles/text_box.dart
@@ -1,9 +1,11 @@
+import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
class AnswerTextBox extends StatelessWidget {
- const AnswerTextBox({super.key, required this.text});
+ final EdgeInsetsGeometry? padding;
+ const AnswerTextBox({super.key, required this.text, this.padding});
final String text;
@@ -13,7 +15,7 @@ class AnswerTextBox extends StatelessWidget {
clipper: WavyBannerClipper(),
child: Container(
height: 90,
- padding: const EdgeInsets.symmetric(horizontal: MySpaces.s10),
+ padding: padding ?? const EdgeInsets.symmetric(horizontal: MySpaces.s10),
alignment: Alignment.center,
decoration: const BoxDecoration(
gradient: LinearGradient(
@@ -25,14 +27,17 @@ class AnswerTextBox extends StatelessWidget {
],
),
),
- child: Text(
+ child: AutoSizeText(
text,
textAlign: TextAlign.center,
style: MYTextStyle.matn2.copyWith(
color: const Color(0XFF322386),
height: 1.2,
),
+ minFontSize: 8,
+ stepGranularity: 0.5,
maxLines: 5,
+ overflow: TextOverflow.ellipsis,
),
),
);
diff --git a/lib/core/widgets/button/my_blue_button.dart b/lib/core/widgets/button/my_blue_button.dart
index 7082d11..12ec3e4 100644
--- a/lib/core/widgets/button/my_blue_button.dart
+++ b/lib/core/widgets/button/my_blue_button.dart
@@ -13,11 +13,14 @@ class MyBlueButton extends StatelessWidget {
this.onTap,
this.title,
this.top,
+ this.textStyle,
+
});
final VoidCallback? onTap;
final String? title;
final double? top;
+ final TextStyle? textStyle;
@override
Widget build(BuildContext context) {
@@ -38,10 +41,18 @@ class MyBlueButton extends StatelessWidget {
),
PositionedDirectional(
top: top ?? setSize(context: context, mobile: MySpaces.s6, tablet: MySpaces.s20),
- child: Text(
- title ?? '',
- style: MYTextStyle.button1.copyWith(
- color: const Color(0XFF1D6EFF),
+ child: SizedBox(
+ width: 170,
+ height: 62,
+ child: FittedBox(
+ child: Text(
+ title ?? '',
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: textStyle ?? MYTextStyle.button1.copyWith(
+ color: const Color(0XFF1D6EFF),
+ ),
+ ),
),
),
),
diff --git a/lib/core/widgets/button/my_white_button.dart b/lib/core/widgets/button/my_white_button.dart
index 62f9b61..8c23781 100644
--- a/lib/core/widgets/button/my_white_button.dart
+++ b/lib/core/widgets/button/my_white_button.dart
@@ -13,11 +13,13 @@ class MyWhiteButton extends StatelessWidget {
this.onTap,
this.title,
this.top,
+ this.bgSvgMobile,
});
final VoidCallback? onTap;
final String? title;
final double? top;
+ final String? bgSvgMobile;
@override
Widget build(BuildContext context) {
@@ -31,7 +33,7 @@ class MyWhiteButton extends StatelessWidget {
MyImage(
image: setSize(
context: context,
- mobile: MyAssets.button3,
+ mobile: bgSvgMobile ?? MyAssets.button3,
tablet: MyAssets.button3Tablet,
) ?? '',
),
diff --git a/lib/core/widgets/dialog/hadith_dialog.dart b/lib/core/widgets/dialog/hadith_dialog.dart
index b10e197..8742165 100644
--- a/lib/core/widgets/dialog/hadith_dialog.dart
+++ b/lib/core/widgets/dialog/hadith_dialog.dart
@@ -38,11 +38,8 @@ class HadithDialog extends StatelessWidget {
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(
- horizontal: setSize(
- context: context,
- mobile: 18,
- tablet: 120,
- ) ?? 0,
+ horizontal:
+ setSize(context: context, mobile: 18, tablet: 120) ?? 0,
),
child: Stack(
clipBehavior: Clip.none,
@@ -50,29 +47,64 @@ class HadithDialog extends StatelessWidget {
DialogBackground(
child: Builder(
builder: (context) {
- if(hadith.isNotEmpty){
- return ListView.separated(
- itemCount: hadith.length,
- separatorBuilder: (context, index) => const Divider(
- height: 40,
- thickness: 1,
- endIndent: MySpaces.s20,
- indent: MySpaces.s20,
- color: Color(0xFFC2BDE4),
- ),
- itemBuilder: (context, index) => Text.rich(
- TextSpan(
- text: '${hadith[index].narratorName ?? ''}:\n',
- style: MYTextStyle.titr1.copyWith(
- color: const Color(0XFF494178),
- ),
+ if (hadith.isNotEmpty) {
+ return Scrollbar(
+ child: ListView.separated(
+ padding: const EdgeInsets.symmetric(horizontal: 11),
+ itemCount: hadith.length,
+ separatorBuilder: (context, index) => const Divider(
+ height: 40,
+ thickness: 1,
+ endIndent: MySpaces.s20,
+ indent: MySpaces.s20,
+ color: Color(0xFFC2BDE4),
+ ),
+ itemBuilder: (context, index) => Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- TextSpan(
- text: hadith[index].hadithText,
- style: MYTextStyle.matn1.copyWith(
- color: const Color(0XFF494178),
+ Text.rich(
+ textDirection: TextDirection.rtl,
+ TextSpan(
+ text:
+ '${hadith[index].narratorName ?? ''}:\n',
+ style: MYTextStyle.titr1.copyWith(
+ color: const Color(0XFF494178),
+ fontFamily: MYTextStyle.notoSansArabic,
+ ),
+ children: [
+ TextSpan(
+ text: hadith[index].hadithText,
+ style: MYTextStyle.matn1.copyWith(
+ color: const Color(0XFF494178),
+ fontFamily: MYTextStyle.notoSansArabic,
+ fontWeight: FontWeight.w600,
+ fontSize: 16
+ ),
+ ),
+ ],
),
),
+ if(hadith[index].translation?.isNotEmpty == true) ...[
+ const SizedBox(height: 6),
+ Text(
+ hadith[index].translation!,
+ style: MYTextStyle.matn1.copyWith(
+ color: const Color(0XFF494178),
+ fontWeight: FontWeight.w600,
+ fontSize: 16
+ ),
+ ),
+ ], if(hadith[index].sourceName?.isNotEmpty == true) ...[
+ const SizedBox(height: 6),
+ Text(
+ hadith[index].sourceName!,
+ style: MYTextStyle.matn1.copyWith(
+ color: const Color(0XFF494178).withValues(alpha: .5),
+ fontWeight: FontWeight.w600,
+ fontSize: 12
+ ),
+ ),
+ ]
],
),
),
diff --git a/lib/core/widgets/dialog/reward_dialog.dart b/lib/core/widgets/dialog/reward_dialog.dart
index e38afd5..19ab254 100644
--- a/lib/core/widgets/dialog/reward_dialog.dart
+++ b/lib/core/widgets/dialog/reward_dialog.dart
@@ -1,5 +1,6 @@
import 'dart:ui';
+import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_animations.dart';
@@ -19,6 +20,16 @@ Future showRewardDialog({
required BuildContext context,
required PrizeEntity prize,
}) async {
+ final result = await Connectivity().checkConnectivity();
+ if (!context.mounted) return;
+ if (result.contains(ConnectivityResult.none)) {
+ final messenger = ScaffoldMessenger.of(
+ context,
+ );
+ messenger.removeCurrentSnackBar();
+ messenger.showSnackBar(SnackBar(content: Text(context.translate.lost_connection)));
+ return;
+ }
await showDialog(
context: context,
builder: (context) => RewardDialog(prize: prize),
@@ -89,17 +100,21 @@ class RewardDialog extends StatelessWidget {
children: [
Image.network(
prize.imageURL ?? '',
- errorBuilder: (context, error, stackTrace) => Container(
- height: 300,
- width: 300,
- color: const Color(0XFFE0E0E0),
- ),
- loadingBuilder: (context, child, loadingProgress) => Container(
- height: 300,
- width: 300,
- color: const Color(0XFFE0E0E0),
- child: child,
- ),
+ errorBuilder:
+ (context, error, stackTrace) =>
+ Container(
+ height: 300,
+ width: 300,
+ color: const Color(0XFFE0E0E0),
+ ),
+ loadingBuilder:
+ (context, child, loadingProgress) =>
+ Container(
+ height: 300,
+ width: 300,
+ color: const Color(0XFFE0E0E0),
+ child: child,
+ ),
fit: BoxFit.cover,
),
const MyImage(
@@ -121,7 +136,11 @@ class RewardDialog extends StatelessWidget {
behavior: HitTestBehavior.opaque,
child: MyImage(
image: MyAssets.closeBtn,
- size: setSize(context: context, mobile: 40, tablet: 60),
+ size: setSize(
+ context: context,
+ mobile: 40,
+ tablet: 60,
+ ),
),
),
),
diff --git a/lib/core/widgets/error/error_state.dart b/lib/core/widgets/error/error_state.dart
index 23d2c4c..b821f56 100644
--- a/lib/core/widgets/error/error_state.dart
+++ b/lib/core/widgets/error/error_state.dart
@@ -15,12 +15,16 @@ class ErrorState extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final retryTitle = context.translate.retry;
return Column(
children: [
const Spacer(),
Text(
context.translate.lost_connection,
- style: MYTextStyle.button1,
+ style: MYTextStyle.button1.copyWith(fontSize: 42),
+ maxLines: 1,
+ textAlign: TextAlign.center,
+ overflow: TextOverflow.ellipsis,
),
const MyImage(image: MyAssets.error),
MySpaces.s40.gapHeight,
@@ -31,9 +35,12 @@ class ErrorState extends StatelessWidget {
),
const Spacer(),
MyBlueButton(
- title: context.translate.retry,
+ title: retryTitle,
onTap: onTap,
top: setSize(context: context, mobile: MySpaces.s6, tablet: MySpaces.s16),
+ textStyle: MYTextStyle.button1.copyWith(
+ color: const Color(0XFF1D6EFF),
+ ),
),
],
);
diff --git a/lib/core/widgets/page_transition/my_page_transition.dart b/lib/core/widgets/page_transition/my_page_transition.dart
index 00703ae..14d509f 100644
--- a/lib/core/widgets/page_transition/my_page_transition.dart
+++ b/lib/core/widgets/page_transition/my_page_transition.dart
@@ -7,11 +7,33 @@ CustomTransitionPage myPageTransition({
}) {
return CustomTransitionPage(
key: key,
+ transitionDuration: const Duration(milliseconds: 200),
+ reverseTransitionDuration: const Duration(milliseconds: 180),
child: child,
- transitionsBuilder: (context, animation, secondaryAnimation, child) =>
- FadeTransition(
- opacity: animation,
+ transitionsBuilder: (context, animation, secondaryAnimation, child) {
+ final curvedAnimation = CurvedAnimation(
+ parent: animation,
+ curve: Curves.fastOutSlowIn,
+ );
+
+ // Simple Android-like Material transition: fade + slight scale
+ final opacityAnimation = Tween(
+ begin: 0.7,
+ end: 1.0,
+ ).animate(curvedAnimation);
+
+ final scaleAnimation = Tween(
+ begin: 0.96,
+ end: 1.0,
+ ).animate(curvedAnimation);
+
+ return FadeTransition(
+ opacity: opacityAnimation,
+ child: ScaleTransition(
+ scale: scaleAnimation,
child: child,
),
+ );
+ },
);
-}
+}
\ No newline at end of file
diff --git a/lib/core/widgets/video/my_video_player.dart b/lib/core/widgets/video/my_video_player.dart
index 365898a..e33955d 100644
--- a/lib/core/widgets/video/my_video_player.dart
+++ b/lib/core/widgets/video/my_video_player.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/services/audio_service.dart';
+import 'package:hadi_hoda_flutter/core/utils/app_life_cycle.dart';
import 'package:hadi_hoda_flutter/core/utils/my_device.dart';
import 'package:hadi_hoda_flutter/init_bindings.dart';
import 'package:pod_player/pod_player.dart';
@@ -16,7 +17,8 @@ class MyVideoPlayer extends StatefulWidget {
}
class _MyVideoPlayerState extends State {
- late final PodPlayerController _controller;
+ PodPlayerController? _controller;
+ bool _isPlayerReady = false;
final AudioService _mainAudioService = locator(
instanceName: MyConstants.mainAudioService,
);
@@ -27,8 +29,17 @@ class _MyVideoPlayerState extends State {
@override
void initState() {
super.initState();
- _mainAudioService.stop();
- _effectAudioService.stop();
+ _initVideoSession();
+ }
+
+ Future _initVideoSession() async {
+ AppLifeCycleController.suppressAutoResumeAudio = true;
+ await MyDevice.setLandscape();
+ await Future.wait([
+ _mainAudioService.pause(),
+ _effectAudioService.pause(),
+ ]);
+ if (!mounted) return;
_controller = PodPlayerController(
podPlayerConfig: const PodPlayerConfig(
autoPlay: false,
@@ -37,13 +48,17 @@ class _MyVideoPlayerState extends State {
),
playVideoFrom: PlayVideoFrom.network(widget.videoURL ?? ''),
)..initialise();
+ if (!mounted) return;
+ setState(() {
+ _isPlayerReady = true;
+ });
}
@override
void dispose() {
- _controller.dispose();
+ _controller?.dispose();
+ AppLifeCycleController.suppressAutoResumeAudio = false;
_mainAudioService.play();
- _effectAudioService.play();
MyDevice.setPortrait();
super.dispose();
}
@@ -57,20 +72,21 @@ class _MyVideoPlayerState extends State {
backgroundColor: MyColors.transparent,
foregroundColor: MyColors.white,
),
- body: PodVideoPlayer(
- controller: _controller,
- matchVideoAspectRatioToFrame: true,
- matchFrameAspectRatioToVideo: true,
- videoAspectRatio: _controller.videoPlayerValue?.aspectRatio ?? 16 / 9,
- podProgressBarConfig: const PodProgressBarConfig(),
- onToggleFullScreen: (isFullScreen) async {
- if (isFullScreen) {
- await MyDevice.setAllOrientations();
- } else {
- await MyDevice.setPortrait();
- }
- },
- ),
+ body: _isPlayerReady
+ ? PodVideoPlayer(
+ controller: _controller!,
+ matchVideoAspectRatioToFrame: true,
+ matchFrameAspectRatioToVideo: true,
+ videoAspectRatio:
+ _controller?.videoPlayerValue?.aspectRatio ?? 16 / 9,
+ podProgressBarConfig: const PodProgressBarConfig(),
+ onToggleFullScreen: (isFullScreen) async {
+ await MyDevice.setLandscape();
+ },
+ )
+ : const Center(
+ child: CircularProgressIndicator(),
+ ),
);
}
}
diff --git a/lib/features/download/presentation/ui/download_page.dart b/lib/features/download/presentation/ui/download_page.dart
index daa3b67..578cfc3 100644
--- a/lib/features/download/presentation/ui/download_page.dart
+++ b/lib/features/download/presentation/ui/download_page.dart
@@ -16,6 +16,7 @@ import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_b
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_event.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_state.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/ui/widgets/download_loading_widget.dart';
+import 'package:wakelock_plus/wakelock_plus.dart';
/// Download page that supports downloading any combination of media types.
///
@@ -35,6 +36,7 @@ class _DownloadPageState extends State {
@override
void initState() {
super.initState();
+ WakelockPlus.enable();
final bloc = context.read();
if (!bloc.isDownloading) {
@@ -46,6 +48,12 @@ class _DownloadPageState extends State {
}
}
+ @override
+ void dispose() {
+ WakelockPlus.disable();
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -74,7 +82,7 @@ class _DownloadPageState extends State {
if (widget.config.redirectTo == Routes.homePage) {
context.goNamed(Routes.homePage);
} else {
- context.pushNamed(
+ context.pushReplacementNamed(
widget.config.redirectTo,
pathParameters: widget.config.routeParams,
);
@@ -130,6 +138,8 @@ class _DownloadPageState extends State {
Widget _text(BuildContext context) {
return PositionedDirectional(
bottom: 130,
+ start: 0,
+ end: 0,
child: Column(
spacing: MySpaces.s6,
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -149,7 +159,7 @@ class _DownloadPageState extends State {
.loadingStream,
builder: (context, snapshot) =>
Text(
- 'Downloading ...${snapshot.data?.downloadedLevels}/${widget
+ '${context.translate.downloading} ...${snapshot.data?.downloadedLevels}/${widget
.config.downloadToLevel}',
textAlign: TextAlign.center,
style: MYTextStyle.matn3,
diff --git a/lib/features/guider/presentation/ui/guider_page.dart b/lib/features/guider/presentation/ui/guider_page.dart
index b3ddd63..fabff6b 100644
--- a/lib/features/guider/presentation/ui/guider_page.dart
+++ b/lib/features/guider/presentation/ui/guider_page.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:gif/gif.dart';
+import 'package:hadi_hoda_flutter/common_ui/resources/my_animations.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';
@@ -14,7 +16,6 @@ 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';
@@ -223,10 +224,12 @@ class _GuiderPageState extends State with TickerProviderStateMixin {
.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),
+ child: Gif(
+ image: const AssetImage(MyAnimations.globeStateNormal),
+ fps: 10,
+ autostart: Autostart.loop,
+ width: setSize(context: context, mobile: 80, tablet: 120) ?? 80,
+ height: setSize(context: context, mobile: 80, tablet: 120) ?? 80,
),
),
PositionedDirectional(
diff --git a/lib/features/home/presentation/ui/home_page.dart b/lib/features/home/presentation/ui/home_page.dart
index 3c5b8b6..587e8bf 100644
--- a/lib/features/home/presentation/ui/home_page.dart
+++ b/lib/features/home/presentation/ui/home_page.dart
@@ -9,14 +9,14 @@ import 'package:hadi_hoda_flutter/core/utils/local_storage.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/slide_down_fade.dart';
-import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
+import 'package:hadi_hoda_flutter/core/widgets/button/my_white_button.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/my_yellow_button.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.dart';
import 'package:hadi_hoda_flutter/core/widgets/pop_scope/my_pop_scope.dart';
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart';
import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_bloc.dart';
+import 'package:hadi_hoda_flutter/features/home/presentation/ui/widgets/payment_dialog.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@@ -26,6 +26,8 @@ class HomePage extends StatefulWidget {
}
class _HomePageState extends State {
+ static const double _muteThreshold = 0.001;
+
@override
void initState() {
super.initState();
@@ -51,6 +53,10 @@ class _HomePageState extends State {
@override
Widget build(BuildContext context) {
+ final lang =
+ LocalStorage.readData(key: MyConstants.selectLanguage) ??
+ MyConstants.defaultLanguage;
+ late final isArabic = lang == 'ar';
return Scaffold(
body: MyPopScope(
child: DecoratedBox(
@@ -64,9 +70,9 @@ class _HomePageState extends State {
child: Stack(
alignment: Alignment.center,
children: [
- _music(context),
+ _topBtns(context, isArabic),
_image(context),
- _bottomBtns(context),
+ _bottomBtns(context, isArabic),
],
),
),
@@ -76,21 +82,16 @@ class _HomePageState extends State {
}
Widget _music(BuildContext context) {
- return PositionedDirectional(
- top: setPlatform(android: MySpaces.s36, iOS: 50),
- end: MySpaces.s16,
- child: SlideDownFade(
- delay: const Duration(milliseconds: 100),
- child: StreamBuilder(
- initialData: 1,
- stream: context.read().volumeStream,
- builder: (context, snapshot) => MyInkwell(
- onTap: () => context.read().changeMute(),
- child: MyImage(
- image: snapshot.data == 0 ? MyAssets.musicOff : MyAssets.musicOn,
- size: setSize(context: context, tablet: 100),
- ),
- ),
+ return StreamBuilder(
+ initialData: 1,
+ stream: context.read().volumeStream,
+ builder: (context, snapshot) => MyInkwell(
+ onTap: () => context.read().changeMute(),
+ child: MyImage(
+ image: (snapshot.data ?? 1) <= _muteThreshold
+ ? MyAssets.musicOff
+ : MyAssets.musicOn,
+ size: setSize(context: context, tablet: 100),
),
),
);
@@ -112,39 +113,105 @@ class _HomePageState extends State {
);
}
- Positioned _bottomBtns(BuildContext context) {
+ Positioned _topBtns(BuildContext context, bool isArabic) {
+ return Positioned(
+ top: setPlatform(android: MySpaces.s36, iOS: 50),
+ right: 20,
+ left: 20,
+ child: !isArabic
+ ? Align(
+ alignment: AlignmentDirectional.topStart,
+ child: _music(context),
+ )
+ : Row(
+ children: [
+ MyInkwell(
+ onTap: () => context.read().showAboutUs(context),
+ child: MyImage(
+ image: MyAssets.theme,
+ size: setSize(context: context, tablet: 100),
+ ),
+ ),
+ const SizedBox(width: 12),
+
+ MyInkwell(
+ onTap: () =>
+ context.read().goToLanguagePage(context),
+ child: MyImage(
+ image: MyAssets.language,
+ size: setSize(context: context, tablet: 100),
+ ),
+ ),
+ const Spacer(),
+
+ _music(context),
+ ],
+ ),
+ );
+ }
+
+ Positioned _bottomBtns(BuildContext context, bool isArabic) {
return Positioned(
bottom: setPlatform(android: MySpaces.s40, iOS: MySpaces.s0),
left: MySpaces.s16,
right: MySpaces.s16,
child: SafeArea(
- child: SlideUpFade(
- delay: const Duration(milliseconds: 100),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- MyInkwell(
- onTap: () => context.read().goToLanguagePage(context),
- child: MyImage(
- image: MyAssets.language,
- size: setSize(context: context, tablet: 100),
- ),
- ),
- MyYellowButton(
- onTap: () => context.read().goToLevelPage(context),
- title: context.translate.start,
- ),
- MyInkwell(
- onTap: () => context.read().showAboutUs(context),
- child: MyImage(
- image: MyAssets.theme,
- size: setSize(context: context, tablet: 100),
- ),
+ child: isArabic
+ ? Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Expanded(
+ flex: 4,
+ child: MyYellowButton(
+ onTap: () {
+ context.read().goToLevelPage(context);
+ },
+ title: context.translate.start,
+ ),
+ ),
+ const SizedBox(width: 20),
+ Expanded(
+ flex: 3,
+ child: MyWhiteButton(
+ title: 'تبرع',
+ onTap: () {
+ showDialog(
+ context: context,
+ builder: (_) => const PaymentDialog(),
+ );
+ },
+ bgSvgMobile: MyAssets.button35,
+ ),
+ ),
+ ],
+ )
+ : Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ MyInkwell(
+ onTap: () =>
+ context.read().goToLanguagePage(context),
+ child: MyImage(
+ image: MyAssets.language,
+ size: setSize(context: context, tablet: 100),
+ ),
+ ),
+ MyYellowButton(
+ onTap: () {
+ context.read().goToLevelPage(context);
+ },
+ title: context.translate.start,
+ ),
+ MyInkwell(
+ onTap: () => context.read().showAboutUs(context),
+ child: MyImage(
+ image: MyAssets.theme,
+ size: setSize(context: context, tablet: 100),
+ ),
+ ),
+ ],
),
- ],
- ),
- ),
),
);
}
diff --git a/lib/features/home/presentation/ui/widgets/payment_dialog.dart b/lib/features/home/presentation/ui/widgets/payment_dialog.dart
new file mode 100644
index 0000000..35bf962
--- /dev/null
+++ b/lib/features/home/presentation/ui/widgets/payment_dialog.dart
@@ -0,0 +1,75 @@
+import 'package:flutter/material.dart';
+import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class PaymentDialog extends StatelessWidget {
+ const PaymentDialog({super.key});
+
+ static const String _paymentUrl =
+ 'https://buy.stripe.com/cNi6oI9EF5Y44oQ5nb4AU0V';
+
+ Future _launchPayment() async {
+ final Uri url = Uri.parse(_paymentUrl);
+ if (await canLaunchUrl(url)) {
+ await launchUrl(url);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Dialog(
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
+ child: Padding(
+ padding: const EdgeInsets.all(20),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ const Text(
+ "ساهم معنا في تطوير مشاريعنا القادمة",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ fontFamily: MYTextStyle.notoSansArabic,
+ ),
+ ),
+
+ const SizedBox(height: 16),
+
+ const Text(
+ '''قال رسول الله (صلى الله عليه وآله): "إذا مات ابن آدم انقطع عمله إلا من ثلاث: صدقة جارية، أو علم ينتفع به، أو ولد صالح يدعو له".
+
+إن دعمكم المستمر لتطوير مشاريعنا القادمة هو التجسيد الحقيقي لـ «الباقيات الصالحات».
+
+تخيل أن كل طفلٍ في أي بقعةٍ من العالم، وبأي لسانٍ كان، يتعلم الصلاة أو يكتسب أدباً من آداب الدين بفضل مساهمتكم؛ سيُكتب ذلك في ميزان حسناتكم «علماً يُنتفع به»، وصدقةً جاريةً لا ينقطع ثوابها أبدَ الآبدين.''',
+ textAlign: TextAlign.right,
+ style: TextStyle(
+ fontSize: 15,
+ height: 1.6,
+ fontFamily: MYTextStyle.notoSansArabic,
+ ),
+ ),
+
+ const SizedBox(height: 24),
+
+ SizedBox(
+ width: double.infinity,
+ child: FilledButton(
+ onPressed: _launchPayment,
+ child: const Text(
+ "تبرع الآن",
+ style: TextStyle(
+ fontFamily: MYTextStyle.notoSansArabic,
+ fontSize: 17,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/language/data/repository_impl/language_repository_impl.dart b/lib/features/language/data/repository_impl/language_repository_impl.dart
index d188e21..fa567e6 100644
--- a/lib/features/language/data/repository_impl/language_repository_impl.dart
+++ b/lib/features/language/data/repository_impl/language_repository_impl.dart
@@ -13,12 +13,18 @@ class LanguageRepositoryImpl implements ILanguageRepository {
LanguageRepositoryImpl(this.languageDatasource);
+ List? _langs;
+
@override
Future, MyException>> getLanguages() async {
try {
+ if(_langs != null) {
+ return DataState.success(_langs!);
+ }
final languages = await languageDatasource.getLanguages();
// Explicitly convert to a List to prevent runtime type errors.
- return DataState.success(List.from(languages));
+ _langs = List.from(languages);
+ return DataState.success(_langs!);
} on MyException catch (e) {
return DataState.error(e);
}
diff --git a/lib/features/language/presentation/bloc/language_bloc.dart b/lib/features/language/presentation/bloc/language_bloc.dart
index 82411d0..efa4bfb 100644
--- a/lib/features/language/presentation/bloc/language_bloc.dart
+++ b/lib/features/language/presentation/bloc/language_bloc.dart
@@ -34,7 +34,7 @@ class LanguageBloc extends Bloc {
if (result.isSuccess) {
final languages = result.data!;
final selectedResult = await _getSelectedLanguageUseCase(languages);
-
+
emit(
state.copyWith(
getLanguagesStatus: const BaseComplete(''),
diff --git a/lib/features/language/presentation/ui/language_page.dart b/lib/features/language/presentation/ui/language_page.dart
index a934b2a..0427f4b 100644
--- a/lib/features/language/presentation/ui/language_page.dart
+++ b/lib/features/language/presentation/ui/language_page.dart
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.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/constants/my_constants.dart';
@@ -13,6 +12,7 @@ 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/button/my_blue_button.dart';
+import 'package:hadi_hoda_flutter/core/widgets/error/error_state.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/app/presentation/bloc/app_bloc.dart';
import 'package:hadi_hoda_flutter/features/app/presentation/bloc/app_event.dart';
@@ -24,6 +24,9 @@ import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_e
import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_state.dart';
import 'package:wheel_chooser/wheel_chooser.dart';
+import '../../../../core/widgets/animations/rotation_anim.dart';
+import '../../domain/entity/language_entity.dart';
+
class LanguagePage extends StatefulWidget {
const LanguagePage({super.key});
@@ -34,10 +37,18 @@ class LanguagePage extends StatefulWidget {
class _LanguagePageState extends State {
final controller = FixedExtentScrollController(initialItem: 50);
bool _isEnabled = false;
+ LanguageEntity? _selectedLanguage;
@override
void initState() {
super.initState();
+ _selectedLanguage =
+ context.read().state.selectedLanguage ??
+ const LanguageEntity(
+ code: 'en',
+ title: '',
+ displayName: 'English (English',
+ );
context.read().add(const GetLanguagesEvent());
}
@@ -74,7 +85,9 @@ class _LanguagePageState extends State {
if (state.languages.isNotEmpty && !_isEnabled) {
int targetIndex = 0;
if (state.selectedLanguage != null) {
- targetIndex = state.languages.indexWhere((l) => l.code == state.selectedLanguage!.code);
+ targetIndex = state.languages.indexWhere(
+ (l) => l.code == state.selectedLanguage!.code,
+ );
if (targetIndex == -1) targetIndex = 0;
}
@@ -88,8 +101,8 @@ class _LanguagePageState extends State {
if (state.selectedLanguage == null && context.mounted) {
context.read().add(
- SelectLanguageEvent(state.languages[targetIndex]),
- );
+ SelectLanguageEvent(state.languages[targetIndex]),
+ );
}
}
setState(() => _isEnabled = true);
@@ -97,17 +110,41 @@ class _LanguagePageState extends State {
},
builder: (context, state) {
if (state.getLanguagesStatus is BaseLoading) {
- return const Center(child: CircularProgressIndicator());
+ return Center(child: _loading(context));
+ }
+ if (state.getLanguagesStatus is BaseError) {
+ return Padding(
+ padding: EdgeInsets.symmetric(
+ vertical:
+ MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16,
+ horizontal: 60,
+ ),
+ child: ErrorState(
+ onTap: () {
+ context.read().add(const GetLanguagesEvent());
+ },
+ ),
+ );
}
- final double itemSize = setSize(context: context, mobile: 45, tablet: 60) ?? 45;
-
+ final double itemSize =
+ setSize(context: context, mobile: 45, tablet: 60) ?? 45;
+ final selectStyle = MYTextStyle.titr1.copyWith(
+ // Keep same size as before (WheelChooser.choices)
+ fontSize: itemSize * 0.34,
+ color: Colors.white,
+ );
+ final unSelectStyle = MYTextStyle.titr1.copyWith(
+ fontSize: itemSize * 0.34,
+ color: Colors.white.withValues(alpha: 0.5),
+ );
return Padding(
padding: EdgeInsets.only(
left: setSize(context: context, mobile: 50, tablet: 0.3.w) ?? 0,
- right: setSize(context: context, mobile: 50, tablet: 0.3.w) ?? 0,
+ right:
+ setSize(context: context, mobile: 50, tablet: 0.3.w) ?? 0,
bottom: MySpaces.s40 + MediaQuery.paddingOf(context).bottom,
- top: 100,
+ top: 60,
),
child: Column(
children: [
@@ -133,43 +170,77 @@ class _LanguagePageState extends State {
Center(
child: Container(
width: double.infinity,
- alignment: Alignment.centerLeft,
+ alignment: Alignment.centerLeft,
height: itemSize,
- padding: const EdgeInsets.symmetric(horizontal: 18),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 18,
+ ),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: .2),
borderRadius: BorderRadius.circular(12),
),
- child: const MyImage(image: MyAssets.check),
+ child: Transform.translate(
+ offset:
+ (state
+ .selectedLanguage
+ ?.displayName
+ .length ??
+ 0) >
+ 21
+ ? const Offset(-10, 0)
+ : Offset.zero,
+ child: const MyImage(image: MyAssets.check),
+ ),
),
),
- WheelChooser.choices(
+ WheelChooser.custom(
controller: controller,
+ datas: null,
startPosition: null,
perspective: 0.00000001,
listWidth: double.infinity,
listHeight: double.infinity,
itemSize: itemSize,
isInfinite: true,
- onChoiceChanged: (value) {
- if (value != null && value is! String) {
- context.read().add(
- SelectLanguageEvent(value),
- );
- }
+ onValueChanged: (position) {
+ if (state.languages.isEmpty) return;
+ final pos = position is int
+ ? position
+ : int.tryParse(position.toString()) ?? 0;
+ final index =
+ ((pos % state.languages.length) +
+ state.languages.length) %
+ state.languages.length;
+ final lang = state.languages[index];
+ setState(() {
+ _selectedLanguage = lang;
+ });
},
- selectTextStyle: MYTextStyle.titr1.copyWith(
- fontSize: itemSize * 0.34,
- color: Colors.white,
- ),
- unSelectTextStyle: MYTextStyle.titr1.copyWith(
- fontSize: itemSize * 0.34,
- color: Colors.white.withOpacity(0.5),
- ),
- choices: state.languages.map((lang) {
- return WheelChoice(
- value: lang,
- title: lang.displayName,
+ children: state.languages.map((lang) {
+ final isSelected =
+ _selectedLanguage?.code == lang.code;
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 36,
+ ),
+ child: MediaQuery(
+ data: MediaQuery.of(context).copyWith(
+ textScaler: const TextScaler.linear(1.5),
+ ),
+ child: AutoSizeText(
+ lang.displayName,
+ textAlign: TextAlign.center,
+ maxLines: 2,
+ minFontSize: 10,
+ stepGranularity: 0.5,
+ overflow: TextOverflow.ellipsis,
+ style: isSelected
+ ? selectStyle
+ : unSelectStyle,
+ ),
+ ),
+ ),
);
}).toList(),
),
@@ -188,6 +259,15 @@ class _LanguagePageState extends State {
);
}
+ Widget _loading(BuildContext context) {
+ return RotationAnim(
+ child: MyImage(
+ image: MyAssets.loading,
+ size: setSize(context: context, mobile: 110, tablet: 145),
+ ),
+ );
+ }
+
Widget _title(BuildContext context) {
return Row(
spacing: MySpaces.s10,
@@ -208,15 +288,18 @@ class _LanguagePageState extends State {
Widget _btn(BuildContext context, LanguageState state) {
return MyBlueButton(
- onTap: (_isEnabled && state.selectedLanguage != null)
+ onTap: (_isEnabled && _selectedLanguage != null)
? () async {
+ context.read().add(
+ SelectLanguageEvent(_selectedLanguage!),
+ );
final downloadBloc = context.read();
// 1. Cancel any previous downloads.
downloadBloc.add(CancelDownloadEvent());
// 2. Update App Locale
context.read().add(
- ChangeLocaleEvent(state.selectedLanguage!.locale),
+ ChangeLocaleEvent(_selectedLanguage!.locale),
);
await Future.delayed(const Duration(milliseconds: 80));
final lastDownloadedLevel = await downloadBloc
diff --git a/lib/features/level/data/datasource/level_datasource.dart b/lib/features/level/data/datasource/level_datasource.dart
index 03f66f8..3d5daaf 100644
--- a/lib/features/level/data/datasource/level_datasource.dart
+++ b/lib/features/level/data/datasource/level_datasource.dart
@@ -1,8 +1,12 @@
import 'package:flutter/foundation.dart';
+import 'package:hadi_hoda_flutter/core/constants/my_api.dart';
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/network/http_request.dart';
import 'package:hadi_hoda_flutter/core/params/level_params.dart';
+import 'package:hadi_hoda_flutter/core/response/base_response.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
+import 'package:hadi_hoda_flutter/features/level/data/model/node_model.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart';
import 'package:hive/hive.dart';
@@ -13,24 +17,66 @@ abstract class ILevelDatasource {
/// Local
class LocalLevelDatasourceImpl implements ILevelDatasource {
- const LocalLevelDatasourceImpl();
+ final IHttpRequest httpRequest;
+
+ LocalLevelDatasourceImpl(this.httpRequest);
+
+ String get _selectedLanguage =>
+ LocalStorage.readData(key: MyConstants.selectLanguage) ??
+ MyConstants.defaultLanguage;
+
+ Future _syncRemoteLevelsToLocal() async {
+ final response = await httpRequest.get(
+ path: MyApi.levels,
+ queryParameters: {'lang': _selectedLanguage},
+ );
+ final List levels = BaseResponse.getDataList(
+ response?['path'],
+ (json) => NodeModel.fromJson(json),
+ );
+ await LocalStorage.saveData(
+ key: MyConstants.maxLevelCount,
+ value: '${levels.length}',
+ );
+
+ final Box levelBox = Hive.box(MyConstants.levelBox);
+ dynamic existingKey;
+ for (final key in levelBox.keys) {
+ final item = levelBox.get(key);
+ if (item?.code == _selectedLanguage) {
+ existingKey = key;
+ break;
+ }
+ }
+
+ if (existingKey != null) {
+ await levelBox.put(
+ existingKey,
+ TotalDataEntity(code: _selectedLanguage, nodes: levels),
+ );
+ return;
+ }
+
+ await levelBox.add(TotalDataEntity(code: _selectedLanguage, nodes: levels));
+ }
@override
Future> getLevels({required LevelParams params}) async {
try {
- final String selectedLanguage = LocalStorage.readData(
- key: MyConstants.selectLanguage) ?? MyConstants.defaultLanguage;
+ if (params.forceRemote) {
+ await _syncRemoteLevelsToLocal();
+ }
+
final Box levelBox = Hive.box(MyConstants.levelBox);
final TotalDataEntity findData = levelBox.values.firstWhere(
- (e) => e.code == selectedLanguage,
+ (e) => e.code == _selectedLanguage,
orElse: () => TotalDataEntity(),
);
- debugPrint("nodesCount : ${findData.nodes?.first.level?.questions}");
return findData.nodes ?? [];
- } catch (e,s) {
+ } catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
- throw const MyException(errorMessage: 'Operation Failed');
+ throw const MyException(errorMessage: 'Operation Failed');
}
}
}
\ No newline at end of file
diff --git a/lib/features/level/domain/entity/node_entity.dart b/lib/features/level/domain/entity/node_entity.dart
index 6c9fa84..ada33d2 100644
--- a/lib/features/level/domain/entity/node_entity.dart
+++ b/lib/features/level/domain/entity/node_entity.dart
@@ -9,11 +9,14 @@ enum NodeType {
@HiveField(0)
level,
@HiveField(1)
- prize;
+ prize,
+ @HiveField(2)
+ comingSoon;
static Map get fromJson => {
'level': NodeType.level,
'prize': NodeType.prize,
+ 'coming_soon': NodeType.comingSoon,
};
}
diff --git a/lib/features/level/domain/entity/node_entity.g.dart b/lib/features/level/domain/entity/node_entity.g.dart
index 41204de..e30471d 100644
--- a/lib/features/level/domain/entity/node_entity.g.dart
+++ b/lib/features/level/domain/entity/node_entity.g.dart
@@ -57,6 +57,8 @@ class NodeTypeAdapter extends TypeAdapter {
return NodeType.level;
case 1:
return NodeType.prize;
+ case 2:
+ return NodeType.comingSoon;
default:
return NodeType.level;
}
@@ -71,6 +73,9 @@ class NodeTypeAdapter extends TypeAdapter {
case NodeType.prize:
writer.writeByte(1);
break;
+ case NodeType.comingSoon:
+ writer.writeByte(2);
+ break;
}
}
diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart
index 851cbb6..90eb0c5 100644
--- a/lib/features/level/presentation/bloc/level_bloc.dart
+++ b/lib/features/level/presentation/bloc/level_bloc.dart
@@ -56,7 +56,7 @@ class LevelBloc extends Bloc {
final GetLastDownloadedLevel _getLastDownloadedLevel;
/// ------------Variables------------
- final List locationList = [
+ List locationList = [
LevelLocation(
bottom: setSize(context: MyContext.get, mobile: -0.03.h, tablet: -0.03.h),
left: setSize(context: MyContext.get, mobile: 0.1.w, tablet: 0.2.w),
@@ -64,7 +64,7 @@ class LevelBloc extends Bloc {
),
LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.03.h, tablet: 0.1.h),
- left: setSize(context: MyContext.get, mobile: 0.28.w, tablet: 0.4.w),
+ left: setSize(context: MyContext.get, mobile: 0.28.w, tablet: 0.38.w),
index: 1,
),
LevelLocation(
@@ -73,48 +73,48 @@ class LevelBloc extends Bloc {
index: 2,
),
LevelLocation(
- bottom: setSize(context: MyContext.get, mobile: 0.2.h, tablet: 0.38.h),
+ bottom: setSize(context: MyContext.get, mobile: 0.2.h, tablet: 0.33.h),
left: setSize(context: MyContext.get, mobile: 0.3.w, tablet: 0.3.w),
index: 3,
),
LevelLocation(
- bottom: setSize(context: MyContext.get, mobile: 0.24.h, tablet: 0.47.h),
- left: setSize(context: MyContext.get, mobile: 0.5.w, tablet: 0.5.w),
+ bottom: setSize(context: MyContext.get, mobile: 0.24.h, tablet: 0.4.h),
+ left: setSize(context: MyContext.get, mobile: 0.5.w, tablet: 0.55.w),
index: 4,
),
LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.33.h, tablet: 0.55.h),
- left: setSize(context: MyContext.get, mobile: 0.63.w, tablet: 0.8.w),
+ left: setSize(context: MyContext.get, mobile: 0.659.w, tablet: 0.77.w),
index: 5,
),
LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.45.h, tablet: 0.8.h),
- left: setSize(context: MyContext.get, mobile: 0.57.w, tablet: 0.8.w),
+ left: setSize(context: MyContext.get, mobile: 0.59.w, tablet: 0.68.w),
index: 6,
),
LevelLocation(
- bottom: setSize(context: MyContext.get, mobile: 0.55.h, tablet: 1.1.h),
- left: setSize(context: MyContext.get, mobile: 0.57.w, tablet: 0.77.w),
+ bottom: setSize(context: MyContext.get, mobile: 0.55.h, tablet: .951.h),
+ left: setSize(context: MyContext.get, mobile: 0.584.w, tablet: 0.64.w),
index: 7,
),
LevelLocation(
- bottom: setSize(context: MyContext.get, mobile: 0.6.h, tablet: 1.2.h),
- left: setSize(context: MyContext.get, mobile: 0.4.w, tablet: 0.65.w),
+ bottom: setSize(context: MyContext.get, mobile: 0.61.h, tablet: 1.03.h),
+ left: setSize(context: MyContext.get, mobile: 0.4.w, tablet: 0.18.w),
index: 8,
),
LevelLocation(
- bottom: setSize(context: MyContext.get, mobile: 0.63.h, tablet: 1.25.h),
- left: setSize(context: MyContext.get, mobile: 0.2.w, tablet: 0.3.w),
+ bottom: setSize(context: MyContext.get, mobile: 0.64.h, tablet: 1.19.h),
+ left: setSize(context: MyContext.get, mobile: 0.2.w, tablet: 0.22.w),
index: 9,
),
LevelLocation(
- bottom: setSize(context: MyContext.get, mobile: 0.7.h, tablet: 1.38.h),
- left: setSize(context: MyContext.get, mobile: 0, tablet: 0.04.w),
+ bottom: setSize(context: MyContext.get, mobile: 0.7.h, tablet: 1.25.h),
+ left: setSize(context: MyContext.get, mobile: 0, tablet: 0.53.w),
index: 10,
),
LevelLocation(
- bottom: setSize(context: MyContext.get, mobile: 0.75.h, tablet: 1.46.h),
- left: setSize(context: MyContext.get, mobile: 0.15.w, tablet: 0.22.w),
+ bottom: setSize(context: MyContext.get, mobile: 0.75.h, tablet: 1.39.h),
+ left: setSize(context: MyContext.get, mobile: 0.15.w, tablet: 0.58.w),
index: 11,
),
LevelLocation(
@@ -287,29 +287,39 @@ class LevelBloc extends Bloc {
final int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1',
);
- await _getLeveslUseCase(LevelParams()).then((value) {
- value.fold((data) async {
- nodeList.addAll(data);
- try {
- emit(
- state.copyWith(
- getLevelStatus: const BaseComplete(''),
- chooseLevel: data
- .firstWhere((e) => e.level?.order == currentLevel)
- .level,
- ),
- );
- } catch (e) {
- emit(
- state.copyWith(
- getLevelStatus: const BaseComplete(''),
- chooseLevel: LevelEntity(),
- ),
- );
- }
- add(StartScrollEvent());
+ final localResult = await _getLeveslUseCase(LevelParams());
+ localResult.fold((data) async {
+ nodeList
+ ..clear()
+ ..addAll(data);
+ try {
+ emit(
+ state.copyWith(
+ getLevelStatus: const BaseComplete(''),
+ chooseLevel: data
+ .firstWhere((e) => e.level?.order == currentLevel)
+ .level,
+ ),
+ );
+ } catch (e) {
+ emit(
+ state.copyWith(
+ getLevelStatus: const BaseComplete(''),
+ chooseLevel: LevelEntity(),
+ ),
+ );
+ }
+ add(StartScrollEvent());
+ }, (error) {});
+
+ if (nodeList.isNotEmpty && nodeList.last.nodeType == NodeType.comingSoon) {
+ final remoteResult = await _getLeveslUseCase(LevelParams(forceRemote: true));
+ remoteResult.fold((data) {
+ nodeList
+ ..clear()
+ ..addAll(data);
}, (error) {});
- });
+ }
}
FutureOr _startScrollEvent(
diff --git a/lib/features/level/presentation/ui/level_page.dart b/lib/features/level/presentation/ui/level_page.dart
index aca3773..4a00d56 100644
--- a/lib/features/level/presentation/ui/level_page.dart
+++ b/lib/features/level/presentation/ui/level_page.dart
@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
@@ -13,15 +14,14 @@ import 'package:hadi_hoda_flutter/core/widgets/animations/ship_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.dart';
import 'package:hadi_hoda_flutter/core/widgets/pop_scope/my_pop_scope.dart';
-import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.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/bloc/download_state.dart';
-import 'package:hadi_hoda_flutter/features/download/presentation/ui/widgets/download_loading_widget.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart';
+import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_bloc.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_event.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_state.dart';
+import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/coming_soon_level.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/diamond_level.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/level_path.dart';
import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/node_widget.dart';
@@ -35,10 +35,12 @@ class LevelPage extends StatefulWidget {
}
class _LevelPageState extends State {
+ static const double _muteThreshold = 0.001;
+
@override
void initState() {
super.initState();
- _triggerRemainingLevelsDownload();
+ if(!kDebugMode)_triggerRemainingLevelsDownload();
}
void _triggerRemainingLevelsDownload() {
@@ -64,8 +66,9 @@ class _LevelPageState extends State {
alignment: Alignment.center,
children: [
_background(context),
- _path(context),
_planets(context),
+ _path(context),
+
],
),
),
@@ -214,12 +217,12 @@ class _LevelPageState extends State {
return Positioned.fill(
top: 250,
bottom: 150,
- right: 60,
- left: 60,
+ right: 50,
+ left: 50,
child: Stack(
alignment: Alignment.center,
children: [
- const Positioned.fill(
+ const Positioned.fill(
child: LevelPath(),
),
Positioned.fill(child: _levelLocation(context)),
@@ -235,7 +238,18 @@ class _LevelPageState extends State {
children: [
...List.generate(
context.read().nodeList.length,
- (index) => Positioned(
+ (index) {
+ final node = context.read().nodeList[index];
+ if(node.nodeType == NodeType.comingSoon) {
+ if(node.nodeType == NodeType.comingSoon) {
+ return Positioned(
+ left: 0,
+ right: 0,
+ bottom: (context.read().locationList[index].bottom ?? 0) + 88,
+ child: const ComingSoonLevel());
+ }
+ }
+ return Positioned(
top: context.read().locationList[index].top,
bottom: context.read().locationList[index].bottom,
right: context.read().locationList[index].right,
@@ -261,7 +275,8 @@ class _LevelPageState extends State {
},
),
),
- ),
+ );
+ },
),
],
),
@@ -316,7 +331,9 @@ class _LevelPageState extends State {
builder: (context, snapshot) => MyInkwell(
onTap: () => context.read().changeMute(),
child: MyImage(
- image: snapshot.data == 0 ? MyAssets.musicOff : MyAssets.musicOn,
+ image: (snapshot.data ?? 1) <= _muteThreshold
+ ? MyAssets.musicOff
+ : MyAssets.musicOn,
size: setSize(context: context, tablet: 80),
),
),
diff --git a/lib/features/level/presentation/ui/widgets/coming_soon_level.dart b/lib/features/level/presentation/ui/widgets/coming_soon_level.dart
new file mode 100644
index 0000000..5a2b843
--- /dev/null
+++ b/lib/features/level/presentation/ui/widgets/coming_soon_level.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
+
+import '../../../../../common_ui/resources/my_assets.dart';
+import '../../../../../core/utils/my_localization.dart';
+import '../../../../../core/widgets/images/my_image.dart';
+
+class ComingSoonLevel extends StatelessWidget {
+ const ComingSoonLevel({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ clipBehavior: Clip.none,
+ children: [
+ Container(
+ alignment: Alignment.center,
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 42,
+ ).copyWith(bottom: 32),
+ width: double.infinity,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(14),
+ gradient: LinearGradient(
+ colors: [
+ const Color(0xff7E6C81).withValues(alpha: .9),
+ const Color(0xff48333F).withValues(alpha: .9),
+ ],
+ ),
+ ),
+ child: Text(context.translate.coming_soon, style: MYTextStyle.titr4, textAlign: TextAlign.center,),
+ ),
+ const Positioned(
+ left: 0,
+ right: 0,
+ top: -58, child: MyImage(image: MyAssets.lock)),
+ ],
+ );
+ }
+}
diff --git a/lib/features/level/presentation/ui/widgets/level_path.dart b/lib/features/level/presentation/ui/widgets/level_path.dart
index d052352..a90f9fe 100644
--- a/lib/features/level/presentation/ui/widgets/level_path.dart
+++ b/lib/features/level/presentation/ui/widgets/level_path.dart
@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:path_drawing/path_drawing.dart';
class LevelPath extends StatelessWidget {
- const LevelPath({
+ const LevelPath({
super.key,
});
- final double width = 357;
+ final double width = 377;
final double height = 2230;
@override
diff --git a/lib/features/level/presentation/ui/widgets/node_widget.dart b/lib/features/level/presentation/ui/widgets/node_widget.dart
index 93cb885..0750bb3 100644
--- a/lib/features/level/presentation/ui/widgets/node_widget.dart
+++ b/lib/features/level/presentation/ui/widgets/node_widget.dart
@@ -9,6 +9,8 @@ import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart
import 'package:hadi_hoda_flutter/features/level/domain/entity/node_entity.dart';
import 'package:hadi_hoda_flutter/features/level/domain/entity/prize_entity.dart';
+import 'coming_soon_level.dart';
+
enum LevelType {
unFinished,
finished,
@@ -55,7 +57,9 @@ class NodeWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Builder(
builder: (context) {
- if (node.nodeType == NodeType.prize) {
+ if (node.nodeType == NodeType.comingSoon) {
+ return const ComingSoonLevel();
+ } else if (node.nodeType == NodeType.prize) {
return MyInkwell(
onTap: () {
if (getReward(node.prize?.afterLevel ?? 1)) {
@@ -75,83 +79,109 @@ class NodeWidget extends StatelessWidget {
),
);
} else {
- return InkWell(
- onTap: () => onTap?.call(
- node.level ?? LevelEntity(),
- type(node.level?.order ?? 1),
- ),
- child: Stack(
- alignment: Alignment.topCenter,
- clipBehavior: Clip.none,
- children: [
- MyImage(
- image:
- LevelType.image[type(node.level?.order ?? 1)] ??
- MyAssets.level,
- fit: BoxFit.cover,
- size: setSize(context: context, tablet: 70, mobile: 44),
- ),
- ShaderMask(
- blendMode: BlendMode.modulate,
- shaderCallback: (bounds) => LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: [
- const Color(0XFFFFFFFF),
- LevelType.textColor[type(node.level?.order ?? 1)] ??
- MyColors.white,
- ],
- ).createShader(bounds),
- child: Text(
- '${node.level?.order ?? 0}',
- maxLines: 1,
- style: MYTextStyle.button1.copyWith(
- fontSize: setSize(
- context: context,
- mobile: 24,
- tablet: 34,
- ),
- shadows: [
- BoxShadow(
- color:
- LevelType.textShadowColor[type(
- node.level?.order ?? 1,
- )] ??
+ return Transform.translate(
+ offset: const Offset(-8, 8),
+ child: InkWell(
+ onTap: () => onTap?.call(
+ node.level ?? LevelEntity(),
+ type(node.level?.order ?? 1),
+ ),
+ child: Container(
+ color: Colors.transparent,
+ padding: const EdgeInsets.all(8),
+ child: Stack(
+ alignment: Alignment.topCenter,
+ clipBehavior: Clip.none,
+ children: [
+ MyImage(
+ image:
+ LevelType.image[type(node.level?.order ?? 1)] ??
+ MyAssets.level,
+ fit: BoxFit.cover,
+ size: setSize(context: context, tablet: 70, mobile: 44),
+ ),
+ ShaderMask(
+ blendMode: BlendMode.modulate,
+ shaderCallback: (bounds) => LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ const Color(0XFFFFFFFF),
+ LevelType.textColor[type(node.level?.order ?? 1)] ??
MyColors.white,
- offset: const Offset(0, 2.97),
+ ],
+ ).createShader(bounds),
+ child: Text(
+ '${node.level?.order ?? 0}',
+ maxLines: 1,
+ style: MYTextStyle.button1.copyWith(
+ fontSize: setSize(
+ context: context,
+ mobile: 24,
+ tablet: 34,
+ ),
+ shadows: [
+ BoxShadow(
+ color:
+ LevelType.textShadowColor[type(
+ node.level?.order ?? 1,
+ )] ??
+ MyColors.white,
+ offset: const Offset(0, 2.97),
+ ),
+ ],
),
- ],
- ),
- ),
- ),
- if (node.level?.id == chooseLevel?.id)
- Positioned(
- top: setSize(context: context, mobile: -20, tablet: -30),
- child: MyImage(
- image: MyAssets.location,
- size: setSize(context: context, mobile: 26, tablet: 40),
+ ),
),
- ),
- if (type(node.level?.order ?? 1) == LevelType.finished)
- Positioned(
- bottom: 0,
- child: Container(
- height: setSize(context: context, mobile: 17, tablet: 24),
- width: setSize(context: context, mobile: 17, tablet: 24),
- padding: const EdgeInsets.all(3),
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- border: Border.all(width: 1, color: const Color(0XFF3CFF3C)),
- gradient: const LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: [Color(0XFF48D336), Color(0XFF2D7C23)],
+ if (node.level?.id == chooseLevel?.id)
+ Positioned(
+ top: setSize(
+ context: context,
+ mobile: -20,
+ tablet: -30,
+ ),
+ child: MyImage(
+ image: MyAssets.location,
+ size: setSize(
+ context: context,
+ mobile: 26,
+ tablet: 40,
+ ),
),
),
- child: const MyImage(image: MyAssets.doneRounded),
- ),
- ),
- ],
+ if (type(node.level?.order ?? 1) == LevelType.finished)
+ Positioned(
+ bottom: 0,
+ child: Container(
+ height: setSize(
+ context: context,
+ mobile: 17,
+ tablet: 24,
+ ),
+ width: setSize(
+ context: context,
+ mobile: 17,
+ tablet: 24,
+ ),
+ padding: const EdgeInsets.all(3),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ border: Border.all(
+ width: 1,
+ color: const Color(0XFF3CFF3C),
+ ),
+ gradient: const LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [Color(0XFF48D336), Color(0XFF2D7C23)],
+ ),
+ ),
+ child: const MyImage(image: MyAssets.doneRounded),
+ ),
+ ),
+ ],
+ ),
+ ),
),
);
}
diff --git a/lib/features/level/presentation/ui/widgets/play_button.dart b/lib/features/level/presentation/ui/widgets/play_button.dart
index 8200877..b8071ed 100644
--- a/lib/features/level/presentation/ui/widgets/play_button.dart
+++ b/lib/features/level/presentation/ui/widgets/play_button.dart
@@ -24,7 +24,7 @@ class PlayButton extends StatelessWidget {
children: [
const MyImage(image: MyAssets.button2),
Positioned(
- top: MySpaces.s2,
+ top: MySpaces.s6,
child: Row(
spacing: MySpaces.s4,
children: [
@@ -54,13 +54,13 @@ class PlayButton extends StatelessWidget {
],
),
),
- Positioned(
- bottom: MySpaces.s20,
- child: Text(
- '${context.translate.step} ${level.order ?? 0}',
- style: MYTextStyle.matn3.copyWith(color: const Color(0XFFD8490B)),
- ),
- ),
+ // Positioned(
+ // bottom: MySpaces.s20,
+ // child: Text(
+ // '',
+ // style: MYTextStyle.matn3.copyWith(color: const Color(0XFFD8490B)),
+ // ),
+ // ),
],
),
);
diff --git a/lib/features/question/data/model/hadith_model.dart b/lib/features/question/data/model/hadith_model.dart
index 5ffc8c0..0ea21ef 100644
--- a/lib/features/question/data/model/hadith_model.dart
+++ b/lib/features/question/data/model/hadith_model.dart
@@ -7,6 +7,7 @@ class HadithModel extends HadithEntity {
super.narratorName,
super.translation,
super.status,
+ super.sourceName,
});
factory HadithModel.fromJson(Map json) {
@@ -16,6 +17,7 @@ class HadithModel extends HadithEntity {
narratorName: json['narrator_name'],
translation: json['translation'],
status: json['status'],
+ sourceName: json['source_name'],
);
}
}
diff --git a/lib/features/question/domain/entity/answer_entity.g.dart b/lib/features/question/domain/entity/answer_entity.g.dart
index bcc224b..9973dc6 100644
--- a/lib/features/question/domain/entity/answer_entity.g.dart
+++ b/lib/features/question/domain/entity/answer_entity.g.dart
@@ -25,6 +25,7 @@ class AnswerEntityAdapter extends TypeAdapter {
isActive: fields[5] as bool?,
audioID: fields[7] as String?,
audioInfo: fields[8] as FileEntity?,
+ levelOrder: fields[10] as int?,
)
..image = fields[6] as String?
..audio = fields[9] as String?;
@@ -33,7 +34,7 @@ class AnswerEntityAdapter extends TypeAdapter {
@override
void write(BinaryWriter writer, AnswerEntity obj) {
writer
- ..writeByte(10)
+ ..writeByte(11)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -53,7 +54,9 @@ class AnswerEntityAdapter extends TypeAdapter {
..writeByte(8)
..write(obj.audioInfo)
..writeByte(9)
- ..write(obj.audio);
+ ..write(obj.audio)
+ ..writeByte(10)
+ ..write(obj.levelOrder);
}
@override
diff --git a/lib/features/question/domain/entity/hadith_entity.dart b/lib/features/question/domain/entity/hadith_entity.dart
index 72abd4b..6088af6 100644
--- a/lib/features/question/domain/entity/hadith_entity.dart
+++ b/lib/features/question/domain/entity/hadith_entity.dart
@@ -14,6 +14,8 @@ class HadithEntity extends HiveObject {
String? translation;
@HiveField(4)
String? status;
+ @HiveField(5)
+ String? sourceName;
HadithEntity({
this.id,
@@ -21,5 +23,6 @@ class HadithEntity extends HiveObject {
this.narratorName,
this.translation,
this.status,
+ this.sourceName,
});
}
diff --git a/lib/features/question/domain/entity/question_entity.dart b/lib/features/question/domain/entity/question_entity.dart
index dab05ec..28e1986 100644
--- a/lib/features/question/domain/entity/question_entity.dart
+++ b/lib/features/question/domain/entity/question_entity.dart
@@ -65,7 +65,7 @@ class QuestionEntity extends HiveObject {
this.levelOrder,
}){
audio = '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/$levelOrder/question_audio/${audioInfo?.filename}';
- correctAudio = '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/$levelOrder/correct_answer_audio/${correctAnswerAudioInfo?.filename}';
+ correctAudio = '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/$levelOrder/question_audio/${correctAnswerAudioInfo?.filename}';
image = '${StoragePath.documentDir.path}/$levelOrder/question_image/${imageInfo?.filename}';
}
}
diff --git a/lib/features/question/domain/entity/question_entity.g.dart b/lib/features/question/domain/entity/question_entity.g.dart
index 8b31e5a..77b8866 100644
--- a/lib/features/question/domain/entity/question_entity.g.dart
+++ b/lib/features/question/domain/entity/question_entity.g.dart
@@ -31,6 +31,7 @@ class QuestionEntityAdapter extends TypeAdapter {
correctAnswerAudioInfo: fields[8] as FileEntity?,
imageId: fields[14] as String?,
imageInfo: fields[15] as FileEntity?,
+ levelOrder: fields[17] as int?,
)
..audio = fields[12] as String?
..correctAudio = fields[13] as String?
@@ -40,7 +41,7 @@ class QuestionEntityAdapter extends TypeAdapter {
@override
void write(BinaryWriter writer, QuestionEntity obj) {
writer
- ..writeByte(17)
+ ..writeByte(18)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -74,7 +75,9 @@ class QuestionEntityAdapter extends TypeAdapter {
..writeByte(15)
..write(obj.imageInfo)
..writeByte(16)
- ..write(obj.image);
+ ..write(obj.image)
+ ..writeByte(17)
+ ..write(obj.levelOrder);
}
@override
diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart
index 07fb5b7..923682a 100644
--- a/lib/features/question/presentation/bloc/question_bloc.dart
+++ b/lib/features/question/presentation/bloc/question_bloc.dart
@@ -27,11 +27,11 @@ import 'package:hadi_hoda_flutter/features/question/presentation/ui/screens/answ
class QuestionBloc extends Bloc {
/// ------------constructor------------
QuestionBloc(
- this._getLevelUseCase,
- this._getNextLevelUseCase,
- this._mainAudioService,
- this._effectAudioService,
- ) : super(const QuestionState()) {
+ this._getLevelUseCase,
+ this._getNextLevelUseCase,
+ this._mainAudioService,
+ this._effectAudioService,
+ ) : super(const QuestionState()) {
volumeStream = _mainAudioService.volumeStream();
initAudios();
on(_getLevelEvent);
@@ -40,7 +40,7 @@ class QuestionBloc extends Bloc {
@override
Future close() {
- if (_mainAudioService.audioVolume != 0) {
+ if (!_mainAudioService.isMuted) {
_mainAudioService.setVolume(volume: MyConstants.musicAudioVolume);
}
_backgroundAudioService.dispose();
@@ -125,6 +125,11 @@ class QuestionBloc extends Bloc {
await _mainAudioService.play();
}
+ Future playRightAnswerAudio() async {
+ await _mainAudioService.setAudio(assetPath: MyAudios.rightAnswer);
+ await _mainAudioService.play();
+ }
+
Future initAudios() async {
await Future.wait([
_mainAudioService.stop(),
@@ -132,7 +137,7 @@ class QuestionBloc extends Bloc {
_backgroundAudioService.setAudio(assetPath: MyAudios.question),
_backgroundAudioService.setLoopMode(isLoop: true),
]);
- if (_mainAudioService.audioVolume != 0) {
+ if (!_mainAudioService.isMuted) {
await Future.wait([
_mainAudioService.setVolume(volume: MyConstants.questionAudioVolume),
_backgroundAudioService.setVolume(volume: 0.1),
@@ -142,8 +147,11 @@ class QuestionBloc extends Bloc {
}
Future playWrongAudio() async {
- await _effectAudioService.setAudio(assetPath: MyAudios.incorrectAnswer);
- await _effectAudioService.play();
+ final audio = MyAudios.incorrectAnswer;
+ debugPrint('playWrongAudio: $audio');
+
+ await _mainAudioService.setAudio(assetPath: audio);
+ await _mainAudioService.play();
}
Future playAnswerAudio({String? audio}) async {
@@ -165,13 +173,13 @@ class QuestionBloc extends Bloc {
}
Future showQueueAnswer() async {
- if(!showAnswerSequence) return;
+ if (!showAnswerSequence) return;
final List answers = state.currentQuestion?.answers ?? [];
if (answers.isNotEmpty) {
answers.removeWhere((e) => e.imageId == null);
}
for (final answer in answers) {
- await Future.delayed(const Duration(milliseconds: 500), () async {
+ await Future.delayed(const Duration(milliseconds: 350), () async {
if (MyContext.get.mounted) {
await showAnswerDialog(
context: MyContext.get,
@@ -191,7 +199,7 @@ class QuestionBloc extends Bloc {
bool showConfetti = false,
bool autoClose = true,
}) async {
- if(showConfetti == false){
+ if (showConfetti == false) {
changeGlobeState(key: MyAnimations.globeStateSpeaking);
}
await Navigator.of(context).push(
@@ -210,17 +218,19 @@ class QuestionBloc extends Bloc {
}
Future getNextLevelEvent({required BuildContext context}) async {
- await _getNextLevelUseCase(QuestionParams()).then((value) =>
- value.fold((data) {
- add(GetLevelEvent('${data.id}', context));
- }, (error) {
- goToLevelPage(context: MyContext.get);
- },
- ),
+ await _getNextLevelUseCase(QuestionParams()).then(
+ (value) => value.fold(
+ (data) {
+ add(GetLevelEvent('${data.id}', context));
+ },
+ (error) {
+ goToLevelPage(context: MyContext.get);
+ },
+ ),
);
}
- void showingAnswerSequence({required bool show}){
+ void showingAnswerSequence({required bool show}) {
showAnswerSequence = show;
}
@@ -246,7 +256,10 @@ class QuestionBloc extends Bloc {
}
/// ------------Event Calls------------
- FutureOr _getLevelEvent(GetLevelEvent event, Emitter emit) async {
+ FutureOr _getLevelEvent(
+ GetLevelEvent event,
+ Emitter emit,
+ ) async {
await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then(
(value) {
value.fold(
@@ -257,16 +270,18 @@ class QuestionBloc extends Bloc {
title: data.title,
questions: [
...?data.questions,
- QuestionEntity(order: (data.questions?.length ?? 0) + 1)
+ QuestionEntity(order: (data.questions?.length ?? 0) + 1),
],
);
- emit(state.copyWith(
- getQuestionStatus: const BaseComplete(''),
- levelEntity: level,
- currentQuestion: data.questions?.first,
- showAnswers: false,
- correctAnswer: false,
- ));
+ emit(
+ state.copyWith(
+ getQuestionStatus: const BaseComplete(''),
+ levelEntity: level,
+ currentQuestion: data.questions?.first,
+ showAnswers: false,
+ correctAnswer: false,
+ ),
+ );
imageAnimationController?.forward();
changeGlobeState(key: MyAnimations.globeStateSpeaking);
await playQuestionAudio();
@@ -276,15 +291,19 @@ class QuestionBloc extends Bloc {
});
},
(error) {
- emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage)));
+ emit(
+ state.copyWith(getQuestionStatus: BaseError(error.errorMessage)),
+ );
},
);
},
);
}
- FutureOr _chooseAnswerEvent(ChooseAnswerEvent event,
- Emitter emit,) async {
+ FutureOr _chooseAnswerEvent(
+ ChooseAnswerEvent event,
+ Emitter emit,
+ ) async {
emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer));
if (event.chooseCorrectAnswer) {
@@ -292,24 +311,29 @@ class QuestionBloc extends Bloc {
await showAnswerDialog(
context: MyContext.get,
correctAudio: state.currentQuestion?.correctAudio,
- answerEntity: state.currentQuestion?.answers?.firstWhereOrNull((e) =>
- e.order == event.correctAnswer) ?? AnswerEntity(),
+ answerEntity:
+ state.currentQuestion?.answers?.firstWhereOrNull(
+ (e) => e.order == event.correctAnswer,
+ ) ??
+ AnswerEntity(),
showConfetti: true,
);
answerAnimationController?.reverse();
await Future.delayed(const Duration(seconds: 1), () async {
final QuestionEntity? findPreQuestion = state.currentQuestion;
final int findIndex = (findPreQuestion?.order ?? 1);
- emit(
- state.copyWith(
- currentQuestion: state.levelEntity?.questions?[findIndex],
- ),
- );
- if (state.currentQuestion?.order ==
- state.levelEntity?.questions?.length) {
+ final newCurrentQuestion = state.levelEntity?.questions?[findIndex];
+ final isLastQuestion =
+ newCurrentQuestion?.order == state.levelEntity?.questions?.length;
+ if (isLastQuestion) {
playDiamondAudio();
+ await Future.delayed(const Duration(milliseconds: 400));
+ }
+ emit(state.copyWith(currentQuestion: newCurrentQuestion));
+ if (isLastQuestion) {
int currentLevel = int.parse(
- LocalStorage.readData(key: MyConstants.currentLevel) ?? '1');
+ LocalStorage.readData(key: MyConstants.currentLevel) ?? '1',
+ );
if (state.levelEntity?.order == currentLevel) {
++currentLevel;
await LocalStorage.saveData(
@@ -334,7 +358,10 @@ class QuestionBloc extends Bloc {
changeGlobeState(key: MyAnimations.globeStateWrong);
playWrongAudio();
await Future.delayed(const Duration(milliseconds: 1500), () {
- changeGlobeState(key: MyAnimations.globeStateAfterWrong);
+ if (globeAnimationController?.value ==
+ globeStates[MyAnimations.globeStateWrong]) {
+ changeGlobeState(key: MyAnimations.globeStateAfterWrong);
+ }
});
}
}
diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart
index 0ec7332..94d084c 100644
--- a/lib/features/question/presentation/ui/question_page.dart
+++ b/lib/features/question/presentation/ui/question_page.dart
@@ -14,9 +14,29 @@ import 'package:hadi_hoda_flutter/features/question/presentation/ui/screens/diam
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/question_title.dart';
+import 'package:wakelock_plus/wakelock_plus.dart';
-class QuestionPage extends StatelessWidget {
+class QuestionPage extends StatefulWidget {
const QuestionPage({super.key});
+ static const double _muteThreshold = 0.001;
+
+ @override
+ State createState() => _QuestionPageState();
+}
+
+class _QuestionPageState extends State {
+
+ @override
+ void initState() {
+ WakelockPlus.enable();
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ WakelockPlus.disable();
+ super.dispose();
+ }
@override
Widget build(BuildContext context) {
@@ -106,7 +126,9 @@ class QuestionPage extends StatelessWidget {
initialData: 1,
stream: context.read().volumeStream,
builder: (context, snapshot) => GlassyButton(
- image: snapshot.data == 0 ? MyAssets.unMusic : MyAssets.music,
+ image: (snapshot.data ?? 1) <= QuestionPage._muteThreshold
+ ? MyAssets.unMusic
+ : MyAssets.music,
onTap: () => context.read().changeMute(),
),
),
diff --git a/lib/features/question/presentation/ui/screens/answer_screen.dart b/lib/features/question/presentation/ui/screens/answer_screen.dart
index 46e2066..34a9db8 100644
--- a/lib/features/question/presentation/ui/screens/answer_screen.dart
+++ b/lib/features/question/presentation/ui/screens/answer_screen.dart
@@ -81,6 +81,7 @@ class _AnswerScreenState extends State {
return Stack(
alignment: Alignment.center,
children: [
+ Container(color: Colors.black12),
Center(
child: Padding(
padding: EdgeInsets.symmetric(
diff --git a/lib/features/question/presentation/ui/screens/diamond_screen.dart b/lib/features/question/presentation/ui/screens/diamond_screen.dart
index 8c8ac35..3318327 100644
--- a/lib/features/question/presentation/ui/screens/diamond_screen.dart
+++ b/lib/features/question/presentation/ui/screens/diamond_screen.dart
@@ -160,17 +160,17 @@ class DiamondScreen extends StatelessWidget {
spacing: MySpaces.s12,
children: [
Expanded(
- child: MyWhiteButton(
+ child: MyYellowButton(
onTap: () => context.read().goToLevelPage(context: context),
title: context.translate.map,
),
),
- Expanded(
- child: MyYellowButton(
- onTap: () => context.read().getNextLevelEvent(context: context),
- title: context.translate.next,
- ),
- ),
+ // Expanded(
+ // child: MyYellowButton(
+ // onTap: () => context.read().getNextLevelEvent(context: context),
+ // title: context.translate.next,
+ // ),
+ // ),
],
);
}
diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart
index 2a9170d..7b4dd80 100644
--- a/lib/features/question/presentation/ui/screens/question_screen.dart
+++ b/lib/features/question/presentation/ui/screens/question_screen.dart
@@ -7,8 +7,10 @@ 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/constants/my_constants.dart';
+import 'package:hadi_hoda_flutter/core/utils/app_life_cycle.dart';
import 'package:hadi_hoda_flutter/core/utils/gap.dart';
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart';
+import 'package:hadi_hoda_flutter/core/utils/my_device.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/animations/fade_anim.dart';
@@ -34,6 +36,7 @@ class QuestionScreen extends StatefulWidget {
class _QuestionScreenState extends State
with TickerProviderStateMixin, WidgetsBindingObserver {
+ late final isTablet = MyDevice.isTablet(context);
@override
void initState() {
super.initState();
@@ -42,22 +45,22 @@ class _QuestionScreenState extends State
.read()
.answerAnimationController = AnimationController(
vsync: this,
- duration: const Duration(milliseconds: 500),
- reverseDuration: const Duration(milliseconds: 500),
+ duration: const Duration(milliseconds: 350),
+ reverseDuration: const Duration(milliseconds: 350),
);
context.read().imageAnimationController = AnimationController(
vsync: this,
- duration: const Duration(milliseconds: 500),
- reverseDuration: const Duration(milliseconds: 500),
+ duration: const Duration(milliseconds: 200),
+ reverseDuration: const Duration(milliseconds: 200),
);
if (LocalStorage.readData(key: MyConstants.firstShowcase) == 'true') {
context.read().imageAnimationController?.forward();
}
context.read().globeAnimationController = AnimationController(
vsync: this,
- duration: const Duration(milliseconds: 500),
- reverseDuration: const Duration(milliseconds: 500),
+ duration: const Duration(milliseconds: 350),
+ reverseDuration: const Duration(milliseconds: 350),
);
}
@@ -68,6 +71,7 @@ class _QuestionScreenState extends State
state == AppLifecycleState.inactive) {
context.read().pauseBackgroundPlaying();
} else if (state == AppLifecycleState.resumed) {
+ if (AppLifeCycleController.suppressAutoResumeAudio) return;
context.read().playBackgroundPlaying();
}
}
@@ -92,23 +96,20 @@ class _QuestionScreenState extends State
}
Widget _questionImage(BuildContext context) {
- return FadeAnimDelayed(
- duration: const Duration(seconds: 1),
- child: FadeAnimController(
- controller: context.read().imageAnimationController!,
- child: SingleChildScrollView(
- child: Column(
- children: [
- _titles(context),
- 20.0.gapHeight,
- BlocBuilder(
- builder: (context, state) => ImageBox(
- key: Key('${state.currentQuestion?.image}'),
- image: state.currentQuestion?.image ?? '',
- ),
+ return FadeAnimController(
+ controller: context.read().imageAnimationController!,
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ _titles(context),
+ 20.0.gapHeight,
+ BlocBuilder(
+ builder: (context, state) => ImageBox(
+ key: Key('${state.currentQuestion?.image}'),
+ image: state.currentQuestion?.image ?? '',
),
- ],
- ),
+ ),
+ ],
),
),
);
@@ -189,9 +190,10 @@ class _QuestionScreenState extends State
null) {
return const SizedBox.shrink();
} else {
- return SizedBox(
- width: 180,
- height: 250,
+ return Container(
+ alignment: isTablet ? Alignment.center : null,
+ width: isTablet ? 320 : 180,
+ height: isTablet ? 300 :250,
child: SlideAnim(
controller: context
.read()
@@ -240,9 +242,10 @@ class _QuestionScreenState extends State
null) {
return const SizedBox.shrink();
} else {
- return SizedBox(
- width: 180,
- height: 250,
+ return Container(
+ alignment: isTablet ? Alignment.center : null,
+ width: isTablet ? 320 : 180,
+ height: isTablet ? 300 :250,
child: SlideAnim(
controller: context
.read()
@@ -297,10 +300,11 @@ class _QuestionScreenState extends State
null) {
return const SizedBox.shrink();
} else {
- return SizedBox(
- width: 180,
- height: 250,
- child: SlideAnim(
+ return Container(
+ alignment: isTablet ? Alignment.center : null,
+ width: isTablet ? 320 : 180,
+ height: isTablet ? 300 :250,
+ child: SlideAnim(
controller: context
.read()
.answerAnimationController!,
@@ -354,10 +358,11 @@ class _QuestionScreenState extends State
null) {
return const SizedBox.shrink();
} else {
- return SizedBox(
- width: 180,
- height: 250,
- child: SlideAnim(
+ return Container(
+ alignment: isTablet ? Alignment.center : null,
+ width: isTablet ? 320 : 180,
+ height: isTablet ? 300 :250,
+ child: SlideAnim(
controller: context
.read()
.answerAnimationController!,
@@ -406,10 +411,11 @@ class _QuestionScreenState extends State
null) {
return const SizedBox.shrink();
} else {
- return SizedBox(
- width: 180,
- height: 250,
- child: SlideAnim(
+ return Container(
+ alignment: isTablet ? Alignment.center : null,
+ width: isTablet ? 320 : 180,
+ height: isTablet ? 300 :250,
+ child: SlideAnim(
controller: context
.read()
.answerAnimationController!,
diff --git a/lib/features/splash/presentation/bloc/splash_bloc.dart b/lib/features/splash/presentation/bloc/splash_bloc.dart
index 266f31f..66a8a1a 100644
--- a/lib/features/splash/presentation/bloc/splash_bloc.dart
+++ b/lib/features/splash/presentation/bloc/splash_bloc.dart
@@ -24,7 +24,7 @@ class SplashBloc extends Bloc {
const Duration(seconds: 2),
() {
if (context.mounted) {
- context.goNamed(Routes.homePage);
+ context.goNamed(Routes.introPage);
}
},
);
diff --git a/lib/features/splash/presentation/ui/splash_page.dart b/lib/features/splash/presentation/ui/splash_page.dart
index 2eba72a..29031e7 100644
--- a/lib/features/splash/presentation/ui/splash_page.dart
+++ b/lib/features/splash/presentation/ui/splash_page.dart
@@ -45,7 +45,7 @@ class _SplashPageState extends State {
scale: 3,
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
- Colors.white.withValues(alpha: 0.2),
+ Colors.white.withValues(alpha: 0.22),
BlendMode.srcIn,
),
),
@@ -53,7 +53,7 @@ class _SplashPageState extends State {
child: Stack(
alignment: Alignment.center,
children: [
- // _image(),
+ Positioned.fill(child: _image()),
_loading(context),
],
),
@@ -63,15 +63,26 @@ class _SplashPageState extends State {
Widget _image() {
return const Stack(
+ alignment: Alignment.center,
children: [
MyImage(
image: MyAssets.hadiHoda,
),
- PositionedDirectional(
- start: MySpaces.s10,
- top: MySpaces.s40,
- child: MyImage(
- image: MyAssets.globe,
+ Positioned(
+ right: 0,
+ left: 0,
+ top: 0,
+ bottom: 0,
+ child: Center(
+ child: Padding(
+ padding: EdgeInsets.only(bottom: 140, right: 235),
+ child: Opacity(
+ opacity: .94,
+ child: MyImage(
+ image: MyAssets.globe,
+ ),
+ ),
+ ),
),
),
],
@@ -82,6 +93,8 @@ class _SplashPageState extends State {
Positioned _loading(BuildContext context) {
return Positioned(
bottom: MySpaces.s40,
+ right: 0,
+ left: 0,
child: RotationAnim(
child: MyImage(
image: MyAssets.loading,
diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart
index a8ce47c..e4c3e22 100644
--- a/lib/init_bindings.dart
+++ b/lib/init_bindings.dart
@@ -68,46 +68,99 @@ void initBindings() {
locator.registerSingleton(AppBloc());
/// Blocs
- locator.registerFactory(() => LanguageBloc(locator(), locator(), locator()));
+ locator.registerFactory(
+ () => LanguageBloc(locator(), locator(), locator()),
+ );
locator.registerFactory(
- () => DownloadBloc(locator(), locator(), locator(), locator(), locator()));
+ () => DownloadBloc(locator(), locator(), locator(), locator(), locator()),
+ );
/// Sample Feature
- locator.registerLazySingleton(() => SampleDatasourceImpl(locator()));
- locator.registerLazySingleton(() => SampleRepositoryImpl(locator()));
- locator.registerLazySingleton(() => GetSampleUseCase(locator()));
+ locator.registerLazySingleton(
+ () => SampleDatasourceImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => SampleRepositoryImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetSampleUseCase(locator()),
+ );
/// Download Feature
- locator.registerLazySingleton(() => DownloadDatasourceImpl(locator()));
- locator.registerLazySingleton(() => DownloadRepositoryImpl(locator()));
- locator.registerLazySingleton(() => SaveLevelsUseCase(locator()));
- locator.registerLazySingleton(() => BatchDownloadUseCase(locator()));
- locator.registerLazySingleton(() => LoadingStreamUseCase(locator()));
- locator.registerLazySingleton(() => CancelDownloadUseCase(locator()));
- locator.registerLazySingleton(() => GetLastDownloadedLevel(locator()));
+ locator.registerLazySingleton(
+ () => DownloadDatasourceImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => DownloadRepositoryImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => SaveLevelsUseCase(locator()),
+ );
+ locator.registerLazySingleton(
+ () => BatchDownloadUseCase(locator()),
+ );
+ locator.registerLazySingleton(
+ () => LoadingStreamUseCase(locator()),
+ );
+ locator.registerLazySingleton(
+ () => CancelDownloadUseCase(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetLastDownloadedLevel(locator()),
+ );
/// Guider Feature
- locator.registerLazySingleton(() => const GuiderDatasourceImpl());
- locator.registerLazySingleton(() => GuiderRepositoryImpl(locator()));
- locator.registerLazySingleton(() => GetFirstLevelUseCase(locator()));
+ locator.registerLazySingleton(
+ () => const GuiderDatasourceImpl(),
+ );
+ locator.registerLazySingleton(
+ () => GuiderRepositoryImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetFirstLevelUseCase(locator()),
+ );
/// Question Feature
- locator.registerLazySingleton(() => const QuestionDatasourceImpl());
- locator.registerLazySingleton(() => QuestionRepositoryImpl(locator()));
- locator.registerLazySingleton(() => GetLevelUseCase(locator()));
- locator.registerLazySingleton(() => GetNextLevelUseCase(locator()));
+ locator.registerLazySingleton(
+ () => const QuestionDatasourceImpl(),
+ );
+ locator.registerLazySingleton(
+ () => QuestionRepositoryImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetLevelUseCase(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetNextLevelUseCase(locator()),
+ );
/// Level Feature
- locator.registerLazySingleton(() => const LocalLevelDatasourceImpl());
- locator.registerLazySingleton(() => LevelRepositoryImpl(locator()));
- locator.registerLazySingleton(() => GetLevelsUseCase(locator()));
+ locator.registerLazySingleton(
+ () => LocalLevelDatasourceImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => LevelRepositoryImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetLevelsUseCase(locator()),
+ );
/// Language Feature
- locator.registerLazySingleton(() => LanguageDatasourceImpl(locator()));
- locator.registerLazySingleton(() => LanguageRepositoryImpl(locator()));
- locator.registerLazySingleton(() => GetLanguagesUseCase(locator()));
- locator.registerLazySingleton(() => GetSelectedLanguageUseCase(locator()));
- locator.registerLazySingleton(() => SaveSelectedLanguageUseCase(locator()));
+ locator.registerLazySingleton(
+ () => LanguageDatasourceImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => LanguageRepositoryImpl(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetLanguagesUseCase(locator()),
+ );
+ locator.registerLazySingleton(
+ () => GetSelectedLanguageUseCase(locator()),
+ );
+ locator.registerLazySingleton(
+ () => SaveSelectedLanguageUseCase(locator()),
+ );
}
Future initDataBase() async {
diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb
index 6999f41..f0a2f1c 100644
--- a/lib/l10n/app_ar.arb
+++ b/lib/l10n/app_ar.arb
@@ -36,5 +36,6 @@
"showcase_stepper": "هنا سترى الأسئلة الخاصة\nبهذه المرحلة للوصول\nإلى الماسة.",
"showcase_hadith": "اطّلع على المصادر والأحاديث\nلهذا السؤال",
"showcase_guide": "هذا دليل سيساعدك\nفي رحلتك.",
- "reward": "جائزة"
+ "reward": "جائزة",
+ "downloading": "جارٍ التنزيل"
}
diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb
index 156cbbd..6fa52ec 100644
--- a/lib/l10n/app_de.arb
+++ b/lib/l10n/app_de.arb
@@ -36,5 +36,6 @@
"showcase_stepper": "Hier siehst du die\nFragen für diese\nStufe, um den\nDiamanten zu erreichen.",
"showcase_hadith": "Quellen und\nHadithe zu dieser\nFrage ansehen.",
"showcase_guide": "Dies ist eine Anleitung,\ndie dir hilft.",
- "reward": "belohnen"
+ "reward": "belohnen",
+ "downloading": "Wird heruntergeladen"
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index e4b3a06..ebc30c1 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -36,5 +36,7 @@
"showcase_stepper": "Here you will see the\nquestions for this\nstage to reach the\ndiamond.",
"showcase_hadith": "View sources and\nhadiths for this\nquestion",
"showcase_guide": "This is a guide that will\nhelp you.",
- "reward": "Reward"
+ "reward": "Reward",
+ "coming_soon": "Next stages will open soon",
+ "downloading": "Downloading"
}
\ No newline at end of file
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 5ba2ce2..e687c4e 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -36,5 +36,6 @@
"showcase_stepper": "Ici, tu verras les\nquestions de cette\nétape pour atteindre\nle diamant.",
"showcase_hadith": "Consulte les sources et\nles hadiths pour cette\nquestion",
"showcase_guide": "Ceci est un guide\nqui t’aidera.",
- "reward": "récompense"
+ "reward": "récompense",
+ "downloading": "Téléchargement"
}
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index 68eec8f..6f69286 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -327,6 +327,18 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Reward'**
String get reward;
+
+ /// No description provided for @coming_soon.
+ ///
+ /// In en, this message translates to:
+ /// **'Next stages will open soon'**
+ String get coming_soon;
+
+ /// No description provided for @downloading.
+ ///
+ /// In en, this message translates to:
+ /// **'Downloading'**
+ String get downloading;
}
class _AppLocalizationsDelegate
diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart
index 3c5f345..6372b36 100644
--- a/lib/l10n/app_localizations_ar.dart
+++ b/lib/l10n/app_localizations_ar.dart
@@ -122,4 +122,10 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get reward => 'جائزة';
+
+ @override
+ String get coming_soon => 'Next stages will open soon';
+
+ @override
+ String get downloading => 'جارٍ التنزيل';
}
diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart
index 49f7a1f..b70c80c 100644
--- a/lib/l10n/app_localizations_de.dart
+++ b/lib/l10n/app_localizations_de.dart
@@ -129,4 +129,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get reward => 'belohnen';
+
+ @override
+ String get coming_soon => 'Next stages will open soon';
+
+ @override
+ String get downloading => 'Wird heruntergeladen';
}
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 991362a..1facbbd 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -126,4 +126,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get reward => 'Reward';
+
+ @override
+ String get coming_soon => 'Next stages will open soon';
+
+ @override
+ String get downloading => 'Downloading';
}
diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart
index 6e6ec23..3c31237 100644
--- a/lib/l10n/app_localizations_fr.dart
+++ b/lib/l10n/app_localizations_fr.dart
@@ -127,4 +127,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get reward => 'récompense';
+
+ @override
+ String get coming_soon => 'Next stages will open soon';
+
+ @override
+ String get downloading => 'Téléchargement';
}
diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart
index e374e4a..076a49d 100644
--- a/lib/l10n/app_localizations_ru.dart
+++ b/lib/l10n/app_localizations_ru.dart
@@ -127,4 +127,10 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get reward => 'награда';
+
+ @override
+ String get coming_soon => 'Next stages will open soon';
+
+ @override
+ String get downloading => 'Загрузка';
}
diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart
index db16200..967e94f 100644
--- a/lib/l10n/app_localizations_tr.dart
+++ b/lib/l10n/app_localizations_tr.dart
@@ -126,4 +126,10 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get reward => 'ödül';
+
+ @override
+ String get coming_soon => 'Next stages will open soon';
+
+ @override
+ String get downloading => 'İndiriliyor';
}
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index 9460133..e546b20 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -36,5 +36,6 @@
"showcase_stepper": "Здесь ты увидишь\nвопросы этого этапа,\nчтобы получить\nалмаз.",
"showcase_hadith": "Просмотри источники и\nхадисы по этому вопросу",
"showcase_guide": "Это руководство,\nкоторое поможет тебе.",
- "reward": "награда"
+ "reward": "награда",
+ "downloading": "Загрузка"
}
diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb
index 9e4691c..2cc995b 100644
--- a/lib/l10n/app_tr.arb
+++ b/lib/l10n/app_tr.arb
@@ -36,5 +36,6 @@
"showcase_stepper": "Burada, elmasa ulaşmak\niçin bu aşamadaki\nsoruları göreceksin.",
"showcase_hadith": "Bu soruya ait\nkaynakları ve hadisleri\nincele",
"showcase_guide": "Bu, sana yardımcı olacak\nbir rehberdir.",
- "reward": "ödül"
+ "reward": "ödül",
+ "downloading": "İndiriliyor"
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 8f93907..1d7c8d9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -10,6 +10,7 @@ dependencies:
auto_size_text: ^3.0.0
bloc: ^9.0.0
collection: ^1.19.1
+ connectivity_plus: ^7.0.0
dio: ^5.9.0
easy_stepper: ^0.8.5+1
equatable: ^2.0.7
@@ -32,7 +33,9 @@ dependencies:
pretty_dio_logger: ^1.4.0
shared_preferences: ^2.5.3
showcaseview: ^5.0.1
+ url_launcher: ^6.3.2
vector_graphics: ^1.1.19
+ wakelock_plus: ^1.4.0
wheel_chooser: ^1.0.1
dev_dependencies:
@@ -52,6 +55,7 @@ flutter:
- assets/images/
- assets/animations/
- assets/audios/
+ - assets/audios/incorrect_answers/
- assets/videos/
- assets/svg/
@@ -76,4 +80,7 @@ flutter:
- asset: assets/fonts/Baloo2-Bold.ttf
weight: 700
- asset: assets/fonts/Baloo2-ExtraBold.ttf
- weight: 800
\ No newline at end of file
+ weight: 800
+ - family: NotoSansArabic
+ fonts:
+ - asset: assets/fonts/NotoSansArabic.ttf
\ No newline at end of file