feat: 首页,收藏页的UI,字体 组件封装
BIN
assets/fonts/Montserrat-Black.ttf
Normal file
BIN
assets/fonts/Montserrat-Bold.ttf
Normal file
BIN
assets/fonts/Montserrat-Medium.ttf
Normal file
BIN
assets/fonts/Montserrat-Regular.ttf
Normal file
BIN
assets/imgs/btn_bg_rad1.png
Normal file
|
After Width: | Height: | Size: 859 B |
BIN
assets/imgs/btn_bg_rad2.png
Normal file
|
After Width: | Height: | Size: 885 B |
BIN
assets/imgs/btn_two_bg.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/imgs/cate_bg.png
Normal file
|
After Width: | Height: | Size: 744 B |
BIN
assets/imgs/home_current_tag.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/imgs/home_first.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
assets/imgs/home_rank_bg.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
assets/imgs/home_search.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
assets/imgs/home_top_logo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/imgs/hot.png
Normal file
|
After Width: | Height: | Size: 791 B |
BIN
assets/imgs/search_bg.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
@ -9,6 +9,8 @@ class ConstColor {
|
|||||||
static Color btWhite = Color(0xffEEEEEE);
|
static Color btWhite = Color(0xffEEEEEE);
|
||||||
// #89F011
|
// #89F011
|
||||||
static Color btGreen = Color(0xff89F011);
|
static Color btGreen = Color(0xff89F011);
|
||||||
|
// black
|
||||||
|
static Color black = Color(0xff222222);
|
||||||
|
|
||||||
static Color withValue(String value) {
|
static Color withValue(String value) {
|
||||||
String colorValue = value.replaceAll('#', '');
|
String colorValue = value.replaceAll('#', '');
|
||||||
|
|||||||
@ -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';
|
||||||
|
}
|
||||||
@ -16,6 +16,25 @@ class ConstImage {
|
|||||||
static const String btMineSel = "assets/main/bt_mine_seled.png";
|
static const String btMineSel = "assets/main/bt_mine_seled.png";
|
||||||
static const String spaceImg = "assets/main/space_img.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";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -9,6 +10,7 @@ void main() {
|
|||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
|
||||||
runApp(const RootApp());
|
runApp(const RootApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,38 +40,45 @@ class _RootAppState extends State<RootApp> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScreenUtilInit(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
designSize: Size(375, 812),
|
value: const SystemUiOverlayStyle(
|
||||||
child: GetMaterialApp(
|
statusBarColor: Colors.white,
|
||||||
debugShowCheckedModeBanner: false,
|
statusBarBrightness: Brightness.dark, // 🔥 iOS 关键:黑色文字
|
||||||
// translations: AppLanguage(),
|
statusBarIconBrightness: Brightness.dark, // Android
|
||||||
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: () {},
|
|
||||||
),
|
),
|
||||||
|
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: () {},
|
||||||
|
),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../tools/widgets/app_bg_page.dart';
|
||||||
|
|
||||||
class CollectionPage extends StatefulWidget {
|
class CollectionPage extends StatefulWidget {
|
||||||
const CollectionPage({super.key});
|
const CollectionPage({super.key});
|
||||||
|
|
||||||
@ -10,9 +12,407 @@ class CollectionPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RootPageState extends State<CollectionPage> {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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';
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
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 {
|
class FirstPage extends StatefulWidget {
|
||||||
const FirstPage({super.key});
|
const FirstPage({super.key});
|
||||||
@ -13,6 +18,27 @@ class _RootPageState extends State<FirstPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
149
lib/pages/first/widgets/home_first_view.dart
Normal file
@ -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
lib/pages/first/widgets/search_view.dart
Normal 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
lib/pages/widgets/cut_corner_clipper.dart
Normal 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
lib/pages/widgets/ny_progress_widget.dart
Normal 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
lib/pages/widgets/ny_tag_widget.dart
Normal 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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:novyronst/common/help/help_image.dart';
|
import 'package:novyronst/common/help/help_image.dart';
|
||||||
import 'package:novyronst/pages/collect/pages/collection_page.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),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
HelpImage.asset(ConstImage.appBottom,
|
Opacity(opacity: 0.9,
|
||||||
width: AppScreen.screenWidth-32, height: _height,
|
child: ImageFiltered(imageFilter: ImageFilter.blur(sigmaX: 1),
|
||||||
fit: BoxFit.fill,
|
child: HelpImage.asset(ConstImage.appBottom,
|
||||||
|
width: AppScreen.screenWidth-32, height: _height,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Padding(padding: const EdgeInsets.symmetric(horizontal: 10),
|
child: Padding(padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: Row(
|
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}) {
|
Widget _buildTabItem(String title, int index, {required Function() onTap}) {
|
||||||
String icon = _currentIndex == index ? _selectedIcons[index] : _unselectedIcons[index];
|
String icon = _currentIndex == index ? _selectedIcons[index] : _unselectedIcons[index];
|
||||||
Color tcolor = _currentIndex == index ? ConstColor.btGreen : ConstColor.btWhite;
|
Color tcolor = _currentIndex == index ? ConstColor.btGreen : ConstColor.btWhite;
|
||||||
|
|||||||
29
lib/tools/widgets/app_bg_page.dart
Normal 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
lib/tools/widgets/novy_button_bg.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ class NovyText extends StatelessWidget {
|
|||||||
this.shadow,
|
this.shadow,
|
||||||
this.decoration,
|
this.decoration,
|
||||||
this.decorationColor,
|
this.decorationColor,
|
||||||
|
this.decorationThickness,
|
||||||
this.textAlign,
|
this.textAlign,
|
||||||
this.fontWeight,
|
this.fontWeight,
|
||||||
this.maxLines,
|
this.maxLines,
|
||||||
@ -29,6 +30,7 @@ class NovyText extends StatelessWidget {
|
|||||||
final double? fontSize;
|
final double? fontSize;
|
||||||
final TextDecoration? decoration;
|
final TextDecoration? decoration;
|
||||||
final Color? decorationColor;
|
final Color? decorationColor;
|
||||||
|
final double? decorationThickness;
|
||||||
final TextAlign? textAlign;
|
final TextAlign? textAlign;
|
||||||
final FontWeight? fontWeight;
|
final FontWeight? fontWeight;
|
||||||
final int? maxLines;
|
final int? maxLines;
|
||||||
@ -52,6 +54,7 @@ class NovyText extends StatelessWidget {
|
|||||||
color: color,
|
color: color,
|
||||||
shadows: shadow == null ? null : [shadow!],
|
shadows: shadow == null ? null : [shadow!],
|
||||||
decorationColor: decorationColor,
|
decorationColor: decorationColor,
|
||||||
|
decorationThickness: decorationThickness ?? 1,
|
||||||
height: lineHeight,
|
height: lineHeight,
|
||||||
letterSpacing: letterSpacing,
|
letterSpacing: letterSpacing,
|
||||||
), maxLines: maxLines,
|
), maxLines: maxLines,
|
||||||
|
|||||||
27
lib/tools/widgets/novy_img_bg.dart
Normal 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()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
pubspec.yaml
@ -53,6 +53,7 @@ flutter:
|
|||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- assets/main/
|
- assets/main/
|
||||||
|
- assets/imgs/
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# 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
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
# list giving the asset and other descriptors for the font. For
|
# list giving the asset and other descriptors for the font. For
|
||||||
# example:
|
# example:
|
||||||
# fonts:
|
fonts:
|
||||||
# - family: Schyler
|
- family: 'Montserrat-Black'
|
||||||
# fonts:
|
fonts:
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
- 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
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
# style: italic
|
# style: italic
|
||||||
# - family: Trajan Pro
|
# - family: Trajan Pro
|
||||||
|
|||||||