From ff119476e2715b883244499782d23bb16fbff60e Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Fri, 3 Oct 2025 00:55:43 +0330 Subject: [PATCH] fix: downloading files and extract --- lib/core/constants/my_api.dart | 6 +- lib/core/constants/my_constants.dart | 2 + lib/core/error_handler/error_handler.dart | 2 +- lib/core/routers/my_routes.dart | 2 +- lib/core/utils/local_storage.dart | 2 +- lib/core/utils/storage_path.dart | 6 +- lib/core/widgets/answer_box/answer_box.dart | 2 +- .../data/datasource/intro_datasource.dart | 89 ++++++++----------- .../intro_repository_impl.dart | 20 +---- .../domain/repository/intro_repository.dart | 3 +- .../domain/usecases/extract_data_usecase.dart | 16 ---- ...ls_usecase.dart => get_files_usecase.dart} | 6 +- .../intro/presentation/bloc/intro_bloc.dart | 65 +++++--------- .../intro/presentation/bloc/intro_event.dart | 4 +- .../intro/presentation/bloc/intro_state.dart | 8 +- .../intro/presentation/ui/intro_page.dart | 23 +++-- .../ui/widgets/intro_loading_widget.dart | 2 - lib/init_bindings.dart | 6 +- lib/main.dart | 2 +- 19 files changed, 103 insertions(+), 163 deletions(-) delete mode 100644 lib/features/intro/domain/usecases/extract_data_usecase.dart rename lib/features/intro/domain/usecases/{save_levels_usecase.dart => get_files_usecase.dart} (76%) diff --git a/lib/core/constants/my_api.dart b/lib/core/constants/my_api.dart index b1ba034..dd59e1f 100644 --- a/lib/core/constants/my_api.dart +++ b/lib/core/constants/my_api.dart @@ -6,6 +6,10 @@ class MyApi { static const Duration timeOut = Duration(seconds: 30); static const String contentType = 'application/json'; static const String defaultError = 'An unexpected error has occurred.'; + static const String noInternetError = 'Please check your internet connection.'; - static const String baseUrl = 'https://api.BASE_URL.com'; + static const String baseUrl = 'https://hadihoda.newhorizonco.uk/api'; + + static const String levels = '/quiz/optimized/levels/'; + static const String files = '/quiz/optimized/download-all-files/'; } diff --git a/lib/core/constants/my_constants.dart b/lib/core/constants/my_constants.dart index c6e11bb..7772e42 100644 --- a/lib/core/constants/my_constants.dart +++ b/lib/core/constants/my_constants.dart @@ -6,4 +6,6 @@ class MyConstants { static const String token = 'TOKEN'; static const String theme = 'THEME'; static const String levelBox = 'LEVEL_BOX'; + static const String downloadCompleted = 'DOWNLOAD_COMPLETED'; + static const String extractCompleted = 'EXTRACT_COMPLETED'; } \ No newline at end of file diff --git a/lib/core/error_handler/error_handler.dart b/lib/core/error_handler/error_handler.dart index ac586c1..8fbe09d 100644 --- a/lib/core/error_handler/error_handler.dart +++ b/lib/core/error_handler/error_handler.dart @@ -10,7 +10,7 @@ class ErrorHandler { static void handleError(DioException e) { if (e.response == null) { throw MyException( - errorMessage: e.message ?? MyApi.defaultError, + errorMessage: MyApi.noInternetError, statusCode: e.response?.statusCode, ); } else { diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index f16e532..e7781c5 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -32,7 +32,7 @@ GoRouter get appPages => GoRouter( name: Routes.introPage, path: Routes.introPage, builder: (context, state) => BlocProvider( - create: (context) => IntroBloc(locator(), locator(), locator()), + create: (context) => IntroBloc(locator(), locator()), child: const IntroPage(), ), ), diff --git a/lib/core/utils/local_storage.dart b/lib/core/utils/local_storage.dart index baa5e7c..b44e69b 100644 --- a/lib/core/utils/local_storage.dart +++ b/lib/core/utils/local_storage.dart @@ -11,7 +11,7 @@ class LocalStorage { _box = await SharedPreferences.getInstance(); } - static Future saveData({required String key, required dynamic value}) async { + static Future saveData({required String key, required String value}) async { await _box.setString(key, value); } diff --git a/lib/core/utils/storage_path.dart b/lib/core/utils/storage_path.dart index 4ecde62..56a6746 100644 --- a/lib/core/utils/storage_path.dart +++ b/lib/core/utils/storage_path.dart @@ -3,9 +3,9 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; class StoragePath { - static Directory applicationDir = Directory(''); + static Directory documentDir = Directory(''); - static Future getApplicationDir() async { - applicationDir = await getApplicationDocumentsDirectory(); + static Future getDocumentDir() async { + documentDir = await getApplicationDocumentsDirectory(); } } \ No newline at end of file diff --git a/lib/core/widgets/answer_box/answer_box.dart b/lib/core/widgets/answer_box/answer_box.dart index e6a0f3b..7038487 100644 --- a/lib/core/widgets/answer_box/answer_box.dart +++ b/lib/core/widgets/answer_box/answer_box.dart @@ -24,7 +24,7 @@ class AnswerBox extends StatelessWidget { AnswerPictureBox( selected: selected ?? false, index: index, - image: '${StoragePath.applicationDir.path}/data/${answer + image: '${StoragePath.documentDir.path}/data/${answer .imageId}${answer.imageInfo?.extension ?? '.png'}', ), Positioned( diff --git a/lib/features/intro/data/datasource/intro_datasource.dart b/lib/features/intro/data/datasource/intro_datasource.dart index 89ed418..c01dbaa 100644 --- a/lib/features/intro/data/datasource/intro_datasource.dart +++ b/lib/features/intro/data/datasource/intro_datasource.dart @@ -1,21 +1,16 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; -import 'package:flutter/services.dart'; import 'package:flutter_archive/flutter_archive.dart'; +import 'package:hadi_hoda_flutter/core/constants/my_api.dart'; import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/network/http_request.dart'; -import 'package:hadi_hoda_flutter/core/response/base_response.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:hive/hive.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; +import 'package:hadi_hoda_flutter/core/utils/storage_path.dart'; abstract class IIntroDatasource { - Future saveLevels(); - Future extractData(); + Future getFiles(); Stream loadingStream(); } @@ -26,64 +21,50 @@ class IntroDatasourceImpl implements IIntroDatasource { IntroDatasourceImpl(this.httpRequest); @override - Future saveLevels() async { - try { - final Box levelBox = Hive.box(MyConstants.levelBox); - if (levelBox.isEmpty) { - final String levelAssets = await rootBundle.loadString( - 'assets/json/levels.json', - ); - final dynamic response = jsonDecode(levelAssets); - final List levelList = BaseResponse.getDataList( - response?['result'], - (json) => LevelModel.fromJson(json), - ); - - await levelBox.addAll(levelList); - } - } catch (e) { - throw MyException(errorMessage: '$e'); - } - } + Future getFiles() async { + final String filePath = '${StoragePath.documentDir.path}/files.zip'; - @override - Future extractData() async { - try { - final Directory dir = await getApplicationDocumentsDirectory(); - final File file = File('${dir.path}/data.zip'); - if (!(await file.exists())) { - final ByteData assetFile = await rootBundle.load('assets/data/data.zip'); - await file.create(recursive: true); - await file.writeAsBytes( - assetFile.buffer.asUint8List( - assetFile.offsetInBytes, - assetFile.lengthInBytes, - ), - flush: true, + if (LocalStorage.readData(key: MyConstants.downloadCompleted) != 'true') { + await httpRequest.download( + urlPath: MyApi.files, + savePath: filePath, + onReceive: (count, total) { + double percent = ((count / total) * 100); + streamController.add(percent); + }, + ).then((value) async { + await LocalStorage.saveData( + key: MyConstants.downloadCompleted, + value: 'true', ); + }); + } + try{ + if (LocalStorage.readData(key: MyConstants.extractCompleted) != 'true') { + final File file = File(filePath); await ZipFile.extractToDirectory( zipFile: file, - destinationDir: dir, + destinationDir: StoragePath.documentDir, onExtracting: (zipEntry, progress) { streamController.add(progress); return ZipFileOperation.includeItem; }, - ); - + ).then((value) async { + await Future.wait([ + LocalStorage.saveData( + key: MyConstants.extractCompleted, + value: 'true', + ), + file.delete(recursive: true), + ]); + }); } else { - streamController.add(20); - await Future.delayed(Duration(milliseconds: 150)); - streamController.add(40); - await Future.delayed(Duration(milliseconds: 150)); - streamController.add(60); - await Future.delayed(Duration(milliseconds: 150)); - streamController.add(80); + streamController.add(50); await Future.delayed(Duration(milliseconds: 150)); streamController.add(100); - await Future.delayed(Duration(milliseconds: 150)); } - } catch (e) { + } catch (e){ throw MyException(errorMessage: '$e'); } } 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 10c458f..9dfa48c 100644 --- a/lib/features/intro/data/repository_impl/intro_repository_impl.dart +++ b/lib/features/intro/data/repository_impl/intro_repository_impl.dart @@ -11,25 +11,9 @@ class IntroRepositoryImpl implements IIntroRepository { const IntroRepositoryImpl(this.datasource); @override - Future> saveLevels() async { + Future> getFiles() 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 - Future> extractData() async { - try { - await datasource.extractData(); + await datasource.getFiles(); return DataState.success(NoParams()); } on MyException catch (e) { return DataState.error(e); diff --git a/lib/features/intro/domain/repository/intro_repository.dart b/lib/features/intro/domain/repository/intro_repository.dart index 6bff8db..717feb4 100644 --- a/lib/features/intro/domain/repository/intro_repository.dart +++ b/lib/features/intro/domain/repository/intro_repository.dart @@ -3,7 +3,6 @@ import 'package:hadi_hoda_flutter/core/params/no_params.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; abstract class IIntroRepository { - Future> saveLevels(); - Future> extractData(); + Future> getFiles(); Stream loadingStream(); } diff --git a/lib/features/intro/domain/usecases/extract_data_usecase.dart b/lib/features/intro/domain/usecases/extract_data_usecase.dart deleted file mode 100644 index c48e061..0000000 --- a/lib/features/intro/domain/usecases/extract_data_usecase.dart +++ /dev/null @@ -1,16 +0,0 @@ -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 ExtractDataUseCase implements UseCase { - final IIntroRepository repository; - - const ExtractDataUseCase(this.repository); - - @override - Future> call(NoParams params) { - return repository.extractData(); - } -} diff --git a/lib/features/intro/domain/usecases/save_levels_usecase.dart b/lib/features/intro/domain/usecases/get_files_usecase.dart similarity index 76% rename from lib/features/intro/domain/usecases/save_levels_usecase.dart rename to lib/features/intro/domain/usecases/get_files_usecase.dart index 87f385b..ca5c7ea 100644 --- a/lib/features/intro/domain/usecases/save_levels_usecase.dart +++ b/lib/features/intro/domain/usecases/get_files_usecase.dart @@ -4,13 +4,13 @@ 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 { +class GetFilesUseCase implements UseCase { final IIntroRepository repository; - const SaveLevelsUseCase(this.repository); + const GetFilesUseCase(this.repository); @override Future> call(NoParams params) { - return repository.saveLevels(); + return repository.getFiles(); } } diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index 71db678..9ec1665 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -1,30 +1,26 @@ import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:go_router/go_router.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/features/intro/domain/usecases/extract_data_usecase.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._saveLevelsUseCase, - this._extractDataUseCase, - this._loadingStreamUseCase, - ) : super(const IntroState()) { - on(_saveLevelsEvent); - on(_extractDataEvent); + IntroBloc(this._getFilesUseCase, this._loadingStreamUseCase) + : super(const IntroState()) { + on(_getFilesEvent); loadingStream = _loadingStreamUseCase(); } /// ------------UseCases------------ - final SaveLevelsUseCase _saveLevelsUseCase; - final ExtractDataUseCase _extractDataUseCase; + final GetFilesUseCase _getFilesUseCase; final LoadingStreamUseCase _loadingStreamUseCase; /// ------------Variables------------ @@ -35,36 +31,23 @@ class IntroBloc extends Bloc { /// ------------Functions------------ /// ------------Api Calls------------ - FutureOr _saveLevelsEvent(SaveLevelsEvent event, - Emitter emit) async { - await _saveLevelsUseCase(NoParams()).then( - (value) { - value.fold( - (data) async { - - add(ExtractDataEvent()); + FutureOr _getFilesEvent( + GetFilesEvent event, + Emitter emit, + ) async { + await _getFilesUseCase(NoParams()).then((value) { + value.fold( + (data) async { + await Future.delayed( + Duration(milliseconds: 300), () { + ContextProvider.context!.goNamed(Routes.homePage); }, - (error) {}, - ); - }, - ); - } - - FutureOr _extractDataEvent(ExtractDataEvent event, - Emitter emit) async { - await _extractDataUseCase(NoParams()).then( - (value) { - value.fold( - (data) async { - await Future.delayed( - Duration(seconds: 1), () { - ContextProvider.context!.goNamed(Routes.homePage); - }, - ); - }, - (error) {}, - ); - }, - ); + ); + }, + (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 81aab0d..70e23cc 100644 --- a/lib/features/intro/presentation/bloc/intro_event.dart +++ b/lib/features/intro/presentation/bloc/intro_event.dart @@ -1,6 +1,4 @@ sealed class IntroEvent { const IntroEvent(); } - -class SaveLevelsEvent extends IntroEvent {} -class ExtractDataEvent extends IntroEvent {} +class GetFilesEvent extends IntroEvent {} diff --git a/lib/features/intro/presentation/bloc/intro_state.dart b/lib/features/intro/presentation/bloc/intro_state.dart index 2c7fd3f..58de6aa 100644 --- a/lib/features/intro/presentation/bloc/intro_state.dart +++ b/lib/features/intro/presentation/bloc/intro_state.dart @@ -1,15 +1,15 @@ import 'package:hadi_hoda_flutter/core/status/base_status.dart'; class IntroState { - final BaseStatus getIntroStatus; + final BaseStatus getFilesStatus; - const IntroState({this.getIntroStatus = const BaseInit()}); + const IntroState({this.getFilesStatus = const BaseInit()}); IntroState copyWith({ - BaseStatus? getIntroStatus, + BaseStatus? getFilesStatus, }) { return IntroState( - getIntroStatus: getIntroStatus ?? this.getIntroStatus, + getFilesStatus: getFilesStatus ?? this.getFilesStatus, ); } } diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index 92c0dd5..b874119 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -16,11 +16,10 @@ class IntroPage extends StatefulWidget { } class _IntroPageState extends State { - @override void initState() { super.initState(); - context.read().add(SaveLevelsEvent()); + context.read().add(GetFilesEvent()); } @override @@ -33,7 +32,10 @@ class _IntroPageState extends State { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [Color(0XFF00154C), Color(0XFF150532)], + colors: [ + Color(0XFF00154C), + Color(0XFF150532), + ], ), image: DecorationImage( image: AssetImage(MyAssets.pattern), @@ -47,19 +49,26 @@ class _IntroPageState extends State { ), child: Stack( alignment: Alignment.center, - children: [_image(), _loading(context)], + children: [ + _image(), + _loading(context), + ], ), ), ); } - MyImage _image() => MyImage(image: MyAssets.hadiHoda, size: 200); + MyImage _image() { + return MyImage( + image: MyAssets.hadiHoda, + size: 200, + ); + } Positioned _loading(BuildContext context) { return Positioned( - bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s10, + bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16, child: IntroLoadingWidget( - percent: 80, loadingStream: context.read().loadingStream, ), ); 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 f7c9d09..de107f7 100644 --- a/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart +++ b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart @@ -6,11 +6,9 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; class IntroLoadingWidget extends StatelessWidget { const IntroLoadingWidget({ super.key, - this.percent, this.loadingStream, }); - final double? percent; final Stream? loadingStream; @override diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index 11a4ef3..c8c7a8c 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -10,9 +10,8 @@ import 'package:hadi_hoda_flutter/features/home/domain/usecases/get_home_usecase import 'package:hadi_hoda_flutter/features/intro/data/datasource/intro_datasource.dart'; import 'package:hadi_hoda_flutter/features/intro/data/repository_impl/intro_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/usecases/extract_data_usecase.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/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'; @@ -47,8 +46,7 @@ void initBindings() { /// Intro Feature locator.registerLazySingleton(() => IntroDatasourceImpl(locator())); locator.registerLazySingleton(() => IntroRepositoryImpl(locator())); - locator.registerLazySingleton(() => SaveLevelsUseCase(locator())); - locator.registerLazySingleton(() => ExtractDataUseCase(locator())); + locator.registerLazySingleton(() => GetFilesUseCase(locator())); locator.registerLazySingleton(() => LoadingStreamUseCase(locator())); /// Home Feature diff --git a/lib/main.dart b/lib/main.dart index 5940746..7740c6e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,8 +14,8 @@ Future main() async { initBindings(); await Future.wait([ LocalStorage.init(), + StoragePath.getDocumentDir(), initDataBase(), - StoragePath.getApplicationDir(), ]); AuthStorage.loadData(); runApp(const MainApp());