diff --git a/assets/images/add_background.png b/assets/images/add_background.png new file mode 100644 index 0000000..eff2d70 Binary files /dev/null and b/assets/images/add_background.png differ diff --git a/assets/images/cup.png b/assets/images/cup.png new file mode 100644 index 0000000..16de293 Binary files /dev/null and b/assets/images/cup.png differ diff --git a/assets/images/icon_crown.png b/assets/images/icon_crown.png new file mode 100644 index 0000000..2c24200 Binary files /dev/null and b/assets/images/icon_crown.png differ diff --git a/assets/images/icon_diamond.png b/assets/images/icon_diamond.png new file mode 100644 index 0000000..c1104f3 Binary files /dev/null and b/assets/images/icon_diamond.png differ diff --git a/assets/images/icon_flash.png b/assets/images/icon_flash.png new file mode 100644 index 0000000..9ca0a4e Binary files /dev/null and b/assets/images/icon_flash.png differ diff --git a/assets/images/icon_profile.png b/assets/images/icon_profile.png new file mode 100644 index 0000000..f48afff Binary files /dev/null and b/assets/images/icon_profile.png differ diff --git a/assets/images/mic.png b/assets/images/mic.png new file mode 100644 index 0000000..ca96910 Binary files /dev/null and b/assets/images/mic.png differ diff --git a/assets/images/mic_blur.png b/assets/images/mic_blur.png new file mode 100644 index 0000000..cd6367e Binary files /dev/null and b/assets/images/mic_blur.png differ diff --git a/assets/images/shia_mind_group.png b/assets/images/shia_mind_group.png new file mode 100644 index 0000000..b47c20e Binary files /dev/null and b/assets/images/shia_mind_group.png differ diff --git a/assets/svg/friend_battle.svg b/assets/svg/friend_battle.svg new file mode 100644 index 0000000..aa8575a --- /dev/null +++ b/assets/svg/friend_battle.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/icon_awards.svg b/assets/svg/icon_awards.svg new file mode 100644 index 0000000..67901d2 --- /dev/null +++ b/assets/svg/icon_awards.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/icon_clock.svg b/assets/svg/icon_clock.svg new file mode 100644 index 0000000..f657f87 --- /dev/null +++ b/assets/svg/icon_clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/icon_home.svg b/assets/svg/icon_home.svg new file mode 100644 index 0000000..a88c424 --- /dev/null +++ b/assets/svg/icon_home.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/icon_plus.svg b/assets/svg/icon_plus.svg new file mode 100644 index 0000000..9c6b2b4 --- /dev/null +++ b/assets/svg/icon_plus.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/svg/icon_setting.svg b/assets/svg/icon_setting.svg new file mode 100644 index 0000000..dfc6c52 --- /dev/null +++ b/assets/svg/icon_setting.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/icon_share.svg b/assets/svg/icon_share.svg new file mode 100644 index 0000000..43b2c59 --- /dev/null +++ b/assets/svg/icon_share.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/icon_shop.svg b/assets/svg/icon_shop.svg new file mode 100644 index 0000000..57c2ab6 --- /dev/null +++ b/assets/svg/icon_shop.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/medal.svg b/assets/svg/medal.svg new file mode 100644 index 0000000..8b4a41a --- /dev/null +++ b/assets/svg/medal.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart index 09d8c1c..6edec51 100644 --- a/lib/common_ui/resources/my_assets.dart +++ b/lib/common_ui/resources/my_assets.dart @@ -7,9 +7,28 @@ class MyAssets { static const String sample = 'assets/images/sample.png'; static const String shiaMind = 'assets/images/shia_mind.png'; static const String question = 'assets/images/question.png'; + static const String iconProfile = 'assets/images/icon_profile.png'; + static const String iconDiamond = 'assets/images/icon_diamond.png'; + static const String iconFlash = 'assets/images/icon_flash.png'; + static const String iconCrown = 'assets/images/icon_crown.png'; + static const String shiaMindGroup = 'assets/images/shia_mind_group.png'; + static const String cup = 'assets/images/cup.png'; + static const String addBackground = 'assets/images/add_background.png'; + static const String mic = 'assets/images/mic.png'; + static const String micBLur = 'assets/images/mic_blur.png'; /// ----- Svg ----- static const String sampleSvg = 'assets/svg/sample.svg'; + static const String iconHome = 'assets/svg/icon_home.svg'; + static const String iconShop = 'assets/svg/icon_shop.svg'; + static const String iconAwards = 'assets/svg/icon_awards.svg'; + static const String iconPlus = 'assets/svg/icon_plus.svg'; + static const String iconSetting = 'assets/svg/icon_setting.svg'; + static const String iconShare = 'assets/svg/icon_share.svg'; + static const String iconClock = 'assets/svg/icon_clock.svg'; + static const String medal = 'assets/svg/medal.svg'; + static const String friendBattle = 'assets/svg/friend_battle.svg'; + /// ----- Audios ----- static const String sampleAudio = 'assets/audios/sample.mp3'; @@ -21,5 +40,10 @@ class MyAssets { static const List images = [ shiaMind, question, + iconProfile, + iconDiamond, + iconFlash, + iconCrown, + shiaMindGroup, ]; } diff --git a/lib/common_ui/resources/my_colors.dart b/lib/common_ui/resources/my_colors.dart index 5af3ddc..087ce37 100644 --- a/lib/common_ui/resources/my_colors.dart +++ b/lib/common_ui/resources/my_colors.dart @@ -8,5 +8,5 @@ class MyColors { static const Color white = Colors.white; static const Color black = Colors.black; static const Color transparent = Colors.transparent; - static const Color introBackgroundColor = Color(0xFF160C30); + static const Color backgroundColor = Color(0xFF160C30); } diff --git a/lib/common_ui/theme/my_theme.dart b/lib/common_ui/theme/my_theme.dart index 9ba99f4..7ee5201 100644 --- a/lib/common_ui/theme/my_theme.dart +++ b/lib/common_ui/theme/my_theme.dart @@ -5,7 +5,7 @@ import 'package:get/get.dart'; enum ColorsName { primaryColor, noColor, - introBackgroundColor, + backgroundColor, } class MyTheme { @@ -20,13 +20,13 @@ class MyTheme { static Map get lightColors => { ColorsName.primaryColor: MyColors.white, ColorsName.noColor: MyColors.transparent, - ColorsName.introBackgroundColor: MyColors.introBackgroundColor, + ColorsName.backgroundColor: MyColors.backgroundColor, }; static Map get darkColors => { ColorsName.primaryColor: MyColors.white, ColorsName.noColor: MyColors.transparent, - ColorsName.introBackgroundColor: MyColors.introBackgroundColor, + ColorsName.backgroundColor: MyColors.backgroundColor, }; } @@ -36,5 +36,5 @@ extension ThemeExtension on BuildContext { Color get primaryColor => customColors[ColorsName.primaryColor]!; Color get noColor => customColors[ColorsName.noColor]!; - Color get introBackgroundColor => customColors[ColorsName.introBackgroundColor]!; + Color get backgroundColor => customColors[ColorsName.backgroundColor]!; } diff --git a/lib/core/params/awards_params.dart b/lib/core/params/awards_params.dart new file mode 100644 index 0000000..63bc3a0 --- /dev/null +++ b/lib/core/params/awards_params.dart @@ -0,0 +1,13 @@ +class AwardsParams { + int? id; + + AwardsParams({this.id}); + + AwardsParams copyWith({ + int? id, + }) { + return AwardsParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/core/params/home_params.dart b/lib/core/params/home_params.dart new file mode 100644 index 0000000..22518f3 --- /dev/null +++ b/lib/core/params/home_params.dart @@ -0,0 +1,13 @@ +class HomeParams { + int? id; + + HomeParams({this.id}); + + HomeParams copyWith({ + int? id, + }) { + return HomeParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/core/params/master_params.dart b/lib/core/params/master_params.dart new file mode 100644 index 0000000..62509d2 --- /dev/null +++ b/lib/core/params/master_params.dart @@ -0,0 +1,13 @@ +class MasterParams { + int? id; + + MasterParams({this.id}); + + MasterParams copyWith({ + int? id, + }) { + return MasterParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/core/params/profile_params.dart b/lib/core/params/profile_params.dart new file mode 100644 index 0000000..9c62c23 --- /dev/null +++ b/lib/core/params/profile_params.dart @@ -0,0 +1,13 @@ +class ProfileParams { + int? id; + + ProfileParams({this.id}); + + ProfileParams copyWith({ + int? id, + }) { + return ProfileParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/core/params/shop_params.dart b/lib/core/params/shop_params.dart new file mode 100644 index 0000000..6d3537b --- /dev/null +++ b/lib/core/params/shop_params.dart @@ -0,0 +1,13 @@ +class ShopParams { + int? id; + + ShopParams({this.id}); + + ShopParams copyWith({ + int? id, + }) { + return ShopParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index 4fece1a..272cf33 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -1,8 +1,18 @@ +import 'package:shia_game_flutter/features/awards/presentation/binding/awards_binding.dart'; +import 'package:shia_game_flutter/features/awards/presentation/ui/awards_page.dart'; +import 'package:shia_game_flutter/features/home/presentation/binding/home_binding.dart'; +import 'package:shia_game_flutter/features/home/presentation/pages/home_page.dart'; import 'package:shia_game_flutter/features/intro/presentation/binding/intro_binding.dart'; import 'package:shia_game_flutter/features/intro/presentation/ui/intro_page.dart'; +import 'package:shia_game_flutter/features/master/presentation/binding/master_binding.dart'; +import 'package:shia_game_flutter/features/master/presentation/ui/master_page.dart'; +import 'package:shia_game_flutter/features/profile/presentation/binding/profile_binding.dart'; +import 'package:shia_game_flutter/features/profile/presentation/ui/profile_page.dart'; import 'package:shia_game_flutter/features/sample/presentation/binding/sample_binding.dart'; import 'package:shia_game_flutter/features/sample/presentation/ui/sample_page.dart'; import 'package:get/get.dart'; +import 'package:shia_game_flutter/features/shop/presentation/binding/shop_binding.dart'; +import 'package:shia_game_flutter/features/shop/presentation/ui/shop_page.dart'; class Routes { static const Routes _i = Routes._internal(); @@ -11,6 +21,11 @@ class Routes { static const String samplePage = '/sample_page'; static const String introPage = '/intro_page'; + static const String masterPage = '/'; + static const String homePage = '/home_page'; + static const String shopPage = '/shop_page'; + static const String awardsPage = '/awards_page'; + static const String profilePage = '/profile_page'; } List get appPages => [ @@ -24,4 +39,35 @@ List get appPages => [ page: () => const IntroPage(), binding: IntroBinding(), ), + GetPage( + name: Routes.masterPage, + page: () => const MasterPage(), + binding: MasterBinding(), + children: [ + GetPage( + name: Routes.homePage, + page: () => const HomePage(), + binding: HomeBinding(), + transition: Transition.fadeIn, + ), + GetPage( + name: Routes.shopPage, + page: () => const ShopPage(), + binding: ShopBinding(), + transition: Transition.fadeIn, + ), + GetPage( + name: Routes.awardsPage, + page: () => const AwardsPage(), + binding: AwardsBinding(), + transition: Transition.fadeIn, + ), + GetPage( + name: Routes.profilePage, + page: () => const ProfilePage(), + binding: ProfileBinding(), + transition: Transition.fadeIn, + ), + ], + ), ]; diff --git a/lib/core/widgets/app_bar/master_app_bar.dart b/lib/core/widgets/app_bar/master_app_bar.dart new file mode 100644 index 0000000..483fdcb --- /dev/null +++ b/lib/core/widgets/app_bar/master_app_bar.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/theme/my_theme.dart'; +import 'package:shia_game_flutter/core/utils/gap.dart'; +import 'package:shia_game_flutter/core/widgets/app_bar/styles/app_bar_action.dart'; +import 'package:shia_game_flutter/core/widgets/app_bar/styles/app_bar_add_widget.dart'; + +class MasterAppBar extends StatelessWidget implements PreferredSizeWidget { + const MasterAppBar({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: context.backgroundColor, + titleSpacing: MySpaces.s30, + title: Row( + children: [ + AppBarAddWidget( + onTap: () {}, + icon: MyAssets.iconDiamond, + number: 999, + gradientColors: [ + Color(0XFF52C3ED), + Color(0XFF4F16A0), + ], + ), + MySpaces.s6.gapWidth, + AppBarAddWidget( + onTap: () {}, + icon: MyAssets.iconFlash, + number: 54, + gradientColors: [ + Color(0XFFEFB345), + Color(0XFF4F16A0), + ], + ), + Spacer(), + AppBarAction( + icon: MyAssets.iconShare, + onTap: () {}, + ), + MySpaces.s12.gapWidth, + AppBarAction( + icon: MyAssets.iconSetting, + onTap: () {}, + ), + ], + ), + ); + } + + @override + Size get preferredSize => Size.fromHeight(kToolbarHeight); +} diff --git a/lib/core/widgets/app_bar/styles/app_bar_action.dart b/lib/core/widgets/app_bar/styles/app_bar_action.dart new file mode 100644 index 0000000..77c0b59 --- /dev/null +++ b/lib/core/widgets/app_bar/styles/app_bar_action.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; + +class AppBarAction extends StatelessWidget { + const AppBarAction({super.key, this.icon, this.onTap}); + + final String? icon; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.all(Radius.circular(100)), + child: Ink( + width: MySpaces.s32, + height: MySpaces.s32, + padding: EdgeInsets.all(MySpaces.s6), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: 1, + color: Color(0XFF6D2ADA), + strokeAlign: BorderSide.strokeAlignInside, + ), + gradient: LinearGradient( + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + colors: [Color(0XFF823FEB), Color(0XFF4F09BF)], + ), + ), + child: MyImage(asset: icon ?? ''), + ), + ); + } +} \ No newline at end of file diff --git a/lib/core/widgets/app_bar/styles/app_bar_add_widget.dart b/lib/core/widgets/app_bar/styles/app_bar_add_widget.dart new file mode 100644 index 0000000..092de31 --- /dev/null +++ b/lib/core/widgets/app_bar/styles/app_bar_add_widget.dart @@ -0,0 +1,77 @@ + +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/common_ui/theme/my_theme.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; + +class AppBarAddWidget extends StatelessWidget { + const AppBarAddWidget({ + super.key, + this.icon, + this.number, + this.onTap, + this.gradientColors, + }); + + final String? icon; + final int? number; + final VoidCallback? onTap; + final List? gradientColors; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.all(Radius.circular(100)), + child: Ink( + width: 96, + height: 32, + padding: EdgeInsets.all(1), + decoration: ShapeDecoration( + shape: StadiumBorder(), + gradient: LinearGradient( + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + colors: gradientColors ?? [], + ), + ), + child: Ink( + padding: EdgeInsetsDirectional.only( + start: MySpaces.s8, + end: MySpaces.s4, + ), + decoration: ShapeDecoration( + shape: StadiumBorder(), + color: context.backgroundColor, + ), + child: Row( + children: [ + MyImage(asset: icon ?? ''), + Expanded( + child: Text( + '$number', + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: Lexend.bold.copyWith(fontSize: 12), + ), + ), + Container( + width: 23, + height: 23, + padding: EdgeInsets.all(MySpaces.s6), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Color(0XFF4F09BF), + ), + child: MyImage(asset: MyAssets.iconPlus), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/core/widgets/bottom_nav_bar/bottom_nav_bar.dart b/lib/core/widgets/bottom_nav_bar/bottom_nav_bar.dart new file mode 100644 index 0000000..307782f --- /dev/null +++ b/lib/core/widgets/bottom_nav_bar/bottom_nav_bar.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; +import 'package:get/get_state_manager/src/simple/get_view.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_item.dart'; +import 'package:shia_game_flutter/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_profile_item.dart'; +import 'package:shia_game_flutter/features/master/presentation/controller/master_controller.dart'; + +class BottomNavBar extends GetView { + const BottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + clipBehavior: Clip.none, + padding: EdgeInsets.symmetric( + horizontal: MySpaces.s30 + ), + decoration: BoxDecoration( + gradient: RadialGradient( + radius: 2.5, + colors: [Color(0XFF4F09BF), Color(0XFF250459)], + ), + ), + child: Obx( + () => BottomNavigationBar( + onTap: (int index) => controller.onChangeBottomNavBar(index), + currentIndex: controller.selectedIndex.value, + backgroundColor: Colors.transparent, + elevation: 0, + type: BottomNavigationBarType.fixed, + unselectedFontSize: 8, + selectedFontSize: 8, + unselectedLabelStyle: Lexend.bold, + selectedLabelStyle: Lexend.bold, + showSelectedLabels: false, + showUnselectedLabels: false, + items: List.generate( + controller.bottomNavList.length, + (index) => index == 3 + ? _bottomNavBarProfileItem(index) + : _bottomNavBarItem(index), + ), + ), + ), + ); + } + + BottomNavigationBarItem _bottomNavBarItem(int index) { + return BottomNavigationBarItem( + icon: Opacity( + opacity: 0.7, + child: BottomNavBarItem( + bottomNavEntity: controller.bottomNavList[index], + ), + ), + activeIcon: BottomNavBarItem( + bottomNavEntity: controller.bottomNavList[index], + ), + label: controller.bottomNavList[index].title, + tooltip: controller.bottomNavList[index].title, + ); + } + + BottomNavigationBarItem _bottomNavBarProfileItem(int index) { + return BottomNavigationBarItem( + icon: Opacity( + opacity: 0.7, + child: BottomNavBarProfileItem( + bottomNavEntity: controller.bottomNavList[index], + ), + ), + activeIcon: BottomNavBarProfileItem( + bottomNavEntity: controller.bottomNavList[index], + ), + label: controller.bottomNavList[index].title, + tooltip: controller.bottomNavList[index].title, + ); + } +} diff --git a/lib/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_item.dart b/lib/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_item.dart new file mode 100644 index 0000000..5ffb738 --- /dev/null +++ b/lib/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_item.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/core/utils/gap.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/bottom_nav_entity.dart'; + +class BottomNavBarItem extends StatelessWidget { + const BottomNavBarItem({super.key, required this.bottomNavEntity}); + + final BottomNavEntity bottomNavEntity; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MyImage(asset: bottomNavEntity.icon ?? ''), + MySpaces.s4.gapHeight, + Text( + bottomNavEntity.title ?? '', + style: Lexend.bold.copyWith(fontSize: MySpaces.s8), + ), + ], + ); + } +} diff --git a/lib/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_profile_item.dart b/lib/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_profile_item.dart new file mode 100644 index 0000000..fe3f58f --- /dev/null +++ b/lib/core/widgets/bottom_nav_bar/styles/bottom_nav_bar_profile_item.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/core/utils/gap.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/bottom_nav_entity.dart'; + +class BottomNavBarProfileItem extends StatelessWidget { + const BottomNavBarProfileItem({super.key, required this.bottomNavEntity}); + + final BottomNavEntity bottomNavEntity; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + height: 26, + width: 26, + padding: EdgeInsets.all(3), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: 1, + color: Color(0XFFF4EEFF), + ) + ), + child: MyImage(asset: bottomNavEntity.icon ?? ''), + ), + MySpaces.s4.gapHeight, + Text( + bottomNavEntity.title ?? '', + style: Lexend.bold.copyWith(fontSize: MySpaces.s8), + ), + ], + ); + } +} diff --git a/lib/core/widgets/container/gradient_container.dart b/lib/core/widgets/container/gradient_container.dart new file mode 100644 index 0000000..f7a41d8 --- /dev/null +++ b/lib/core/widgets/container/gradient_container.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +class GradientContainer extends StatelessWidget { + const GradientContainer({ + super.key, + this.width, + this.height, + this.margin, + this.padding, + this.shapeBorder, + this.boxShape, + this.borderGradient, + this.borderRadius, + this.boxShadow, + this.color, + this.child, + this.image, + this.gradient, + this.borderColor, + }); + + final double? width; + final double? height; + final EdgeInsetsGeometry? margin; + final EdgeInsetsGeometry? padding; + final ShapeBorder? shapeBorder; + final BoxShape? boxShape; + final Gradient? borderGradient; + final Gradient? gradient; + final BorderRadiusGeometry? borderRadius; + final List? boxShadow; + final Color? color; + final Color? borderColor; + final Widget? child; + final DecorationImage? image; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(1), + width: width, + height: height, + margin: margin, + decoration: shapeBorder == null + ? BoxDecoration( + shape: boxShape ?? BoxShape.rectangle, + gradient: borderGradient, + borderRadius: borderRadius, + boxShadow: boxShadow, + color: borderColor, + ) + : ShapeDecoration( + shape: shapeBorder!, + gradient: borderGradient, + shadows: boxShadow, + color: borderColor, + ), + child: Container( + padding: padding, + decoration: shapeBorder == null + ? BoxDecoration( + shape: boxShape ?? BoxShape.rectangle, + gradient: gradient, + borderRadius: borderRadius, + color: color, + image: image, + ) + : ShapeDecoration( + shape: shapeBorder!, + gradient: gradient, + color: color, + image: image, + ), + child: child, + ), + ); + } +} diff --git a/lib/features/awards/data/datasource/awards_datasource.dart b/lib/features/awards/data/datasource/awards_datasource.dart new file mode 100644 index 0000000..33ebc37 --- /dev/null +++ b/lib/features/awards/data/datasource/awards_datasource.dart @@ -0,0 +1,28 @@ +import 'package:shia_game_flutter/core/constants/my_api.dart'; +import 'package:shia_game_flutter/core/network/http_request.dart'; +import 'package:shia_game_flutter/core/params/awards_params.dart'; +import 'package:shia_game_flutter/core/response/base_response.dart'; +import 'package:shia_game_flutter/features/awards/data/model/awards_model.dart'; +import 'package:shia_game_flutter/features/awards/domain/entity/awards_entity.dart'; + +abstract class IAwardsDatasource { + Future getData({required AwardsParams params}); +} + +class AwardsDatasourceImpl implements IAwardsDatasource { + final IHttpRequest httpRequest; + + const AwardsDatasourceImpl(this.httpRequest); + + @override + Future getData({required AwardsParams params}) async { + final response = await httpRequest.get( + path: MyApi.baseUrl, + ); + + return BaseResponse.getData( + response?['data'], + (json) => AwardsModel.fromJson(json), + ); + } +} diff --git a/lib/features/awards/data/model/awards_model.dart b/lib/features/awards/data/model/awards_model.dart new file mode 100644 index 0000000..5002ffc --- /dev/null +++ b/lib/features/awards/data/model/awards_model.dart @@ -0,0 +1,13 @@ +import 'package:shia_game_flutter/features/awards/domain/entity/awards_entity.dart'; + +class AwardsModel extends AwardsEntity { + const AwardsModel({ + super.id, + }); + + factory AwardsModel.fromJson(Map json) { + return AwardsModel( + id: json['id'], + ); + } +} diff --git a/lib/features/awards/data/repository_impl/awards_repository_impl.dart b/lib/features/awards/data/repository_impl/awards_repository_impl.dart new file mode 100644 index 0000000..91f3ab9 --- /dev/null +++ b/lib/features/awards/data/repository_impl/awards_repository_impl.dart @@ -0,0 +1,29 @@ +import 'package:flutter/foundation.dart'; +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/awards_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/awards/data/datasource/awards_datasource.dart'; +import 'package:shia_game_flutter/features/awards/domain/entity/awards_entity.dart'; +import 'package:shia_game_flutter/features/awards/domain/repository/awards_repository.dart'; + +class AwardsRepositoryImpl implements IAwardsRepository { + final IAwardsDatasource datasource; + + const AwardsRepositoryImpl(this.datasource); + + @override + Future> getData({required AwardsParams params}) async { + try { + final AwardsEntity 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/awards/domain/entity/awards_entity.dart b/lib/features/awards/domain/entity/awards_entity.dart new file mode 100644 index 0000000..edfb882 --- /dev/null +++ b/lib/features/awards/domain/entity/awards_entity.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class AwardsEntity extends Equatable { + final int? id; + + const AwardsEntity({ + this.id, + }); + + @override + List get props => [ + id, + ]; +} diff --git a/lib/features/awards/domain/repository/awards_repository.dart b/lib/features/awards/domain/repository/awards_repository.dart new file mode 100644 index 0000000..4a603d1 --- /dev/null +++ b/lib/features/awards/domain/repository/awards_repository.dart @@ -0,0 +1,8 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/awards_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/awards/domain/entity/awards_entity.dart'; + +abstract class IAwardsRepository { + Future> getData({required AwardsParams params}); +} diff --git a/lib/features/awards/domain/usecases/get_awards_usecase.dart b/lib/features/awards/domain/usecases/get_awards_usecase.dart new file mode 100644 index 0000000..16fdae5 --- /dev/null +++ b/lib/features/awards/domain/usecases/get_awards_usecase.dart @@ -0,0 +1,19 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/awards_params.dart'; +import 'package:shia_game_flutter/core/usecase/usecase.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/awards/domain/entity/awards_entity.dart'; +import 'package:shia_game_flutter/features/awards/domain/repository/awards_repository.dart'; + +class GetAwardsUseCase implements UseCase { + final IAwardsRepository repository; + + const GetAwardsUseCase(this.repository); + + @override + Future> call(AwardsParams params) { + return repository.getData(params: params); + } +} + + diff --git a/lib/features/awards/presentation/binding/awards_binding.dart b/lib/features/awards/presentation/binding/awards_binding.dart new file mode 100644 index 0000000..5609f9c --- /dev/null +++ b/lib/features/awards/presentation/binding/awards_binding.dart @@ -0,0 +1,20 @@ +import 'package:shia_game_flutter/features/awards/presentation/controller/awards_controller.dart'; +import 'package:get/get.dart'; + +class AwardsBinding extends Bindings { + @override + void dependencies() { + Get.put(AwardsController(Get.find()), permanent: true); + } + + Future deleteBindings() async { + await Future.wait([ + Get.delete(), + ]); + } + + Future refreshBinding() async { + await deleteBindings(); + dependencies(); + } +} diff --git a/lib/features/awards/presentation/controller/awards_controller.dart b/lib/features/awards/presentation/controller/awards_controller.dart new file mode 100644 index 0000000..e1b0bb4 --- /dev/null +++ b/lib/features/awards/presentation/controller/awards_controller.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:shia_game_flutter/core/params/awards_params.dart'; +import 'package:shia_game_flutter/core/status/base_status.dart'; +import 'package:shia_game_flutter/features/awards/domain/entity/awards_entity.dart'; +import 'package:shia_game_flutter/features/awards/domain/usecases/get_awards_usecase.dart'; +import 'package:get/get.dart'; + +class AwardsController extends GetxController with StateMixin { + /// ----- Constructor ----- + AwardsController(this.getAwardsUseCase); + + @override + void onInit() { + super.onInit(); + change('', status: RxStatus.success()); + } + + @override + void onClose() { + textEditingController.dispose(); + super.onClose(); + } + + /// ----- UseCases ----- + final GetAwardsUseCase getAwardsUseCase; + + /// ----- Variables ----- + final Rx awardsParams = Rx(AwardsParams()); + final Rx awardsEntity = Rx(const AwardsEntity()); + + /// ------ Controllers ------ + final TextEditingController textEditingController = TextEditingController(); + + /// ------ Statuses ------ + final Rx getAwardsStatus = Rx(const BaseInit()); + + /// ------ Functions ------ + + /// ------ Api Calls ------ + Future getAwards() async { + change('', status: RxStatus.loading()); + await getAwardsUseCase(awardsParams.value).then( + (value) => value.fold( + (data) { + awardsEntity.value = data; + change('', status: RxStatus.success()); + }, + (error) { + change('', status: RxStatus.error(error.errorMessage)); + }, + ), + ); + } +} diff --git a/lib/features/awards/presentation/ui/awards_page.dart b/lib/features/awards/presentation/ui/awards_page.dart new file mode 100644 index 0000000..f7d27b5 --- /dev/null +++ b/lib/features/awards/presentation/ui/awards_page.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/features/awards/presentation/controller/awards_controller.dart'; +import 'package:get/get.dart'; + +class AwardsPage extends GetView { + const AwardsPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text('Awards Page'), + ); + } +} diff --git a/lib/features/home/data/datasource/home_datasource.dart b/lib/features/home/data/datasource/home_datasource.dart new file mode 100644 index 0000000..5cb70c4 --- /dev/null +++ b/lib/features/home/data/datasource/home_datasource.dart @@ -0,0 +1,28 @@ +import 'package:shia_game_flutter/core/constants/my_api.dart'; +import 'package:shia_game_flutter/core/network/http_request.dart'; +import 'package:shia_game_flutter/core/params/home_params.dart'; +import 'package:shia_game_flutter/core/response/base_response.dart'; +import 'package:shia_game_flutter/features/home/data/model/home_model.dart'; +import 'package:shia_game_flutter/features/home/domain/entity/home_entity.dart'; + +abstract class IHomeDatasource { + Future getData({required HomeParams params}); +} + +class HomeDatasourceImpl implements IHomeDatasource { + final IHttpRequest httpRequest; + + const HomeDatasourceImpl(this.httpRequest); + + @override + Future getData({required HomeParams params}) async { + final response = await httpRequest.get( + path: MyApi.baseUrl, + ); + + return BaseResponse.getData( + response?['data'], + (json) => HomeModel.fromJson(json), + ); + } +} diff --git a/lib/features/home/data/model/home_model.dart b/lib/features/home/data/model/home_model.dart new file mode 100644 index 0000000..b1d2b84 --- /dev/null +++ b/lib/features/home/data/model/home_model.dart @@ -0,0 +1,13 @@ +import 'package:shia_game_flutter/features/home/domain/entity/home_entity.dart'; + +class HomeModel extends HomeEntity { + const HomeModel({ + super.id, + }); + + factory HomeModel.fromJson(Map json) { + return HomeModel( + id: json['id'], + ); + } +} diff --git a/lib/features/home/data/repository_impl/home_repository_impl.dart b/lib/features/home/data/repository_impl/home_repository_impl.dart new file mode 100644 index 0000000..203d244 --- /dev/null +++ b/lib/features/home/data/repository_impl/home_repository_impl.dart @@ -0,0 +1,29 @@ +import 'package:flutter/foundation.dart'; +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/home_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/home/data/datasource/home_datasource.dart'; +import 'package:shia_game_flutter/features/home/domain/entity/home_entity.dart'; +import 'package:shia_game_flutter/features/home/domain/repository/home_repository.dart'; + +class HomeRepositoryImpl implements IHomeRepository { + final IHomeDatasource datasource; + + const HomeRepositoryImpl(this.datasource); + + @override + Future> getData({required HomeParams params}) async { + try { + final HomeEntity 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/home/domain/entity/home_entity.dart b/lib/features/home/domain/entity/home_entity.dart new file mode 100644 index 0000000..582723c --- /dev/null +++ b/lib/features/home/domain/entity/home_entity.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class HomeEntity extends Equatable { + final int? id; + + const HomeEntity({ + this.id, + }); + + @override + List get props => [ + id, + ]; +} diff --git a/lib/features/home/domain/repository/home_repository.dart b/lib/features/home/domain/repository/home_repository.dart new file mode 100644 index 0000000..71709c3 --- /dev/null +++ b/lib/features/home/domain/repository/home_repository.dart @@ -0,0 +1,8 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/home_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/home/domain/entity/home_entity.dart'; + +abstract class IHomeRepository { + Future> getData({required HomeParams params}); +} diff --git a/lib/features/home/domain/usecases/get_home_usecase.dart b/lib/features/home/domain/usecases/get_home_usecase.dart new file mode 100644 index 0000000..dc2ad83 --- /dev/null +++ b/lib/features/home/domain/usecases/get_home_usecase.dart @@ -0,0 +1,17 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/home_params.dart'; +import 'package:shia_game_flutter/core/usecase/usecase.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/home/domain/entity/home_entity.dart'; +import 'package:shia_game_flutter/features/home/domain/repository/home_repository.dart'; + +class GetHomeUseCase implements UseCase { + final IHomeRepository repository; + + const GetHomeUseCase(this.repository); + + @override + Future> call(HomeParams params) { + return repository.getData(params: params); + } +} diff --git a/lib/features/home/presentation/binding/home_binding.dart b/lib/features/home/presentation/binding/home_binding.dart new file mode 100644 index 0000000..51d91de --- /dev/null +++ b/lib/features/home/presentation/binding/home_binding.dart @@ -0,0 +1,9 @@ +import 'package:shia_game_flutter/features/home/presentation/controller/home_controller.dart'; +import 'package:get/get.dart'; + +class HomeBinding extends Bindings { + @override + void dependencies() { + Get.put(HomeController(Get.find()), permanent: true); + } +} diff --git a/lib/features/home/presentation/controller/home_controller.dart b/lib/features/home/presentation/controller/home_controller.dart new file mode 100644 index 0000000..ce21b15 --- /dev/null +++ b/lib/features/home/presentation/controller/home_controller.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:shia_game_flutter/core/params/home_params.dart'; +import 'package:shia_game_flutter/core/status/base_status.dart'; +import 'package:shia_game_flutter/features/home/domain/entity/home_entity.dart'; +import 'package:shia_game_flutter/features/home/domain/usecases/get_home_usecase.dart'; + +class HomeController extends GetxController with StateMixin { + /// ----- Constructor ----- + HomeController(this.getHomeUseCase); + + @override + void onInit() { + super.onInit(); + change('', status: RxStatus.success()); + } + + @override + void onClose() { + textEditingController.dispose(); + super.onClose(); + } + + /// ----- UseCases ----- + final GetHomeUseCase getHomeUseCase; + + /// ----- Variables ----- + final Rx homeParams = Rx(HomeParams()); + final Rx homeEntity = Rx(const HomeEntity()); + + /// ------ Controllers ------ + final TextEditingController textEditingController = TextEditingController(); + + /// ------ Statuses ------ + final Rx getHomeStatus = Rx(const BaseInit()); + + /// ------ Functions ------ + + /// ------ Api Calls ------ + Future getHome() async { + change('', status: RxStatus.loading()); + await getHomeUseCase(homeParams.value).then( + (value) => value.fold( + (data) { + homeEntity.value = data; + change('', status: RxStatus.success()); + }, + (error) { + change('', status: RxStatus.error(error.errorMessage)); + }, + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/home_page.dart b/lib/features/home/presentation/pages/home_page.dart new file mode 100644 index 0000000..fc404e1 --- /dev/null +++ b/lib/features/home/presentation/pages/home_page.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart' hide BoxShadow, BoxDecoration; +import 'package:get/get.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/core/utils/gap.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; +import 'package:shia_game_flutter/features/home/presentation/controller/home_controller.dart'; +import 'package:shia_game_flutter/features/home/presentation/pages/widgets/home_battle_cast.dart'; +import 'package:shia_game_flutter/features/home/presentation/pages/widgets/home_battle_league.dart'; +import 'package:shia_game_flutter/features/home/presentation/pages/widgets/home_custom_widget.dart'; +import 'package:shia_game_flutter/features/home/presentation/pages/widgets/home_membership.dart'; + +class HomePage extends GetView { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: MySpaces.s32), + child: Column( + children: [ + MySpaces.s28.gapHeight, + MyImage(asset: MyAssets.shiaMindGroup), + MySpaces.s40.gapHeight, + HomeMembership(), + HomeBattleLeague(), + MySpaces.s20.gapHeight, + _customWidgets(), + MySpaces.s20.gapHeight, + HomeBattleCast(), + ], + ), + ); + } + + Widget _customWidgets() { + return Row( + spacing: MySpaces.s22, + children: [ + Expanded( + child: HomeCustomWidget( + type: CustomWidgetType.customLeague, + firstText: 'ongoing:', + secondText: + 'Saba Center League - Imam Hassan ministry London school', + ), + ), + Expanded( + child: HomeCustomWidget( + type: CustomWidgetType.friendBattle, + firstText: 'online:', + secondText: 'No one\'s online', + ), + ), + ], + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/home_battle_cast.dart b/lib/features/home/presentation/pages/widgets/home_battle_cast.dart new file mode 100644 index 0000000..2cd7d8f --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/home_battle_cast.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart' hide BoxDecoration, BoxShadow; +import 'package:flutter_inset_box_shadow_update/flutter_inset_box_shadow_update.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/core/utils/gap.dart'; +import 'package:shia_game_flutter/core/widgets/container/gradient_container.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; + +class HomeBattleCast extends StatelessWidget { + const HomeBattleCast({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + GradientContainer( + height: 120, + borderRadius: BorderRadiusDirectional.all(Radius.circular(MySpaces.s20)), + borderGradient: LinearGradient( + begin: AlignmentDirectional.topCenter, + end: AlignmentDirectional.bottomCenter, + colors: [Color(0XFF567EFF), Color(0XFF304DB8)], + ), + gradient: RadialGradient( + radius: 1, + center: Alignment(-0.5, -1), + colors: [Color(0XFF104CBA).withValues(alpha: 0.2), Color(0XFF104CBA)], + ), + padding: EdgeInsets.all(MySpaces.s10), + image: DecorationImage( + image: AssetImage(MyAssets.addBackground), + repeat: ImageRepeat.repeat, + colorFilter: ColorFilter.mode( + Colors.black.withValues(alpha: 0.02), + BlendMode.srcIn, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ShaderMask( + blendMode: BlendMode.modulate, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFFFFFFFF), Color(0XFFE4E3FF)], + ).createShader(bounds), + child: Text( + 'Battle Cast', + style: Lexend.extraBold.copyWith( + fontSize: 22, + shadows: [ + BoxShadow( + color: Color(0xFF3C38C4), + blurRadius: 0.52, + offset: Offset(0, 1.04), + spreadRadius: 0, + ), + ], + ), + ), + ), + MySpaces.s10.gapHeight, + Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + color: Color(0XFF203689), + boxShadow: [ + BoxShadow( + color: Color(0XFF000000).withValues(alpha: 0.3), + blurRadius: 3, + offset: Offset(0, 2), + inset: true, + ), + ], + ), + child: Text( + 'Sheikh.Sadra VS Ali Masoudi (online) - Sheikh.Sadra number tow VS Ali Masoudi tabar (tomorrow)', + maxLines: 1, + style: Lexend.semiBold.copyWith( + fontSize: 8, + color: Color(0XFFB6B3FF), + ), + ), + ), + ], + ), + ), + PositionedDirectional( + start: -1, + top: -MySpaces.s4, + child: GradientContainer( + color: Color(0XFFCC9B22), + padding: EdgeInsets.symmetric( + vertical: MySpaces.s2, + horizontal: MySpaces.s8, + ), + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6), + topEnd: Radius.circular(6), + bottomStart: Radius.circular(0), + bottomEnd: Radius.circular(6), + ), + child: Text( + '2 New', + style: Lexend.extraBold.copyWith( + fontSize: 11, + ), + ), + ), + ), + PositionedDirectional( + end: MySpaces.s20, + top: -MySpaces.s10, + child: Row( + children: [ + MyImage( + asset: MyAssets.mic, + ), + MyImage( + asset: MyAssets.micBLur, + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/home_battle_league.dart b/lib/features/home/presentation/pages/widgets/home_battle_league.dart new file mode 100644 index 0000000..aac63c7 --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/home_battle_league.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/core/utils/screen_size.dart'; +import 'package:shia_game_flutter/core/widgets/container/gradient_container.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; + +class HomeBattleLeague extends StatelessWidget { + const HomeBattleLeague({super.key}); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + GradientContainer( + width: context.widthScreen, + height: 120, + padding: EdgeInsets.symmetric( + horizontal: MySpaces.s12, + vertical: MySpaces.s16, + ), + borderGradient: LinearGradient( + begin: AlignmentDirectional.centerStart, + end: AlignmentDirectional.centerEnd, + colors: [Color(0XFF3A0A85), Color(0XFF6C2ECD)], + ), + gradient: LinearGradient( + begin: AlignmentDirectional.centerStart, + end: AlignmentDirectional.centerEnd, + colors: [Color(0XFF3A0D83), Color(0XFF4F09BF)], + ), + borderRadius: BorderRadius.all(Radius.circular(20)), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShaderMask( + blendMode: BlendMode.modulate, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFFFFFFFF), Color(0XFFCAA8FF)], + ).createShader(bounds), + child: Text( + 'Battle League', + style: Lexend.extraBold.copyWith( + fontSize: 22, + shadows: [ + BoxShadow( + color: Color(0xFF3E1381), + blurRadius: 0.84, + offset: Offset(0, 1.69), + spreadRadius: 0, + ), + ], + ), + ), + ), + Text( + 'The faster you answer, the winner', + style: Lexend.medium.copyWith( + fontSize: 10, + color: Color(0XFFA183D2), + ), + ), + ], + ), + ), + MyImage(asset: MyAssets.cup), + ], + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/home_custom_widget.dart b/lib/features/home/presentation/pages/widgets/home_custom_widget.dart new file mode 100644 index 0000000..dc8853e --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/home_custom_widget.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart' hide BoxDecoration, BoxShadow; +import 'package:flutter_inset_box_shadow_update/flutter_inset_box_shadow_update.dart'; +import 'package:get/get.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/core/utils/gap.dart'; +import 'package:shia_game_flutter/core/utils/my_localization.dart'; +import 'package:shia_game_flutter/core/utils/screen_size.dart'; +import 'package:shia_game_flutter/core/widgets/container/gradient_container.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; + +enum CustomWidgetType { + customLeague, + friendBattle; + + static Map get borderGradient => { + CustomWidgetType.customLeague: LinearGradient( + begin: AlignmentDirectional.topCenter, + end: AlignmentDirectional.bottomCenter, + colors: [Color(0XFF4BAD42), Color(0XFF147743)], + ), + CustomWidgetType.friendBattle: LinearGradient( + begin: AlignmentDirectional.topCenter, + end: AlignmentDirectional.bottomCenter, + colors: [Color(0XFFED9851), Color(0XFFC77041)], + ), + }; + + static Map get gradient => { + CustomWidgetType.customLeague: RadialGradient( + radius: 0.7, + center: Alignment(-0.6, -0.8), + colors: [Color(0XFF58AE23), Color(0XFF066A36)], + ), + CustomWidgetType.friendBattle: RadialGradient( + radius: 0.7, + center: Alignment(-0.6, -0.8), + colors: [Color(0XFFE99E53), Color(0XFFBC673A)], + ), + }; + + static Map get image => { + CustomWidgetType.customLeague: MyAssets.medal, + CustomWidgetType.friendBattle: MyAssets.friendBattle, + }; + + static Map get containerColor => { + CustomWidgetType.customLeague: Color(0XFF05542B), + CustomWidgetType.friendBattle: Color(0XFFA45A31), + }; + + static Map get firstTextColor => { + CustomWidgetType.customLeague: Color(0XFF4FDF94), + CustomWidgetType.friendBattle: Color(0XFFE3DFD5), + }; + + static Map get secondTextColor => { + CustomWidgetType.customLeague: Color(0XFF85C9A6), + CustomWidgetType.friendBattle: Color(0XFFDFBC9D), + }; + + static Map get title => { + CustomWidgetType.customLeague: ShaderMask( + blendMode: BlendMode.modulate, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFFFFFFFF), Color(0XFFFFFFFF), Color(0XFFBEF8DA)], + ).createShader(bounds), + child: Text( + Get.context?.translate.custom_league ?? '', + style: Lexend.extraBold.copyWith( + fontSize: 14, + shadows: [ + BoxShadow( + color: Color(0xFF07592F), + blurRadius: 0.52, + offset: Offset(0, 1.04), + spreadRadius: 0, + ), + ], + ), + ), + ), + CustomWidgetType.friendBattle: ShaderMask( + blendMode: BlendMode.modulate, + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFFFFFFFF), Color(0XFFFFFFFF), Color(0XFFFFB994)], + ).createShader(bounds), + child: Text( + Get.context?.translate.friends_battle ?? '', + style: Lexend.extraBold.copyWith( + fontSize: 14, + shadows: [ + BoxShadow( + color: Color(0xFFAA5B31), + blurRadius: 0.52, + offset: Offset(0, 1.04), + spreadRadius: 0, + ), + ], + ), + ), + ), + }; +} + +class HomeCustomWidget extends StatelessWidget { + const HomeCustomWidget({ + super.key, + this.type = CustomWidgetType.customLeague, + this.firstText, + this.secondText, + }); + + final CustomWidgetType type; + final String? firstText; + final String? secondText; + + @override + Widget build(BuildContext context) { + return GradientContainer( + height: 130, + borderRadius: BorderRadius.all(Radius.circular(20)), + borderGradient: CustomWidgetType.borderGradient[type], + gradient: CustomWidgetType.gradient[type], + image: DecorationImage( + image: AssetImage(MyAssets.addBackground), + repeat: ImageRepeat.repeat, + colorFilter: ColorFilter.mode( + Colors.black.withValues(alpha: 0.02), + BlendMode.srcIn, + ), + ), + padding: EdgeInsets.all(MySpaces.s10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: AlignmentDirectional.centerEnd, + child: MyImage(asset: CustomWidgetType.image[type] ?? '',), + ), + Spacer(), + CustomWidgetType.title[type] ?? SizedBox.shrink(), + MySpaces.s10.gapHeight, + Container( + padding: EdgeInsets.all(5), + width: context.widthScreen, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + color: CustomWidgetType.containerColor[type], + boxShadow: [ + BoxShadow( + color: Color(0XFF000000).withValues(alpha: 0.3), + blurRadius: 3, + offset: Offset(0, 2), + inset: true, + ), + ], + ), + child: Text.rich( + maxLines: 1, + overflow: TextOverflow.ellipsis, + TextSpan( + children: [ + TextSpan( + text: firstText, + style: Lexend.semiBold.copyWith( + fontSize: 8, + color: CustomWidgetType.firstTextColor[type], + ), + ), + TextSpan( + text: ' $secondText', + style: Lexend.semiBold.copyWith( + fontSize: 8, + color: CustomWidgetType.secondTextColor[type], + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/home/presentation/pages/widgets/home_membership.dart b/lib/features/home/presentation/pages/widgets/home_membership.dart new file mode 100644 index 0000000..a575d0a --- /dev/null +++ b/lib/features/home/presentation/pages/widgets/home_membership.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_spaces.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; +import 'package:shia_game_flutter/common_ui/theme/my_theme.dart'; +import 'package:shia_game_flutter/core/utils/gap.dart'; +import 'package:shia_game_flutter/core/utils/my_localization.dart'; +import 'package:shia_game_flutter/core/utils/screen_size.dart'; +import 'package:shia_game_flutter/core/widgets/container/gradient_container.dart'; +import 'package:shia_game_flutter/core/widgets/image/my_image.dart'; + +class HomeMembership extends StatelessWidget { + const HomeMembership({super.key}); + + @override + Widget build(BuildContext context) { + return GradientContainer( + width: context.widthScreen, + color: context.backgroundColor, + borderGradient: LinearGradient( + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + colors: [ + Color(0XFFDE8B4D).withValues(alpha: 0.3), + Color(0XFFDE8B4D).withValues(alpha: 0.05), + ], + ), + borderRadius: BorderRadius.all(Radius.circular(MySpaces.s20)), + boxShadow: [ + BoxShadow( + color: Color(0x3F000000), + blurRadius: 17, + offset: Offset(0, 4), + spreadRadius: 20, + ), + ], + padding: EdgeInsets.symmetric(horizontal: MySpaces.s16, vertical: 13), + child: Row( + children: [ + MyImage(asset: MyAssets.iconCrown), + MySpaces.s8.gapWidth, + Text( + context.translate.pro_membership, + style: Lexend.semiBold.copyWith( + fontSize: 12, + color: Color(0XFFFCC230), + ), + ), + Spacer(), + MyImage(asset: MyAssets.iconClock), + MySpaces.s6.gapWidth, + Text('125d 4h', style: Lexend.semiBold.copyWith(fontSize: 12)), + ], + ), + ); + } +} diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index c965cea..175925c 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -16,7 +16,7 @@ class IntroPage extends GetView { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: context.introBackgroundColor, + backgroundColor: context.backgroundColor, body: SafeArea( child: SizedBox.expand( child: Stack( diff --git a/lib/features/master/data/datasource/master_datasource.dart b/lib/features/master/data/datasource/master_datasource.dart new file mode 100644 index 0000000..b7d83ac --- /dev/null +++ b/lib/features/master/data/datasource/master_datasource.dart @@ -0,0 +1,28 @@ +import 'package:shia_game_flutter/core/constants/my_api.dart'; +import 'package:shia_game_flutter/core/network/http_request.dart'; +import 'package:shia_game_flutter/core/params/master_params.dart'; +import 'package:shia_game_flutter/core/response/base_response.dart'; +import 'package:shia_game_flutter/features/master/data/model/master_model.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/master_entity.dart'; + +abstract class IMasterDatasource { + Future getData({required MasterParams params}); +} + +class MasterDatasourceImpl implements IMasterDatasource { + final IHttpRequest httpRequest; + + const MasterDatasourceImpl(this.httpRequest); + + @override + Future getData({required MasterParams params}) async { + final response = await httpRequest.get( + path: MyApi.baseUrl, + ); + + return BaseResponse.getData( + response?['data'], + (json) => MasterModel.fromJson(json), + ); + } +} diff --git a/lib/features/master/data/model/master_model.dart b/lib/features/master/data/model/master_model.dart new file mode 100644 index 0000000..9fe433e --- /dev/null +++ b/lib/features/master/data/model/master_model.dart @@ -0,0 +1,13 @@ +import 'package:shia_game_flutter/features/master/domain/entity/master_entity.dart'; + +class MasterModel extends MasterEntity { + const MasterModel({ + super.id, + }); + + factory MasterModel.fromJson(Map json) { + return MasterModel( + id: json['id'], + ); + } +} diff --git a/lib/features/master/data/repository_impl/master_repository_impl.dart b/lib/features/master/data/repository_impl/master_repository_impl.dart new file mode 100644 index 0000000..059428f --- /dev/null +++ b/lib/features/master/data/repository_impl/master_repository_impl.dart @@ -0,0 +1,29 @@ +import 'package:flutter/foundation.dart'; +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/master_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/master/data/datasource/master_datasource.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/master_entity.dart'; +import 'package:shia_game_flutter/features/master/domain/repository/master_repository.dart'; + +class MasterRepositoryImpl implements IMasterRepository { + final IMasterDatasource datasource; + + const MasterRepositoryImpl(this.datasource); + + @override + Future> getData({required MasterParams params}) async { + try { + final MasterEntity 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/master/domain/entity/bottom_nav_entity.dart b/lib/features/master/domain/entity/bottom_nav_entity.dart new file mode 100644 index 0000000..76f20b7 --- /dev/null +++ b/lib/features/master/domain/entity/bottom_nav_entity.dart @@ -0,0 +1,12 @@ +import 'package:equatable/equatable.dart'; + +class BottomNavEntity extends Equatable { + final String? icon; + final String? title; + final String? route; + + const BottomNavEntity({this.icon, this.title, this.route}); + + @override + List get props => [icon, title, route]; +} diff --git a/lib/features/master/domain/entity/master_entity.dart b/lib/features/master/domain/entity/master_entity.dart new file mode 100644 index 0000000..b4e7f6b --- /dev/null +++ b/lib/features/master/domain/entity/master_entity.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class MasterEntity extends Equatable { + final int? id; + + const MasterEntity({ + this.id, + }); + + @override + List get props => [ + id, + ]; +} diff --git a/lib/features/master/domain/repository/master_repository.dart b/lib/features/master/domain/repository/master_repository.dart new file mode 100644 index 0000000..ab66cae --- /dev/null +++ b/lib/features/master/domain/repository/master_repository.dart @@ -0,0 +1,8 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/master_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/master_entity.dart'; + +abstract class IMasterRepository { + Future> getData({required MasterParams params}); +} diff --git a/lib/features/master/domain/usecases/get_master_usecase.dart b/lib/features/master/domain/usecases/get_master_usecase.dart new file mode 100644 index 0000000..601780d --- /dev/null +++ b/lib/features/master/domain/usecases/get_master_usecase.dart @@ -0,0 +1,19 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/master_params.dart'; +import 'package:shia_game_flutter/core/usecase/usecase.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/master_entity.dart'; +import 'package:shia_game_flutter/features/master/domain/repository/master_repository.dart'; + +class GetMasterUseCase implements UseCase { + final IMasterRepository repository; + + const GetMasterUseCase(this.repository); + + @override + Future> call(MasterParams params) { + return repository.getData(params: params); + } +} + + diff --git a/lib/features/master/presentation/binding/master_binding.dart b/lib/features/master/presentation/binding/master_binding.dart new file mode 100644 index 0000000..53c1ba8 --- /dev/null +++ b/lib/features/master/presentation/binding/master_binding.dart @@ -0,0 +1,20 @@ +import 'package:shia_game_flutter/features/master/presentation/controller/master_controller.dart'; +import 'package:get/get.dart'; + +class MasterBinding extends Bindings { + @override + void dependencies() { + Get.put(MasterController(Get.find())); + } + + Future deleteBindings() async { + await Future.wait([ + Get.delete(), + ]); + } + + Future refreshBinding() async { + await deleteBindings(); + dependencies(); + } +} diff --git a/lib/features/master/presentation/controller/master_controller.dart b/lib/features/master/presentation/controller/master_controller.dart new file mode 100644 index 0000000..ba31280 --- /dev/null +++ b/lib/features/master/presentation/controller/master_controller.dart @@ -0,0 +1,85 @@ +import 'package:flutter/cupertino.dart'; +import 'package:shia_game_flutter/common_ui/resources/my_assets.dart'; +import 'package:shia_game_flutter/core/params/master_params.dart'; +import 'package:shia_game_flutter/core/routers/my_routes.dart'; +import 'package:shia_game_flutter/core/status/base_status.dart'; +import 'package:shia_game_flutter/core/utils/my_localization.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/bottom_nav_entity.dart'; +import 'package:shia_game_flutter/features/master/domain/entity/master_entity.dart'; +import 'package:shia_game_flutter/features/master/domain/usecases/get_master_usecase.dart'; +import 'package:get/get.dart'; + +class MasterController extends GetxController with StateMixin { + /// ----- Constructor ----- + MasterController(this.getMasterUseCase); + + @override + void onInit() { + super.onInit(); + change('', status: RxStatus.success()); + } + + @override + void onClose() { + textEditingController.dispose(); + super.onClose(); + } + + /// ----- UseCases ----- + final GetMasterUseCase getMasterUseCase; + + /// ----- Variables ----- + final Rx masterParams = Rx(MasterParams()); + final Rx masterEntity = Rx(const MasterEntity()); + final List bottomNavList = [ + BottomNavEntity( + icon: MyAssets.iconHome, + title: Get.context?.translate.home, + route: Routes.homePage, + ), + BottomNavEntity( + icon: MyAssets.iconShop, + title: Get.context?.translate.shop, + route: Routes.shopPage, + ), + BottomNavEntity( + icon: MyAssets.iconAwards, + title: Get.context?.translate.awards, + route: Routes.awardsPage, + ), + BottomNavEntity( + icon: MyAssets.iconProfile, + title: Get.context?.translate.profile, + route: Routes.profilePage, + ), + ]; + final Rx selectedIndex = Rx(0); + + /// ------ Controllers ------ + final TextEditingController textEditingController = TextEditingController(); + + /// ------ Statuses ------ + final Rx getMasterStatus = Rx(const BaseInit()); + + /// ------ Functions ------ + void onChangeBottomNavBar(int index) { + selectedIndex.value = index; + Get.rootDelegate.toNamed(bottomNavList[index].route ?? ''); + } + + /// ------ Api Calls ------ + Future getMaster() async { + change('', status: RxStatus.loading()); + await getMasterUseCase(masterParams.value).then( + (value) => value.fold( + (data) { + masterEntity.value = data; + change('', status: RxStatus.success()); + }, + (error) { + change('', status: RxStatus.error(error.errorMessage)); + }, + ), + ); + } +} diff --git a/lib/features/master/presentation/ui/master_page.dart b/lib/features/master/presentation/ui/master_page.dart new file mode 100644 index 0000000..4866d68 --- /dev/null +++ b/lib/features/master/presentation/ui/master_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/common_ui/theme/my_theme.dart'; +import 'package:shia_game_flutter/core/routers/my_routes.dart'; +import 'package:shia_game_flutter/core/widgets/app_bar/master_app_bar.dart'; +import 'package:shia_game_flutter/core/widgets/bottom_nav_bar/bottom_nav_bar.dart'; +import 'package:shia_game_flutter/features/master/presentation/controller/master_controller.dart'; +import 'package:get/get.dart'; + +class MasterPage extends GetView { + const MasterPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: context.backgroundColor, + appBar: MasterAppBar(), + bottomNavigationBar: BottomNavBar(), + body: GetRouterOutlet( + initialRoute: Routes.homePage, + anchorRoute: Routes.masterPage, + delegate: Get.rootDelegate, + ), + ); + } +} diff --git a/lib/features/profile/data/datasource/profile_datasource.dart b/lib/features/profile/data/datasource/profile_datasource.dart new file mode 100644 index 0000000..00cab8f --- /dev/null +++ b/lib/features/profile/data/datasource/profile_datasource.dart @@ -0,0 +1,28 @@ +import 'package:shia_game_flutter/core/constants/my_api.dart'; +import 'package:shia_game_flutter/core/network/http_request.dart'; +import 'package:shia_game_flutter/core/params/profile_params.dart'; +import 'package:shia_game_flutter/core/response/base_response.dart'; +import 'package:shia_game_flutter/features/profile/data/model/profile_model.dart'; +import 'package:shia_game_flutter/features/profile/domain/entity/profile_entity.dart'; + +abstract class IProfileDatasource { + Future getData({required ProfileParams params}); +} + +class ProfileDatasourceImpl implements IProfileDatasource { + final IHttpRequest httpRequest; + + const ProfileDatasourceImpl(this.httpRequest); + + @override + Future getData({required ProfileParams params}) async { + final response = await httpRequest.get( + path: MyApi.baseUrl, + ); + + return BaseResponse.getData( + response?['data'], + (json) => ProfileModel.fromJson(json), + ); + } +} diff --git a/lib/features/profile/data/model/profile_model.dart b/lib/features/profile/data/model/profile_model.dart new file mode 100644 index 0000000..cc17c4b --- /dev/null +++ b/lib/features/profile/data/model/profile_model.dart @@ -0,0 +1,13 @@ +import 'package:shia_game_flutter/features/profile/domain/entity/profile_entity.dart'; + +class ProfileModel extends ProfileEntity { + const ProfileModel({ + super.id, + }); + + factory ProfileModel.fromJson(Map json) { + return ProfileModel( + id: json['id'], + ); + } +} diff --git a/lib/features/profile/data/repository_impl/profile_repository_impl.dart b/lib/features/profile/data/repository_impl/profile_repository_impl.dart new file mode 100644 index 0000000..bdbcaa1 --- /dev/null +++ b/lib/features/profile/data/repository_impl/profile_repository_impl.dart @@ -0,0 +1,29 @@ +import 'package:flutter/foundation.dart'; +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/profile_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/profile/data/datasource/profile_datasource.dart'; +import 'package:shia_game_flutter/features/profile/domain/entity/profile_entity.dart'; +import 'package:shia_game_flutter/features/profile/domain/repository/profile_repository.dart'; + +class ProfileRepositoryImpl implements IProfileRepository { + final IProfileDatasource datasource; + + const ProfileRepositoryImpl(this.datasource); + + @override + Future> getData({required ProfileParams params}) async { + try { + final ProfileEntity 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/profile/domain/entity/profile_entity.dart b/lib/features/profile/domain/entity/profile_entity.dart new file mode 100644 index 0000000..3d7155b --- /dev/null +++ b/lib/features/profile/domain/entity/profile_entity.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class ProfileEntity extends Equatable { + final int? id; + + const ProfileEntity({ + this.id, + }); + + @override + List get props => [ + id, + ]; +} diff --git a/lib/features/profile/domain/repository/profile_repository.dart b/lib/features/profile/domain/repository/profile_repository.dart new file mode 100644 index 0000000..75a2c82 --- /dev/null +++ b/lib/features/profile/domain/repository/profile_repository.dart @@ -0,0 +1,8 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/profile_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/profile/domain/entity/profile_entity.dart'; + +abstract class IProfileRepository { + Future> getData({required ProfileParams params}); +} diff --git a/lib/features/profile/domain/usecases/get_profile_usecase.dart b/lib/features/profile/domain/usecases/get_profile_usecase.dart new file mode 100644 index 0000000..cb7422a --- /dev/null +++ b/lib/features/profile/domain/usecases/get_profile_usecase.dart @@ -0,0 +1,19 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/profile_params.dart'; +import 'package:shia_game_flutter/core/usecase/usecase.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/profile/domain/entity/profile_entity.dart'; +import 'package:shia_game_flutter/features/profile/domain/repository/profile_repository.dart'; + +class GetProfileUseCase implements UseCase { + final IProfileRepository repository; + + const GetProfileUseCase(this.repository); + + @override + Future> call(ProfileParams params) { + return repository.getData(params: params); + } +} + + diff --git a/lib/features/profile/presentation/binding/profile_binding.dart b/lib/features/profile/presentation/binding/profile_binding.dart new file mode 100644 index 0000000..e10d983 --- /dev/null +++ b/lib/features/profile/presentation/binding/profile_binding.dart @@ -0,0 +1,20 @@ +import 'package:shia_game_flutter/features/profile/presentation/controller/profile_controller.dart'; +import 'package:get/get.dart'; + +class ProfileBinding extends Bindings { + @override + void dependencies() { + Get.put(ProfileController(Get.find()), permanent: true); + } + + Future deleteBindings() async { + await Future.wait([ + Get.delete(), + ]); + } + + Future refreshBinding() async { + await deleteBindings(); + dependencies(); + } +} diff --git a/lib/features/profile/presentation/controller/profile_controller.dart b/lib/features/profile/presentation/controller/profile_controller.dart new file mode 100644 index 0000000..c66df30 --- /dev/null +++ b/lib/features/profile/presentation/controller/profile_controller.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:shia_game_flutter/core/params/profile_params.dart'; +import 'package:shia_game_flutter/core/status/base_status.dart'; +import 'package:shia_game_flutter/features/profile/domain/entity/profile_entity.dart'; +import 'package:shia_game_flutter/features/profile/domain/usecases/get_profile_usecase.dart'; +import 'package:get/get.dart'; + +class ProfileController extends GetxController with StateMixin { + /// ----- Constructor ----- + ProfileController(this.getProfileUseCase); + + @override + void onInit() { + super.onInit(); + change('', status: RxStatus.success()); + } + + @override + void onClose() { + textEditingController.dispose(); + super.onClose(); + } + + /// ----- UseCases ----- + final GetProfileUseCase getProfileUseCase; + + /// ----- Variables ----- + final Rx profileParams = Rx(ProfileParams()); + final Rx profileEntity = Rx(const ProfileEntity()); + + /// ------ Controllers ------ + final TextEditingController textEditingController = TextEditingController(); + + /// ------ Statuses ------ + final Rx getProfileStatus = Rx(const BaseInit()); + + /// ------ Functions ------ + + /// ------ Api Calls ------ + Future getProfile() async { + change('', status: RxStatus.loading()); + await getProfileUseCase(profileParams.value).then( + (value) => value.fold( + (data) { + profileEntity.value = data; + change('', status: RxStatus.success()); + }, + (error) { + change('', status: RxStatus.error(error.errorMessage)); + }, + ), + ); + } +} diff --git a/lib/features/profile/presentation/ui/profile_page.dart b/lib/features/profile/presentation/ui/profile_page.dart new file mode 100644 index 0000000..8a4a3a3 --- /dev/null +++ b/lib/features/profile/presentation/ui/profile_page.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/features/profile/presentation/controller/profile_controller.dart'; +import 'package:get/get.dart'; + +class ProfilePage extends GetView { + const ProfilePage({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text('Profile Page'), + ); + } +} diff --git a/lib/features/shop/data/datasource/shop_datasource.dart b/lib/features/shop/data/datasource/shop_datasource.dart new file mode 100644 index 0000000..b0898d6 --- /dev/null +++ b/lib/features/shop/data/datasource/shop_datasource.dart @@ -0,0 +1,28 @@ +import 'package:shia_game_flutter/core/constants/my_api.dart'; +import 'package:shia_game_flutter/core/network/http_request.dart'; +import 'package:shia_game_flutter/core/params/shop_params.dart'; +import 'package:shia_game_flutter/core/response/base_response.dart'; +import 'package:shia_game_flutter/features/shop/data/model/shop_model.dart'; +import 'package:shia_game_flutter/features/shop/domain/entity/shop_entity.dart'; + +abstract class IShopDatasource { + Future getData({required ShopParams params}); +} + +class ShopDatasourceImpl implements IShopDatasource { + final IHttpRequest httpRequest; + + const ShopDatasourceImpl(this.httpRequest); + + @override + Future getData({required ShopParams params}) async { + final response = await httpRequest.get( + path: MyApi.baseUrl, + ); + + return BaseResponse.getData( + response?['data'], + (json) => ShopModel.fromJson(json), + ); + } +} diff --git a/lib/features/shop/data/model/shop_model.dart b/lib/features/shop/data/model/shop_model.dart new file mode 100644 index 0000000..3f3a933 --- /dev/null +++ b/lib/features/shop/data/model/shop_model.dart @@ -0,0 +1,13 @@ +import 'package:shia_game_flutter/features/shop/domain/entity/shop_entity.dart'; + +class ShopModel extends ShopEntity { + const ShopModel({ + super.id, + }); + + factory ShopModel.fromJson(Map json) { + return ShopModel( + id: json['id'], + ); + } +} diff --git a/lib/features/shop/data/repository_impl/shop_repository_impl.dart b/lib/features/shop/data/repository_impl/shop_repository_impl.dart new file mode 100644 index 0000000..b8d6734 --- /dev/null +++ b/lib/features/shop/data/repository_impl/shop_repository_impl.dart @@ -0,0 +1,29 @@ +import 'package:flutter/foundation.dart'; +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/shop_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/shop/data/datasource/shop_datasource.dart'; +import 'package:shia_game_flutter/features/shop/domain/entity/shop_entity.dart'; +import 'package:shia_game_flutter/features/shop/domain/repository/shop_repository.dart'; + +class ShopRepositoryImpl implements IShopRepository { + final IShopDatasource datasource; + + const ShopRepositoryImpl(this.datasource); + + @override + Future> getData({required ShopParams params}) async { + try { + final ShopEntity 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/shop/domain/entity/shop_entity.dart b/lib/features/shop/domain/entity/shop_entity.dart new file mode 100644 index 0000000..1e453aa --- /dev/null +++ b/lib/features/shop/domain/entity/shop_entity.dart @@ -0,0 +1,14 @@ +import 'package:equatable/equatable.dart'; + +class ShopEntity extends Equatable { + final int? id; + + const ShopEntity({ + this.id, + }); + + @override + List get props => [ + id, + ]; +} diff --git a/lib/features/shop/domain/repository/shop_repository.dart b/lib/features/shop/domain/repository/shop_repository.dart new file mode 100644 index 0000000..68bfb7c --- /dev/null +++ b/lib/features/shop/domain/repository/shop_repository.dart @@ -0,0 +1,8 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/shop_params.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/shop/domain/entity/shop_entity.dart'; + +abstract class IShopRepository { + Future> getData({required ShopParams params}); +} diff --git a/lib/features/shop/domain/usecases/get_shop_usecase.dart b/lib/features/shop/domain/usecases/get_shop_usecase.dart new file mode 100644 index 0000000..7fb6399 --- /dev/null +++ b/lib/features/shop/domain/usecases/get_shop_usecase.dart @@ -0,0 +1,19 @@ +import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; +import 'package:shia_game_flutter/core/params/shop_params.dart'; +import 'package:shia_game_flutter/core/usecase/usecase.dart'; +import 'package:shia_game_flutter/core/utils/data_state.dart'; +import 'package:shia_game_flutter/features/shop/domain/entity/shop_entity.dart'; +import 'package:shia_game_flutter/features/shop/domain/repository/shop_repository.dart'; + +class GetShopUseCase implements UseCase { + final IShopRepository repository; + + const GetShopUseCase(this.repository); + + @override + Future> call(ShopParams params) { + return repository.getData(params: params); + } +} + + diff --git a/lib/features/shop/presentation/binding/shop_binding.dart b/lib/features/shop/presentation/binding/shop_binding.dart new file mode 100644 index 0000000..8044122 --- /dev/null +++ b/lib/features/shop/presentation/binding/shop_binding.dart @@ -0,0 +1,20 @@ +import 'package:shia_game_flutter/features/shop/presentation/controller/shop_controller.dart'; +import 'package:get/get.dart'; + +class ShopBinding extends Bindings { + @override + void dependencies() { + Get.put(ShopController(Get.find()), permanent: true); + } + + Future deleteBindings() async { + await Future.wait([ + Get.delete(), + ]); + } + + Future refreshBinding() async { + await deleteBindings(); + dependencies(); + } +} diff --git a/lib/features/shop/presentation/controller/shop_controller.dart b/lib/features/shop/presentation/controller/shop_controller.dart new file mode 100644 index 0000000..7098604 --- /dev/null +++ b/lib/features/shop/presentation/controller/shop_controller.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:shia_game_flutter/core/params/shop_params.dart'; +import 'package:shia_game_flutter/core/status/base_status.dart'; +import 'package:shia_game_flutter/features/shop/domain/entity/shop_entity.dart'; +import 'package:shia_game_flutter/features/shop/domain/usecases/get_shop_usecase.dart'; +import 'package:get/get.dart'; + +class ShopController extends GetxController with StateMixin { + /// ----- Constructor ----- + ShopController(this.getShopUseCase); + + @override + void onInit() { + super.onInit(); + change('', status: RxStatus.success()); + } + + @override + void onClose() { + textEditingController.dispose(); + super.onClose(); + } + + /// ----- UseCases ----- + final GetShopUseCase getShopUseCase; + + /// ----- Variables ----- + final Rx shopParams = Rx(ShopParams()); + final Rx shopEntity = Rx(const ShopEntity()); + + /// ------ Controllers ------ + final TextEditingController textEditingController = TextEditingController(); + + /// ------ Statuses ------ + final Rx getShopStatus = Rx(const BaseInit()); + + /// ------ Functions ------ + + /// ------ Api Calls ------ + Future getShop() async { + change('', status: RxStatus.loading()); + await getShopUseCase(shopParams.value).then( + (value) => value.fold( + (data) { + shopEntity.value = data; + change('', status: RxStatus.success()); + }, + (error) { + change('', status: RxStatus.error(error.errorMessage)); + }, + ), + ); + } +} diff --git a/lib/features/shop/presentation/ui/shop_page.dart b/lib/features/shop/presentation/ui/shop_page.dart new file mode 100644 index 0000000..a1f6f85 --- /dev/null +++ b/lib/features/shop/presentation/ui/shop_page.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:shia_game_flutter/features/shop/presentation/controller/shop_controller.dart'; +import 'package:get/get.dart'; + +class ShopPage extends GetView { + const ShopPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text('Shop Page'), + ); + } +} diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index c1b1d7b..0d703b9 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -1,14 +1,34 @@ import 'package:get/get.dart'; import 'package:shia_game_flutter/core/network/http_request.dart'; import 'package:shia_game_flutter/core/network/http_request_impl.dart'; +import 'package:shia_game_flutter/features/awards/data/datasource/awards_datasource.dart'; +import 'package:shia_game_flutter/features/awards/data/repository_impl/awards_repository_impl.dart'; +import 'package:shia_game_flutter/features/awards/domain/repository/awards_repository.dart'; +import 'package:shia_game_flutter/features/awards/domain/usecases/get_awards_usecase.dart'; +import 'package:shia_game_flutter/features/home/data/datasource/home_datasource.dart'; +import 'package:shia_game_flutter/features/home/data/repository_impl/home_repository_impl.dart'; +import 'package:shia_game_flutter/features/home/domain/repository/home_repository.dart'; +import 'package:shia_game_flutter/features/home/domain/usecases/get_home_usecase.dart'; import 'package:shia_game_flutter/features/intro/data/datasource/intro_datasource.dart'; import 'package:shia_game_flutter/features/intro/data/repository_impl/intro_repository_impl.dart'; import 'package:shia_game_flutter/features/intro/domain/repository/intro_repository.dart'; import 'package:shia_game_flutter/features/intro/domain/usecases/get_intro_usecase.dart'; +import 'package:shia_game_flutter/features/master/data/datasource/master_datasource.dart'; +import 'package:shia_game_flutter/features/master/data/repository_impl/master_repository_impl.dart'; +import 'package:shia_game_flutter/features/master/domain/repository/master_repository.dart'; +import 'package:shia_game_flutter/features/master/domain/usecases/get_master_usecase.dart'; +import 'package:shia_game_flutter/features/profile/data/datasource/profile_datasource.dart'; +import 'package:shia_game_flutter/features/profile/data/repository_impl/profile_repository_impl.dart'; +import 'package:shia_game_flutter/features/profile/domain/repository/profile_repository.dart'; +import 'package:shia_game_flutter/features/profile/domain/usecases/get_profile_usecase.dart'; import 'package:shia_game_flutter/features/sample/data/datasource/sample_datasource.dart'; import 'package:shia_game_flutter/features/sample/data/repository_impl/sample_repository_impl.dart'; import 'package:shia_game_flutter/features/sample/domain/repository/sample_repository.dart'; import 'package:shia_game_flutter/features/sample/domain/usecases/get_sample_usecase.dart'; +import 'package:shia_game_flutter/features/shop/data/datasource/shop_datasource.dart'; +import 'package:shia_game_flutter/features/shop/data/repository_impl/shop_repository_impl.dart'; +import 'package:shia_game_flutter/features/shop/domain/repository/shop_repository.dart'; +import 'package:shia_game_flutter/features/shop/domain/usecases/get_shop_usecase.dart'; void initBindings() { /// ----- Classes ----- @@ -23,4 +43,29 @@ void initBindings() { Get.lazyPut(() => IntroDatasourceImpl(Get.find())); Get.lazyPut(() => IntroRepositoryImpl(Get.find())); Get.lazyPut(() => GetIntroUseCase(Get.find())); + + /// ----- Master Feature ----- + Get.lazyPut(() => MasterDatasourceImpl(Get.find())); + Get.lazyPut(() => MasterRepositoryImpl(Get.find())); + Get.lazyPut(() => GetMasterUseCase(Get.find())); + + /// ----- Home Feature ----- + Get.lazyPut(() => HomeDatasourceImpl(Get.find()), fenix: true); + Get.lazyPut(() => HomeRepositoryImpl(Get.find()), fenix: true); + Get.lazyPut(() => GetHomeUseCase(Get.find()), fenix: true); + + /// ----- Shop Feature ----- + Get.lazyPut(() => ShopDatasourceImpl(Get.find()), fenix: true); + Get.lazyPut(() => ShopRepositoryImpl(Get.find()), fenix: true); + Get.lazyPut(() => GetShopUseCase(Get.find()), fenix: true); + + /// ----- Awards Feature ----- + Get.lazyPut(() => AwardsDatasourceImpl(Get.find()), fenix: true); + Get.lazyPut(() => AwardsRepositoryImpl(Get.find()), fenix: true); + Get.lazyPut(() => GetAwardsUseCase(Get.find()), fenix: true); + + /// ----- Profile Feature ----- + Get.lazyPut(() => ProfileDatasourceImpl(Get.find()), fenix: true); + Get.lazyPut(() => ProfileRepositoryImpl(Get.find()), fenix: true); + Get.lazyPut(() => GetProfileUseCase(Get.find()), fenix: true); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ee262a0..886f871 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,4 +1,11 @@ { "@@locale": "en", - "loading": "Loading..." + "loading": "Loading...", + "home": "Home", + "shop": "Shop", + "awards": "Awards", + "profile": "Profile", + "pro_membership": "Pro Membership", + "custom_league": "Custom League", + "friends_battle": "Friends Battle" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index f7b95b7..c19bb98 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -99,6 +99,48 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Loading...'** String get loading; + + /// No description provided for @home. + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// No description provided for @shop. + /// + /// In en, this message translates to: + /// **'Shop'** + String get shop; + + /// No description provided for @awards. + /// + /// In en, this message translates to: + /// **'Awards'** + String get awards; + + /// No description provided for @profile. + /// + /// In en, this message translates to: + /// **'Profile'** + String get profile; + + /// No description provided for @pro_membership. + /// + /// In en, this message translates to: + /// **'Pro Membership'** + String get pro_membership; + + /// No description provided for @custom_league. + /// + /// In en, this message translates to: + /// **'Custom League'** + String get custom_league; + + /// No description provided for @friends_battle. + /// + /// In en, this message translates to: + /// **'Friends Battle'** + String get friends_battle; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 3bb6aa4..ba4430c 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -10,4 +10,25 @@ class AppLocalizationsEn extends AppLocalizations { @override String get loading => 'Loading...'; + + @override + String get home => 'Home'; + + @override + String get shop => 'Shop'; + + @override + String get awards => 'Awards'; + + @override + String get profile => 'Profile'; + + @override + String get pro_membership => 'Pro Membership'; + + @override + String get custom_league => 'Custom League'; + + @override + String get friends_battle => 'Friends Battle'; } diff --git a/lib/main.dart b/lib/main.dart index dedefa7..98bea17 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,7 +31,7 @@ class MainApp extends StatelessWidget { fallbackLocale: const Locale('en', 'US'), supportedLocales: const [Locale('en', 'US')], getPages: appPages, - initialRoute: Routes.introPage, + initialRoute: Routes.masterPage, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/pubspec.lock b/pubspec.lock index 108f0fa..fc3519b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -102,6 +102,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_inset_box_shadow_update: + dependency: "direct main" + description: + name: flutter_inset_box_shadow_update + sha256: "91c912d01c049e761d0b7e9d81061acc62677f33bdde4e854257f1b10d8f0821" + url: "https://pub.dev" + source: hosted + version: "0.0.1" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index f19b4db..a55a011 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: equatable: ^2.0.7 flutter: sdk: flutter + flutter_inset_box_shadow_update: ^0.0.1 flutter_localizations: sdk: flutter get: ^4.7.2