diff --git a/assets/images/error.png b/assets/images/error.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3b679c14bdc87cd1634382d71518ccd8c0e666 GIT binary patch literal 14810 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 get lightColors => { - ColorsName.noColor: MyColors.transparent, ColorsName.primaryColor: MyColors.white, ColorsName.backgroundDialog: MyColors.purple, }; static Map get darkColors => { - ColorsName.noColor: MyColors.transparent, ColorsName.primaryColor: MyColors.black, ColorsName.backgroundDialog: MyColors.purple, }; -} - -extension ThemeExtension on BuildContext { - Map get customColors => - Theme.of(ContextProvider.context!).brightness == Brightness.dark - ? MyTheme.darkColors - : MyTheme.lightColors; - - Color get primaryColor => customColors[ColorsName.primaryColor]!; - Color get noColor => customColors[ColorsName.noColor]!; - Color get backgroundDialog => customColors[ColorsName.backgroundDialog]!; -} +} \ No newline at end of file diff --git a/lib/core/constants/my_constants.dart b/lib/core/constants/my_constants.dart index e96b59e..76b8d54 100644 --- a/lib/core/constants/my_constants.dart +++ b/lib/core/constants/my_constants.dart @@ -9,4 +9,5 @@ class MyConstants { static const String downloadCompleted = 'DOWNLOAD_COMPLETED'; static const String extractCompleted = 'EXTRACT_COMPLETED'; static const String selectLanguage = 'SELECT_LANGUAGE'; + static const String firstLanguagePage = 'FIRST_LANGUAGE_PAGE'; } \ No newline at end of file diff --git a/lib/core/middleware/language_middleware.dart b/lib/core/middleware/language_middleware.dart deleted file mode 100644 index 3d092bd..0000000 --- a/lib/core/middleware/language_middleware.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; -import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; - -class LanguageMiddleware { - static const LanguageMiddleware _i = LanguageMiddleware._internal(); - const LanguageMiddleware._internal(); - factory LanguageMiddleware() => _i; - - static FutureOr redirect(BuildContext context, GoRouterState state) async { - if (LocalStorage.readData(key: MyConstants.selectLanguage).isNotEmpty) { - return Routes.homePage; - } else { - return null; - } - } -} diff --git a/lib/core/params/language_params.dart b/lib/core/params/language_params.dart index c5866ec..7e926b1 100644 --- a/lib/core/params/language_params.dart +++ b/lib/core/params/language_params.dart @@ -11,7 +11,7 @@ class LanguageParams { ); } - Map get toHeader => { - if (code != null) 'HTTP_X_USER_LANGUAGE': code, + Map get toQuery => { + if (code != null) 'lang': code, }; } diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index 5066c81..13f5e6b 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -1,12 +1,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import 'package:hadi_hoda_flutter/core/middleware/language_middleware.dart'; import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_bloc.dart'; import 'package:hadi_hoda_flutter/features/home/presentation/ui/home_page.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/ui/intro_page.dart'; import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_bloc.dart'; +import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_event.dart'; import 'package:hadi_hoda_flutter/features/language/presentation/ui/language_page.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'; @@ -36,16 +36,15 @@ GoRouter get appPages => GoRouter( name: Routes.introPage, path: Routes.introPage, builder: (context, state) => BlocProvider( - create: (context) => IntroBloc(locator(), locator()), + create: (context) => IntroBloc(locator(), locator(), locator()), child: const IntroPage(), ), ), GoRoute( name: Routes.languagePage, path: Routes.languagePage, - redirect: LanguageMiddleware.redirect, builder: (context, state) => BlocProvider( - create: (context) => LanguageBloc(locator()), + create: (context) => LanguageBloc()..add(InitLanguageEvent()), child: const LanguagePage(), ), ), diff --git a/lib/core/utils/context_provider.dart b/lib/core/utils/context_provider.dart index e92a900..f7d5724 100644 --- a/lib/core/utils/context_provider.dart +++ b/lib/core/utils/context_provider.dart @@ -6,5 +6,5 @@ class ContextProvider { factory ContextProvider() => _i; static GlobalKey navigatorKey = GlobalKey(); - static BuildContext? context = navigatorKey.currentContext; + static BuildContext context = navigatorKey.currentContext!; } diff --git a/lib/core/utils/convert_size.dart b/lib/core/utils/convert_size.dart new file mode 100644 index 0000000..0a114d9 --- /dev/null +++ b/lib/core/utils/convert_size.dart @@ -0,0 +1,5 @@ +extension ConvertSize on double? { + String get toMB { + return this == null ? '0.0' : (this! / (1024 * 1024)).toStringAsFixed(1); + } +} diff --git a/lib/core/utils/my_image.dart b/lib/core/utils/my_image.dart index 586b81c..bf977e3 100644 --- a/lib/core/utils/my_image.dart +++ b/lib/core/utils/my_image.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; class MyImage extends StatelessWidget { const MyImage({ @@ -32,7 +31,7 @@ class MyImage extends StatelessWidget { width: size, height: size, colorFilter: color != null - ? ColorFilter.mode(color ?? context.primaryColor, BlendMode.srcIn) + ? ColorFilter.mode(color!, BlendMode.srcIn) : null, ); } diff --git a/lib/core/widgets/about_us_dialog/about_us_dialog.dart b/lib/core/widgets/about_us_dialog/about_us_dialog.dart index 0817498..a982547 100644 --- a/lib/core/widgets/about_us_dialog/about_us_dialog.dart +++ b/lib/core/widgets/about_us_dialog/about_us_dialog.dart @@ -3,8 +3,8 @@ import 'dart:ui'; import 'package:flutter/material.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_text_style.dart'; -import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/check_platform.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/my_localization.dart'; @@ -14,7 +14,7 @@ Future showAboutUsDialog({required BuildContext context}) async { await showDialog( context: context, builder: (context) => AboutUsDialog(), - barrierColor: context.backgroundDialog.withValues(alpha: 0.82), + barrierColor: MyColors.purple.withValues(alpha: 0.82), useSafeArea: false, ); } @@ -25,7 +25,7 @@ class AboutUsDialog extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: context.noColor, + backgroundColor: MyColors.transparent, body: BackdropFilter( filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), child: Center( diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index bef6f28..6609a71 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -1,9 +1,9 @@ import 'dart:io'; import 'package:flutter/material.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/common_ui/theme/my_theme.dart'; class AnswerPictureBox extends StatelessWidget { const AnswerPictureBox({super.key, required this.selected, required this.image, required this.index}); @@ -49,7 +49,7 @@ class AnswerPictureBox extends StatelessWidget { child: Text( '$index', style: Marhey.semiBold17.copyWith( - color: context.primaryColor, + color: MyColors.white, ), ), ), diff --git a/lib/core/widgets/button/my_button.dart b/lib/core/widgets/button/my_button.dart index 188cb3a..a0b48fd 100644 --- a/lib/core/widgets/button/my_button.dart +++ b/lib/core/widgets/button/my_button.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_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/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/widgets/button/enum/button_type.dart'; @@ -25,8 +25,8 @@ class MyButton extends StatelessWidget { width: 194, child: InkWell( onTap: onTap, - highlightColor: context.noColor, - splashColor: context.noColor, + highlightColor: MyColors.transparent, + splashColor: MyColors.transparent, child: Stack( alignment: Alignment.center, children: [ diff --git a/lib/core/widgets/error/error_state.dart b/lib/core/widgets/error/error_state.dart new file mode 100644 index 0000000..d39e965 --- /dev/null +++ b/lib/core/widgets/error/error_state.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; +import 'package:hadi_hoda_flutter/core/utils/gap.dart'; +import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; +import 'package:hadi_hoda_flutter/core/utils/my_localization.dart'; + +class ErrorState extends StatelessWidget { + const ErrorState({super.key, this.onTap}); + + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Spacer(), + Text( + context.translate.lost_connection, + style: DinoKids.regular45.copyWith(color: MyColors.white), + ), + MyImage(image: MyAssets.error), + MySpaces.s40.gapHeight, + Text( + context.translate.connected_to_internet, + textAlign: TextAlign.center, + style: Marhey.medium12.copyWith(color: MyColors.white), + ), + Spacer(), + SizedBox( + height: 84, + width: 194, + child: InkWell( + onTap: onTap, + highlightColor: MyColors.transparent, + splashColor: MyColors.transparent, + child: Stack( + alignment: Alignment.center, + children: [ + MyImage(image: MyAssets.button), + PositionedDirectional( + top: MySpaces.s8, + child: Text( + context.translate.try_again, + style: DinoKids.regular35.copyWith( + color: Color(0XFF1D6EFF), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/core/widgets/showcase/question_showcase.dart b/lib/core/widgets/showcase/question_showcase.dart index efc1c2a..79c4f5b 100644 --- a/lib/core/widgets/showcase/question_showcase.dart +++ b/lib/core/widgets/showcase/question_showcase.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; -import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; import 'package:showcaseview/showcaseview.dart'; @@ -26,10 +26,10 @@ class QuestionShowcase extends StatelessWidget { targetShapeBorder: CircleBorder(), tooltipBackgroundColor: Colors.transparent, disableMovingAnimation: true, - textColor: context.primaryColor, + textColor: MyColors.white, descriptionTextAlign: TextAlign.center, descTextStyle: Marhey.bold12.copyWith( - color: context.primaryColor, + color: MyColors.white, ), disableScaleAnimation: true, tooltipPadding: EdgeInsets.only(top: 60), diff --git a/lib/features/home/presentation/bloc/home_bloc.dart b/lib/features/home/presentation/bloc/home_bloc.dart index 114736f..7360c97 100644 --- a/lib/features/home/presentation/bloc/home_bloc.dart +++ b/lib/features/home/presentation/bloc/home_bloc.dart @@ -29,6 +29,10 @@ class HomeBloc extends Bloc { context.pushNamed(Routes.levelPage); } + void goToLanguagePage(BuildContext context){ + context.pushNamed(Routes.languagePage); + } + /// ------------Api Calls------------ FutureOr _getHomeEvent(event, emit) async { await _getHomeUseCase(event.homeParams).then( diff --git a/lib/features/home/presentation/ui/home_page.dart b/lib/features/home/presentation/ui/home_page.dart index 82c5c85..17472af 100644 --- a/lib/features/home/presentation/ui/home_page.dart +++ b/lib/features/home/presentation/ui/home_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; -import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; import 'package:hadi_hoda_flutter/core/utils/check_platform.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; @@ -13,7 +13,7 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(backgroundColor: context.noColor), + appBar: AppBar(backgroundColor: MyColors.transparent), extendBodyBehindAppBar: true, body: DecoratedBox( decoration: BoxDecoration( @@ -70,9 +70,12 @@ class HomePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - MyImage( - image: MyAssets.language, - size: checkSize(context: context, tablet: 120), + InkWell( + onTap: () => context.read().goToLanguagePage(context), + child: MyImage( + image: MyAssets.language, + size: checkSize(context: context, tablet: 120), + ), ), InkWell( child: MyImage( diff --git a/lib/features/intro/data/datasource/intro_datasource.dart b/lib/features/intro/data/datasource/intro_datasource.dart index 15d4a31..d43a046 100644 --- a/lib/features/intro/data/datasource/intro_datasource.dart +++ b/lib/features/intro/data/datasource/intro_datasource.dart @@ -6,31 +6,48 @@ 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/response/base_response.dart'; import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; import 'package:hadi_hoda_flutter/core/utils/storage_path.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; +import 'package:hadi_hoda_flutter/features/level/data/model/level_model.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart'; +import 'package:hive/hive.dart'; abstract class IIntroDatasource { Future getFiles(); - Stream loadingStream(); + Future saveLevels(); + Stream loadingStream(); } class IntroDatasourceImpl implements IIntroDatasource { final IHttpRequest httpRequest; - final StreamController streamController = StreamController.broadcast(); + final StreamController streamController = StreamController.broadcast(); IntroDatasourceImpl(this.httpRequest); @override Future getFiles() async { final String filePath = '${StoragePath.documentDir.path}/files.zip'; + final String selectedLanguage = + LocalStorage.readData(key: MyConstants.selectLanguage).isEmpty + ? 'fa' + : LocalStorage.readData(key: MyConstants.selectLanguage); if (LocalStorage.readData(key: MyConstants.downloadCompleted) != 'true') { await httpRequest.download( urlPath: MyApi.files, savePath: filePath, + queryParameters: { + 'lang': selectedLanguage, + }, onReceive: (count, total) { - double percent = ((count / total) * 100); - streamController.add(percent); + streamController.add(DownloadEntity( + count: count / 1, + total: total / 1, + percent: (count / total) * 100, + )); }, ).then((value) async { await LocalStorage.saveData( @@ -43,12 +60,11 @@ class IntroDatasourceImpl implements IIntroDatasource { try{ if (LocalStorage.readData(key: MyConstants.extractCompleted) != 'true') { final File file = File(filePath); - final Directory directory = Directory('${StoragePath.documentDir.path}/files/'); + final Directory directory = Directory('${StoragePath.documentDir.path}/$selectedLanguage/files/'); await ZipFile.extractToDirectory( zipFile: file, destinationDir: directory, onExtracting: (zipEntry, progress) { - streamController.add(progress); return ZipFileOperation.includeItem; }, ).then((value) async { @@ -61,9 +77,9 @@ class IntroDatasourceImpl implements IIntroDatasource { ]); }); } else { - streamController.add(50); + streamController.add(DownloadEntity(percent: 50)); await Future.delayed(Duration(milliseconds: 150)); - streamController.add(100); + streamController.add(DownloadEntity(percent: 100)); } } catch (e){ throw MyException(errorMessage: '$e'); @@ -71,5 +87,32 @@ class IntroDatasourceImpl implements IIntroDatasource { } @override - Stream loadingStream() => streamController.stream; + Future saveLevels() async { + final String selectedLanguage = + LocalStorage.readData(key: MyConstants.selectLanguage).isEmpty + ? 'fa' + : LocalStorage.readData(key: MyConstants.selectLanguage); + final Box data = Hive.box(MyConstants.levelBox); + final TotalDataEntity findData = data.values.singleWhere( + (e) => e.code == selectedLanguage, + orElse: () => TotalDataEntity(), + ); + + if (findData.code != selectedLanguage) { + final response = await httpRequest.get( + path: MyApi.levels, + queryParameters: {'lang': selectedLanguage}, + ); + final List levels = BaseResponse.getDataList( + response?['result'], + (json) => LevelModel.fromJson(json), + ); + await Future.wait([ + data.add(TotalDataEntity(code: selectedLanguage, levels: levels)), + ]); + } + } + + @override + Stream loadingStream() => streamController.stream; } diff --git a/lib/features/intro/data/repository_impl/intro_repository_impl.dart b/lib/features/intro/data/repository_impl/intro_repository_impl.dart index 9dfa48c..fbae565 100644 --- a/lib/features/intro/data/repository_impl/intro_repository_impl.dart +++ b/lib/features/intro/data/repository_impl/intro_repository_impl.dart @@ -3,6 +3,7 @@ import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/params/no_params.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; import 'package:hadi_hoda_flutter/features/intro/data/datasource/intro_datasource.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; class IntroRepositoryImpl implements IIntroRepository { @@ -27,7 +28,23 @@ class IntroRepositoryImpl implements IIntroRepository { } @override - Stream loadingStream() { + Future> saveLevels() async { + try { + await datasource.saveLevels(); + return DataState.success(NoParams()); + } on MyException catch (e) { + return DataState.error(e); + } catch (e) { + if (kDebugMode) { + rethrow; + } else { + return DataState.error(MyException(errorMessage: '$e')); + } + } + } + + @override + Stream loadingStream() { return datasource.loadingStream(); } } diff --git a/lib/features/intro/domain/entities/download_entity.dart b/lib/features/intro/domain/entities/download_entity.dart new file mode 100644 index 0000000..589fd5a --- /dev/null +++ b/lib/features/intro/domain/entities/download_entity.dart @@ -0,0 +1,7 @@ +class DownloadEntity { + final double? count; + final double? total; + final double? percent; + + const DownloadEntity({this.count, this.total, this.percent}); +} diff --git a/lib/features/intro/domain/repository/intro_repository.dart b/lib/features/intro/domain/repository/intro_repository.dart index 717feb4..adb6554 100644 --- a/lib/features/intro/domain/repository/intro_repository.dart +++ b/lib/features/intro/domain/repository/intro_repository.dart @@ -1,8 +1,10 @@ import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/params/no_params.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; abstract class IIntroRepository { Future> getFiles(); - Stream loadingStream(); + Future> saveLevels(); + Stream loadingStream(); } diff --git a/lib/features/intro/domain/usecases/loading_stream_usecase.dart b/lib/features/intro/domain/usecases/loading_stream_usecase.dart index a24d6ed..5c44199 100644 --- a/lib/features/intro/domain/usecases/loading_stream_usecase.dart +++ b/lib/features/intro/domain/usecases/loading_stream_usecase.dart @@ -1,3 +1,4 @@ +import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; class LoadingStreamUseCase { @@ -5,7 +6,7 @@ class LoadingStreamUseCase { const LoadingStreamUseCase(this.repository); - Stream call() { + Stream call() { return repository.loadingStream(); } } diff --git a/lib/features/intro/domain/usecases/save_levels_usecase.dart b/lib/features/intro/domain/usecases/save_levels_usecase.dart new file mode 100644 index 0000000..87f385b --- /dev/null +++ b/lib/features/intro/domain/usecases/save_levels_usecase.dart @@ -0,0 +1,16 @@ +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/no_params.dart'; +import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; +import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; + +class SaveLevelsUseCase implements UseCase { + final IIntroRepository repository; + + const SaveLevelsUseCase(this.repository); + + @override + Future> call(NoParams params) { + return repository.saveLevels(); + } +} diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index 26fbddd..8ff8483 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -2,29 +2,39 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:go_router/go_router.dart'; +import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/params/no_params.dart'; import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; +import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/usecases/get_files_usecase.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/usecases/loading_stream_usecase.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/save_levels_usecase.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'; class IntroBloc extends Bloc { /// ------------constructor------------ - IntroBloc(this._getFilesUseCase, this._loadingStreamUseCase) + IntroBloc( + this._getFilesUseCase, + this._loadingStreamUseCase, + this._saveLevelsUseCase, + ) : super(const IntroState()) { on(_getFilesEvent); + on(_saveLevelsEvent); loadingStream = _loadingStreamUseCase(); } /// ------------UseCases------------ final GetFilesUseCase _getFilesUseCase; + final SaveLevelsUseCase _saveLevelsUseCase; final LoadingStreamUseCase _loadingStreamUseCase; /// ------------Variables------------ - Stream loadingStream = Stream.empty(); + Stream loadingStream = Stream.empty(); /// ------------Controllers------------ @@ -35,19 +45,38 @@ class IntroBloc extends Bloc { GetFilesEvent event, Emitter emit, ) async { + emit(state.copyWith(getFilesStatus: BaseInit())); await _getFilesUseCase(NoParams()).then((value) { value.fold( (data) async { - await Future.delayed( - Duration(milliseconds: 300), () { - ContextProvider.context!.goNamed(Routes.languagePage); - }, - ); + add(SaveLevelsEvent()); }, - (error) { + (error) async { emit(state.copyWith(getFilesStatus: BaseError(error.errorMessage))); }, ); }); } + + FutureOr _saveLevelsEvent(SaveLevelsEvent event, + Emitter emit) async { + await _saveLevelsUseCase(NoParams()).then((value) => + value.fold( + (data) async { + if(LocalStorage.readData(key: MyConstants.firstLanguagePage) != 'true'){ + ContextProvider.context.goNamed(Routes.languagePage); + } else { + await Future.delayed(Duration(milliseconds: 500), () { + if (ContextProvider.context.mounted) { + ContextProvider.context.goNamed(Routes.homePage); + } + }); + } + }, + (error) { + emit(state.copyWith(getFilesStatus: BaseError(error.errorMessage))); + }, + ), + ); + } } diff --git a/lib/features/intro/presentation/bloc/intro_event.dart b/lib/features/intro/presentation/bloc/intro_event.dart index 70e23cc..271b2a0 100644 --- a/lib/features/intro/presentation/bloc/intro_event.dart +++ b/lib/features/intro/presentation/bloc/intro_event.dart @@ -2,3 +2,4 @@ sealed class IntroEvent { const IntroEvent(); } class GetFilesEvent extends IntroEvent {} +class SaveLevelsEvent extends IntroEvent {} diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index b874119..bec6972 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -1,11 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_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/status/base_status.dart'; +import 'package:hadi_hoda_flutter/core/utils/convert_size.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.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/error/error_state.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.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/ui/widgets/intro_loading_widget.dart'; class IntroPage extends StatefulWidget { @@ -47,12 +55,31 @@ class _IntroPageState extends State { ), ), ), - child: Stack( - alignment: Alignment.center, - children: [ - _image(), - _loading(context), - ], + child: BlocBuilder( + buildWhen: (previous, current) => + previous.getFilesStatus != current.getFilesStatus, + builder: (context, state) { + if (state.getFilesStatus is BaseError) { + return Padding( + padding: EdgeInsets.symmetric( + vertical: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16, + horizontal: 60, + ), + child: ErrorState( + onTap: () => context.read().add(GetFilesEvent()), + ), + ); + } else { + return Stack( + alignment: Alignment.center, + children: [ + _image(), + _text(context), + _loading(context), + ], + ); + } + } ), ), ); @@ -65,6 +92,33 @@ class _IntroPageState extends State { ); } + Widget _text(BuildContext context) { + return PositionedDirectional( + bottom: 130, + child: Column( + spacing: MySpaces.s6, + children: [ + Text( + context.translate.please_wait, + style: Marhey.medium22.copyWith( + color: MyColors.white, + ), + ), + StreamBuilder( + initialData: DownloadEntity(), + stream: context.read().loadingStream, + builder: (context, snapshot) => Text( + '${context.translate.downloading_data} (${snapshot.data?.count.toMB ?? 0.0}mb / ${snapshot.data?.total.toMB ?? 0.0}mb)', + style: Marhey.medium12.copyWith( + color: MyColors.white, + ), + ), + ), + ], + ), + ); + } + Positioned _loading(BuildContext context) { return Positioned( bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16, diff --git a/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart index de107f7..3d2667b 100644 --- a/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart +++ b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart @@ -1,6 +1,7 @@ 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'; +import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; class IntroLoadingWidget extends StatelessWidget { @@ -9,7 +10,7 @@ class IntroLoadingWidget extends StatelessWidget { this.loadingStream, }); - final Stream? loadingStream; + final Stream? loadingStream; @override Widget build(BuildContext context) { @@ -32,8 +33,8 @@ class IntroLoadingWidget extends StatelessWidget { ], ), ), - child: StreamBuilder( - initialData: 0, + child: StreamBuilder( + initialData: DownloadEntity(), stream: loadingStream, builder: (context, snapshot) { return Row( @@ -45,7 +46,7 @@ class IntroLoadingWidget extends StatelessWidget { child: AnimatedContainer( duration: const Duration(milliseconds: 300), padding: EdgeInsetsDirectional.only( - end: 260 - ((snapshot.data ?? 0) * 260 / 100), + end: 260 - ((snapshot.data?.percent ?? 0) * 260 / 100), ), decoration: BoxDecoration( color: Color(0xFF1F59BD).withValues(alpha: 0.25), @@ -71,7 +72,7 @@ class IntroLoadingWidget extends StatelessWidget { flex: 15, child: Center( child: Text( - '${snapshot.data?.toInt() ?? 0}%', + '${snapshot.data?.percent?.toInt() ?? 0}%', style: DinoKids.regular17.copyWith( color: Color(0XFF6E83A8), ), diff --git a/lib/features/language/data/datasource/language_datasource.dart b/lib/features/language/data/datasource/language_datasource.dart deleted file mode 100644 index 6c37a9d..0000000 --- a/lib/features/language/data/datasource/language_datasource.dart +++ /dev/null @@ -1,46 +0,0 @@ -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/network/http_request.dart'; -import 'package:hadi_hoda_flutter/core/params/language_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/level_model.dart'; -import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; -import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart'; -import 'package:hive/hive.dart'; - -abstract class ILanguageDatasource { - Future saveLevels({required LanguageParams params}); -} - -class LanguageDatasourceImpl implements ILanguageDatasource { - final IHttpRequest httpRequest; - - const LanguageDatasourceImpl(this.httpRequest); - - @override - Future saveLevels({required LanguageParams params}) async { - await LocalStorage.saveData( - key: MyConstants.selectLanguage, - value: params.code ?? 'fa', - ); - final Box data = Hive.box(MyConstants.levelBox); - final TotalDataEntity findData = data.values.singleWhere( - (e) => e.code == params.code, - orElse: () => TotalDataEntity(), - ); - - if (findData.code != params.code) { - final response = await httpRequest.get( - path: MyApi.levels, - header: params.toHeader, - ); - final List levels = BaseResponse.getDataList( - response?['result'], - (json) => LevelModel.fromJson(json), - ); - - await data.add(TotalDataEntity(code: params.code, levels: levels)); - } - } -} diff --git a/lib/features/language/data/repository_impl/language_repository_impl.dart b/lib/features/language/data/repository_impl/language_repository_impl.dart deleted file mode 100644 index 7441fac..0000000 --- a/lib/features/language/data/repository_impl/language_repository_impl.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:hadi_hoda_flutter/core/params/language_params.dart'; -import 'package:flutter/foundation.dart'; -import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; -import 'package:hadi_hoda_flutter/core/params/no_params.dart'; -import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; -import 'package:hadi_hoda_flutter/features/language/data/datasource/language_datasource.dart'; -import 'package:hadi_hoda_flutter/features/language/domain/repository/language_repository.dart'; - -class LanguageRepositoryImpl implements ILanguageRepository { - final ILanguageDatasource datasource; - - const LanguageRepositoryImpl(this.datasource); - - @override - Future> saveLevels({required LanguageParams params}) async { - try { - await datasource.saveLevels(params: params); - return DataState.success(NoParams()); - } on MyException catch (e) { - return DataState.error(e); - } catch (e) { - if (kDebugMode) { - rethrow; - } else { - return DataState.error(MyException(errorMessage: '$e')); - } - } - } -} diff --git a/lib/features/language/domain/repository/language_repository.dart b/lib/features/language/domain/repository/language_repository.dart deleted file mode 100644 index 734c561..0000000 --- a/lib/features/language/domain/repository/language_repository.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; -import 'package:hadi_hoda_flutter/core/params/language_params.dart'; -import 'package:hadi_hoda_flutter/core/params/no_params.dart'; -import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; - -abstract class ILanguageRepository { - Future> saveLevels({required LanguageParams params}); -} diff --git a/lib/features/language/domain/usecases/save_levels_usecase.dart b/lib/features/language/domain/usecases/save_levels_usecase.dart deleted file mode 100644 index 61c6546..0000000 --- a/lib/features/language/domain/usecases/save_levels_usecase.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; -import 'package:hadi_hoda_flutter/core/params/language_params.dart'; -import 'package:hadi_hoda_flutter/core/params/no_params.dart'; -import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; -import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; -import 'package:hadi_hoda_flutter/features/language/domain/repository/language_repository.dart'; - -class SaveLevelsUseCase implements UseCase { - final ILanguageRepository repository; - - const SaveLevelsUseCase(this.repository); - - @override - Future> call(LanguageParams params) { - return repository.saveLevels(params: params); - } -} diff --git a/lib/features/language/presentation/bloc/language_bloc.dart b/lib/features/language/presentation/bloc/language_bloc.dart index c339a88..2dbf38f 100644 --- a/lib/features/language/presentation/bloc/language_bloc.dart +++ b/lib/features/language/presentation/bloc/language_bloc.dart @@ -1,26 +1,26 @@ import 'dart:async'; +import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:go_router/go_router.dart'; -import 'package:hadi_hoda_flutter/core/params/language_params.dart'; +import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; -import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; +import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; +import 'package:hadi_hoda_flutter/core/utils/storage_path.dart'; import 'package:hadi_hoda_flutter/features/language/domain/entity/language_entity.dart'; -import 'package:hadi_hoda_flutter/features/language/domain/usecases/save_levels_usecase.dart'; import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_event.dart'; import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_state.dart'; class LanguageBloc extends Bloc { /// ------------constructor------------ - LanguageBloc( - this._saveLevelsUseCase, - ) : super(const LanguageState()) { + LanguageBloc() : super(const LanguageState()) { + LocalStorage.saveData(key: MyConstants.firstLanguagePage, value: 'true'); on(_changeLanguageEvent); on(_saveLevelsEvent); + on(_initLanguageEvent); } /// ------------UseCases------------ - final SaveLevelsUseCase _saveLevelsUseCase; /// ------------Variables------------ final List languages = [ @@ -44,17 +44,31 @@ class LanguageBloc extends Bloc { SaveLevelsEvent event, Emitter emit, ) async { - emit(state.copyWith(saveLevelsStatus: const BaseLoading())); - await _saveLevelsUseCase(LanguageParams(code: state.selectedLang.code)).then( - (value) => value.fold( - (data) { - emit(state.copyWith(saveLevelsStatus: const BaseInit())); - ContextProvider.context!.goNamed(Routes.homePage); - }, - (error) { - emit(state.copyWith(saveLevelsStatus: const BaseInit())); - }, - ), - ); + await LocalStorage.saveData( + key: MyConstants.selectLanguage, + value: state.selectedLang.code ?? 'fa', + ); + if (Directory( + '${StoragePath.documentDir.path}/${state.selectedLang.code}/files') + .existsSync()) { + if (ContextProvider.context.mounted) { + ContextProvider.context.goNamed(Routes.homePage); + } + } else { + await Future.wait([ + LocalStorage.deleteData(key: MyConstants.downloadCompleted), + LocalStorage.deleteData(key: MyConstants.extractCompleted), + ]); + if (ContextProvider.context.mounted) { + ContextProvider.context.goNamed(Routes.introPage); + } + } + } + + FutureOr _initLanguageEvent(InitLanguageEvent event, Emitter emit) { + final String selectedLanguage = LocalStorage.readData(key: MyConstants.selectLanguage).isEmpty + ? 'fa' + : LocalStorage.readData(key: MyConstants.selectLanguage); + emit(state.copyWith(selectedLang: LanguageEntity(code: selectedLanguage))); } } diff --git a/lib/features/language/presentation/bloc/language_event.dart b/lib/features/language/presentation/bloc/language_event.dart index 2531db4..accf591 100644 --- a/lib/features/language/presentation/bloc/language_event.dart +++ b/lib/features/language/presentation/bloc/language_event.dart @@ -3,14 +3,15 @@ import 'package:hadi_hoda_flutter/features/language/domain/entity/language_entit sealed class LanguageEvent { const LanguageEvent(); } - class ChangeLanguageEvent extends LanguageEvent { final LanguageEntity lang; const ChangeLanguageEvent(this.lang); } - class SaveLevelsEvent extends LanguageEvent { const SaveLevelsEvent(); } +class InitLanguageEvent extends LanguageEvent { + const InitLanguageEvent(); +} diff --git a/lib/features/language/presentation/ui/language_page.dart b/lib/features/language/presentation/ui/language_page.dart index 4c81130..cd5af65 100644 --- a/lib/features/language/presentation/ui/language_page.dart +++ b/lib/features/language/presentation/ui/language_page.dart @@ -1,11 +1,9 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_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/common_ui/theme/my_theme.dart'; -import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/my_localization.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; @@ -71,7 +69,7 @@ class LanguagePage extends StatelessWidget { Expanded _list(BuildContext context) { return Expanded( child: Material( - color: context.noColor, + color: MyColors.transparent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( @@ -90,7 +88,7 @@ class LanguagePage extends StatelessWidget { }, title: Text(context.read().languages[index].title ?? ''), titleTextStyle: Marhey.medium16.copyWith( - color: context.primaryColor, + color: MyColors.white, ), contentPadding: EdgeInsets.symmetric( vertical: MySpaces.s12, @@ -100,18 +98,6 @@ class LanguagePage extends StatelessWidget { minTileHeight: 0, minLeadingWidth: 0, horizontalTitleGap: MySpaces.s12, - trailing: BlocBuilder( - builder: (context, state) { - if (state.saveLevelsStatus is BaseLoading && (state - .selectedLang.code == languageBloc.languages[index].code)) { - return CupertinoActivityIndicator( - color: context.primaryColor, - ); - } else { - return SizedBox.shrink(); - } - }, - ), leading: state.selectedLang.code == languageBloc.languages[index].code ? Container( height: 17, @@ -137,8 +123,8 @@ class LanguagePage extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(12)), ), - selectedTileColor: context.primaryColor.withValues(alpha: 0.2), - selectedColor: context.primaryColor, + selectedTileColor: MyColors.white.withValues(alpha: 0.2), + selectedColor: MyColors.white, ); } ), diff --git a/lib/features/level/presentation/ui/widgets/level_widget.dart b/lib/features/level/presentation/ui/widgets/level_widget.dart index ce1e44c..420c6c4 100644 --- a/lib/features/level/presentation/ui/widgets/level_widget.dart +++ b/lib/features/level/presentation/ui/widgets/level_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; -import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; @@ -43,7 +43,7 @@ class LevelWidget extends StatelessWidget { Text( '$index', style: DinoKids.regular26.copyWith( - color: context.primaryColor, + color: MyColors.white, shadows: [ Shadow( color: Color(0XFF5B5B5B), diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index 9e68191..9663bf0 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_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/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/utils/gap.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/my_localization.dart'; @@ -82,7 +82,7 @@ class QuestionPage extends StatelessWidget { builder: (context, state) => Text( state.levelEntity?.title ?? '', style: Marhey.bold14.copyWith( - color: context.primaryColor, + color: MyColors.white, ), ), ), @@ -109,7 +109,7 @@ class QuestionPage extends StatelessWidget { builder: (context, state) => Text( 'Question ${state.currentStep} / ${state.levelEntity?.questions?.length ?? 0}', style: Marhey.medium12.copyWith( - color: context.primaryColor.withValues(alpha: 0.5), + color: MyColors.white.withValues(alpha: 0.5), shadows: [ Shadow( offset: Offset(0, 1), @@ -125,7 +125,7 @@ class QuestionPage extends StatelessWidget { state.levelEntity?.questions?[state.currentStep].title ?? '', textAlign: TextAlign.center, style: Marhey.medium12.copyWith( - color: context.primaryColor, + color: MyColors.white, shadows: [ Shadow( offset: Offset(0, 1), diff --git a/lib/features/question/presentation/ui/widgets/refresh_button.dart b/lib/features/question/presentation/ui/widgets/refresh_button.dart index 801678e..2f4a8bf 100644 --- a/lib/features/question/presentation/ui/widgets/refresh_button.dart +++ b/lib/features/question/presentation/ui/widgets/refresh_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; class RefreshButton extends StatelessWidget { const RefreshButton({super.key, this.onTap,}); @@ -9,7 +9,7 @@ class RefreshButton extends StatelessWidget { @override Widget build(BuildContext context) { return Material( - color: context.noColor, + color: MyColors.transparent, child: Ink( height: 48, width: 48, diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index bad6e5e..997baae 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -12,10 +12,7 @@ import 'package:hadi_hoda_flutter/features/intro/data/repository_impl/intro_repo import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/usecases/get_files_usecase.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/usecases/loading_stream_usecase.dart'; -import 'package:hadi_hoda_flutter/features/language/data/datasource/language_datasource.dart'; -import 'package:hadi_hoda_flutter/features/language/data/repository_impl/language_repository_impl.dart'; -import 'package:hadi_hoda_flutter/features/language/domain/repository/language_repository.dart'; -import 'package:hadi_hoda_flutter/features/language/domain/usecases/save_levels_usecase.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/save_levels_usecase.dart'; import 'package:hadi_hoda_flutter/features/level/data/datasource/level_datasource.dart'; import 'package:hadi_hoda_flutter/features/level/data/repository_impl/level_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; @@ -52,12 +49,10 @@ void initBindings() { locator.registerLazySingleton(() => IntroDatasourceImpl(locator())); locator.registerLazySingleton(() => IntroRepositoryImpl(locator())); locator.registerLazySingleton(() => GetFilesUseCase(locator())); + locator.registerLazySingleton(() => SaveLevelsUseCase(locator())); locator.registerLazySingleton(() => LoadingStreamUseCase(locator())); /// Language Feature - locator.registerLazySingleton(() => LanguageDatasourceImpl(locator())); - locator.registerLazySingleton(() => LanguageRepositoryImpl(locator())); - locator.registerLazySingleton(() => SaveLevelsUseCase(locator())); /// Home Feature locator.registerLazySingleton(() => HomeDatasourceImpl(locator())); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fd36a8d..66f926e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -3,5 +3,10 @@ "about_us_desc" : "Rive combines an interactive design tool, a new stateful graphics format, a lightweight multi-platform runtime, and a blazing-fast vector renderer. \nThis end-to-end pipeline brings interfaces to life with motion. It gives designers and devs the tools to build.", "tap_to_select": "Tap the correct option to select.", "select_language": "Select language", - "select": "Select" + "select": "Select", + "please_wait": "Please wait a few moments...", + "downloading_data": "Downloading initial data", + "lost_connection": "Lost connection!", + "try_again": "Try Again", + "connected_to_internet": "You must be connected to the internet to download the initial game data." } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8b63165..7d3e5a1 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -123,6 +123,36 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Select'** String get select; + + /// No description provided for @please_wait. + /// + /// In en, this message translates to: + /// **'Please wait a few moments...'** + String get please_wait; + + /// No description provided for @downloading_data. + /// + /// In en, this message translates to: + /// **'Downloading initial data'** + String get downloading_data; + + /// No description provided for @lost_connection. + /// + /// In en, this message translates to: + /// **'Lost connection!'** + String get lost_connection; + + /// No description provided for @try_again. + /// + /// In en, this message translates to: + /// **'Try Again'** + String get try_again; + + /// No description provided for @connected_to_internet. + /// + /// In en, this message translates to: + /// **'You must be connected to the internet to download the initial game data.'** + String get connected_to_internet; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 290f727..521588d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -23,4 +23,20 @@ class AppLocalizationsEn extends AppLocalizations { @override String get select => 'Select'; + + @override + String get please_wait => 'Please wait a few moments...'; + + @override + String get downloading_data => 'Downloading initial data'; + + @override + String get lost_connection => 'Lost connection!'; + + @override + String get try_again => 'Try Again'; + + @override + String get connected_to_internet => + 'You must be connected to the internet to download the initial game data.'; }