diff --git a/analysis_options.yaml b/analysis_options.yaml index 9172093..b443051 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -43,6 +43,7 @@ linter: prefer_function_declarations_over_variables: true prefer_interpolation_to_compose_strings: true prefer_null_aware_operators: true + avoid_slow_async_io : true # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule diff --git a/assets/icons/personal_select.png b/assets/icons/personal_select.png new file mode 100644 index 0000000..0cbe809 Binary files /dev/null and b/assets/icons/personal_select.png differ diff --git a/assets/icons/personal_unselect.png b/assets/icons/personal_unselect.png new file mode 100644 index 0000000..dc2b8a0 Binary files /dev/null and b/assets/icons/personal_unselect.png differ diff --git a/assets/icons/work_stage_select.png b/assets/icons/work_stage_select.png new file mode 100644 index 0000000..52a2598 Binary files /dev/null and b/assets/icons/work_stage_select.png differ diff --git a/assets/icons/work_stage_unselect.png b/assets/icons/work_stage_unselect.png new file mode 100644 index 0000000..103b8b7 Binary files /dev/null and b/assets/icons/work_stage_unselect.png differ diff --git a/assets/images/app_title.png b/assets/images/app_title.png new file mode 100644 index 0000000..996a9f1 Binary files /dev/null and b/assets/images/app_title.png differ diff --git a/assets/images/home_bg.png b/assets/images/home_bg.png new file mode 100644 index 0000000..c51a213 Binary files /dev/null and b/assets/images/home_bg.png differ diff --git a/lib/constants/app_theme.dart b/lib/constants/app_theme.dart index f08b073..6d36279 100644 --- a/lib/constants/app_theme.dart +++ b/lib/constants/app_theme.dart @@ -164,16 +164,33 @@ class MyAppStyle extends ThemeExtension { final BoxDecoration? bottomButtonDecoration; final TextStyle? bottomButtonText; + ///输入框提示文字 + final TextStyle? hintText; + + ///浅黑色 (用于没有焦点的文字或者线条等 + final Color? lightDark; + + ///标题文字 + final TextStyle? titleText; + @override - MyAppStyle copyWith( - {Color? mainColor, - TextStyle? bottomButtonText, - BoxDecoration? bottomButtonDecoration}) { + MyAppStyle copyWith({ + Color? mainColor, + TextStyle? bottomButtonText, + BoxDecoration? bottomButtonDecoration, + TextStyle? hintText, + Color? lightDark, + TextStyle? titleText, + }) { return MyAppStyle( - mainColor: mainColor ?? this.mainColor, - bottomButtonDecoration: - bottomButtonDecoration ?? this.bottomButtonDecoration, - bottomButtonText: bottomButtonText ?? this.bottomButtonText); + mainColor: mainColor ?? this.mainColor, + bottomButtonDecoration: + bottomButtonDecoration ?? this.bottomButtonDecoration, + bottomButtonText: bottomButtonText ?? this.bottomButtonText, + hintText: hintText ?? this.hintText, + lightDark: lightDark ?? this.lightDark, + titleText: titleText ?? this.titleText, + ); } @override @@ -187,6 +204,9 @@ class MyAppStyle extends ThemeExtension { bottomButtonDecoration, other.bottomButtonDecoration, t), bottomButtonText: TextStyle.lerp(bottomButtonText, other.bottomButtonText, t), + hintText: TextStyle.lerp(hintText, other.hintText, t), + lightDark: Color.lerp(lightDark, other.lightDark, t), + titleText: TextStyle.lerp(titleText, other.titleText, t), ); } @@ -194,19 +214,27 @@ class MyAppStyle extends ThemeExtension { this.bottomButtonText, this.mainColor, this.bottomButtonDecoration, + this.hintText, + this.lightDark, + this.titleText, }); MyAppStyle.def() : mainColor = Colors.blue, - bottomButtonDecoration = - BoxDecoration( + bottomButtonDecoration = BoxDecoration( gradient: const LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, - colors: [Color(0xFF0593FF), Color(0xFF027AFF)]), - borderRadius: BorderRadius.circular(8.w)), + colors: [Color(0xFF0593FF), Color(0xFF027AFF)]), + borderRadius: BorderRadius.circular(4.w)), bottomButtonText = TextStyle( - fontSize: 30.sp, - color: const Color(0xFF333333), - ); + fontSize: 14.sp, + color: const Color(0xFFFFFFFF), + ), + hintText = TextStyle(fontSize: 18.sp, color: const Color(0xFFCCCCCC)), + lightDark = const Color(0xFFCCCCCC), + titleText = TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: const Color(0xFF111111)); } diff --git a/lib/extensions/list_ext.dart b/lib/extensions/list_ext.dart new file mode 100644 index 0000000..7b26015 --- /dev/null +++ b/lib/extensions/list_ext.dart @@ -0,0 +1,38 @@ + +extension ListExt on List { + List sep(T sep) => _sepIterable(sep).toList(); + + Iterable _sepIterable(T separate) sync* { + final it = iterator; + if (!it.moveNext()) return; + yield it.current; + while (it.moveNext()) { + yield separate; + yield it.current; + } + } +} + +extension OddListExt on List { + List oddList() { + List newList = []; + for (var element in this) { + if (indexOf(element).isEven) { + newList.add(element); + } + } + return newList; + } +} + +extension EvenListExt on List { + List evenList() { + List newList = []; + forEach((element) { + if (indexOf(element).isOdd) { + newList.add(element); + } + }); + return newList; + } +} diff --git a/lib/extensions/num_ext.dart b/lib/extensions/num_ext.dart new file mode 100644 index 0000000..08e20c2 --- /dev/null +++ b/lib/extensions/num_ext.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +extension NumExt on num { + Widget get wb => SizedBox(width: w); + + Widget get hb => SizedBox(height: w); +} diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart new file mode 100644 index 0000000..b210e6b --- /dev/null +++ b/lib/gen/assets.gen.dart @@ -0,0 +1,95 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// ignore_for_file: directives_ordering,unnecessary_import + +import 'package:flutter/widgets.dart'; + +class $AssetsIconsGen { + const $AssetsIconsGen(); + + /// File path: assets/icons/personal_select.png + AssetGenImage get personalSelect => + const AssetGenImage('assets/icons/personal_select.png'); + + /// File path: assets/icons/personal_unselect.png + AssetGenImage get personalUnselect => + const AssetGenImage('assets/icons/personal_unselect.png'); + + /// File path: assets/icons/work_stage_select.png + AssetGenImage get workStageSelect => + const AssetGenImage('assets/icons/work_stage_select.png'); + + /// File path: assets/icons/work_stage_unselect.png + AssetGenImage get workStageUnselect => + const AssetGenImage('assets/icons/work_stage_unselect.png'); +} + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + /// File path: assets/images/app_title.png + AssetGenImage get appTitle => + const AssetGenImage('assets/images/app_title.png'); + + /// File path: assets/images/home_bg.png + AssetGenImage get homeBg => const AssetGenImage('assets/images/home_bg.png'); +} + +class Assets { + Assets._(); + + static const $AssetsIconsGen icons = $AssetsIconsGen(); + static const $AssetsImagesGen images = $AssetsImagesGen(); +} + +class AssetGenImage extends AssetImage { + const AssetGenImage(String assetName) : super(assetName); + + Image image({ + Key? key, + ImageFrameBuilder? frameBuilder, + ImageLoadingBuilder? loadingBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? width, + double? height, + Color? color, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + FilterQuality filterQuality = FilterQuality.low, + }) { + return Image( + key: key, + image: this, + frameBuilder: frameBuilder, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + width: width, + height: height, + color: color, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + filterQuality: filterQuality, + ); + } + + String get path => assetName; +} diff --git a/lib/main.dart b/lib/main.dart index 427fe9e..e2531fe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -50,7 +50,7 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (context) => UserProvider()), ], child: ScreenUtilInit( - designSize: const Size(750, 1334), + designSize: const Size(375, 812), builder: (context) => GestureDetector( onTap: () { //点击输入框外部隐藏键盘⌨️ diff --git a/lib/ui/home/home_page.dart b/lib/ui/home/home_page.dart new file mode 100644 index 0000000..8e40539 --- /dev/null +++ b/lib/ui/home/home_page.dart @@ -0,0 +1,47 @@ +import 'package:cloud_car_internal/gen/assets.gen.dart'; +import 'package:cloud_car_internal/utils/user_tool.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + title: Row( + children: [ + Text( + 'HI,张三', + style: UserTool.myAppStyle.titleText, + ) + ], + ), + centerTitle: false, + ), + extendBody: true, + extendBodyBehindAppBar: true, + body: Stack( + children: [ + Assets.images.homeBg + .image(fit: BoxFit.fill, width: double.infinity, height: 200.w), + CustomScrollView( + slivers: [ + SliverGrid.count( + crossAxisCount: 4, + children: [], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/ui/login/login_page.dart b/lib/ui/login/login_page.dart index 2f7a6fb..a0695f9 100644 --- a/lib/ui/login/login_page.dart +++ b/lib/ui/login/login_page.dart @@ -1,4 +1,12 @@ +import 'package:cloud_car_internal/extensions/num_ext.dart'; +import 'package:cloud_car_internal/gen/assets.gen.dart'; +import 'package:cloud_car_internal/ui/tab_navigator.dart'; +import 'package:cloud_car_internal/widget/buttons/cloud_bottom_button.dart'; +import 'package:cloud_car_internal/widget/scaffold/cloud_scaffold.dart'; +import 'package:cloud_car_internal/widget/text_filed/account_input_text_field.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @@ -10,6 +18,41 @@ class LoginPage extends StatefulWidget { class _LoginPageState extends State { @override Widget build(BuildContext context) { - return Container(); + return CloudScaffold( + appbar: AppBar( + title: const Text(''), + backgroundColor: const Color(0xFFF9F9F9), + ), + body: SafeArea( + child: Column( + children: [ + 40.hb, + Assets.images.appTitle.image(width: 150.w, height: 30.w), + 70.hb, + Padding( + padding: EdgeInsets.symmetric(horizontal: 32.w), + child: AccountInputTextField( + hintText: '请输入账号', + onChange: (text) {}, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 32.w), + child: AccountInputTextField( + hintText: '请输入密码', + onChange: (text) {}, + ), + ), + 48.hb, + CloudBottomButton( + onTap: () { + Get.offAll(const TabNavigator()); + }, + text: '登录', + ), + ], + ), + ), + ); } } diff --git a/lib/ui/personal/personal_page.dart b/lib/ui/personal/personal_page.dart new file mode 100644 index 0000000..3d0331c --- /dev/null +++ b/lib/ui/personal/personal_page.dart @@ -0,0 +1,16 @@ +import 'package:cloud_car_internal/widget/scaffold/cloud_scaffold.dart'; +import 'package:flutter/material.dart'; + +class PersonalPage extends StatefulWidget { + const PersonalPage({super.key}); + + @override + _PersonalPageState createState() => _PersonalPageState(); +} + +class _PersonalPageState extends State { + @override + Widget build(BuildContext context) { + return CloudScaffold(); + } +} diff --git a/lib/ui/tab_navigator.dart b/lib/ui/tab_navigator.dart index d071ba3..623c265 100644 --- a/lib/ui/tab_navigator.dart +++ b/lib/ui/tab_navigator.dart @@ -1,15 +1,112 @@ +import 'package:bot_toast/bot_toast.dart'; +import 'package:cloud_car_internal/gen/assets.gen.dart'; +import 'package:cloud_car_internal/ui/personal/personal_page.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../utils/user_tool.dart'; +import 'home/home_page.dart'; class TabNavigator extends StatefulWidget { - const TabNavigator({super.key}); + final int? index; + + const TabNavigator({ + super.key, + this.index, + }); @override _TabNavigatorState createState() => _TabNavigatorState(); } -class _TabNavigatorState extends State { +class _TabNavigatorState extends State + with SingleTickerProviderStateMixin { + TabController? _tabController; + int _currentIndex = 0; + DateTime? _lastPressed; + + //页面列表 + List get _pages => [ + const HomePage(), + const PersonalPage(), + ]; + + @override + void initState() { + super.initState(); + + _tabController = TabController( + length: _pages.length, vsync: this, initialIndex: widget.index ?? 0); + } + + _buildBottomBar( + String title, + String unselected, + String selected, + ) { + return BottomNavigationBarItem( + icon: Image.asset( + unselected, + height: 44.w, + width: 44.w, + //color: Colors.black38, + ), + activeIcon: Image.asset( + selected, + height: 44.w, + width: 44.w, + ), + label: title, + ); + } + + late bool isOpen = UserTool.userProvider.userInfo.level < 0; + @override Widget build(BuildContext context) { - return Container(); + //底部导航来 + List bottomNav = [ + _buildBottomBar( + '工作台', + Assets.icons.workStageUnselect.path, + Assets.icons.workStageSelect.path, + ), + _buildBottomBar( + '我的', + Assets.icons.personalUnselect.path, + Assets.icons.personalSelect.path, + ), + ]; + return Scaffold( + body: WillPopScope( + onWillPop: () async { + if (_lastPressed == null || + DateTime.now().difference(_lastPressed!) > + const Duration(seconds: 1)) { + //两次点击间隔超过1秒重新计算 + _lastPressed = DateTime.now(); + BotToast.showText(text: '再点击一次返回退出'); + return false; + } + //否则关闭app + return true; + }, + child: TabBarView( + controller: _tabController, + physics: const NeverScrollableScrollPhysics(), + children: _pages, + ), + ), + bottomNavigationBar: BottomNavigationBar( + items: bottomNav, + backgroundColor: Colors.white, + currentIndex: _currentIndex, + selectedFontSize: 12.sp, + unselectedFontSize: 12.sp, + onTap: (index) { + _tabController!.animateTo(index, curve: Curves.easeInOutCubic); + setState(() => _currentIndex = index); + }), + ); } } diff --git a/lib/widget/buttons/cloud_bottom_button.dart b/lib/widget/buttons/cloud_bottom_button.dart index f2f2e38..f69ecad 100644 --- a/lib/widget/buttons/cloud_bottom_button.dart +++ b/lib/widget/buttons/cloud_bottom_button.dart @@ -25,8 +25,8 @@ class _CloudBottomButtonState extends State { color:Colors.transparent, child: Container( alignment: Alignment.center, - padding: EdgeInsets.symmetric(vertical: 15.w), - margin: EdgeInsets.symmetric(horizontal: 32.w), + padding: EdgeInsets.symmetric(vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal:16.w), decoration:widget.decoration?? UserTool.myAppStyle.bottomButtonDecoration!, child: Text( widget.text, diff --git a/lib/widget/scaffold/cloud_scaffold.dart b/lib/widget/scaffold/cloud_scaffold.dart index 9479d03..a804145 100644 --- a/lib/widget/scaffold/cloud_scaffold.dart +++ b/lib/widget/scaffold/cloud_scaffold.dart @@ -49,7 +49,7 @@ class CloudScaffold extends StatelessWidget { extendBody: extendBody, body: body, appBar: PreferredSize( - preferredSize: Size.fromHeight(176.w), + preferredSize: Size.fromHeight(88.w), child: title == null ? appbar! : AppBar( @@ -62,7 +62,7 @@ class CloudScaffold extends StatelessWidget { style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold, - fontSize: 32.sp), + fontSize: 16.sp), ), actions: actions, ), diff --git a/lib/widget/text_filed/account_input_text_field.dart b/lib/widget/text_filed/account_input_text_field.dart new file mode 100644 index 0000000..7cc228b --- /dev/null +++ b/lib/widget/text_filed/account_input_text_field.dart @@ -0,0 +1,22 @@ +import 'package:cloud_car_internal/utils/user_tool.dart'; +import 'package:flutter/material.dart'; + +class AccountInputTextField extends StatelessWidget { + final String hintText; + final Function(String) onChange; + + const AccountInputTextField( + {super.key, required this.hintText, required this.onChange}); + + @override + Widget build(BuildContext context) { + return TextField( + onChanged: onChange, + decoration: InputDecoration( + border: UnderlineInputBorder( + borderSide: BorderSide(color: UserTool.myAppStyle.lightDark!)), + hintText: hintText, + hintStyle: UserTool.myAppStyle.hintText), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 3d83238..e5ca20d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,4 +83,8 @@ dev_dependencies: flutter: uses-material-design: true - + generate: true + assets: + - assets/ + - assets/icons/ + - assets/images/