Browse Source

update

develop
sina 2 days ago
parent
commit
acb59c1577
  1. 3
      android/app/src/main/AndroidManifest.xml
  2. 12
      lib/core/services/audio_service.dart
  3. 161
      lib/core/widgets/animations/smart_gif.dart
  4. 7
      lib/core/widgets/answer_box/styles/image_box.dart
  5. 333
      lib/core/widgets/answer_box/styles/picture_box.dart
  6. 3
      lib/core/widgets/images/my_image.dart
  7. 21
      lib/features/download/data/datasource/download_datasource.dart
  8. 58
      lib/features/intro/presentation/bloc/intro_bloc.dart
  9. 32
      lib/features/intro/presentation/ui/intro_page.dart
  10. 9
      lib/features/language/presentation/ui/language_page.dart
  11. 7
      lib/features/level/data/datasource/level_datasource.dart
  12. 36
      lib/features/level/presentation/bloc/level_bloc.dart
  13. 4
      lib/features/level/presentation/ui/level_page.dart
  14. 6
      lib/features/question/domain/entity/question_entity.dart
  15. 19
      lib/features/question/presentation/bloc/question_bloc.dart
  16. 4
      lib/features/question/presentation/ui/question_page.dart
  17. 14
      lib/features/question/presentation/ui/screens/question_screen.dart
  18. 3
      lib/l10n/app_ar.arb
  19. 3
      lib/l10n/app_de.arb
  20. 3
      lib/l10n/app_fr.arb
  21. 2
      lib/l10n/app_localizations_ar.dart
  22. 2
      lib/l10n/app_localizations_de.dart
  23. 2
      lib/l10n/app_localizations_fr.dart
  24. 2
      lib/l10n/app_localizations_ru.dart
  25. 2
      lib/l10n/app_localizations_tr.dart
  26. 1
      lib/l10n/app_ru.arb
  27. 1
      lib/l10n/app_tr.arb
  28. 2
      lib/main.dart
  29. 1
      pubspec.yaml

3
android/app/src/main/AndroidManifest.xml

@ -32,6 +32,9 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and

12
lib/core/services/audio_service.dart

@ -106,15 +106,13 @@ class AudioService {
bool get isMuted => _player.volume <= _muteThreshold; bool get isMuted => _player.volume <= _muteThreshold;
Stream<bool> playingStream() async* { Stream<bool> playingStream() async* {
_player.processingStateStream.listen((event) {
await for (final event in _player.processingStateStream) {
if (event == ProcessingState.ready) { if (event == ProcessingState.ready) {
_streamController.add(true);
yield true;
} else if (event == ProcessingState.completed) {
yield false;
} }
if (event == ProcessingState.completed) {
_streamController.add(false);
}
});
yield* _streamController.stream;
}
} }
Future<void> setLoopMode({bool isLoop = false}) async { Future<void> setLoopMode({bool isLoop = false}) async {

161
lib/core/widgets/animations/smart_gif.dart

@ -0,0 +1,161 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:gif/gif.dart';
import 'package:image/image.dart' as img;
class SmartGif extends StatefulWidget {
final String gifPath;
final Autostart autoStart;
final BoxFit fit;
final int? cacheWidth;
final Color? color;
final BlendMode? colorBlendMode;
const SmartGif({
super.key,
required this.gifPath,
required this.autoStart,
this.fit = BoxFit.cover,
this.cacheWidth,
this.color,
this.colorBlendMode,
});
@override
State<SmartGif> createState() => SmartGifState();
}
class SmartGifState extends State<SmartGif>
with SingleTickerProviderStateMixin {
late GifController _gifController;
// bool _isPlaying = false;
// ImageProvider? _gifProvider;
@override
void initState() {
super.initState();
// _provideGif();
_gifController = GifController(vsync: this);
// _isPlaying = widget.autoStart;
debugPrint("SmartGif initState: gifPath=${widget.gifPath}, autoStart=${widget.autoStart}");
// if (_isPlaying) {
// debugPrint("SmartGif: starting animation in initState");
// _gifController.repeat(
// min: 0,
// max: 1,
// period: const Duration(milliseconds: 1000), // Default speed
// );
// } else {
// _gifController.value = 0;
// }
}
// void _provideGif() async {
// final file = ;
// final bytes = await file.readAsBytes();
// final gif = img.decodeGif(bytes);
//
// // resize frames
// final resized = img.copyResize(gif!, width: 300);
//
// // save temp file
// final newFile = File('${widget.gifPath}-resized.gif')..writeAsBytesSync(img.encodeGif(resized));
// if(mounted) {
// setState(() {
// _gifProvider = FileImage(newFile);
// });
// }
// }
// @override
// void didUpdateWidget(covariant SmartGif oldWidget) {
// super.didUpdateWidget(oldWidget);
//
// if (oldWidget.gifPath != widget.gifPath) {
// _isPlaying = widget.autoStart;
// if (_isPlaying) {
// _gifController.repeat();
// } else {
// _gifController.stop();
// _gifController.value = 0;
// }
// } else if (oldWidget.autoStart != widget.autoStart) {
// if (widget.autoStart) {
// start();
// } else {
// stop();
// }
// }
// }
@override
void dispose() {
_gifController.stop();
_gifController.dispose();
super.dispose();
}
// void start() {
// if (!mounted) return;
// setState(() {
// // _isPlaying = true;
// _gifController.repeat();
// });
// }
// void stop() {
// if (!mounted) return;
// setState(() {
// // _isPlaying = false;
// _gifController.stop();
// _gifController.value = 0; // Show first frame
// });
// }
//
// void restart() {
// if (!mounted) return;
// setState(() {
// // _isPlaying = true;
// _gifController.value = 0;
// _gifController.repeat();
// });
// }
@override
Widget build(BuildContext context) {
// bool showPreview = !_isPlaying;
// bool hasPreview =
// widget.previewPath != null && File(widget.previewPath!).existsSync();
//
// if (showPreview && hasPreview) {
// return Image.file(
// File(widget.previewPath!),
// fit: widget.fit,
// cacheWidth: widget.cacheWidth,
// color: widget.color,
// colorBlendMode: widget.colorBlendMode,
// gaplessPlayback: true,
// );
// }
//
// debugPrint("SmartGif build: _isPlaying=$_isPlaying, hasPreview=$hasPreview");
// 🔴 PLAY/STOPPED GIF (using controller for frame control)
// final ImageProvider imageProvider = FileImage(File(widget.gifPath));
// final ImageProvider finalProvider = widget.cacheWidth != null
// ? ResizeImage(imageProvider, width: widget.cacheWidth)
// : imageProvider;
return RepaintBoundary(
child: Gif(
image: FileImage(File(widget.gifPath)),
controller: _gifController,
autostart: widget.autoStart,
fit: widget.fit,
color: widget.color,
colorBlendMode: widget.colorBlendMode,
),
);
}
}

7
lib/core/widgets/answer_box/styles/image_box.dart

@ -27,10 +27,11 @@ class ImageBox extends StatelessWidget {
reverseDuration: const Duration(milliseconds: 150), reverseDuration: const Duration(milliseconds: 150),
switchInCurve: Curves.linear, switchInCurve: Curves.linear,
switchOutCurve: Curves.linear, switchOutCurve: Curves.linear,
child: Image(
child: Image.file(
File(image),
width: context.widthScreen, width: context.widthScreen,
height: context.heightScreen,
image: FileImage(File(image)),
height: context.widthScreen,
cacheWidth: (context.widthScreen * MediaQuery.of(context).devicePixelRatio).round(),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
transitionBuilder: (child, animation) => transitionBuilder: (child, animation) =>

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

@ -10,7 +10,9 @@ 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/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart'; import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
class AnswerPictureBox extends StatelessWidget {
import '../../animations/smart_gif.dart';
class AnswerPictureBox extends StatefulWidget {
const AnswerPictureBox({ const AnswerPictureBox({
super.key, super.key,
required this.selected, required this.selected,
@ -30,9 +32,26 @@ class AnswerPictureBox extends StatelessWidget {
final Autostart autostart; final Autostart autostart;
final bool showIndex; final bool showIndex;
@override
State<AnswerPictureBox> createState() => _AnswerPictureBoxState();
}
class _AnswerPictureBoxState extends State<AnswerPictureBox> {
@override
void initState() {
debugPrint("init picture box : ${widget.image}");
super.initState();
}
@override
void dispose() {
debugPrint("dispose picture box : ${widget.image}");
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint('image $index : $image');
return CustomPaint( return CustomPaint(
painter: _CustomShapePainter(), painter: _CustomShapePainter(),
child: ClipPath( child: ClipPath(
@ -42,40 +61,9 @@ class AnswerPictureBox extends StatelessWidget {
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 150),
reverseDuration: const Duration(milliseconds: 150),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
child: selected && (index != correctAnswer) ?
Gif(
key: const Key('1'),
width: context.widthScreen,
height: context.heightScreen,
image: FileImage(File(image)),
fps: 20,
autostart: autostart,
fit: BoxFit.cover,
color: MyColors.black,
colorBlendMode: BlendMode.color,
) :
Gif(
key: const Key('2'),
width: context.widthScreen,
height: context.heightScreen,
image: FileImage(File(image)),
fps: 20,
autostart: autostart,
fit: BoxFit.cover,
),
transitionBuilder: (child, animation) =>
FadeTransition(
opacity: animation,
child: child,
),
),
child: _buildImageContent(context),
), ),
if (showIndex)
if (widget.showIndex)
PositionedDirectional( PositionedDirectional(
top: 0, top: 0,
child: Container( child: Container(
@ -88,20 +76,29 @@ class AnswerPictureBox extends StatelessWidget {
), ),
), ),
child: Text( child: Text(
'$index',
'${widget.index}',
style: MYTextStyle.titr1.copyWith( style: MYTextStyle.titr1.copyWith(
color: const Color(0XFF9B85D8), color: const Color(0XFF9B85D8),
), ),
), ),
), ),
), ),
if(selected)
if (widget.selected)
PositionedDirectional( PositionedDirectional(
top: setSize(context: context, mobile: MySpaces.s8, tablet: MySpaces.s20),
start: setSize(context: context, mobile: MySpaces.s8, tablet: MySpaces.s20),
top: setSize(
context: context,
mobile: MySpaces.s8,
tablet: MySpaces.s20,
),
start: setSize(
context: context,
mobile: MySpaces.s8,
tablet: MySpaces.s20,
),
child: MyImage( child: MyImage(
image: index == correctAnswer ? MyAssets.correct : MyAssets
.wrong,
image: widget.index == widget.correctAnswer
? MyAssets.correct
: MyAssets.wrong,
), ),
), ),
], ],
@ -109,6 +106,21 @@ class AnswerPictureBox extends StatelessWidget {
), ),
); );
} }
Widget _buildImageContent(BuildContext context) {
final bool isWrongSelection =
widget.selected && (widget.index != widget.correctAnswer);
final autoStart = widget.autostart != Autostart.no;
debugPrint("autoStart : $autoStart");
return SmartGif(
// key: ValueKey('gif_${isWrongSelection}_${widget.index}_${widget.image}'),
gifPath: widget.image,
autoStart: widget.autostart,
fit: BoxFit.cover,
color: isWrongSelection ? MyColors.black : null,
colorBlendMode: isWrongSelection ? BlendMode.color : null,
);
}
} }
class _CustomShapePainter extends CustomPainter { class _CustomShapePainter extends CustomPainter {
@ -143,33 +155,218 @@ class _CustomShapeClipper extends CustomClipper<Path> {
// The path is defined using the scaled coordinates from the SVG. // The path is defined using the scaled coordinates from the SVG.
final Path path = Path() final Path path = Path()
..moveTo(148.483 * scaleX, 4.10254 * scaleY) ..moveTo(148.483 * scaleX, 4.10254 * scaleY)
..cubicTo(131.624 * scaleX, 1.93333 * scaleY, 111.221 * scaleX, 1.00169 * scaleY, 91.2451 * scaleX, 1.2666 * scaleY)
..cubicTo(71.2667 * scaleX, 1.53156 * scaleY, 51.7626 * scaleX, 2.99274 * scaleY, 36.6973 * scaleX, 5.59668 * scaleY)
..cubicTo(29.1597 * scaleX, 6.8995 * scaleY, 22.7796 * scaleX, 8.48114 * scaleY, 18.0205 * scaleX, 10.3203 * scaleY)
..cubicTo(15.641 * scaleX, 11.2399 * scaleY, 13.7026 * scaleX, 12.2101 * scaleY, 12.2383 * scaleX, 13.2188 * scaleY)
..cubicTo(10.7653 * scaleX, 14.2333 * scaleY, 9.84633 * scaleX, 15.2359 * scaleY, 9.3916 * scaleX, 16.1904 * scaleY)
..cubicTo(8.252 * scaleX, 18.5828 * scaleY, 7.18153 * scaleX, 22.466 * scaleY, 6.2207 * scaleX, 27.5654 * scaleY)
..cubicTo(5.26481 * scaleX, 32.6387 * scaleY, 4.43215 * scaleX, 38.8273 * scaleY, 3.73535 * scaleX, 45.7744 * scaleY)
..cubicTo(2.34189 * scaleX, 59.6675 * scaleY, 1.49647 * scaleX, 76.5363 * scaleY, 1.27832 * scaleX, 93.4678 * scaleY)
..cubicTo(1.06017 * scaleX, 110.4 * scaleY, 1.47057 * scaleX, 127.372 * scaleY, 2.58301 * scaleX, 141.473 * scaleY)
..cubicTo(3.13928 * scaleX, 148.524 * scaleY, 3.86921 * scaleX, 154.841 * scaleY, 4.78125 * scaleX, 160.068 * scaleY)
..cubicTo(5.69748 * scaleX, 165.32 * scaleY, 6.78334 * scaleX, 169.385 * scaleY, 8.01367 * scaleX, 171.984 * scaleY)
..cubicTo(8.53417 * scaleX, 173.084 * scaleY, 9.59654 * scaleX, 174.216 * scaleY, 11.2891 * scaleX, 175.343 * scaleY)
..cubicTo(12.9722 * scaleX, 176.463 * scaleY, 15.1988 * scaleX, 177.524 * scaleY, 17.9219 * scaleX, 178.515 * scaleY)
..cubicTo(23.3679 * scaleX, 180.496 * scaleY, 30.6491 * scaleX, 182.138 * scaleY, 39.1807 * scaleX, 183.437 * scaleY)
..cubicTo(56.2336 * scaleX, 186.032 * scaleY, 78.0934 * scaleX, 187.222 * scaleY, 99.8242 * scaleX, 187.064 * scaleY)
..cubicTo(121.556 * scaleX, 186.906 * scaleY, 143.101 * scaleX, 185.4 * scaleY, 159.525 * scaleX, 182.622 * scaleY)
..cubicTo(167.745 * scaleX, 181.232 * scaleY, 174.627 * scaleX, 179.531 * scaleY, 179.594 * scaleX, 177.548 * scaleY)
..cubicTo(182.079 * scaleX, 176.556 * scaleY, 184.034 * scaleX, 175.512 * scaleY, 185.429 * scaleX, 174.437 * scaleY)
..cubicTo(186.83 * scaleX, 173.355 * scaleY, 187.568 * scaleX, 172.319 * scaleY, 187.812 * scaleX, 171.361 * scaleY)
..lineTo(187.812 * scaleX, 171.361 * scaleY) // In SVG, this was H (horizontal line), equivalent to lineTo in Flutter
..cubicTo(189.156 * scaleX, 166.074 * scaleY, 190.148 * scaleX, 155.525 * scaleY, 190.773 * scaleX, 142.157 * scaleY)
..cubicTo(191.396 * scaleX, 128.832 * scaleY, 191.651 * scaleX, 112.822 * scaleY, 191.552 * scaleX, 96.6875 * scaleY)
..cubicTo(191.453 * scaleX, 80.5539 * scaleY, 191.001 * scaleX, 64.3091 * scaleY, 190.213 * scaleX, 50.5156 * scaleY)
..cubicTo(189.423 * scaleX, 36.6928 * scaleY, 188.299 * scaleX, 25.4153 * scaleY, 186.876 * scaleX, 19.167 * scaleY)
..cubicTo(186.404 * scaleX, 17.0929 * scaleY, 185.566 * scaleX, 15.3424 * scaleY, 184.087 * scaleX, 14.1582 * scaleY)
..cubicTo(181.343 * scaleX, 11.9613 * scaleY, 176.72 * scaleX, 9.98089 * scaleY, 170.561 * scaleX, 8.27539 * scaleY)
..cubicTo(164.434 * scaleX, 6.579 * scaleY, 156.914 * scaleX, 5.18731 * scaleY, 148.483 * scaleX, 4.10254 * scaleY)
..cubicTo(
131.624 * scaleX,
1.93333 * scaleY,
111.221 * scaleX,
1.00169 * scaleY,
91.2451 * scaleX,
1.2666 * scaleY,
)
..cubicTo(
71.2667 * scaleX,
1.53156 * scaleY,
51.7626 * scaleX,
2.99274 * scaleY,
36.6973 * scaleX,
5.59668 * scaleY,
)
..cubicTo(
29.1597 * scaleX,
6.8995 * scaleY,
22.7796 * scaleX,
8.48114 * scaleY,
18.0205 * scaleX,
10.3203 * scaleY,
)
..cubicTo(
15.641 * scaleX,
11.2399 * scaleY,
13.7026 * scaleX,
12.2101 * scaleY,
12.2383 * scaleX,
13.2188 * scaleY,
)
..cubicTo(
10.7653 * scaleX,
14.2333 * scaleY,
9.84633 * scaleX,
15.2359 * scaleY,
9.3916 * scaleX,
16.1904 * scaleY,
)
..cubicTo(
8.252 * scaleX,
18.5828 * scaleY,
7.18153 * scaleX,
22.466 * scaleY,
6.2207 * scaleX,
27.5654 * scaleY,
)
..cubicTo(
5.26481 * scaleX,
32.6387 * scaleY,
4.43215 * scaleX,
38.8273 * scaleY,
3.73535 * scaleX,
45.7744 * scaleY,
)
..cubicTo(
2.34189 * scaleX,
59.6675 * scaleY,
1.49647 * scaleX,
76.5363 * scaleY,
1.27832 * scaleX,
93.4678 * scaleY,
)
..cubicTo(
1.06017 * scaleX,
110.4 * scaleY,
1.47057 * scaleX,
127.372 * scaleY,
2.58301 * scaleX,
141.473 * scaleY,
)
..cubicTo(
3.13928 * scaleX,
148.524 * scaleY,
3.86921 * scaleX,
154.841 * scaleY,
4.78125 * scaleX,
160.068 * scaleY,
)
..cubicTo(
5.69748 * scaleX,
165.32 * scaleY,
6.78334 * scaleX,
169.385 * scaleY,
8.01367 * scaleX,
171.984 * scaleY,
)
..cubicTo(
8.53417 * scaleX,
173.084 * scaleY,
9.59654 * scaleX,
174.216 * scaleY,
11.2891 * scaleX,
175.343 * scaleY,
)
..cubicTo(
12.9722 * scaleX,
176.463 * scaleY,
15.1988 * scaleX,
177.524 * scaleY,
17.9219 * scaleX,
178.515 * scaleY,
)
..cubicTo(
23.3679 * scaleX,
180.496 * scaleY,
30.6491 * scaleX,
182.138 * scaleY,
39.1807 * scaleX,
183.437 * scaleY,
)
..cubicTo(
56.2336 * scaleX,
186.032 * scaleY,
78.0934 * scaleX,
187.222 * scaleY,
99.8242 * scaleX,
187.064 * scaleY,
)
..cubicTo(
121.556 * scaleX,
186.906 * scaleY,
143.101 * scaleX,
185.4 * scaleY,
159.525 * scaleX,
182.622 * scaleY,
)
..cubicTo(
167.745 * scaleX,
181.232 * scaleY,
174.627 * scaleX,
179.531 * scaleY,
179.594 * scaleX,
177.548 * scaleY,
)
..cubicTo(
182.079 * scaleX,
176.556 * scaleY,
184.034 * scaleX,
175.512 * scaleY,
185.429 * scaleX,
174.437 * scaleY,
)
..cubicTo(
186.83 * scaleX,
173.355 * scaleY,
187.568 * scaleX,
172.319 * scaleY,
187.812 * scaleX,
171.361 * scaleY,
)
..lineTo(
187.812 * scaleX,
171.361 * scaleY,
) // In SVG, this was H (horizontal line), equivalent to lineTo in Flutter
..cubicTo(
189.156 * scaleX,
166.074 * scaleY,
190.148 * scaleX,
155.525 * scaleY,
190.773 * scaleX,
142.157 * scaleY,
)
..cubicTo(
191.396 * scaleX,
128.832 * scaleY,
191.651 * scaleX,
112.822 * scaleY,
191.552 * scaleX,
96.6875 * scaleY,
)
..cubicTo(
191.453 * scaleX,
80.5539 * scaleY,
191.001 * scaleX,
64.3091 * scaleY,
190.213 * scaleX,
50.5156 * scaleY,
)
..cubicTo(
189.423 * scaleX,
36.6928 * scaleY,
188.299 * scaleX,
25.4153 * scaleY,
186.876 * scaleX,
19.167 * scaleY,
)
..cubicTo(
186.404 * scaleX,
17.0929 * scaleY,
185.566 * scaleX,
15.3424 * scaleY,
184.087 * scaleX,
14.1582 * scaleY,
)
..cubicTo(
181.343 * scaleX,
11.9613 * scaleY,
176.72 * scaleX,
9.98089 * scaleY,
170.561 * scaleX,
8.27539 * scaleY,
)
..cubicTo(
164.434 * scaleX,
6.579 * scaleY,
156.914 * scaleX,
5.18731 * scaleY,
148.483 * scaleX,
4.10254 * scaleY,
)
..close(); // Closes the path to form a complete shape. ..close(); // Closes the path to form a complete shape.
return path; return path;

3
lib/core/widgets/images/my_image.dart

@ -18,8 +18,9 @@ class MyImage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (image.endsWith('.png') || image.endsWith('.jpg')) { if (image.endsWith('.png') || image.endsWith('.jpg')) {
final int? cacheWidth = size != null ? (size! * MediaQuery.of(context).devicePixelRatio).round() : null;
return Image( return Image(
image: AssetImage(image),
image: cacheWidth != null ? ResizeImage(AssetImage(image), width: cacheWidth) : AssetImage(image),
fit: fit, fit: fit,
width: size, width: size,
height: size, height: size,

21
lib/features/download/data/datasource/download_datasource.dart

@ -126,7 +126,9 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
queryParameters: {'batch_start': level, 'batch_size': 1}, queryParameters: {'batch_start': level, 'batch_size': 1},
cancelToken: _imageCancelToken, cancelToken: _imageCancelToken,
onReceive: (count, total) { onReceive: (count, total) {
debugPrint("Started _getLevelImage($level) : ${(count / total) * 100}");
debugPrint(
"Started _getLevelImage($level) : ${(count / total) * 100}",
);
}, },
); );
@ -209,14 +211,12 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
final response = await httpRequest.download( final response = await httpRequest.download(
urlPath: MyApi.audios, urlPath: MyApi.audios,
savePath: filePath, savePath: filePath,
queryParameters: {
'batch_start': level,
'batch_size': 1,
'lang': lang,
},
queryParameters: {'batch_start': level, 'batch_size': 1, 'lang': lang},
cancelToken: _audioCancelToken, cancelToken: _audioCancelToken,
onReceive: (count, total) { onReceive: (count, total) {
debugPrint("Started _getLevelAudio($level) : ${(count / total) * 100}");
debugPrint(
"Started _getLevelAudio($level) : ${(count / total) * 100}",
);
}, },
); );
@ -304,7 +304,12 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
response?['path'], response?['path'],
(json) => NodeModel.fromJson(json), (json) => NodeModel.fromJson(json),
); );
LocalStorage.saveData(key: MyConstants.maxLevelCount, value: '${levels.length}');
final maxLevel = levels.takeWhile((e) => e.nodeType != NodeType.comingSoon).length;
LocalStorage.saveData(
key: MyConstants.maxLevelCount,
value:
'$maxLevel',
);
await data.add(TotalDataEntity(code: selectedLanguage, nodes: levels)); await data.add(TotalDataEntity(code: selectedLanguage, nodes: levels));
} }
} }

58
lib/features/intro/presentation/bloc/intro_bloc.dart

@ -46,8 +46,14 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
Intro5Screen(key: Key('4')), Intro5Screen(key: Key('4')),
]; ];
late final _supportedIntroLangs = const ['en', 'ar']; late final _supportedIntroLangs = const ['en', 'ar'];
late final _lang = LocalStorage.readData(key: MyConstants.selectLanguage) ?? MyConstants.defaultLanguage;
late final _introLang = _supportedIntroLangs.contains(_lang) ? _lang : MyConstants.defaultLanguage;
late final _lang =
LocalStorage.readData(key: MyConstants.selectLanguage) ??
MyConstants.defaultLanguage;
late final _introLang = _supportedIntroLangs.contains(_lang)
? _lang
: MyConstants.defaultLanguage;
bool _shouldResumeCurrentVideo = false;
/// ------------Controllers------------ /// ------------Controllers------------
late final PodPlayerController podController1 = PodPlayerController( late final PodPlayerController podController1 = PodPlayerController(
playVideoFrom: PlayVideoFrom.asset('assets/videos/intro_1_$_introLang.mp4'), playVideoFrom: PlayVideoFrom.asset('assets/videos/intro_1_$_introLang.mp4'),
@ -94,8 +100,54 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
podController5.addListener(() => onVideoEnd(podController5, 4)); podController5.addListener(() => onVideoEnd(podController5, 4));
} }
PodPlayerController get currentPodController {
return switch (state.currentIntro) {
0 => podController1,
1 => podController2,
2 => podController3,
3 => podController4,
4 => podController5,
_ => podController1,
};
}
void handleAppLifecycleState(AppLifecycleState state) async {
final controller = currentPodController;
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.hidden ||
state == AppLifecycleState.paused) {
_shouldResumeCurrentVideo =
_shouldResumeCurrentVideo || controller.isVideoPlaying;
if (_shouldResumeCurrentVideo) {
controller.pause();
}
return;
}
if (state == AppLifecycleState.resumed) {
await Future.delayed(const Duration(seconds: 1));
// if (_shouldResumeCurrentVideo && _hasRemainingPlayback(controller)) {
controller.play();
// }
_shouldResumeCurrentVideo = false;
}
}
bool _hasRemainingPlayback(PodPlayerController controller) {
final position = controller.videoPlayerValue?.position;
final duration = controller.videoPlayerValue?.duration;
if (position == null || duration == null) return false;
return position < duration;
}
/// ------------Api Calls------------ /// ------------Api Calls------------
FutureOr<void> _changeVideosEvent(ChangeVideosEvent event, Emitter emit) async {
FutureOr<void> _changeVideosEvent(
ChangeVideosEvent event,
Emitter emit,
) async {
_shouldResumeCurrentVideo = false;
switch (state.currentIntro) { switch (state.currentIntro) {
case 0: case 0:
podController2.play(); podController2.play();

32
lib/features/intro/presentation/ui/intro_page.dart

@ -8,21 +8,39 @@ import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.da
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_event.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_event.dart';
import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_state.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_state.dart';
class IntroPage extends StatelessWidget {
class IntroPage extends StatefulWidget {
const IntroPage({super.key}); const IntroPage({super.key});
@override
State<IntroPage> createState() => _IntroPageState();
}
class _IntroPageState extends State<IntroPage> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (!mounted) return;
context.read<IntroBloc>().handleAppLifecycleState(state);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: SizedBox.expand( body: SizedBox.expand(
child: GestureDetector( child: GestureDetector(
onTap: () => context.read<IntroBloc>().add(ChangeVideosEvent()), onTap: () => context.read<IntroBloc>().add(ChangeVideosEvent()),
child: Stack(
children: [
_mainScreen(),
_skipButton(context),
],
),
child: Stack(children: [_mainScreen(), _skipButton(context)]),
), ),
), ),
); );

9
lib/features/language/presentation/ui/language_page.dart

@ -181,14 +181,7 @@ class _LanguagePageState extends State<LanguagePage> {
), ),
child: Transform.translate( child: Transform.translate(
offset: offset:
(state
.selectedLanguage
?.displayName
.length ??
0) >
21
? const Offset(-10, 0)
: Offset.zero,
const Offset(-4, 0),
child: const MyImage(image: MyAssets.check), child: const MyImage(image: MyAssets.check),
), ),
), ),

7
lib/features/level/data/datasource/level_datasource.dart

@ -34,9 +34,12 @@ class LocalLevelDatasourceImpl implements ILevelDatasource {
response?['path'], response?['path'],
(json) => NodeModel.fromJson(json), (json) => NodeModel.fromJson(json),
); );
await LocalStorage.saveData(
final maxLevel = levels.takeWhile((e) => e.nodeType != NodeType.comingSoon).length;
LocalStorage.saveData(
key: MyConstants.maxLevelCount, key: MyConstants.maxLevelCount,
value: '${levels.length}',
value:
'$maxLevel',
); );
final Box<TotalDataEntity> levelBox = Hive.box(MyConstants.levelBox); final Box<TotalDataEntity> levelBox = Hive.box(MyConstants.levelBox);

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

@ -58,8 +58,8 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
/// ------------Variables------------ /// ------------Variables------------
final List<LevelLocation> locationListShort = [ final List<LevelLocation> locationListShort = [
LevelLocation( 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),
bottom: setSize(context: MyContext.get, mobile: -0.01.h, tablet: -0.01.h),
left: setSize(context: MyContext.get, mobile: 0.15.w, tablet: 0.25.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.03.h, tablet: 0.075.h), bottom: setSize(context: MyContext.get, mobile: 0.03.h, tablet: 0.075.h),
@ -98,37 +98,37 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
left: setSize(context: MyContext.get, mobile: 0.575.w, tablet: 0.64.w), left: setSize(context: MyContext.get, mobile: 0.575.w, tablet: 0.64.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.61.h, tablet: 1.01.h),
left: setSize(context: MyContext.get, mobile: 0.33.w, tablet: 0.27.w),
bottom: setSize(context: MyContext.get, mobile: 0.6.h , tablet: 1.01.h),
left: setSize(context: MyContext.get, mobile: 0.5.w, tablet: 0.27.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.593.h, tablet: 1.05.h),
left: setSize(context: MyContext.get, mobile: 0.5.w, tablet: 0.08.w),
bottom: setSize(context: MyContext.get, mobile: 0.6185.h, tablet: 1.05.h),
left: setSize(context: MyContext.get, mobile: 0.33.w, tablet: 0.08.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.64.h, tablet: 1.17.h),
bottom: setSize(context: MyContext.get, mobile: 0.646.h, tablet: 1.17.h),
left: setSize(context: MyContext.get, mobile: 0.143.w, tablet: 0.09.w), left: setSize(context: MyContext.get, mobile: 0.143.w, tablet: 0.09.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.64.h, tablet: 1.22.h),
left: setSize(context: MyContext.get, mobile: 0.143.w, tablet: 0.34.w),
bottom: setSize(context: MyContext.get, mobile: 0.69.h, tablet: 1.22.h),
left: setSize(context: MyContext.get, mobile: 0, tablet: 0.34.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.686.h, tablet: 1.25.h),
left: setSize(context: MyContext.get, mobile: 0, tablet: 0.53.w),
bottom: setSize(context: MyContext.get, mobile: 0.74.h, tablet: 1.25.h),
left: setSize(context: MyContext.get, mobile: 0.07.w, tablet: 0.53.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.74.h, tablet: 1.37.h),
left: setSize(context: MyContext.get, mobile: 0.13.w, tablet: 0.635.w),
bottom: setSize(context: MyContext.get, mobile: 0.759.h, tablet: 1.37.h),
left: setSize(context: MyContext.get, mobile: 0.21.w, tablet: 0.635.w),
), ),
////// //////
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.768.h, tablet: 1.43.h),
left: setSize(context: MyContext.get, mobile: 0.31.w, tablet: 0.545.w),
bottom: setSize(context: MyContext.get, mobile: 0.776.h, tablet: 1.43.h),
left: setSize(context: MyContext.get, mobile: 0.345.w, tablet: 0.545.w),
), ),
///// /////
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.8.h, tablet: 1.52.h),
bottom: setSize(context: MyContext.get, mobile: 0.808.h, tablet: 1.52.h),
left: setSize(context: MyContext.get, mobile: 0.475.w, tablet: 0.55.w), left: setSize(context: MyContext.get, mobile: 0.475.w, tablet: 0.55.w),
), ),
LevelLocation( LevelLocation(
@ -144,7 +144,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
left: setSize(context: MyContext.get, mobile: 0.45.w, tablet: 0.11.w), left: setSize(context: MyContext.get, mobile: 0.45.w, tablet: 0.11.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 1.02.h, tablet: 1.7.h),
bottom: setSize(context: MyContext.get, mobile: 1.042.h, tablet: 1.7.h),
left: setSize(context: MyContext.get, mobile: 0.2.w, tablet: 0.0.w), left: setSize(context: MyContext.get, mobile: 0.2.w, tablet: 0.0.w),
), ),
LevelLocation( LevelLocation(
@ -153,7 +153,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 1.19.h, tablet: 1.89.h), bottom: setSize(context: MyContext.get, mobile: 1.19.h, tablet: 1.89.h),
left: setSize(context: MyContext.get, mobile: 0.05.w, tablet: 0.19.w),
left: setSize(context: MyContext.get, mobile: 0, tablet: 0.19.w),
), ),
LevelLocation( LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 1.23.h, tablet: 1.98.h), bottom: setSize(context: MyContext.get, mobile: 1.23.h, tablet: 1.98.h),

4
lib/features/level/presentation/ui/level_page.dart

@ -259,13 +259,13 @@ class _LevelPageState extends State<LevelPage> {
top: 0, top: 0,
right: 0, right: 0,
left: 0, left: 0,
bottom: (comingSoon.bottom ?? 0) + 190,
bottom: (comingSoon.bottom ?? 0) + 120,
child: _lockMapShadowCover(), child: _lockMapShadowCover(),
), ),
Positioned( Positioned(
left: 50, left: 50,
right: 50, right: 50,
bottom: (comingSoon.bottom ?? 0) + 195,
bottom: (comingSoon.bottom ?? 0) + 176,
child: const ComingSoonLevel(), child: const ComingSoonLevel(),
), ),
], ],

6
lib/features/question/domain/entity/question_entity.dart

@ -64,8 +64,8 @@ class QuestionEntity extends HiveObject {
this.imageInfo, this.imageInfo,
this.levelOrder, 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/question_audio/${correctAnswerAudioInfo?.filename}';
image = '${StoragePath.documentDir.path}/$levelOrder/question_image/${imageInfo?.filename}';
audio = audioInfo?.filename?.isNotEmpty == true ? '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/$levelOrder/question_audio/${audioInfo?.filename}' : '';
correctAudio = correctAnswerAudioInfo?.filename?.isNotEmpty == true ? '${StoragePath.documentDir.path}/${LocalStorage.readData(key: MyConstants.selectLanguage)}/$levelOrder/question_audio/${correctAnswerAudioInfo?.filename}' : '';
image = imageInfo?.filename?.isNotEmpty == true ? '${StoragePath.documentDir.path}/$levelOrder/question_image/${imageInfo?.filename}' : '';
} }
} }

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

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -39,16 +40,18 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
} }
@override @override
Future<void> close() {
Future<void> close() async {
_sequenceToken++; _sequenceToken++;
if (!_mainAudioService.isMuted) { if (!_mainAudioService.isMuted) {
_mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume);
} }
_backgroundAudioService.dispose();
await _backgroundAudioService.dispose();
answerAnimationController?.dispose(); answerAnimationController?.dispose();
imageAnimationController?.dispose(); imageAnimationController?.dispose();
globeAnimationController?.dispose(); globeAnimationController?.dispose();
scrollController.dispose(); scrollController.dispose();
PaintingBinding.instance.imageCache.clear();
PaintingBinding.instance.imageCache.clearLiveImages();
return super.close(); return super.close();
} }
@ -160,7 +163,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
debugPrint('playWrongAudio: $audio'); debugPrint('playWrongAudio: $audio');
await _mainAudioService.setAudio(assetPath: audio); await _mainAudioService.setAudio(assetPath: audio);
await _mainAudioService.play(waitUntilComplete: true);
await _mainAudioService.play(waitUntilComplete: false);
} }
Future<void> playAnswerAudio({String? audio}) async { Future<void> playAnswerAudio({String? audio}) async {
@ -339,6 +342,9 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
AnswerEntity(), AnswerEntity(),
showConfetti: true, showConfetti: true,
); );
PaintingBinding.instance.imageCache.clear();
PaintingBinding.instance.imageCache.clearLiveImages();
await Future.delayed(const Duration(milliseconds: 30));
if (!_isActiveSequence(token)) return; if (!_isActiveSequence(token)) return;
scrollController.jumpTo(0); scrollController.jumpTo(0);
answerAnimationController?.reverse(); answerAnimationController?.reverse();
@ -353,6 +359,8 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
playDiamondAudio(); playDiamondAudio();
await Future.delayed(const Duration(milliseconds: 400)); await Future.delayed(const Duration(milliseconds: 400));
} }
PaintingBinding.instance.imageCache.clear();
PaintingBinding.instance.imageCache.clearLiveImages();
emit(state.copyWith(currentQuestion: newCurrentQuestion)); emit(state.copyWith(currentQuestion: newCurrentQuestion));
if (!_isActiveSequence(token)) return; if (!_isActiveSequence(token)) return;
if (isLastQuestion) { if (isLastQuestion) {
@ -360,10 +368,13 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1', LocalStorage.readData(key: MyConstants.currentLevel) ?? '1',
); );
if (state.levelEntity?.order == currentLevel) { if (state.levelEntity?.order == currentLevel) {
final maxLevel = int.parse(
LocalStorage.readData(key: MyConstants.maxLevelCount) ?? '20',
);
++currentLevel; ++currentLevel;
await LocalStorage.saveData( await LocalStorage.saveData(
key: MyConstants.currentLevel, key: MyConstants.currentLevel,
value: '$currentLevel',
value: '${min(maxLevel, currentLevel)}',
); );
} }
} else { } else {

4
lib/features/question/presentation/ui/question_page.dart

@ -34,6 +34,8 @@ class _QuestionPageState extends State<QuestionPage> {
@override @override
void dispose() { void dispose() {
PaintingBinding.instance.imageCache.clear();
PaintingBinding.instance.imageCache.clearLiveImages();
WakelockPlus.disable(); WakelockPlus.disable();
super.dispose(); super.dispose();
} }
@ -60,7 +62,7 @@ class _QuestionPageState extends State<QuestionPage> {
colors: [Color(0XFF6930DA), Color(0XFF263AA1)], colors: [Color(0XFF6930DA), Color(0XFF263AA1)],
), ),
image: DecorationImage( image: DecorationImage(
image: const AssetImage(MyAssets.pattern),
image: const ResizeImage(AssetImage(MyAssets.pattern), width: 100),
scale: 3, scale: 3,
repeat: ImageRepeat.repeat, repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode( colorFilter: ColorFilter.mode(

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

@ -38,10 +38,11 @@ class _QuestionScreenState extends State<QuestionScreen>
with TickerProviderStateMixin, WidgetsBindingObserver { with TickerProviderStateMixin, WidgetsBindingObserver {
late final isTablet = MyDevice.isTablet(context); late final isTablet = MyDevice.isTablet(context);
late final boxRation = isTablet ? 320 / 300 : 180 / 250; late final boxRation = isTablet ? 320 / 300 : 180 / 250;
late final GifController _gifController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_gifController = GifController(vsync: this);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
context context
.read<QuestionBloc>() .read<QuestionBloc>()
@ -81,6 +82,8 @@ class _QuestionScreenState extends State<QuestionScreen>
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
_gifController.stop();
_gifController.dispose();
super.dispose(); super.dispose();
} }
@ -107,10 +110,14 @@ class _QuestionScreenState extends State<QuestionScreen>
_titles(context), _titles(context),
20.0.gapHeight, 20.0.gapHeight,
BlocBuilder<QuestionBloc, QuestionState>( BlocBuilder<QuestionBloc, QuestionState>(
builder: (context, state) => ImageBox(
builder: (context, state) {
debugPrint("currentQuestion image : ${state.currentQuestion?.image}");
return ImageBox(
key: Key('${state.currentQuestion?.image}'), key: Key('${state.currentQuestion?.image}'),
image: state.currentQuestion?.image ?? '', image: state.currentQuestion?.image ?? '',
),
);
},
), ),
], ],
), ),
@ -513,6 +520,7 @@ class _QuestionScreenState extends State<QuestionScreen>
AnimatedBuilder( AnimatedBuilder(
animation: context.read<QuestionBloc>().globeAnimationController!, animation: context.read<QuestionBloc>().globeAnimationController!,
builder: (context, child) => Gif( builder: (context, child) => Gif(
controller: _gifController,
image: AssetImage( image: AssetImage(
context.read<QuestionBloc>().statesGlobe[context context.read<QuestionBloc>().statesGlobe[context
.read<QuestionBloc>() .read<QuestionBloc>()

3
lib/l10n/app_ar.arb

@ -37,5 +37,6 @@
"showcase_hadith": "اطّلع على المصادر والأحاديث\nلهذا السؤال", "showcase_hadith": "اطّلع على المصادر والأحاديث\nلهذا السؤال",
"showcase_guide": "هذا دليل سيساعدك\nفي رحلتك.", "showcase_guide": "هذا دليل سيساعدك\nفي رحلتك.",
"reward": "جائزة", "reward": "جائزة",
"downloading": "جارٍ التنزيل"
"downloading": "جارٍ التنزيل",
"coming_soon": "ستُفتح المراحل القادمة قريبًا"
} }

3
lib/l10n/app_de.arb

@ -37,5 +37,6 @@
"showcase_hadith": "Quellen und\nHadithe zu dieser\nFrage ansehen.", "showcase_hadith": "Quellen und\nHadithe zu dieser\nFrage ansehen.",
"showcase_guide": "Dies ist eine Anleitung,\ndie dir hilft.", "showcase_guide": "Dies ist eine Anleitung,\ndie dir hilft.",
"reward": "Belohnung", "reward": "Belohnung",
"downloading": "Wird heruntergeladen"
"downloading": "Wird heruntergeladen",
"coming_soon": "Die nächsten Phasen werden bald eröffnet."
} }

3
lib/l10n/app_fr.arb

@ -37,5 +37,6 @@
"showcase_hadith": "Consulte les sources et\nles hadiths pour cette\nquestion", "showcase_hadith": "Consulte les sources et\nles hadiths pour cette\nquestion",
"showcase_guide": "Ceci est un guide\nqui t’aidera.", "showcase_guide": "Ceci est un guide\nqui t’aidera.",
"reward": "récompense", "reward": "récompense",
"downloading": "Téléchargement"
"downloading": "Téléchargement",
"coming_soon": "Les prochaines étapes ouvriront bientôt."
} }

2
lib/l10n/app_localizations_ar.dart

@ -124,7 +124,7 @@ class AppLocalizationsAr extends AppLocalizations {
String get reward => 'جائزة'; String get reward => 'جائزة';
@override @override
String get coming_soon => 'Next stages will open soon';
String get coming_soon => 'ستُفتح المراحل القادمة قريبًا';
@override @override
String get downloading => 'جارٍ التنزيل'; String get downloading => 'جارٍ التنزيل';

2
lib/l10n/app_localizations_de.dart

@ -131,7 +131,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get reward => 'Belohnung'; String get reward => 'Belohnung';
@override @override
String get coming_soon => 'Next stages will open soon';
String get coming_soon => 'Die nächsten Phasen werden bald eröffnet.';
@override @override
String get downloading => 'Wird heruntergeladen'; String get downloading => 'Wird heruntergeladen';

2
lib/l10n/app_localizations_fr.dart

@ -129,7 +129,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get reward => 'récompense'; String get reward => 'récompense';
@override @override
String get coming_soon => 'Next stages will open soon';
String get coming_soon => 'Les prochaines étapes ouvriront bientôt.';
@override @override
String get downloading => 'Téléchargement'; String get downloading => 'Téléchargement';

2
lib/l10n/app_localizations_ru.dart

@ -129,7 +129,7 @@ class AppLocalizationsRu extends AppLocalizations {
String get reward => 'Награда'; String get reward => 'Награда';
@override @override
String get coming_soon => 'Next stages will open soon';
String get coming_soon => 'Следующие этапы скоро откроются.';
@override @override
String get downloading => 'Загрузка...'; String get downloading => 'Загрузка...';

2
lib/l10n/app_localizations_tr.dart

@ -128,7 +128,7 @@ class AppLocalizationsTr extends AppLocalizations {
String get reward => 'Ödül'; String get reward => 'Ödül';
@override @override
String get coming_soon => 'Next stages will open soon';
String get coming_soon => 'Sonraki aşamalar yakında açılacak.';
@override @override
String get downloading => 'İndiriliyor...'; String get downloading => 'İndiriliyor...';

1
lib/l10n/app_ru.arb

@ -37,5 +37,6 @@
"showcase_hadith": "Посмотри источники\nи хадисы к этому\nвопросу.", "showcase_hadith": "Посмотри источники\nи хадисы к этому\nвопросу.",
"showcase_guide": "Это руководство\nпоможет тебе.", "showcase_guide": "Это руководство\nпоможет тебе.",
"reward": "Награда", "reward": "Награда",
"coming_soon": "Следующие этапы скоро откроются.",
"downloading": "Загрузка..." "downloading": "Загрузка..."
} }

1
lib/l10n/app_tr.arb

@ -37,5 +37,6 @@
"showcase_hadith": "Bu soruya ait\nkaynakları ve Hadisleri\nincele.", "showcase_hadith": "Bu soruya ait\nkaynakları ve Hadisleri\nincele.",
"showcase_guide": "Bu, sana yardımcı\nolacak bir rehberdir.", "showcase_guide": "Bu, sana yardımcı\nolacak bir rehberdir.",
"reward": "Ödül", "reward": "Ödül",
"coming_soon": "Sonraki aşamalar yakında açılacak.",
"downloading": "İndiriliyor..." "downloading": "İndiriliyor..."
} }

2
lib/main.dart

@ -19,6 +19,8 @@ import 'features/download/presentation/bloc/download_bloc.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
PaintingBinding.instance.imageCache.maximumSizeBytes = 30 << 20; // 30MB
PaintingBinding.instance.imageCache.maximumSize = 10; // 10 images
AppLifeCycleController(); AppLifeCycleController();
initBindings(); initBindings();
await Future.wait([ await Future.wait([

1
pubspec.yaml

@ -24,6 +24,7 @@ dependencies:
gif: ^2.3.0 gif: ^2.3.0
go_router: ^16.1.0 go_router: ^16.1.0
hive: ^2.2.3 hive: ^2.2.3
image: ^4.8.0
intl: ^0.20.2 intl: ^0.20.2
just_audio: ^0.10.5 just_audio: ^0.10.5
lottie: ^3.3.2 lottie: ^3.3.2

Loading…
Cancel
Save