diff --git a/assets/fonts/Montserrat-Black.ttf b/assets/fonts/Montserrat-Black.ttf new file mode 100644 index 0000000..2fab7ab Binary files /dev/null and b/assets/fonts/Montserrat-Black.ttf differ diff --git a/assets/fonts/Montserrat-Bold.ttf b/assets/fonts/Montserrat-Bold.ttf new file mode 100644 index 0000000..4033587 Binary files /dev/null and b/assets/fonts/Montserrat-Bold.ttf differ diff --git a/assets/fonts/Montserrat-Medium.ttf b/assets/fonts/Montserrat-Medium.ttf new file mode 100644 index 0000000..c9a39ea Binary files /dev/null and b/assets/fonts/Montserrat-Medium.ttf differ diff --git a/assets/fonts/Montserrat-Regular.ttf b/assets/fonts/Montserrat-Regular.ttf new file mode 100644 index 0000000..895e220 Binary files /dev/null and b/assets/fonts/Montserrat-Regular.ttf differ diff --git a/assets/imgs/btn_bg_rad1.png b/assets/imgs/btn_bg_rad1.png new file mode 100644 index 0000000..1ce12f7 Binary files /dev/null and b/assets/imgs/btn_bg_rad1.png differ diff --git a/assets/imgs/btn_bg_rad2.png b/assets/imgs/btn_bg_rad2.png new file mode 100644 index 0000000..d80022e Binary files /dev/null and b/assets/imgs/btn_bg_rad2.png differ diff --git a/assets/imgs/btn_two_bg.png b/assets/imgs/btn_two_bg.png new file mode 100644 index 0000000..0cc4f06 Binary files /dev/null and b/assets/imgs/btn_two_bg.png differ diff --git a/assets/imgs/cate_bg.png b/assets/imgs/cate_bg.png new file mode 100644 index 0000000..3337e48 Binary files /dev/null and b/assets/imgs/cate_bg.png differ diff --git a/assets/imgs/home_current_tag.png b/assets/imgs/home_current_tag.png new file mode 100644 index 0000000..f178a89 Binary files /dev/null and b/assets/imgs/home_current_tag.png differ diff --git a/assets/imgs/home_first.png b/assets/imgs/home_first.png new file mode 100644 index 0000000..40b7e40 Binary files /dev/null and b/assets/imgs/home_first.png differ diff --git a/assets/imgs/home_rank_bg.png b/assets/imgs/home_rank_bg.png new file mode 100644 index 0000000..839f552 Binary files /dev/null and b/assets/imgs/home_rank_bg.png differ diff --git a/assets/imgs/home_search.png b/assets/imgs/home_search.png new file mode 100644 index 0000000..e237396 Binary files /dev/null and b/assets/imgs/home_search.png differ diff --git a/assets/imgs/home_top_logo.png b/assets/imgs/home_top_logo.png new file mode 100644 index 0000000..da9391a Binary files /dev/null and b/assets/imgs/home_top_logo.png differ diff --git a/assets/imgs/hot.png b/assets/imgs/hot.png new file mode 100644 index 0000000..56ae71c Binary files /dev/null and b/assets/imgs/hot.png differ diff --git a/assets/imgs/search_bg.png b/assets/imgs/search_bg.png new file mode 100644 index 0000000..3da694b Binary files /dev/null and b/assets/imgs/search_bg.png differ diff --git a/lib/common/const/const_color.dart b/lib/common/const/const_color.dart index f39406c..340e810 100644 --- a/lib/common/const/const_color.dart +++ b/lib/common/const/const_color.dart @@ -9,6 +9,8 @@ class ConstColor { static Color btWhite = Color(0xffEEEEEE); // #89F011 static Color btGreen = Color(0xff89F011); + // black + static Color black = Color(0xff222222); static Color withValue(String value) { String colorValue = value.replaceAll('#', ''); diff --git a/lib/common/const/const_string.dart b/lib/common/const/const_string.dart index e69de29..2eb33d9 100644 --- a/lib/common/const/const_string.dart +++ b/lib/common/const/const_string.dart @@ -0,0 +1,11 @@ + +class ConstString { + +} + +class ConstFont { + static const String montBlack = 'Montserrat-Black'; + static const String montBold = 'Montserrat-Bold'; + static const String montMedium = 'Montserrat-Medium'; + static const String montRegular = 'Montserrat-Regular'; +} \ No newline at end of file diff --git a/lib/common/help/help_image.dart b/lib/common/help/help_image.dart index 6116356..912859e 100644 --- a/lib/common/help/help_image.dart +++ b/lib/common/help/help_image.dart @@ -16,6 +16,25 @@ class ConstImage { static const String btMineSel = "assets/main/bt_mine_seled.png"; static const String spaceImg = "assets/main/space_img.png"; + /// imgs + static const String searchBg = "assets/imgs/search_bg.png"; + // home_search top_logo + static const String homeSearch = "assets/imgs/home_search.png"; + static const String homeTopLogo = "assets/imgs/home_top_logo.png"; + static const String homeFirst = "assets/imgs/home_first.png"; + static const String homeCurrentTag = "assets/imgs/home_current_tag.png"; + static const String homeRank = "assets/imgs/home_rank_bg.png"; + // btn_two_bg + static const String btnTwoBg = "assets/imgs/btn_two_bg.png"; + // cate_bg + static const String cateBg = "assets/imgs/cate_bg.png"; + // hot + static const String hot = "assets/imgs/hot.png"; + // btn_bg_rad1 + static const String btnBgRad1 = "assets/imgs/btn_bg_rad1.png"; + static const String btnBgRad2 = "assets/imgs/btn_bg_rad2.png"; + + } diff --git a/lib/main.dart b/lib/main.dart index 763945d..db10f0a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -9,6 +10,7 @@ void main() { WidgetsFlutterBinding.ensureInitialized(); + runApp(const RootApp()); } @@ -38,38 +40,45 @@ class _RootAppState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - return ScreenUtilInit( - designSize: Size(375, 812), - child: GetMaterialApp( - debugShowCheckedModeBanner: false, - // translations: AppLanguage(), - locale: widget.locale ?? Get.deviceLocale, - fallbackLocale: const Locale('en'), - initialBinding: AppBindings(), - // initialRoute: SkyPages.initial, - // getPages: SkyPages.routes, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: ConstColor.themeColor, - ), - appBarTheme: AppBarTheme(elevation: 0, centerTitle: true), - splashColor: Colors.transparent, - useMaterial3: true, - scaffoldBackgroundColor: Colors.white, - ), - builder: FlutterSmartDialog.init( - builder: (context, child) { - return MediaQuery( - data: MediaQuery.of( - context, - ).copyWith(textScaler: TextScaler.linear(1.0)), - child: child!, - ); - }, - ), - home: widget.home ?? RootPage(), - onInit: () {}, + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + statusBarColor: Colors.white, + statusBarBrightness: Brightness.dark, // 🔥 iOS 关键:黑色文字 + statusBarIconBrightness: Brightness.dark, // Android ), + child: ScreenUtilInit( + designSize: Size(375, 812), + child: GetMaterialApp( + debugShowCheckedModeBanner: false, + // translations: AppLanguage(), + locale: widget.locale ?? Get.deviceLocale, + fallbackLocale: const Locale('en'), + initialBinding: AppBindings(), + // initialRoute: SkyPages.initial, + // getPages: SkyPages.routes, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: ConstColor.themeColor, + ), + appBarTheme: AppBarTheme(elevation: 0, centerTitle: true), + splashColor: Colors.transparent, + useMaterial3: true, + scaffoldBackgroundColor: Colors.white, + ), + builder: FlutterSmartDialog.init( + builder: (context, child) { + return MediaQuery( + data: MediaQuery.of( + context, + ).copyWith(textScaler: TextScaler.linear(1.0)), + child: child!, + ); + }, + ), + home: widget.home ?? RootPage(), + onInit: () {}, + ), + ) ); } diff --git a/lib/pages/collect/pages/collection_page.dart b/lib/pages/collect/pages/collection_page.dart index 8493b0a..f0bd1e0 100644 --- a/lib/pages/collect/pages/collection_page.dart +++ b/lib/pages/collect/pages/collection_page.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../../../tools/widgets/app_bg_page.dart'; + class CollectionPage extends StatefulWidget { const CollectionPage({super.key}); @@ -10,9 +12,407 @@ class CollectionPage extends StatefulWidget { } class _RootPageState extends State { + static const List<_DramaItem> _favoriteItems = [ + _DramaItem( + title: 'An Uninvited Guest', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/09/26/66f4d0ce67383.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Vanished Without a Word', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/06/11/6667f17c9df69.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Dawn of Stardom', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/07/19/669a15b8a2e28.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Marry Me Twice', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/04/30/6630c89b746a8.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Not Your Bride', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/05/22/664daf9d042d4.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: "Husband's Hidden Crown", + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/08/12/66b9dfd8182cc.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'In the City of Love', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/07/24/66a0b88fda163.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: "Mommy's Genius Squad", + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/05/17/664716f187b2b.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Burned by Love', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/07/05/66876b720daf4.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + ]; + + static const List<_DramaItem> _historyItems = [ + _DramaItem( + title: 'Shadows of Vengeance', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/09/10/66e01c7ab24ab.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: "CEO's Naughty Wife Is Hidden Rich", + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/08/02/66accc37da3a5.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Her Veiled Protector', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/06/28/667e7312bc731.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Love Deep As The Ocean', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/06/14/666c06fb54983.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Not Your Bride', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/05/22/664daf9d042d4.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Shadows of Vengeance', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/09/10/66e01c7ab24ab.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + _DramaItem( + title: 'Shadows of Vengeance', + imageUrl: + 'https://static.goodshort.com/upload/serial/cover/2024/09/10/66e01c7ab24ab.jpg', + currentEpisode: 1, + totalEpisode: 51, + ), + ]; + + bool _showHistory = false; @override Widget build(BuildContext context) { - return Scaffold(); + return AppBgPage( + child: SafeArea( + bottom: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 180), + child: _showHistory + ? _buildHistoryList() + : _buildFavoritesGrid(), + ), + ), + ], + ), + ), + ); } -} \ No newline at end of file + + Widget _buildHeader() { + return Padding( + padding: const EdgeInsets.fromLTRB(30, 18, 26, 12), + child: Row( + children: [ + _buildTab( + 'Favorites', + active: !_showHistory, + onTap: () { + setState(() { + _showHistory = false; + }); + }, + ), + const SizedBox(width: 16), + _buildTab( + 'History', + active: _showHistory, + onTap: () { + setState(() { + _showHistory = true; + }); + }, + ), + const Spacer(), + const Icon(Icons.edit_outlined, color: Colors.white, size: 22), + ], + ), + ); + } + + Widget _buildTab( + String title, { + required bool active, + required VoidCallback onTap, + }) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + color: active + ? Colors.white + : Colors.white.withValues(alpha: 0.42), + fontSize: 16, + fontFamily: 'Montserrat-Bold', + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 5), + AnimatedContainer( + duration: const Duration(milliseconds: 180), + width: active ? 54 : 0, + height: 2, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(1), + ), + ), + ], + ), + ); + } + + Widget _buildFavoritesGrid() { + return GridView.builder( + key: const ValueKey('favorites'), + padding: const EdgeInsets.fromLTRB(30, 12, 14, 96), + itemCount: _favoriteItems.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 8, + mainAxisSpacing: 14, + childAspectRatio: 0.55, + ), + itemBuilder: (context, index) { + return _FavoriteCard(item: _favoriteItems[index]); + }, + ); + } + + Widget _buildHistoryList() { + return ListView.separated( + key: const ValueKey('history'), + padding: const EdgeInsets.fromLTRB(30, 8, 20, 96), + itemCount: _historyItems.length, + separatorBuilder: (context, index) => const SizedBox(height: 13), + itemBuilder: (context, index) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 30, + child: Icon( + Icons.bookmark_border, + color: Colors.white.withValues(alpha: 0.28), + size: 32, + ), + ), + const SizedBox(width: 18), + Expanded(child: _HistoryItem(item: _historyItems[index])), + ], + ); + }, + ); + } +} + +class _FavoriteCard extends StatelessWidget { + const _FavoriteCard({required this.item}); + + final _DramaItem item; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(2), + child: Image.network( + item.imageUrl, + width: constraints.maxWidth, + height: constraints.maxWidth * 1.33, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + width: constraints.maxWidth, + height: constraints.maxWidth * 1.33, + color: Colors.white.withValues(alpha: 0.12), + ); + }, + ), + ), + const SizedBox(height: 8), + Text( + item.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + height: 1.18, + fontFamily: 'Montserrat-Bold', + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 8), + Text( + item.episodeText, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.48), + fontSize: 11, + fontFamily: 'Montserrat-Medium', + fontWeight: FontWeight.w500, + ), + ), + ], + ); + }, + ); + } +} + +class _HistoryItem extends StatelessWidget { + const _HistoryItem({required this.item}); + + final _DramaItem item; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 68, + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(2), + child: Image.network( + item.imageUrl, + width: 51, + height: 68, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + width: 51, + height: 68, + color: Colors.white.withValues(alpha: 0.12), + ); + }, + ), + ), + const SizedBox(width: 15), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + height: 1.22, + fontFamily: 'Montserrat-Bold', + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 8), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'EP.${item.currentEpisode}', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + TextSpan(text: ' /EP.${item.totalEpisode}'), + ], + ), + style: TextStyle( + color: Colors.white.withValues(alpha: 0.45), + fontSize: 12, + fontFamily: 'Montserrat-Medium', + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +class _DramaItem { + const _DramaItem({ + required this.title, + required this.imageUrl, + required this.currentEpisode, + required this.totalEpisode, + }); + + final String title; + final String imageUrl; + final int currentEpisode; + final int totalEpisode; + + String get episodeText => 'EP.$currentEpisode/EP.$totalEpisode'; +} diff --git a/lib/pages/first/pages/first_page.dart b/lib/pages/first/pages/first_page.dart index b655946..9c3643a 100644 --- a/lib/pages/first/pages/first_page.dart +++ b/lib/pages/first/pages/first_page.dart @@ -1,4 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:novyronst/common/help/help_image.dart'; +import 'package:novyronst/pages/first/widgets/home_first_view.dart'; +import 'package:novyronst/pages/first/widgets/search_view.dart'; + +import '../../../tools/widgets/app_bg_page.dart'; class FirstPage extends StatefulWidget { const FirstPage({super.key}); @@ -13,6 +18,27 @@ class _RootPageState extends State { @override Widget build(BuildContext context) { - return Scaffold(); + return AppBgPage( + child: Column( + children: [ + HomeSearchView(), + Expanded( + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: HomeFirstView(), + ), + SliverPadding( + padding: const EdgeInsets.only(left: 16, right: 10, top: 16), + sliver: SliverToBoxAdapter( + child: HelpImage.asset(ConstImage.homeRank), + ) + ) + ], + ), + ) + ], + ), + ); } } \ No newline at end of file diff --git a/lib/pages/first/widgets/home_first_view.dart b/lib/pages/first/widgets/home_first_view.dart new file mode 100644 index 0000000..f3b3e4d --- /dev/null +++ b/lib/pages/first/widgets/home_first_view.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:novyronst/pages/widgets/cut_corner_clipper.dart'; +import 'package:novyronst/pages/widgets/ny_progress_widget.dart'; +import 'package:novyronst/pages/widgets/ny_tag_widget.dart'; +import 'package:novyronst/root/lib_export.dart'; +import 'package:novyronst/tools/widgets/novy_button_bg.dart'; +import 'package:novyronst/tools/widgets/novy_com_widget.dart'; +import 'package:novyronst/tools/widgets/novy_img_bg.dart'; + +class HomeFirstView extends StatefulWidget { + + const HomeFirstView({super.key}); + + @override + State createState() { + return _HomeFirstViewState(); + } +} + +class _HomeFirstViewState extends State { + + bool _hasHistory = true; + + @override + Widget build(BuildContext context) { + const double _space = 25; + return Stack( + children: [ + // 151 + SizedBox( + height: 151 - _space + 200 + 52 + 20, + ), + HelpImage.asset(ConstImage.homeFirst, width: AppScreen.screenWidth, fit: BoxFit.fill), + Positioned( + top: 151-_space, left: 16, right: 16, + child: Column( + children: [ + CutCornerClipper( + width: AppScreen.screenWidth - 32, + height: 204, + cutSize: 80, + child: _buildVideoItem(), + ), + const SizedBox(height: 16), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate(3, (index) { + return Container( + margin: const EdgeInsets.only(right: 8), + child: NovyImgBg(imgBg: ConstImage.cateBg, + height: 52, + child: Stack( + children:[ + NovyText('ClassPp', color: ConstColor.btGreen, + fontSize: 21, + textAlign: TextAlign.center, + fontFamily: ConstFont.montBlack, + shadow: Shadow( + offset: const Offset(1, 1), // X: 1, Y: 1 + blurRadius: 0, // Blur: 0 + color: Colors.black, // #000000 + ), + ), + Positioned( + bottom: 0, left: 0, right: 0, + child: Container( + height: 2, + color: Colors.black, + ), + ) + ] + ), + ), + ); + }), + ), + ), + ], + ) + ) + ], + ); + } + + _buildVideoItem() { + return Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Row( + children: [ + NovyText('Current Viewing Progress', + color: Colors.black, + fontSize: 16, + fontFamily: ConstFont.montBlack, + ), + const SizedBox(width: 10), + HelpImage.asset(ConstImage.homeCurrentTag, height: 10) + ], + ), + const SizedBox(height: 10), + Row( + children: [ + Container( + color: Colors.grey.withAlpha(200), + width: 60, height: 90, + ), + const SizedBox(width: 12), + SizedBox( + height: 90, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + NovyText('Jdksyjs dsaoy', + color: Colors.black, + fontSize: 14, + fontFamily: ConstFont.montMedium, + ), + const SizedBox(height: 4), + NovyText('Jdksyjs dsaoy dasku dsaldsa dljas', + color: Colors.grey, + fontSize: 10, + fontFamily: ConstFont.montRegular, + maxLines: 2, + ), + Spacer(), + Row( + children: [ + NyTagWidget(text: 'Safiray'), + const SizedBox(width: 8), + NyHotWidget(text: '12.4k'), + ], + ) + ], + ), + ) + ], + ), + const SizedBox(height: 10), + NyProgressWidget(), + const SizedBox(height: 6), + NovyButtonBg(text: 'Keep Watching', type: 2, width: 327.w,) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/first/widgets/search_view.dart b/lib/pages/first/widgets/search_view.dart new file mode 100644 index 0000000..35ea907 --- /dev/null +++ b/lib/pages/first/widgets/search_view.dart @@ -0,0 +1,78 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:novyronst/root/lib_export.dart'; + +class HomeSearchView extends StatefulWidget { + const HomeSearchView({super.key}); + + @override + State createState() { + return _SearchViewState(); + } +} + +class _SearchViewState extends State { + + final _searchController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Container( + height: AppScreen.navBarHeight, + padding: EdgeInsets.only(top: AppScreen.statusBarHeight, + left: 16, right: 16, + bottom: 5 + ), + child: SizedBox( + height: 36, + child: Row( + children: [ + HelpImage.asset(ConstImage.homeTopLogo, width: 36), + const SizedBox(width: 16), + Expanded( + child: Stack( + children: [ + HelpImage.asset(ConstImage.searchBg, height: 36, fit: BoxFit.fill), + Padding( + padding: EdgeInsets.only(left: 11), + child: Row( + children: [ + Icon(Icons.search, color: Colors.white, size: 24), + + Expanded(child: CupertinoTextField( + controller: _searchController, + // focusNode: widget.focusNode, + decoration: BoxDecoration(), + placeholder: 'What are...... ', + placeholderStyle: TextStyle( + fontSize: 13, + color: Colors.white.withAlpha(127), + ), + style: TextStyle( + fontSize: 14, + color: Colors.white, + ), + + textInputAction: TextInputAction.search, + onEditingComplete: () { + + }, + ),), + + HelpImage.asset(ConstImage.homeSearch, width: 78, height: 36, fit: BoxFit.fill) + + + ], + ), + ) + ], + ), + ) + ], + ), + ), + ); + } + + +} \ No newline at end of file diff --git a/lib/pages/widgets/cut_corner_clipper.dart b/lib/pages/widgets/cut_corner_clipper.dart new file mode 100644 index 0000000..ec6e083 --- /dev/null +++ b/lib/pages/widgets/cut_corner_clipper.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; + +class CutCornerClipper extends StatelessWidget { + + const CutCornerClipper({super.key, + this.width, + this.height, + this.cutCorner = CutCorner.topRight, + this.cutSize, + this.child, + }); + + final double? width; + final double? height; + final CutCorner cutCorner; + final double? cutSize; + final Widget? child; + + @override + Widget build(BuildContext context) { + return ClipPath( + clipper: EqualCutCornerClipper( + cutSize: cutSize ?? 35, + corner: cutCorner, + ), + child: Container( + width: width, + height: height, + decoration: BoxDecoration( + color: Colors.white, + ), + child: child, + ), + ); + } +} + +/// 长方形缺一个角(等边缺角) +class EqualCutCornerClipper extends CustomClipper { + final double cutSize; // 缺角两条直角边的长度(相等) + final CutCorner corner; + + const EqualCutCornerClipper({ + this.cutSize = 20, + this.corner = CutCorner.topRight, + }); + + @override + Path getClip(Size size) { + final path = Path(); + final w = size.width; + final h = size.height; + final c = cutSize; + + switch (corner) { + case CutCorner.topRight: + path.moveTo(0, 0); + path.lineTo(w - c, 0); // 上边到缺角起点 + path.lineTo(w, c); // 🔥 45° 斜线(两条边相等) + path.lineTo(w, h); + path.lineTo(0, h); + break; + + case CutCorner.topLeft: + path.moveTo(c, 0); + path.lineTo(w, 0); + path.lineTo(w, h); + path.lineTo(0, h); + path.lineTo(0, c); + break; + + case CutCorner.bottomRight: + path.moveTo(0, 0); + path.lineTo(w, 0); + path.lineTo(w, h - c); + path.lineTo(w - c, h); + path.lineTo(0, h); + break; + + case CutCorner.bottomLeft: + path.moveTo(0, 0); + path.lineTo(w, 0); + path.lineTo(w, h); + path.lineTo(c, h); + path.lineTo(0, h - c); + break; + } + path.close(); + return path; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) { + if (oldClipper is EqualCutCornerClipper) { + return oldClipper.cutSize != cutSize || oldClipper.corner != corner; + } + return true; + } +} + +enum CutCorner { topLeft, topRight, bottomLeft, bottomRight } \ No newline at end of file diff --git a/lib/pages/widgets/ny_progress_widget.dart b/lib/pages/widgets/ny_progress_widget.dart new file mode 100644 index 0000000..5e88b37 --- /dev/null +++ b/lib/pages/widgets/ny_progress_widget.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:novyronst/root/lib_export.dart'; +import 'package:novyronst/tools/widgets/novy_com_widget.dart'; + +class NyProgressWidget extends StatelessWidget { + const NyProgressWidget({super.key, + this.progress = 0.4, + this.width, + }); + + final double progress; + final double? width; + static final double _width = AppScreen.screenWidth - 50; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + SizedBox( + height: 14, + width: width ?? _width + ), + Positioned( + left: 0, right: 0, top: 4.5, + child: Container( + height: 5, + padding: EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(2.5), + ), + child: Row( + children: [ + Expanded( + flex: (progress * 100).toInt(), + child: _line(), + ), + Expanded( + flex: 100 - (progress * 100).toInt(), + child: _line(isActive: false), + ) + ], + ), + ), + ), + Positioned( + top: 0, left: (width ?? _width - 50) * progress, + child: _pTag(), + ) + ], + ); + } + + Container _line({bool isActive = true}) { + return Container( + height: 2, + decoration: BoxDecoration( + color: isActive ? ConstColor.btGreen : ConstColor.btGreen.withAlpha(127), + ), + ); + } + + Widget _pTag() { + return Container( + height: 14, + padding: EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: ConstColor.btGreen, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(4), + bottomLeft: Radius.circular(4), + topRight: Radius.circular(16), + bottomRight: Radius.circular(4), + ) + ), + child: NovyText('${(progress * 100).toInt()}%', + fontSize: 10, + color: Colors.black, + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/widgets/ny_tag_widget.dart b/lib/pages/widgets/ny_tag_widget.dart new file mode 100644 index 0000000..1b2ee6d --- /dev/null +++ b/lib/pages/widgets/ny_tag_widget.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:novyronst/common/const/const_color.dart'; +import 'package:novyronst/common/help/help_image.dart'; +import 'package:novyronst/tools/widgets/novy_com_widget.dart'; + +class NyTagWidget extends StatelessWidget { + const NyTagWidget({super.key, + required this.text, + this.bgColor, + this.textColor, + }); + + final String text; + final Color? bgColor; + final Color? textColor; + + @override + Widget build(BuildContext context) { + return Container( + height: 20, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: bgColor ?? ConstColor.withValue('EEEEEE'), + borderRadius: BorderRadius.circular(4), + ), + child: Center( + child: NovyText(text, + fontSize: 10, + color: textColor ?? ConstColor.black, + ), + ), + ); + } +} + +class NyHotWidget extends StatelessWidget { + const NyHotWidget({super.key, + required this.text, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + HelpImage.asset(ConstImage.hot, width: 12), + NovyText(text, + fontSize: 10, + color: Colors.black, + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/root/root_page.dart b/lib/root/root_page.dart index f52dee0..5a1f554 100644 --- a/lib/root/root_page.dart +++ b/lib/root/root_page.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:novyronst/common/help/help_image.dart'; import 'package:novyronst/pages/collect/pages/collection_page.dart'; @@ -90,10 +92,15 @@ class _AppBottomViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: Stack( children: [ - HelpImage.asset(ConstImage.appBottom, - width: AppScreen.screenWidth-32, height: _height, - fit: BoxFit.fill, + Opacity(opacity: 0.9, + child: ImageFiltered(imageFilter: ImageFilter.blur(sigmaX: 1), + child: HelpImage.asset(ConstImage.appBottom, + width: AppScreen.screenWidth-32, height: _height, + fit: BoxFit.fill, + ), + ), ), + Positioned.fill( child: Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Row( @@ -115,6 +122,25 @@ class _AppBottomViewState extends State { ); } + _aaaa(){ + + return ClipRRect( + borderRadius: BorderRadius.circular(12), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + color: Colors.white.withOpacity(0.1), + child: const Center( + child: Text( + '毛玻璃效果', + style: TextStyle(color: Colors.white), + ), + ), + ), + ), + ); + } + Widget _buildTabItem(String title, int index, {required Function() onTap}) { String icon = _currentIndex == index ? _selectedIcons[index] : _unselectedIcons[index]; Color tcolor = _currentIndex == index ? ConstColor.btGreen : ConstColor.btWhite; diff --git a/lib/tools/widgets/app_bg_page.dart b/lib/tools/widgets/app_bg_page.dart new file mode 100644 index 0000000..d6a3244 --- /dev/null +++ b/lib/tools/widgets/app_bg_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:novyronst/root/lib_export.dart'; + +class AppBgPage extends StatelessWidget { + + const AppBgPage({ + super.key, + this.child, + + }); + + final Widget? child; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + SizedBox( + width: double.infinity, + height: double.infinity, + child: HelpImage.asset(ConstImage.pageBg, fit: BoxFit.fill), + ), + child ?? SizedBox() + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/tools/widgets/novy_button_bg.dart b/lib/tools/widgets/novy_button_bg.dart new file mode 100644 index 0000000..dc437c3 --- /dev/null +++ b/lib/tools/widgets/novy_button_bg.dart @@ -0,0 +1,39 @@ + +import 'package:flutter/material.dart'; +import 'package:novyronst/common/const/const_string.dart'; +import 'package:novyronst/common/help/help_image.dart'; +import 'package:novyronst/tools/widgets/extend_widget.dart'; +import 'package:novyronst/tools/widgets/novy_com_widget.dart'; + +class NovyButtonBg extends StatelessWidget { + const NovyButtonBg({super.key, + this.width, + required this.text, + this.onTap, + this.type = 1, + }); + + final double? width; + final String text; + final VoidCallback? onTap; + final int type; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + HelpImage.asset(type==1 ? ConstImage.btnBgRad1 : ConstImage.btnBgRad2, + width: width, height: 33, + fit: BoxFit.fill, + ), + Positioned.fill(child: Center( + child: NovyText(text, + fontSize: 14, + color: Colors.black, + fontFamily: ConstFont.montBold + ), + )), + ], + ).addInkWell(onTap: onTap); + } +} \ No newline at end of file diff --git a/lib/tools/widgets/novy_com_widget.dart b/lib/tools/widgets/novy_com_widget.dart index 0fd1d8c..915132f 100644 --- a/lib/tools/widgets/novy_com_widget.dart +++ b/lib/tools/widgets/novy_com_widget.dart @@ -13,6 +13,7 @@ class NovyText extends StatelessWidget { this.shadow, this.decoration, this.decorationColor, + this.decorationThickness, this.textAlign, this.fontWeight, this.maxLines, @@ -29,6 +30,7 @@ class NovyText extends StatelessWidget { final double? fontSize; final TextDecoration? decoration; final Color? decorationColor; + final double? decorationThickness; final TextAlign? textAlign; final FontWeight? fontWeight; final int? maxLines; @@ -52,6 +54,7 @@ class NovyText extends StatelessWidget { color: color, shadows: shadow == null ? null : [shadow!], decorationColor: decorationColor, + decorationThickness: decorationThickness ?? 1, height: lineHeight, letterSpacing: letterSpacing, ), maxLines: maxLines, diff --git a/lib/tools/widgets/novy_img_bg.dart b/lib/tools/widgets/novy_img_bg.dart new file mode 100644 index 0000000..0e83ba1 --- /dev/null +++ b/lib/tools/widgets/novy_img_bg.dart @@ -0,0 +1,27 @@ + +import 'package:flutter/material.dart'; +import 'package:novyronst/common/help/help_image.dart'; + +class NovyImgBg extends StatelessWidget { + const NovyImgBg({super.key, + required this.imgBg, + this.width, + this.height, + this.child + }); + + final String imgBg; + final double? width; + final double? height; + final Widget? child; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + HelpImage.asset(imgBg, width: width, height: height), + Positioned.fill(child: Center(child: child) ?? Container()), + ], + ); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 592ab7e..bfea2dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/main/ + - assets/imgs/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see @@ -66,10 +67,22 @@ flutter: # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf + fonts: + - family: 'Montserrat-Black' + fonts: + - asset: assets/fonts/Montserrat-Black.ttf + + - family: 'Montserrat-Bold' + fonts: + - asset: assets/fonts/Montserrat-Bold.ttf + + - family: 'Montserrat-Medium' + fonts: + - asset: assets/fonts/Montserrat-Medium.ttf + + - family: 'Montserrat-Regular' + fonts: + - asset: assets/fonts/Montserrat-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro