Browse Source

fix and update

develop
sina 2 days ago
parent
commit
87188d4523
  1. 32
      assets/svg/check.svg
  2. BIN
      assets/videos/globe_state_after_wrong.gif
  3. BIN
      assets/videos/globe_state_normal.gif
  4. BIN
      assets/videos/globe_state_speaking.gif
  5. BIN
      assets/videos/globe_state_success.gif
  6. BIN
      assets/videos/globe_state_wrong.gif
  7. 1
      lib/common_ui/resources/my_assets.dart
  8. 6
      lib/core/constants/my_constants.dart
  9. 1
      lib/core/widgets/answer_box/answer_box_showcase.dart
  10. 3
      lib/core/widgets/showcase/my_showcase_widget.dart
  11. 2
      lib/features/app/presentation/bloc/app_bloc.dart
  12. 37
      lib/features/download/data/datasource/download_datasource.dart
  13. 35
      lib/features/download/presentation/ui/download_page.dart
  14. 3
      lib/features/language/data/model/language_model.dart
  15. 3
      lib/features/language/domain/entity/language_entity.dart
  16. 19
      lib/features/language/presentation/ui/language_page.dart
  17. 2
      lib/features/level/presentation/bloc/level_bloc.dart
  18. 32
      lib/features/level/presentation/ui/level_page.dart
  19. 6
      lib/features/level/presentation/ui/widgets/play_button.dart
  20. 17
      lib/features/question/presentation/ui/screens/question_screen.dart

32
assets/svg/check.svg

@ -0,0 +1,32 @@
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1_15746)">
<rect width="17.1111" height="17.1111" rx="8.55556" fill="url(#paint0_linear_1_15746)"/>
<rect x="0.203704" y="0.203704" width="16.7037" height="16.7037" rx="8.35185" stroke="#3CFF3C" stroke-width="0.407407"/>
<g clip-path="url(#clip0_1_15746)">
<g clip-path="url(#clip1_1_15746)">
<path d="M11.9125 5.32364C11.7829 5.23652 11.6374 5.17584 11.4844 5.14506C11.3313 5.11428 11.1737 5.11402 11.0205 5.14428C10.8673 5.17454 10.7216 5.23473 10.5916 5.32142C10.4616 5.4081 10.35 5.51959 10.263 5.6495L7.73141 9.42956L6.57869 8.06317C6.47894 7.94067 6.35582 7.8393 6.21653 7.76499C6.07724 7.69067 5.92456 7.6449 5.7674 7.63033C5.61024 7.61577 5.45176 7.63271 5.30118 7.68016C5.15061 7.72761 5.01097 7.80463 4.89041 7.90671C4.76985 8.0088 4.67078 8.13391 4.59899 8.27475C4.5272 8.41558 4.48412 8.56932 4.47227 8.727C4.46042 8.88467 4.48003 9.04313 4.52996 9.19311C4.57989 9.34309 4.65914 9.4816 4.76308 9.60056L6.9329 12.1726C7.1379 12.4162 7.42958 12.565 7.74109 12.5911L7.90614 12.593C8.08982 12.5828 8.26865 12.53 8.42853 12.4388C8.58842 12.3476 8.72501 12.2205 8.82754 12.0674L12.2367 6.97603C12.3237 6.84617 12.3844 6.70043 12.4152 6.54711C12.446 6.39379 12.4464 6.23591 12.4163 6.08247C12.3862 5.92903 12.3262 5.78304 12.2398 5.65284C12.1533 5.52264 12.0421 5.41078 11.9125 5.32364Z" fill="white"/>
</g>
</g>
</g>
<defs>
<filter id="filter0_d_1_15746" x="0" y="0" width="17.1094" height="18.3336" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1.22222"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_15746"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_15746" result="shape"/>
</filter>
<linearGradient id="paint0_linear_1_15746" x1="8.55556" y1="0" x2="8.55556" y2="17.1111" gradientUnits="userSpaceOnUse">
<stop stop-color="#48D336"/>
<stop offset="1" stop-color="#2D7C23"/>
</linearGradient>
<clipPath id="clip0_1_15746">
<rect width="9.77778" height="9.77778" fill="white" transform="translate(3.66797 3.6665)"/>
</clipPath>
<clipPath id="clip1_1_15746">
<rect width="9.77778" height="9.77778" fill="white" transform="translate(3.66797 3.6665)"/>
</clipPath>
</defs>
</svg>

BIN
assets/videos/globe_state_after_wrong.gif

Before

Width: 320  |  Height: 320  |  Size: 2.7 MiB

After

Width: 320  |  Height: 320  |  Size: 1001 KiB

BIN
assets/videos/globe_state_normal.gif

Before

Width: 320  |  Height: 320  |  Size: 2.7 MiB

After

Width: 320  |  Height: 320  |  Size: 819 KiB

BIN
assets/videos/globe_state_speaking.gif

Before

Width: 800  |  Height: 800  |  Size: 6.3 MiB

After

Width: 320  |  Height: 320  |  Size: 808 KiB

BIN
assets/videos/globe_state_success.gif

Before

Width: 320  |  Height: 320  |  Size: 2.9 MiB

After

Width: 320  |  Height: 320  |  Size: 906 KiB

BIN
assets/videos/globe_state_wrong.gif

Before

Width: 320  |  Height: 320  |  Size: 2.8 MiB

After

Width: 320  |  Height: 320  |  Size: 824 KiB

1
lib/common_ui/resources/my_assets.dart

@ -73,6 +73,7 @@ class MyAssets {
static const String iconPlay = 'assets/svg/icon_play.svg.vec';
static const String iconNotif = 'assets/svg/icon_notif.svg.vec';
static const String iconPlayVideo = 'assets/svg/icon_play_video.svg.vec';
static const String check = 'assets/svg/check.svg.vec';
static final List<String> images = [
backgroundHome,

6
lib/core/constants/my_constants.dart

@ -30,8 +30,8 @@ class MyConstants {
static const double effectAudioVolume = 0.2;
static const String defaultLanguage = 'en';
static const List<LanguageEntity> languages = [
LanguageEntity(title: 'English (English)', code: 'en'),
LanguageEntity(title: 'German (Germany)', code: 'de'),
LanguageEntity(title: 'Arabic (العربية)', code: 'ar'),
LanguageEntity(displayName: 'English (English)', code: 'en', title: ''),
LanguageEntity(displayName: 'German (Germany)', code: 'de', title: ''),
LanguageEntity(displayName: 'Arabic (العربية)', code: 'ar', title: ''),
];
}

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

@ -89,6 +89,7 @@ class _AnswerBoxShowCaseState extends State<AnswerBoxShowCase> {
),
child: MyShowcaseWidget(
globalKey: widget.globalKey,
highlightRadiusIncrease: 15,
type: ShowcaseTooltipType.bottom,
description: context.translate.showcase_notif,
child: GestureDetector(

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

@ -86,12 +86,14 @@ class MyShowcaseWidget extends StatelessWidget {
this.globalKey,
this.description,
this.type = ShowcaseTooltipType.bottom,
this.highlightRadiusIncrease = 0,
});
final GlobalKey? globalKey;
final String? description;
final Widget child;
final ShowcaseTooltipType type;
final double highlightRadiusIncrease;
@override
Widget build(BuildContext context) {
@ -99,6 +101,7 @@ class MyShowcaseWidget extends StatelessWidget {
key: globalKey ?? GlobalKey(),
disableBarrierInteraction: false,
targetShapeBorder: const CircleBorder(),
targetPadding: EdgeInsets.all(highlightRadiusIncrease),
overlayColor: const Color(0XFF0F0041),
overlayOpacity: 0.82,
/// ToolTip

2
lib/features/app/presentation/bloc/app_bloc.dart

@ -21,7 +21,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
// Try to find in constants first, or create a temporary one to get locale
final LanguageEntity findLanguage = MyConstants.languages.firstWhere(
(e) => e.code == selectLanguage,
orElse: () => LanguageEntity(code: selectLanguage, title: ''),
orElse: () => LanguageEntity(code: selectLanguage, title: '', displayName: ''),
);
emit(state.copyWith(locale: findLanguage.locale));

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

@ -68,6 +68,13 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
int _audioCompleted = 0;
int _targetLevel = 0;
Future<void> _ensureLevelFolderExists(String levelPath) async {
final Directory directory = Directory(levelPath);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
}
void _emitProgress() {
final int totalTasks = _targetLevel * 2;
final int completedTasks = _imageCompleted + _audioCompleted;
@ -89,17 +96,7 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
final Directory directory = Directory(levelPath);
if (!await directory.exists()) {
return false;
}
final List<FileSystemEntity> files = directory.listSync(recursive: false);
if (files.isEmpty) {
return false;
}
return true;
return directory.exists();
}
Future<void> _getLevelsImages(int toLevel) async {
@ -135,6 +132,9 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
if (response is Response && response.statusCode == 204) {
debugPrint("No image content for level $level (204)");
await _ensureLevelFolderExists(
'${StoragePath.documentDir.path}/$level/',
);
_imageCompleted++;
_emitProgress();
return;
@ -178,17 +178,7 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
final Directory directory = Directory(levelPath);
if (!await directory.exists()) {
return false;
}
final List<FileSystemEntity> files = directory.listSync(recursive: false);
if (files.isEmpty) {
return false;
}
return true;
return directory.exists();
}
Future<void> _getLevelsAudios(int toLevel) async {
@ -232,6 +222,9 @@ class DownloadDatasourceImpl implements IDownloadDatasource {
if (response is Response && response.statusCode == 204) {
debugPrint("No audio content for level $level (204)");
await _ensureLevelFolderExists(
'${StoragePath.documentDir.path}/$lang/$level/',
);
_audioCompleted++;
_emitProgress();
return;

35
lib/features/download/presentation/ui/download_page.dart

@ -25,7 +25,7 @@ import 'package:hadi_hoda_flutter/features/download/presentation/ui/widgets/down
class DownloadPage extends StatefulWidget {
final DownloadPageConfig config;
const DownloadPage({super.key,required this.config});
const DownloadPage({super.key, required this.config});
@override
State<DownloadPage> createState() => _DownloadPageState();
@ -70,8 +70,8 @@ class _DownloadPageState extends State<DownloadPage> {
),
child: BlocConsumer<DownloadBloc, DownloadState>(
listener: (context, state) {
if(state.status is BaseComplete) {
if(widget.config.redirectTo == Routes.homePage) {
if (state.status is BaseComplete) {
if (widget.config.redirectTo == Routes.homePage) {
context.goNamed(Routes.homePage);
} else {
context.pushNamed(
@ -87,7 +87,9 @@ class _DownloadPageState extends State<DownloadPage> {
return Padding(
padding: EdgeInsets.symmetric(
vertical:
MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16,
MediaQuery
.viewPaddingOf(context)
.bottom + MySpaces.s16,
horizontal: 60,
),
child: ErrorState(
@ -130,13 +132,26 @@ class _DownloadPageState extends State<DownloadPage> {
bottom: 130,
child: Column(
spacing: MySpaces.s6,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(context.translate.please_wait, style: MYTextStyle.titr0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(context.translate.please_wait,
style: MYTextStyle.titr0.copyWith(fontSize: 24),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis),
),
StreamBuilder<DownloadEntity>(
initialData: DownloadEntity.empty(),
stream: context.read<DownloadBloc>().loadingStream,
builder: (context, snapshot) => Text(
'Downloading ...${snapshot.data?.downloadedLevels}/${widget.config.downloadToLevel}',
stream: context
.read<DownloadBloc>()
.loadingStream,
builder: (context, snapshot) =>
Text(
'Downloading ...${snapshot.data?.downloadedLevels}/${widget
.config.downloadToLevel}',
textAlign: TextAlign.center,
style: MYTextStyle.matn3,
),
),
@ -149,7 +164,9 @@ class _DownloadPageState extends State<DownloadPage> {
return Positioned(
bottom: MySpaces.s40,
child: DownloadLoadingWidget(
loadingStream: context.read<DownloadBloc>().loadingStream,
loadingStream: context
.read<DownloadBloc>()
.loadingStream,
),
);
}

3
lib/features/language/data/model/language_model.dart

@ -1,12 +1,13 @@
import 'package:hadi_hoda_flutter/features/language/domain/entity/language_entity.dart';
class LanguageModel extends LanguageEntity {
const LanguageModel({required super.code, required super.title});
const LanguageModel({required super.code,required super.displayName, required super.title});
factory LanguageModel.fromJson(Map<String, dynamic> json) {
return LanguageModel(
code: json['code'] ?? '',
title: json['name'] ?? '', // API returns 'name', we map to 'title'
displayName: json['display_name'] ?? '', // API returns 'name', we map to 'title'
);
}
}

3
lib/features/language/domain/entity/language_entity.dart

@ -4,8 +4,9 @@ import 'package:equatable/equatable.dart';
class LanguageEntity extends Equatable {
final String code;
final String title;
final String displayName;
const LanguageEntity({required this.code, required this.title});
const LanguageEntity({required this.code, required this.title, required this.displayName});
Locale get locale {
final parts = code.split('_');

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

@ -133,11 +133,14 @@ class _LanguagePageState extends State<LanguagePage> {
Center(
child: Container(
width: double.infinity,
height: itemSize + 4,
alignment: Alignment.centerLeft,
height: itemSize,
padding: const EdgeInsets.symmetric(horizontal: 18),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
borderRadius: BorderRadius.circular(16),
color: Colors.white.withValues(alpha: .2),
borderRadius: BorderRadius.circular(12),
),
child: const MyImage(image: MyAssets.check),
),
),
WheelChooser.choices(
@ -156,17 +159,17 @@ class _LanguagePageState extends State<LanguagePage> {
}
},
selectTextStyle: MYTextStyle.titr1.copyWith(
fontSize: itemSize * 0.4,
fontSize: itemSize * 0.34,
color: Colors.white,
),
unSelectTextStyle: MYTextStyle.titr1.copyWith(
fontSize: itemSize * 0.4,
fontSize: itemSize * 0.34,
color: Colors.white.withOpacity(0.5),
),
choices: state.languages.map((lang) {
return WheelChoice(
value: lang,
title: lang.title,
title: lang.displayName,
);
}).toList(),
),
@ -191,8 +194,7 @@ class _LanguagePageState extends State<LanguagePage> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const MyImage(image: MyAssets.lang, size: 28),
Expanded(
child: AutoSizeText(
AutoSizeText(
context.translate.select_language,
minFontSize: 12,
maxFontSize: 20,
@ -200,7 +202,6 @@ class _LanguagePageState extends State<LanguagePage> {
textAlign: TextAlign.center,
style: MYTextStyle.titr0.copyWith(color: const Color(0XFF847AC4)),
),
),
],
);
}

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

@ -84,7 +84,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
),
LevelLocation(
bottom: setSize(context: MyContext.get, mobile: 0.33.h, tablet: 0.55.h),
left: setSize(context: MyContext.get, mobile: 0.65.w, tablet: 0.8.w),
left: setSize(context: MyContext.get, mobile: 0.63.w, tablet: 0.8.w),
index: 5,
),
LevelLocation(

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

@ -329,22 +329,22 @@ class _LevelPageState extends State<LevelPage> {
);
}
Widget _downloadIndicator() {
return BlocBuilder<DownloadBloc, DownloadState>(
buildWhen: (prev, curr) => prev.status != curr.status,
builder: (context, downloadState) {
if (downloadState.status is! BaseLoading) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.only(top: MySpaces.s10),
child: DownloadLoadingWidget(
loadingStream: context.read<DownloadBloc>().loadingStream,
),
);
},
);
}
// Widget _downloadIndicator() {
// return BlocBuilder<DownloadBloc, DownloadState>(
// buildWhen: (prev, curr) => prev.status != curr.status,
// builder: (context, downloadState) {
// if (downloadState.status is! BaseLoading) {
// return const SizedBox.shrink();
// }
// return Padding(
// padding: const EdgeInsets.only(top: MySpaces.s10),
// child: DownloadLoadingWidget(
// loadingStream: context.read<DownloadBloc>().loadingStream,
// ),
// );
// },
// );
// }
Widget _background(BuildContext context) {
return const MyImage(

6
lib/features/level/presentation/ui/widgets/play_button.dart

@ -15,6 +15,8 @@ class PlayButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final playTitle = context.translate.play;
final playTitleLength = playTitle.length;
return MyInkwell(
onTap: () => onTap?.call(level),
child: Stack(
@ -35,9 +37,11 @@ class PlayButton extends StatelessWidget {
colors: [Color(0XFFF9601F), Color(0XFFD93D16)],
).createShader(bounds),
child: Text(
context.translate.play,
playTitle,
maxLines: 1,
style: MYTextStyle.button1.copyWith(
fontSize: playTitleLength > 5 ? 36: null,
height: playTitleLength > 5 ? 1.75: null,
shadows: [
BoxShadow(
color: const Color(0XFFFFFFAB).withValues(alpha: 0.40),

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

@ -96,6 +96,7 @@ class _QuestionScreenState extends State<QuestionScreen>
duration: const Duration(seconds: 1),
child: FadeAnimController(
controller: context.read<QuestionBloc>().imageAnimationController!,
child: SingleChildScrollView(
child: Column(
children: [
_titles(context),
@ -109,6 +110,7 @@ class _QuestionScreenState extends State<QuestionScreen>
],
),
),
),
);
}
@ -134,7 +136,6 @@ class _QuestionScreenState extends State<QuestionScreen>
textAlign: TextAlign.center,
minFontSize: 16,
maxFontSize: 20,
maxLines: 8,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
@ -148,7 +149,18 @@ class _QuestionScreenState extends State<QuestionScreen>
}
Widget _answers(BuildContext context) {
return ListView(
return AnimatedBuilder(
animation: context.read<QuestionBloc>().imageAnimationController!,
builder: (context, child) => IgnorePointer(
ignoring:
context
.read<QuestionBloc>()
.imageAnimationController
?.isForwardOrCompleted ??
false,
child: child,
),
child: ListView(
controller: context.read<QuestionBloc>().scrollController,
padding: const EdgeInsets.only(top: 10),
children: [
@ -445,6 +457,7 @@ class _QuestionScreenState extends State<QuestionScreen>
),
),
],
),
);
}

Loading…
Cancel
Save