diff --git a/assets/images/background_intro.png b/assets/images/background_intro.png new file mode 100644 index 0000000..2905983 Binary files /dev/null and b/assets/images/background_intro.png differ diff --git a/assets/images/hadi_hoda.png b/assets/images/hadi_hoda.png new file mode 100644 index 0000000..8d05776 Binary files /dev/null and b/assets/images/hadi_hoda.png differ diff --git a/assets/images/music_off.svg b/assets/images/music_off.svg new file mode 100644 index 0000000..6c8ad5e --- /dev/null +++ b/assets/images/music_off.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/music_on.svg b/assets/images/music_on.svg new file mode 100644 index 0000000..c53351d --- /dev/null +++ b/assets/images/music_on.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/start.svg b/assets/images/start.svg new file mode 100644 index 0000000..8151c85 --- /dev/null +++ b/assets/images/start.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/theme.svg b/assets/images/theme.svg new file mode 100644 index 0000000..93f83cf --- /dev/null +++ b/assets/images/theme.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart index b9e70da..4efdc1f 100644 --- a/lib/common_ui/resources/my_assets.dart +++ b/lib/common_ui/resources/my_assets.dart @@ -3,5 +3,10 @@ class MyAssets { const MyAssets._internal(); factory MyAssets() => _i; - static const String sample = 'assets/image/sample.png'; + static const String backgroundIntro = 'assets/images/background_intro.png'; + static const String hadiHoda = 'assets/images/hadi_hoda.png'; + static const String musicOff = 'assets/images/music_off.svg'; + static const String musicOn = 'assets/images/music_on.svg'; + static const String start = 'assets/images/start.svg'; + static const String theme = 'assets/images/theme.svg'; } \ No newline at end of file diff --git a/lib/common_ui/theme/my_theme.dart b/lib/common_ui/theme/my_theme.dart index 1d8dd96..43e6ba9 100644 --- a/lib/common_ui/theme/my_theme.dart +++ b/lib/common_ui/theme/my_theme.dart @@ -2,7 +2,7 @@ import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; import 'package:flutter/material.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; -enum ColorsName { primaryColor } +enum ColorsName { primaryColor, noColor } class MyTheme { static const MyTheme _i = MyTheme._internal(); @@ -14,10 +14,12 @@ class MyTheme { static final ThemeData dark = ThemeData(brightness: Brightness.dark); static Map get lightColors => { + ColorsName.noColor: MyColors.transparent, ColorsName.primaryColor: MyColors.white, }; static Map get darkColors => { + ColorsName.noColor: MyColors.transparent, ColorsName.primaryColor: MyColors.black, }; } @@ -29,4 +31,5 @@ extension ThemeExtension on BuildContext { : MyTheme.lightColors; Color get primaryColor => customColors[ColorsName.primaryColor]!; + Color get noColor => customColors[ColorsName.noColor]!; } diff --git a/lib/core/middleware/auth_middleware.dart b/lib/core/middleware/auth_middleware.dart index b03c337..16064ec 100644 --- a/lib/core/middleware/auth_middleware.dart +++ b/lib/core/middleware/auth_middleware.dart @@ -12,7 +12,7 @@ class AuthMiddleware { static FutureOr redirect(BuildContext context, GoRouterState state) async { if (AuthStorage.isLogin()) { - return Routes.samplePage; + return Routes.introPage; } else { return null; } diff --git a/lib/core/params/intro_params.dart b/lib/core/params/intro_params.dart new file mode 100644 index 0000000..e03d91f --- /dev/null +++ b/lib/core/params/intro_params.dart @@ -0,0 +1,13 @@ +class IntroParams { + int? id; + + IntroParams({this.id}); + + IntroParams copyWith({ + int? id, + }) { + return IntroParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index 6a31ae9..63a185b 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -1,28 +1,30 @@ -import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; -import 'package:hadi_hoda_flutter/features/sample/presentation/bloc/sample_bloc.dart'; -import 'package:hadi_hoda_flutter/features/sample/presentation/ui/sample_page.dart'; -import 'package:hadi_hoda_flutter/init_bindings.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import 'package:hadi_hoda_flutter/core/utils/context_provider.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/init_bindings.dart'; class Routes { static const Routes _i = Routes._internal(); + const Routes._internal(); + factory Routes() => _i; - static const String samplePage = '/sample_page'; + static const String introPage = '/intro_page'; } GoRouter get appPages => GoRouter( - initialLocation: Routes.samplePage, + initialLocation: Routes.introPage, navigatorKey: ContextProvider.navigatorKey, routes: [ GoRoute( - name: Routes.samplePage, - path: Routes.samplePage, + name: Routes.introPage, + path: Routes.introPage, builder: (context, state) => BlocProvider( - create: (context) => SampleBloc(locator()), - child: const SamplePage(), + create: (context) => IntroBloc(locator()), + child: const IntroPage(), ), ), ], diff --git a/lib/core/utils/my_image.dart b/lib/core/utils/my_image.dart new file mode 100644 index 0000000..9408284 --- /dev/null +++ b/lib/core/utils/my_image.dart @@ -0,0 +1,42 @@ +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({ + super.key, + required this.image, + this.fit, + this.width, + this.height, + this.color, + }); + + final String image; + final BoxFit? fit; + final double? width; + final double? height; + final Color? color; + + @override + Widget build(BuildContext context) { + if (image.endsWith('.png') || image.endsWith('.jpg')) { + return Image( + image: AssetImage(image), + fit: fit, + width: width, + height: height, + ); + } else { + return SvgPicture.asset( + image, + fit: fit ?? BoxFit.contain, + width: width, + height: height, + colorFilter: color != null + ? ColorFilter.mode(color ?? context.primaryColor, BlendMode.srcIn) + : null, + ); + } + } +} diff --git a/lib/features/intro/data/datasource/intro_datasource.dart b/lib/features/intro/data/datasource/intro_datasource.dart new file mode 100644 index 0000000..996c916 --- /dev/null +++ b/lib/features/intro/data/datasource/intro_datasource.dart @@ -0,0 +1,28 @@ +import 'package:hadi_hoda_flutter/core/constants/my_api.dart'; +import 'package:hadi_hoda_flutter/core/network/http_request.dart'; +import 'package:hadi_hoda_flutter/core/params/intro_params.dart'; +import 'package:hadi_hoda_flutter/core/response/base_response.dart'; +import 'package:hadi_hoda_flutter/features/intro/data/model/intro_model.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; + +abstract class IIntroDatasource { + Future getData({required IntroParams params}); +} + +class IntroDatasourceImpl implements IIntroDatasource { + final IHttpRequest httpRequest; + + const IntroDatasourceImpl(this.httpRequest); + + @override + Future getData({required IntroParams params}) async { + final response = await httpRequest.get( + path: MyApi.baseUrl, + ); + + return BaseResponse.getData( + response?['data'], + (json) => IntroModel.fromJson(json), + ); + } +} diff --git a/lib/features/intro/data/model/intro_model.dart b/lib/features/intro/data/model/intro_model.dart new file mode 100644 index 0000000..548de3d --- /dev/null +++ b/lib/features/intro/data/model/intro_model.dart @@ -0,0 +1,13 @@ +import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; + +class IntroModel extends IntroEntity { + const IntroModel({ + super.id, + }); + + factory IntroModel.fromJson(Map json) { + return IntroModel( + id: json['id'], + ); + } +} diff --git a/lib/features/intro/data/repository_impl/intro_repository_impl.dart b/lib/features/intro/data/repository_impl/intro_repository_impl.dart new file mode 100644 index 0000000..362779c --- /dev/null +++ b/lib/features/intro/data/repository_impl/intro_repository_impl.dart @@ -0,0 +1,29 @@ +import 'package:hadi_hoda_flutter/core/params/intro_params.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.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/entity/intro_entity.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; + +class IntroRepositoryImpl implements IIntroRepository { + final IIntroDatasource datasource; + + const IntroRepositoryImpl(this.datasource); + + @override + Future> getData({required IntroParams params}) async { + try { + final IntroEntity response = await datasource.getData(params: params); + return DataState.success(response); + } 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/intro/domain/entity/intro_entity.dart b/lib/features/intro/domain/entity/intro_entity.dart new file mode 100644 index 0000000..15c2e44 --- /dev/null +++ b/lib/features/intro/domain/entity/intro_entity.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class IntroEntity extends Equatable { + final int? id; + + const IntroEntity({ + this.id, + }); + + @override + List get props => [ + id, + ]; +} diff --git a/lib/features/intro/domain/repository/intro_repository.dart b/lib/features/intro/domain/repository/intro_repository.dart new file mode 100644 index 0000000..a4b8814 --- /dev/null +++ b/lib/features/intro/domain/repository/intro_repository.dart @@ -0,0 +1,8 @@ +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/intro_params.dart'; +import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; + +abstract class IIntroRepository { + Future> getData({required IntroParams params}); +} diff --git a/lib/features/intro/domain/usecases/get_intro_usecase.dart b/lib/features/intro/domain/usecases/get_intro_usecase.dart new file mode 100644 index 0000000..39c8bcc --- /dev/null +++ b/lib/features/intro/domain/usecases/get_intro_usecase.dart @@ -0,0 +1,17 @@ +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/intro_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/entity/intro_entity.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; + +class GetIntroUseCase implements UseCase { + final IIntroRepository repository; + + const GetIntroUseCase(this.repository); + + @override + Future> call(IntroParams params) { + return repository.getData(params: params); + } +} diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart new file mode 100644 index 0000000..bef4f8c --- /dev/null +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -0,0 +1,41 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:hadi_hoda_flutter/core/status/base_status.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/get_intro_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._getIntroUseCase, + ) : super(const IntroState()) { + on(_getIntroEvent); + } + + /// ------------UseCases------------ + final GetIntroUseCase _getIntroUseCase; + + /// ------------Variables------------ + + /// ------------Controllers------------ + + /// ------------Functions------------ + + /// ------------Api Calls------------ + FutureOr _getIntroEvent(event, emit) async { + await _getIntroUseCase(event.introParams).then( + (value) { + value.fold( + (data) { + emit(state.copyWith(getIntroStatus: BaseComplete(data))); + }, + (error) { + emit(state.copyWith(getIntroStatus: BaseError(error.errorMessage))); + }, + ); + }, + ); + } +} diff --git a/lib/features/intro/presentation/bloc/intro_event.dart b/lib/features/intro/presentation/bloc/intro_event.dart new file mode 100644 index 0000000..4bdd16b --- /dev/null +++ b/lib/features/intro/presentation/bloc/intro_event.dart @@ -0,0 +1,5 @@ +sealed class IntroEvent { + const IntroEvent(); +} + +class GetIntroEvent extends IntroEvent {} diff --git a/lib/features/intro/presentation/bloc/intro_state.dart b/lib/features/intro/presentation/bloc/intro_state.dart new file mode 100644 index 0000000..2c7fd3f --- /dev/null +++ b/lib/features/intro/presentation/bloc/intro_state.dart @@ -0,0 +1,15 @@ +import 'package:hadi_hoda_flutter/core/status/base_status.dart'; + +class IntroState { + final BaseStatus getIntroStatus; + + const IntroState({this.getIntroStatus = const BaseInit()}); + + IntroState copyWith({ + BaseStatus? getIntroStatus, + }) { + return IntroState( + getIntroStatus: getIntroStatus ?? this.getIntroStatus, + ); + } +} diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart new file mode 100644 index 0000000..1e1a78f --- /dev/null +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.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/core/utils/my_image.dart'; +import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; + +class IntroPage extends StatelessWidget { + const IntroPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(backgroundColor: context.noColor), + extendBodyBehindAppBar: true, + body: DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(MyAssets.backgroundIntro), + fit: BoxFit.cover, + ), + ), + child: SizedBox( + width: context.widthScreen, + height: context.heightScreen, + child: Stack( + alignment: Alignment.center, + children: [ + _name(), + _bottomBtns(), + ], + ), + ), + ), + ); + } + + Positioned _name() { + return Positioned( + top: 130, + child: MyImage( + image: MyAssets.hadiHoda, + width: 220, + height: 220, + fit: BoxFit.cover, + ), + ); + } + + Positioned _bottomBtns() { + return Positioned( + bottom: 20, + left: 20, + right: 20, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyImage(image: MyAssets.musicOn, height: 60, width: 60), + MyImage(image: MyAssets.start, height: 80, width: 80), + MyImage(image: MyAssets.theme, height: 60, width: 60), + ], + ), + ); + } +} diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index 7a63fe8..faea518 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -1,5 +1,9 @@ import 'package:hadi_hoda_flutter/core/network/http_request.dart'; import 'package:hadi_hoda_flutter/core/network/http_request_impl.dart'; +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/get_intro_usecase.dart'; import 'package:hadi_hoda_flutter/features/sample/data/datasource/sample_datasource.dart'; import 'package:hadi_hoda_flutter/features/sample/data/repository_impl/sample_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/sample/domain/repository/sample_repository.dart'; @@ -16,4 +20,9 @@ void initBindings() { locator.registerLazySingleton(() => SampleDatasourceImpl(locator())); locator.registerLazySingleton(() => SampleRepositoryImpl(locator())); locator.registerLazySingleton(() => GetSampleUseCase(locator())); + + /// Intro Feature + locator.registerLazySingleton(() => IntroDatasourceImpl(locator())); + locator.registerLazySingleton(() => IntroRepositoryImpl(locator())); + locator.registerLazySingleton(() => GetIntroUseCase(locator())); } diff --git a/pubspec.lock b/pubspec.lock index 008a414..a53a89a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -123,6 +131,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 + url: "https://pub.dev" + source: hosted + version: "2.2.1" flutter_test: dependency: "direct dev" description: flutter @@ -149,6 +165,14 @@ packages: url: "https://pub.dev" source: hosted version: "16.2.4" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" http_parser: dependency: transitive description: @@ -253,6 +277,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider_linux: dependency: transitive description: @@ -277,6 +309,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" platform: dependency: transitive description: @@ -426,6 +466,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" vector_math: dependency: transitive description: @@ -458,6 +522,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" sdks: dart: ">=3.9.2 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 07a52a3..b38de34 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: flutter_bloc: ^9.1.1 flutter_localizations: sdk: flutter + flutter_svg: ^2.2.1 get_it: ^8.2.0 go_router: ^16.1.0 intl: ^0.20.2 @@ -29,3 +30,6 @@ dev_dependencies: flutter: uses-material-design: true generate: true + + assets: + - assets/images/