feat: 首页,收藏页的UI,字体 组件封装

This commit is contained in:
chengsiwen committed 2026-07-03 15:38:01 +08:00
1 parent f233f1c642
commit 7f329aef44
32 files changed
+1110 -41

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

+2
View File
@@ -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('#', '');
+11
View File
@@ -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';
}
+19
View File
@@ -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";
}
+40 -31
View File
@@ -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<RootApp> 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<SystemUiOverlayStyle>(
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: () {},
),
)
);
}
+402 -2
View File
@@ -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<CollectionPage> {
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(),
),
),
],
),
),
);
}
}
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';
}
+27 -1
View File
@@ -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<FirstPage> {
@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),
)
)
],
),
)
],
),
);
}
}
@@ -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<StatefulWidget> createState() {
return _HomeFirstViewState();
}
}
class _HomeFirstViewState extends State<HomeFirstView> {
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,)
],
),
);
}
}
+78
View File
@@ -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<StatefulWidget> createState() {
return _SearchViewState();
}
}
class _SearchViewState extends State<HomeSearchView> {
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)
],
),
)
],
),
)
],
),
),
);
}
}
+101
View File
@@ -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<Path> {
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<Path> oldClipper) {
if (oldClipper is EqualCutCornerClipper) {
return oldClipper.cutSize != cutSize || oldClipper.corner != corner;
}
return true;
}
}
enum CutCorner { topLeft, topRight, bottomLeft, bottomRight }
+82
View File
@@ -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,
),
);
}
}
+55
View File
@@ -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,
)
],
);
}
}
+29 -3
View File
@@ -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<AppBottomView> {
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<AppBottomView> {
);
}
_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;
+29
View File
@@ -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()
],
),
);
}
}
+39
View File
@@ -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);
}
}
+3
View File
@@ -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,
+27
View File
@@ -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()),
],
);
}
}
+17 -4
View File
@@ -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