diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index ea90dee..2a88360 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -31,7 +31,7 @@ GoRouter get appPages => GoRouter( name: Routes.introPage, path: Routes.introPage, builder: (context, state) => BlocProvider( - create: (context) => IntroBloc(locator()), + create: (context) => IntroBloc(locator(), locator(), locator()), child: const IntroPage(), ), ), diff --git a/lib/features/intro/data/datasource/intro_datasource.dart b/lib/features/intro/data/datasource/intro_datasource.dart index f49fb4b..89ed418 100644 --- a/lib/features/intro/data/datasource/intro_datasource.dart +++ b/lib/features/intro/data/datasource/intro_datasource.dart @@ -1,6 +1,9 @@ +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_constants.dart'; import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/network/http_request.dart'; @@ -8,15 +11,19 @@ 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'; abstract class IIntroDatasource { Future saveLevels(); + Future extractData(); + Stream loadingStream(); } class IntroDatasourceImpl implements IIntroDatasource { final IHttpRequest httpRequest; + final StreamController streamController = StreamController.broadcast(); - const IntroDatasourceImpl(this.httpRequest); + IntroDatasourceImpl(this.httpRequest); @override Future saveLevels() async { @@ -38,4 +45,49 @@ class IntroDatasourceImpl implements IIntroDatasource { throw MyException(errorMessage: '$e'); } } + + @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, + ); + + await ZipFile.extractToDirectory( + zipFile: file, + destinationDir: dir, + onExtracting: (zipEntry, progress) { + streamController.add(progress); + return ZipFileOperation.includeItem; + }, + ); + + } 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); + await Future.delayed(Duration(milliseconds: 150)); + streamController.add(100); + await Future.delayed(Duration(milliseconds: 150)); + } + } catch (e) { + throw MyException(errorMessage: '$e'); + } + } + + @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 f0be4ba..10c458f 100644 --- a/lib/features/intro/data/repository_impl/intro_repository_impl.dart +++ b/lib/features/intro/data/repository_impl/intro_repository_impl.dart @@ -25,4 +25,25 @@ class IntroRepositoryImpl implements IIntroRepository { } } } + + @override + Future> extractData() async { + try { + await datasource.extractData(); + 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/repository/intro_repository.dart b/lib/features/intro/domain/repository/intro_repository.dart index 636e5ec..6bff8db 100644 --- a/lib/features/intro/domain/repository/intro_repository.dart +++ b/lib/features/intro/domain/repository/intro_repository.dart @@ -4,4 +4,6 @@ import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; abstract class IIntroRepository { Future> saveLevels(); + Future> extractData(); + Stream loadingStream(); } diff --git a/lib/features/intro/domain/usecases/extract_data_usecase.dart b/lib/features/intro/domain/usecases/extract_data_usecase.dart new file mode 100644 index 0000000..c48e061 --- /dev/null +++ b/lib/features/intro/domain/usecases/extract_data_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 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/loading_stream_usecase.dart b/lib/features/intro/domain/usecases/loading_stream_usecase.dart new file mode 100644 index 0000000..a24d6ed --- /dev/null +++ b/lib/features/intro/domain/usecases/loading_stream_usecase.dart @@ -0,0 +1,11 @@ +import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; + +class LoadingStreamUseCase { + final IIntroRepository repository; + + const LoadingStreamUseCase(this.repository); + + Stream call() { + return repository.loadingStream(); + } +} diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index 886787a..5a64c25 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -4,6 +4,8 @@ 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/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/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'; @@ -12,14 +14,21 @@ class IntroBloc extends Bloc { /// ------------constructor------------ IntroBloc( this._saveLevelsUseCase, + this._extractDataUseCase, + this._loadingStreamUseCase, ) : super(const IntroState()) { on(_saveLevelsEvent); + on(_extractDataEvent); + loadingStream = _loadingStreamUseCase(); } /// ------------UseCases------------ final SaveLevelsUseCase _saveLevelsUseCase; + final ExtractDataUseCase _extractDataUseCase; + final LoadingStreamUseCase _loadingStreamUseCase; /// ------------Variables------------ + Stream loadingStream = Stream.empty(); /// ------------Controllers------------ @@ -32,15 +41,32 @@ class IntroBloc extends Bloc { (value) { value.fold( (data) async { - await Future.delayed( - Duration(seconds: 1), () { - ContextProvider.context!.goNamed(Routes.homePage); - }, - ); + + add(ExtractDataEvent()); }, (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) { + print(error.errorMessage); + }, + ); + }, + ); + } } diff --git a/lib/features/intro/presentation/bloc/intro_event.dart b/lib/features/intro/presentation/bloc/intro_event.dart index 3d80e11..81aab0d 100644 --- a/lib/features/intro/presentation/bloc/intro_event.dart +++ b/lib/features/intro/presentation/bloc/intro_event.dart @@ -3,3 +3,4 @@ sealed class IntroEvent { } class SaveLevelsEvent extends IntroEvent {} +class ExtractDataEvent extends IntroEvent {} diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index afc90af..9710e94 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -57,7 +57,10 @@ class _IntroPageState extends State { Positioned _loading(BuildContext context) { return Positioned( bottom: MediaQuery.viewPaddingOf(context).bottom, - child: IntroLoadingWidget(percent: 80), + 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 56816f3..42cf086 100644 --- a/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart +++ b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart @@ -7,9 +7,11 @@ class IntroLoadingWidget extends StatelessWidget { const IntroLoadingWidget({ super.key, this.percent, + this.loadingStream, }); final double? percent; + final Stream? loadingStream; @override Widget build(BuildContext context) { @@ -32,49 +34,56 @@ class IntroLoadingWidget extends StatelessWidget { ], ), ), - child: Row( - children: [ - Expanded( - flex: 85, - child: ClipPath( - clipper: BubbleClip(), - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - padding: EdgeInsetsDirectional.only( - end: 260 - ((percent ?? 0) * 260 / 100), - ), - decoration: BoxDecoration( - color: Color(0xFF1F59BD).withValues(alpha: 0.25), - ), + child: StreamBuilder( + initialData: 0, + stream: loadingStream, + builder: (context, snapshot) { + print(snapshot.data); + return Row( + children: [ + Expanded( + flex: 85, child: ClipPath( clipper: BubbleClip(), - child: Container( + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: EdgeInsetsDirectional.only( + end: 260 - ((snapshot.data ?? 0) * 260 / 100), + ), decoration: BoxDecoration( - gradient: RadialGradient( - radius: 2, - colors: [ - Color(0xFFFFBD00), // #CADCFF - Color(0xFFFF772C), // #CADCFF - ], + color: Color(0xFF1F59BD).withValues(alpha: 0.25), + ), + child: ClipPath( + clipper: BubbleClip(), + child: Container( + decoration: BoxDecoration( + gradient: RadialGradient( + radius: 2, + colors: [ + Color(0xFFFFBD00), // #CADCFF + Color(0xFFFF772C), // #CADCFF + ], + ), + ), ), ), ), ), ), - ), - ), - Expanded( - flex: 15, - child: Center( - child: Text( - '${percent?.toInt() ?? 0}%', - style: MyTextStyle.normal17.copyWith( - color: Color(0XFF6E83A8), + Expanded( + flex: 15, + child: Center( + child: Text( + '${snapshot.data?.toInt() ?? 0}%', + style: MyTextStyle.normal17.copyWith( + color: Color(0XFF6E83A8), + ), + ), ), ), - ), - ), - ], + ], + ); + } ), ), ); diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index d82b2b2..a51866f 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -10,6 +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/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'; @@ -46,6 +48,8 @@ void initBindings() { locator.registerLazySingleton(() => IntroDatasourceImpl(locator())); locator.registerLazySingleton(() => IntroRepositoryImpl(locator())); locator.registerLazySingleton(() => SaveLevelsUseCase(locator())); + locator.registerLazySingleton(() => ExtractDataUseCase(locator())); + locator.registerLazySingleton(() => LoadingStreamUseCase(locator())); /// Home Feature locator.registerLazySingleton(() => HomeDatasourceImpl(locator())); diff --git a/pubspec.lock b/pubspec.lock index 346dcbd..5e4c4f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -254,6 +254,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_archive: + dependency: "direct main" + description: + name: flutter_archive + sha256: "5ca235f304c12bf468979235f400f79846d204169d715939e39197106f5fc970" + url: "https://pub.dev" + source: hosted + version: "6.0.3" flutter_bloc: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 61f27b7..59e9aa3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: equatable: ^2.0.7 flutter: sdk: flutter + flutter_archive: ^6.0.3 flutter_bloc: ^9.1.1 flutter_localizations: sdk: flutter