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);
|
||||
// #89F011
|
||||
static Color btGreen = Color(0xff89F011);
|
||||
// black
|
||||
static Color black = Color(0xff222222);
|
||||
|
||||
static Color withValue(String value) {
|
||||
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 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/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: () {},
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
}
|
||||
|
||||
@ -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),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
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: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
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.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
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:
|
||||
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
|
||||
|
||||