feat: UI pages

This commit is contained in:
csw 2026-06-28 17:16:31 +08:00
parent fbcd33ea2b
commit 197c3e9b88
41 changed files with 1270 additions and 274 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/imgs/hot_bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
assets/imgs/hot_bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
assets/imgs/purple_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
assets/imgs/red_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
assets/imgs/yellow_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
assets/pics/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/pics/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

BIN
assets/pics/hot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/pics/like_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
assets/pics/look.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

BIN
assets/pics/playlog_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/pics/search_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

BIN
assets/pics/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

View File

@ -1,6 +1,8 @@
import 'package:skywood/global/request/api_const.dart'; import 'package:skywood/global/request/api_const.dart';
import 'package:skywood/global/request/app_request.dart'; import 'package:skywood/global/request/app_request.dart';
import 'package:skywood/global/tool/sky_log.dart';
import 'package:skywood/utils/models/drama/skywood_category.dart';
import 'package:skywood/utils/models/drama/skywood_home.dart'; import 'package:skywood/utils/models/drama/skywood_home.dart';
class PlayClient { class PlayClient {
@ -18,4 +20,22 @@ class PlayClient {
return []; return [];
}); });
} }
static Future<List<SkywoodCategory>> getCategoriesList() {
return AppRequest.get(ApiStringPlay.getCategoriesList).then((value) {
SkyLog.print('vvvvv: ${value.data}');
if (!value.isSuccess) return [];
Map<String, dynamic> data = value.data['data'];
List? list = data['list'];
List<SkywoodCategory> categoryList = [];
if (list != null) {
// id 28 ,
categoryList = list.map((e) => SkywoodCategory.fromJson(e)).toList();
categoryList.removeWhere((element) => element.id == 28);
}
return categoryList;
});
}
} }

View File

@ -6,5 +6,6 @@ export 'package:skywood/global/const/common_string.dart';
export 'package:skywood/utils/tools/app_screen.dart'; export 'package:skywood/utils/tools/app_screen.dart';
export 'package:skywood/utils/tools/help_img.dart'; export 'package:skywood/utils/tools/help_img.dart';
export 'package:skywood/utils/tools/help_nav.dart';
export 'package:flutter_screenutil/flutter_screenutil.dart'; export 'package:flutter_screenutil/flutter_screenutil.dart';

View File

@ -3,7 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:skywood/pages/home/home_page.dart'; import 'package:skywood/pages/home/home_page.dart';
import 'package:skywood/pages/like/like_page.dart'; import 'package:skywood/pages/like/like_page.dart';
import 'package:skywood/pages/mine/mine_page.dart'; import 'package:skywood/pages/mine/mine_page.dart';
import 'package:skywood/pages/search/search_page.dart'; import 'package:skywood/pages/search/page/search_page.dart';
import 'package:skywood/utils/tools/help_img.dart'; import 'package:skywood/utils/tools/help_img.dart';
class MainRoot extends StatefulWidget { class MainRoot extends StatefulWidget {

View File

@ -1,9 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:skywood/global/client/play_client.dart'; import 'package:skywood/global/client/play_client.dart';
import 'package:skywood/global/client/user_client.dart'; import 'package:skywood/global/client/user_client.dart';
import 'package:skywood/global/tool/sky_log.dart';
import 'package:skywood/main/lib_file.dart'; import 'package:skywood/main/lib_file.dart';
import 'package:skywood/pages/home/widgets/daily_quest_widget.dart'; import 'package:skywood/pages/home/widgets/daily_quest_widget.dart';
import 'package:skywood/pages/home/widgets/home_cate_widget.dart';
import 'package:skywood/pages/home/widgets/hot_cartridge_widget.dart';
import 'package:skywood/pages/home/widgets/rank_list_widget.dart';
import 'package:skywood/pages/widgets/common_bg_page.dart'; import 'package:skywood/pages/widgets/common_bg_page.dart';
import 'package:skywood/utils/models/drama/skywood_category.dart';
import 'package:skywood/utils/models/drama/skywood_home.dart'; import 'package:skywood/utils/models/drama/skywood_home.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@ -18,22 +23,19 @@ class HomePage extends StatefulWidget {
class _MainRootState extends State<HomePage> { class _MainRootState extends State<HomePage> {
List<SkywoodHome> _homeData = []; List<SkywoodHome> _homeData = [];
List<SkywoodCategory> _categories = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_getHomeData(); // _getHomeData();
// _getDramaCate();
} }
void _getHomeData() { void _getHomeData() {
// home_v3_recommand // home_v3_recommand new_recommand week_ranking week_highest_recommend
// new_recommand // marquee home_banner get_details_recommand
// week_ranking
// week_highest_recommend
// marquee
// home_banner
// get_details_recommand
PlayClient.getHomeData().then((value) { PlayClient.getHomeData().then((value) {
for (SkywoodHome element in value) { for (SkywoodHome element in value) {
print('lele: ${element.moduleKey} : ${element.dataList.length}'); print('lele: ${element.moduleKey} : ${element.dataList.length}');
@ -44,6 +46,15 @@ class _MainRootState extends State<HomePage> {
}); });
} }
void _getDramaCate() {
PlayClient.getCategoriesList().then((value) {
print('cateeeee: ${value.length}');
setState(() {
_categories = value;
});
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -67,11 +78,31 @@ class _MainRootState extends State<HomePage> {
), ),
_buildSpace(height: 10), _buildSpace(height: 10),
_buildTitleImg(ConstImg.homeTitleHot), _buildTitleImg(ConstImg.homeTitleHot),
_buildSpace(height: 300), _buildSpace(),
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 15.w),
sliver: HotCartridgeWidget(
dramaList: _homeData.firstWhere((element) => element.moduleKey == 'new_recommand').dataList,
),
),
_buildSpace(height: 10),
_buildTitleImg(ConstImg.homeTitleWeekly), _buildTitleImg(ConstImg.homeTitleWeekly),
_buildSpace(height: 300), SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 15.w),
sliver: SliverToBoxAdapter(
child: RankListWidget(
dramaList: _homeData.firstWhere((element) => element.moduleKey == 'week_ranking').dataList,
),
),
),
_buildTitleImg(ConstImg.homeTitleHotCate), _buildTitleImg(ConstImg.homeTitleHotCate),
_buildSpace(height: 300), SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 15.w),
sliver: HomeCateWidget(
categories: _categories,
),
),
_buildSpace(height: 100),
], ],
), ),
), ),

View File

@ -20,7 +20,8 @@ class DailyQuestWidget extends StatelessWidget {
double height = 281.h; double height = 281.h;
double width = 345.w; double width = 345.w;
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: SizedBox( child: Container(
padding: EdgeInsets.only(left: 15.w),
width: width, width: width,
height: height, height: height,
child: Stack( child: Stack(
@ -66,6 +67,7 @@ class DailyQuestWidget extends StatelessWidget {
), ),
child: HelpImg.network(drama.imageUrl, width: 300.w, height: 160.h, fit: BoxFit.cover), child: HelpImg.network(drama.imageUrl, width: 300.w, height: 160.h, fit: BoxFit.cover),
), ),
SizedBox(height: 4.h),
Row( Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -86,7 +88,8 @@ class DailyQuestWidget extends StatelessWidget {
], ],
) )
], ],
) ),
SizedBox(height: 3.h),
], ],
); );
} }

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/utils/models/drama/skywood_category.dart';
class HomeCateWidget extends StatelessWidget {
const HomeCateWidget({super.key,
required this.categories,
});
final List<SkywoodCategory> categories;
@override
Widget build(BuildContext context) {
return SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 🔥 2
crossAxisSpacing: 10, //
mainAxisSpacing: 10, //
childAspectRatio: 0.7, // /
), itemBuilder: (context, index) {
return _buildCateItem(categories[index], index);
}
);
}
Widget _buildCateItem(SkywoodCategory category, int index) {
String imgName = index%3 == 0 ? ConstImg.purpleBg : (index%3 == 1 ? ConstImg.yellowBg : ConstImg.redBg);
return Stack(
children: [
HelpImg.asset(imgName, width: 140.w, height: 131.h, fit: BoxFit.cover),
],
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:skywood/main/lib_file.dart';
import '../../../utils/models/drama/skywood_drama.dart';
class HotCartridgeWidget extends StatelessWidget {
const HotCartridgeWidget({super.key,
required this.dramaList,
});
final List<SkywoodDrama> dramaList;
@override
Widget build(BuildContext context) {
return SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 2
crossAxisSpacing: 12, //
mainAxisSpacing: 5, //
childAspectRatio: 110 / 174, //
),
itemBuilder: (context, index) {
return _buildItem(dramaList[index], index);
}, itemCount: dramaList.length>6 ? 6 : dramaList.length);
}
Widget _buildItem(SkywoodDrama drama, int index) {
return Stack(
children: [
SizedBox(
width: 110.w, height: 174.h,
),
Positioned(
top: 1, left: 5.w, right: 5.w,
child: HelpImg.network(drama.imageUrl, width: 100.w, height: 130.h),
),
HelpImg.asset(index<3?ConstImg.hotBg1:ConstImg.hotBg2, width: 110.w, height: 174.h)
],
);
}
}

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:skywood/global/const/common_color.dart';
import '../../../utils/tools/help_img.dart';
import '../../../utils/tools/sky_time_tool.dart';
import '../../../utils/tools/widgets/sky_common_widget.dart';
class LikeNumWidget extends StatelessWidget {
const LikeNumWidget({super.key, required this.watchTotal,
this.tColor,
});
final int watchTotal;
final Color? tColor;
@override
Widget build(BuildContext context) {
return Row(
children: [
// Icon(Icons.heart_broken, size: 15, color: Colors.redAccent,),
HelpImg.asset(ConstImg.heart, width: 18.w, height: 18.w),
const SizedBox(width: 3),
SkyText(text: SkyTimeTool.formatNum(watchTotal),
fontSize: 12,
fontWeight: FontWeight.w400,
color: tColor ?? CommonColor.black,
)
],
);
}
}

View File

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/pages/home/widgets/like_num_widget.dart';
import 'package:skywood/utils/tools/widgets/sky_common_widget.dart';
import '../../../utils/models/drama/skywood_drama.dart';
class RankListWidget extends StatelessWidget {
const RankListWidget({super.key,
required this.dramaList,
});
final List<SkywoodDrama> dramaList;
@override
Widget build(BuildContext context) {
return Stack(
children: [
HelpImg.asset(ConstImg.homeRankBg, width: 325.w, height: 481.h),
Positioned(
left: 24.5.w, right: 24.5.w,
top: 44.5.h, bottom: 34.5.h,
child: ListView.separated(
padding: EdgeInsets.only(right: 10.w),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) => _itemBuilder(index),
separatorBuilder: (context, index) {
return Divider(
height: 1, thickness: 0.5,
color: CommonColor.withValue('7A67C1'),
);
},
itemCount: dramaList.length>6 ? 6:dramaList.length,
),
)
],
);
}
Widget _itemBuilder(int index) {
SkywoodDrama drama = dramaList[index];
return SizedBox(
height: 65.h, width: 296.w,
child: Row(
children: [
_rankIndex(index),
HelpImg.network(drama.imageUrl, width: 40.w, height: 52.h, fit: BoxFit.cover),
const SizedBox(width: 7),
Expanded(child: SkyText(text: drama.name,
fontSize: 13.w,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
lineHeight: 1.23,
color: CommonColor.black,
maxLines: 2,
)),
SizedBox(width: 8.w),
LikeNumWidget(watchTotal: drama.watchTotal)
],
),
);
}
Widget _rankIndex(int index) {
return SizedBox(
width: 40.w,
child: SkyText(text: formatNumber(index + 1),
fontSize: 15,
color: CommonColor.black,
fontFamily: 'Anton',
fontWeight: FontWeight.bold,
lineHeight: 1,
),
);
}
String formatNumber(int number) {
return number.toString().padLeft(2, '0');
}
}

View File

@ -1,6 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart'; import 'package:skywood/main/lib_file.dart';
import 'package:skywood/pages/like/pages/collection_page.dart';
import 'package:skywood/pages/like/pages/play_log_page.dart';
import 'package:skywood/pages/like/widget/like_header.dart';
import 'package:skywood/pages/widgets/common_bg_page.dart';
import 'package:skywood/pages/widgets/pixel_widgets.dart'; import 'package:skywood/pages/widgets/pixel_widgets.dart';
import 'package:skywood/utils/extend/app_keep_alive.dart';
class LikePage extends StatefulWidget { class LikePage extends StatefulWidget {
const LikePage({super.key}); const LikePage({super.key});
@ -13,6 +18,7 @@ class LikePage extends StatefulWidget {
class _MainRootState extends State<LikePage> { class _MainRootState extends State<LikePage> {
int _tabIndex = 0; int _tabIndex = 0;
final _pageController = PageController(initialPage: 0);
final List<String> _collectionTitles = const [ final List<String> _collectionTitles = const [
'Undercover Play', 'Undercover Play',
@ -25,32 +31,44 @@ class _MainRootState extends State<LikePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return CommonBgPage(
body: PixelPageBackground( child: SafeArea(
child: SafeArea(
bottom: false, bottom: false,
child: Padding( child: Padding(
padding: EdgeInsets.fromLTRB(15.w, 44.h, 15.w, 0), padding: EdgeInsets.only(left: 15.w, right: 15.w, top: 20.h, bottom: 10.h+50.h),
child: PixelPanel( child: Stack(
padding: EdgeInsets.fromLTRB(10.w, 18.h, 10.w, 22.h), children: [
child: Column( HelpImg.asset(ConstImg.likeBg, width: AppScreen.screenWidth - 30.w, height: 700.h,
children: [ fit: BoxFit.fill,
_buildTabs(), ),
SizedBox(height: 20.h), Padding(
Expanded( padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
child: AnimatedSwitcher( child: Column(
duration: const Duration(milliseconds: 180), children: [
child: _tabIndex == 0 Padding(
? _buildCollection() padding: const EdgeInsets.only(left: 10, top: 10),
: _buildPlayLog(), child: LikeHeader(
), onSelectedIndex: (index) {
_pageController.jumpToPage(index);
},
),
),
//
Expanded(
child: PageView(
controller: _pageController,
children: [
AppKeepAlive(child: CollectionPage()),
AppKeepAlive(child: PlayLogPage())
],
),
)
],
), ),
], ),
), ],
), ),
), )),
),
),
); );
} }

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
class CollectionPage extends StatefulWidget {
const CollectionPage({super.key});
@override
State<StatefulWidget> createState() {
return _CollectionPageState();
}
}
class _CollectionPageState extends State<CollectionPage> {
@override
Widget build(BuildContext context) {
return GridView.builder(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 10.h),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 4,
crossAxisSpacing: 10,
childAspectRatio: 100 / 174.0
),
itemBuilder: (context, index) {
return _buildItem(index);
},
itemCount: 5,
);
}
Widget _buildItem(int index) {
return Stack(
children: [
HelpImg.asset(ConstImg.collectionBg, width: 100.w, height: 174.w, fit: BoxFit.fill,),
],
);
}
}

View File

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/utils/tools/widgets/sky_common_widget.dart';
class PlayLogPage extends StatefulWidget {
const PlayLogPage({super.key});
@override
State<StatefulWidget> createState() {
return _CollectionPageState();
}
}
class _CollectionPageState extends State<PlayLogPage> {
@override
Widget build(BuildContext context) {
return ListView.separated(
padding: const EdgeInsets.only(top: 20, left: 12, right: 12),
itemCount: 5,
itemBuilder: (context, index) {
return index == 0 ? _buildFirstItem() : _buildNormalItem();
},
separatorBuilder: (context, index) {
return SizedBox(height: 5);
}
);
}
Widget _buildNormalItem() {
return Stack(
children: [
HelpImg.asset(ConstImg.playlogBg, width: double.infinity, height: 90, fit: BoxFit.fill),
_ProgressLine(value: 0.2)
],
);
}
Widget _buildFirstItem() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HelpImg.asset(ConstImg.playlogBg, width: double.infinity, height: 140, fit: BoxFit.fill),
const SizedBox(height: 20),
SkyText(text: 'Watch History',
color: CommonColor.black,
fontSize: 16,
fontWeight: FontWeight.bold,
)
],
);
}
}
class _ProgressLine extends StatelessWidget {
const _ProgressLine({required this.value});
final double value;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Container(
height: 6.h,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.7),
border: Border.all(color: const Color(0xFF1C1E1F), width: 0.5.w),
borderRadius: BorderRadius.circular(10.r),
),
alignment: Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: value,
child: Container(
decoration: BoxDecoration(
color: const Color(0xFF00EE02),
borderRadius: BorderRadius.circular(10.r),
),
),
),
),
),
SizedBox(width: 4.w),
Text('${(value * 100.0).toInt()}%', style: TextStyle(fontSize: 10.sp, height: 1)),
],
);
}
}

View File

@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/utils/tools/widgets/sky_common_widget.dart';
class LikeHeader extends StatefulWidget {
const LikeHeader({super.key,
required this.onSelectedIndex,
});
final Function(int) onSelectedIndex;
@override
State<StatefulWidget> createState() {
return _LikeHeaderState();
}
}
class _LikeHeaderState extends State<LikeHeader> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 55,
child: Row(
children: [
_buildCollectionBg(0, 'My collection'),
_buildCollectionBg(1, 'Play Log'),
],
),
);
}
// shadows: [Shadow(offset: Offset(1, 1), blurRadius: 0, color: Color(0xFF040408).withOpacity(1.00))],
Widget _buildCollectionBg(int index, String title) {
String img = index == _selectedIndex ? ConstImg.likeSelectedBg : ConstImg.likeUnselectBg;
return SizedBox(
width: 155.w, height: 55,
child: Stack(
children: [
HelpImg.asset(img, width: 155.w, height: 55, fit: BoxFit.fill),
Positioned(
right: 22, bottom: 12, left: 28,
child: SkyText(text: title,
fontSize: 14,
fontWeight: FontWeight.w800,
color: index == _selectedIndex ? Colors.white : CommonColor.withValue('B1B1B1'),
shadow: index == _selectedIndex
? Shadow(offset: Offset(1, 1), blurRadius: 0, color: Color(0xFF040408).withOpacity(1.00))
: null,
textAlign: TextAlign.center,
),
)
],
),
).addInkWell(onTap: () {
if (_selectedIndex == index) return;
setState(() {
_selectedIndex = index;
});
widget.onSelectedIndex(index);
},);
}
_buildLogBg() {
return Container(
width: 160.w, height: 55,
child: HelpImg.asset(ConstImg.playlogBg, fit: BoxFit.fill),
).addInkWell(onTap: () {
if (_selectedIndex == 1) return;
setState(() {
_selectedIndex = 1;
});
},);
}
}

View File

@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/pages/widgets/pixel_widgets.dart';
import 'package:skywood/utils/tools/widgets/sky_app_bar.dart';
import 'package:skywood/utils/tools/widgets/sky_common_widget.dart';
import '../../../utils/tools/widgets/sky_text_field.dart';
class ResultPage extends StatefulWidget {
const ResultPage({super.key,
required this.keyword,
});
final String keyword;
@override
State<StatefulWidget> createState() {
return _ResultPageState();
}
}
class _ResultPageState extends State<ResultPage> {
List<String> _searchHistory = [];
final _searchController = TextEditingController();
final _searchFocus = FocusNode();
@override
void initState() {
super.initState();
_searchController.text = widget.keyword;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: SkyAppBar(
titleWidget: SkyTextField(
controller: _searchController,
focusNode: _searchFocus,
border: Border.all(width: 0.6, color: CommonColor.black),
borderRadius: BorderRadius.circular(20),
width: 300, height: 36,
placeholder: 'Search',
prefix: HelpImg.asset(ConstImg.search, width: 14.w, height: 14.w),
textInputAction: TextInputAction.search,
onEditingComplete: () {
print('object');
_searchFocus.unfocus();
if (_searchController.text.trim().isEmpty) return;
// _onGoSearch(_searchController.text);
},
),
),
body: ListView.separated(
itemBuilder: (context, index) => ResultItemView(),
separatorBuilder: (context, index) => SizedBox(height: 12),
itemCount: 2
),
);
}
}
class ResultItemView extends StatelessWidget {
const ResultItemView({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 140.h,
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
children: [
PosterImage(width: 105.w, height: 140.h),
const SizedBox(width: 10),
Expanded(child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SkyText(text: 'Love in the City of Twilight',
fontSize: 14,
fontWeight: FontWeight.bold,
color: CommonColor.black,
),
const SizedBox(height: 12),
_tagView(),
const SizedBox(height: 12),
SkyText(text: 'Haunted by fading memories, a man navigates a labyrinth of dreams and reality ds dsajkj',
fontSize: 11,
color: CommonColor.withValue('4D4047'),
maxLines: 2,
),
Spacer(),
Row(
children: [
_tipView(true, text: '10.0'),
const SizedBox(width: 15),
_tipView(false, text: '10.2k'),
],
)
],
))
],
),
);
}
Widget _tipView(bool isStar, {required String text}) {
String imgName = isStar ? ConstImg.star : ConstImg.look;
return Row(
children: [
HelpImg.asset(imgName, width: 12.w, height: 12.w),
const SizedBox(width: 2),
SkyText(text: text, fontSize: 10, color: Colors.grey)
],
);
}
Widget _tagView() {
return Container(
padding: const EdgeInsets.only(top: 3, left: 6, right: 6, bottom: 4),
decoration: ShapeDecoration(
color: const Color(0xFFEBFFE5),
shape: RoundedRectangleBorder(
side: BorderSide(
width: 0.50,
color: const Color(0xFF9BE89C),
),
borderRadius: BorderRadius.circular(1),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 10,
children: [
Text(
'Satisfying',
style: TextStyle(
color: const Color(0xFF040408) /* 主题黑 */,
fontSize: 10,
fontFamily: 'Inter',
fontWeight: FontWeight.w400,
height: 1,
),
),
],
),
);
}
}

View File

@ -0,0 +1,209 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/pages/search/page/result_page.dart';
import 'package:skywood/pages/search/widget/search_util.dart';
import 'package:skywood/pages/search/widget/search_view.dart';
import 'package:skywood/pages/widgets/pixel_widgets.dart';
import 'package:skywood/utils/tools/help_nav.dart';
import 'package:skywood/utils/tools/widgets/sky_app_bar.dart';
import 'package:skywood/utils/tools/widgets/sky_text_field.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<StatefulWidget> createState() {
return _MainRootState();
}
}
class _MainRootState extends State<SearchPage> {
List<String> _searchHistory = [];
final _searchController = TextEditingController();
final _searchFocus = FocusNode();
final List<String> _titles = const [
"Mr. Gu's Sweet Weakness",
'Secret Wife with a Scar',
'A Second Chance at Love',
'Shadows of the Heart',
'Boarding Pass To Love',
'The Love Blueprint',
'Who Dared Touch My Bride',
'Famous Families Fled',
];
Future<void> _loadSearchHistory() async {
final list = await SearchUtil.getSearchList();
if (!mounted) return;
setState(() {
_searchHistory = list;
});
}
Future<void> _onGoSearch(String keyword, {bool isTapKey = false}) async {
if (isTapKey == true) {
} else {
await SearchUtil.addSearch(keyword);
await _loadSearchHistory();
}
HelpNav.to(ResultPage(
keyword: keyword,
));
//
}
@override
void initState() {
super.initState();
_loadSearchHistory();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: SkyAppBar(
showBackButton: false,
titleWidget: SkyTextField(
controller: _searchController,
focusNode: _searchFocus,
border: Border.all(width: 0.6, color: CommonColor.black),
borderRadius: BorderRadius.circular(20),
width: 300, height: 36,
placeholder: 'Search',
prefix: HelpImg.asset(ConstImg.search, width: 14.w, height: 14.w),
textInputAction: TextInputAction.search,
onEditingComplete: () {
print('object');
_searchFocus.unfocus();
if (_searchController.text.trim().isEmpty) return;
_onGoSearch(_searchController.text);
},
),
),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(child: _buildHistory()),
SliverToBoxAdapter(child: SizedBox(height: 30.h)),
SliverToBoxAdapter(child: _buildTitle('Trending Now')),
SliverToBoxAdapter(child: SizedBox(height: 10.h)),
SliverToBoxAdapter(child: _buildTrendingList()),
SliverToBoxAdapter(child: SizedBox(height: 20.h)),
],
),
);
}
Widget _buildHistory() {
return SearchView(
searchHistory: _searchHistory,
onTapClear: () {
_searchHistory.clear();
setState(() {});
},
onTapHistory: (value) {
print('object: $value');
HelpNav.to(ResultPage(keyword: value));
},
);
}
Widget _buildTitle(String title, {double fontSize = 18}) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Text(
title,
style: TextStyle(
color: CommonColor.black,
fontSize: fontSize.sp,
fontWeight: FontWeight.w700,
height: 1.1,
),
),
);
}
Widget _buildTrendingList() {
return SizedBox(
height: 446.h,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Wrap(
direction: Axis.vertical,
spacing: 10.h,
runSpacing: 10.w,
children: _titles
.map((title) => _TrendingItem(title: title))
.toList(),
),
),
);
}
}
class _TrendingItem extends StatelessWidget {
const _TrendingItem({required this.title});
final String title;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 236.w,
height: 104.h,
child: Row(
children: [
const PosterImage(
width: 80,
height: 104,
borderRadius: 4,
showBorder: false,
),
SizedBox(width: 8.w),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 10.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: CommonColor.black,
fontSize: 13.sp,
fontWeight: FontWeight.bold,
height: 1.2,
),
),
Row(
children: [
HelpImg.asset(ConstImg.hot, width: 15.w, height: 15.w),
SizedBox(width: 2.w),
Text(
'17.3K',
style: TextStyle(
color: const Color(0x80000000),
fontSize: 13.w,
height: 1,
),
),
],
),
],
),
),
),
],
),
);
}
}

View File

@ -1,234 +0,0 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/pages/widgets/pixel_widgets.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<StatefulWidget> createState() {
return _MainRootState();
}
}
class _MainRootState extends State<SearchPage> {
final List<String> _titles = const [
"Mr. Gu's Sweet Weakness",
'Secret Wife with a Scar',
'A Second Chance at Love',
'Shadows of the Heart',
'Boarding Pass To Love',
'The Love Blueprint',
'Who Dared Touch My Bride',
'Famous Families Fled',
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
bottom: false,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(child: _buildSearchBar()),
SliverToBoxAdapter(child: SizedBox(height: 16.h)),
SliverToBoxAdapter(child: _buildHistory()),
SliverToBoxAdapter(child: SizedBox(height: 30.h)),
SliverToBoxAdapter(child: _buildTitle('Trending Now')),
SliverToBoxAdapter(child: SizedBox(height: 10.h)),
SliverToBoxAdapter(child: _buildTrendingList()),
SliverToBoxAdapter(child: SizedBox(height: 120.h)),
],
),
),
);
}
Widget _buildSearchBar() {
return Padding(
padding: EdgeInsets.fromLTRB(15.w, 10.h, 15.w, 0),
child: Row(
children: [
Container(
width: 34.w,
height: 34.w,
decoration: BoxDecoration(
color: const Color(0xFFB6FFA7),
border: Border.all(color: CommonColor.black, width: 1.w),
boxShadow: const [
BoxShadow(color: Color(0xFF91F473), offset: Offset(2, 2)),
],
),
child: Icon(
Icons.arrow_back_ios_new,
size: 16.w,
color: CommonColor.black,
),
),
SizedBox(width: 10.w),
Expanded(
child: Container(
height: 36.h,
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
border: Border.all(color: CommonColor.black, width: 1.w),
borderRadius: BorderRadius.circular(40.r),
),
child: Row(
children: [
Icon(Icons.search, size: 16.w, color: CommonColor.black),
SizedBox(width: 7.w),
Text(
'Search',
style: TextStyle(
color: const Color(0xFF3E3D40),
fontSize: 12.sp,
height: 1,
),
),
],
),
),
),
],
),
);
}
Widget _buildHistory() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: _buildTitle('Search History', fontSize: 14)),
Icon(
Icons.delete_outline,
size: 18.w,
color: const Color(0xFF888888),
),
],
),
SizedBox(height: 10.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
decoration: BoxDecoration(
color: const Color(0x0D000000),
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
'Love',
style: TextStyle(
color: const Color(0x80000000),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
height: 1,
),
),
),
],
),
);
}
Widget _buildTitle(String title, {double fontSize = 18}) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Text(
title,
style: TextStyle(
color: CommonColor.black,
fontSize: fontSize.sp,
fontWeight: FontWeight.w700,
height: 1.1,
),
),
);
}
Widget _buildTrendingList() {
return SizedBox(
height: 446.h,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Wrap(
direction: Axis.vertical,
spacing: 10.h,
runSpacing: 10.w,
children: _titles
.map((title) => _TrendingItem(title: title))
.toList(),
),
),
);
}
}
class _TrendingItem extends StatelessWidget {
const _TrendingItem({required this.title});
final String title;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 236.w,
height: 104.h,
child: Row(
children: [
const PosterImage(
width: 80,
height: 104,
borderRadius: 4,
showBorder: false,
),
SizedBox(width: 8.w),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 10.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: CommonColor.black,
fontSize: 13.sp,
fontWeight: FontWeight.w500,
height: 1.2,
),
),
Row(
children: [
Icon(
Icons.local_fire_department,
size: 15.w,
color: const Color(0xFFFF6A00),
),
SizedBox(width: 2.w),
Text(
'17.3K',
style: TextStyle(
color: const Color(0x80000000),
fontSize: 10.sp,
height: 1,
),
),
],
),
],
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,63 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../utils/tools/app_sp.dart';
/// SharedPreferenceskey [AppSp] `.search.history`
class SearchUtil {
SearchUtil._();
static const String _storageKey = 'sky.search.history';
static const int _maxHistory = 20;
static Future<List<String>> _readList(SharedPreferences prefs) async {
final raw = prefs.getString(_storageKey);
if (raw == null || raw.isEmpty) return [];
try {
final decoded = jsonDecode(raw);
if (decoded is! List) return [];
return decoded.map((e) => e.toString()).where((s) => s.isNotEmpty).toList();
} catch (_) {
return [];
}
}
static Future<void> _writeList(SharedPreferences prefs, List<String> list) async {
await prefs.setString(_storageKey, jsonEncode(list));
}
///
static Future<List<String>> getSearchList() async {
final prefs = await AppSp.getInstance();
return _readList(prefs);
}
///
static Future<void> addSearch(String keyword) async {
final q = keyword.trim();
if (q.isEmpty) return;
final prefs = await AppSp.getInstance();
final list = await _readList(prefs);
list.removeWhere((e) => e == q);
list.insert(0, q);
if (list.length > _maxHistory) {
list.removeRange(_maxHistory, list.length);
}
await _writeList(prefs, list);
}
///
static Future<void> deleteSearch(String keyword) async {
final prefs = await AppSp.getInstance();
final list = await _readList(prefs);
list.removeWhere((e) => e == keyword);
await _writeList(prefs, list);
}
///
static Future<void> clearAllSearch() async {
final prefs = await AppSp.getInstance();
await prefs.remove(_storageKey);
}
}

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:skywood/global/const/common_color.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/utils/tools/widgets/sky_common_widget.dart';
class SearchView extends StatelessWidget {
const SearchView({super.key,
required this.searchHistory,
this.onTapHistory,
this.onTapClear,
});
final List<String> searchHistory;
final ValueChanged<String>? onTapHistory;
final Function()? onTapClear;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: SkyText(text: 'Search History',
fontSize: 14,
color: CommonColor.black,
fontWeight: FontWeight.bold,
lineHeight: 1,
)),
IconButton(onPressed: () {
onTapClear?.call();
}, icon: HelpImg.asset(ConstImg.delete, width: 16.w, height: 16.w)),
],
),
Wrap(
spacing: 16.w,
runSpacing: 14.h,
children: searchHistory
.map(
(item) => _HistoryChip(
text: item,
onTap: () => onTapHistory?.call(item),
),
)
.toList(),
),
],
),
);
}
}
class _HistoryChip extends StatelessWidget {
const _HistoryChip({
required this.text,
this.onTap,
});
final String text;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
decoration: BoxDecoration(
color: const Color(0x0D000000),
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
text,
style: TextStyle(
color: const Color(0x80000000),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
height: 1,
),
),
),
);
}
}

View File

@ -32,7 +32,33 @@ class ConstImg {
// splash_page // splash_page
static const String splashPage = "assets/imgs/splash_page.png"; static const String splashPage = "assets/imgs/splash_page.png";
// heart // heart
static const String heart = "assets/images/heart.png"; static const String heart = "assets/imgs/heart.png";
// hot_bg1
static const String hotBg1 = "assets/imgs/hot_bg1.png";
static const String hotBg2 = "assets/imgs/hot_bg2.png";
// home_rank_bg
static const String homeRankBg = "assets/imgs/home_rank_bg.png";
// yellow_bg purple red
static const String yellowBg = "assets/imgs/yellow_bg.png";
static const String purpleBg = "assets/imgs/purple_bg.png";
static const String redBg = "assets/imgs/red_bg.png";
/// pics
static const String delete = "assets/pics/delete.png";
static const String back = "assets/pics/back.png";
static const String search = "assets/pics/search_icon.png";
static const String hot = "assets/pics/hot.png";
static const String star = "assets/pics/star.png";
static const String look = "assets/pics/look.png";
// bgs
static const String likeBg = "assets/pics/like_bg.png";
static const String collectionBg = "assets/pics/collection_bg.png";
static const String playlogBg = "assets/pics/playlog_bg.png";
// like_selected_bg
static const String likeSelectedBg = "assets/pics/like_selected_bg.png";
static const String likeUnselectBg = "assets/pics/like_unselect_bg.png";
static const String continueWatch = "assets/pics/continue_watch.png";
// nodata // nodata
static const String noData = "assets/images/nodata.png"; static const String noData = "assets/images/nodata.png";

View File

@ -43,11 +43,11 @@ class SkyTimeTool {
if (value < 1000) { if (value < 1000) {
return addSeparator(value.toStringAsFixed(0)); return addSeparator(value.toStringAsFixed(0));
} else if (value < 1000000) { } else if (value < 1000000) {
final formatted = (value / 1000).toStringAsFixed(decimalDigits); final formatted = (value / 10000).toStringAsFixed(decimalDigits);
return '${removeTrailingZeros(formatted)}K'; return '${removeTrailingZeros(formatted)}w';
} else { } else {
final formatted = (value / 1000000).toStringAsFixed(decimalDigits); final formatted = (value / 1000000).toStringAsFixed(decimalDigits);
return '${removeTrailingZeros(formatted)}M'; return '${removeTrailingZeros(formatted)}m';
} }
} }
} }

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:skywood/main/lib_file.dart';
import 'package:skywood/utils/tools/widgets/sky_common_widget.dart';
import '../help_nav.dart';
class SkyAppBar extends StatelessWidget implements PreferredSizeWidget {
const SkyAppBar({
super.key,
this.title,
this.titleWidget,
this.leading,
this.actions,
this.onBack,
this.showBackButton = true,
this.centerTitle = true,
this.backgroundColor = Colors.white,
this.foregroundColor,
this.elevation = 0,
this.bottom,
this.toolbarHeight = kToolbarHeight,
});
final String? title;
final Widget? titleWidget;
final Widget? leading;
final List<Widget>? actions;
final VoidCallback? onBack;
final bool showBackButton;
final bool centerTitle;
final Color? backgroundColor;
final Color? foregroundColor;
final double elevation;
final PreferredSizeWidget? bottom;
final double toolbarHeight;
@override
Size get preferredSize =>
Size.fromHeight(toolbarHeight + (bottom?.preferredSize.height ?? 0));
@override
Widget build(BuildContext context) {
return _buildNormalAppBar(context);
}
PreferredSizeWidget _buildNormalAppBar(BuildContext context) {
final canPop = Navigator.of(context).canPop();
Widget? resolvedLeading = leading;
if (resolvedLeading == null && showBackButton && canPop) {
resolvedLeading = IconButton(
icon: HelpImg.asset(ConstImg.back, width: 32),
onPressed: onBack ?? () => HelpNav.back(),
);
}
return AppBar(
title: titleWidget ?? (title != null
? SkyText(text: title!, fontSize: 18, fontWeight: FontWeight.bold)
: null),
leading: resolvedLeading,
actions: actions,
centerTitle: centerTitle,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
elevation: elevation,
scrolledUnderElevation: 0,
toolbarHeight: toolbarHeight,
);
}
}

View File

@ -0,0 +1,131 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../global/const/common_color.dart';
class SkyTextField extends StatefulWidget {
const SkyTextField({
super.key,
this.controller,
this.focusNode,
this.borderRadius = const BorderRadius.all(Radius.circular(7)),
this.padding = const EdgeInsets.only(left: 10, right: 10, top: 2, bottom: 2),
this.placeholder,
this.clearButtonMode = OverlayVisibilityMode.never,
this.prefix,
this.suffix,
this.keyboardType,
this.fontSize,
this.maxLines = 1,
this.maxLength,
this.onChanged,
this.inputFormatters,
this.height = 40.0,
this.width,
this.obscureText = false,
this.bgColor,
this.textInputAction,
this.onSubmitted,
this.onTap,
this.readOnly,
this.border,
this.onEditingComplete,
this.textColor,
this.showBottomBorder,
});
final Function()? onEditingComplete;
final Color? textColor;
final double? width;
final bool? readOnly;
final GestureTapCallback? onTap;
final ValueChanged<String>? onSubmitted;
final TextInputAction? textInputAction;
final TextEditingController? controller;
final FocusNode? focusNode;
final BorderRadius borderRadius;
final Border? border;
final EdgeInsetsGeometry padding;
final String? placeholder;
// final Color? placeholderColor;
final OverlayVisibilityMode clearButtonMode;
final Widget? prefix;
final Widget? suffix;
final TextInputType? keyboardType;
final double? fontSize;
final int? maxLines;
final int? maxLength;
final ValueChanged<String>? onChanged;
final List<TextInputFormatter>? inputFormatters;
final double? height;
final bool obscureText;
final Color? bgColor;
final bool? showBottomBorder;
@override
State<StatefulWidget> createState() {
return _ComTextFieldState();
}
}
class _ComTextFieldState extends State<SkyTextField> {
@override
Widget build(BuildContext context) {
return Container(
height: widget.height, width: widget.width,
decoration: BoxDecoration(
color: widget.bgColor ?? Colors.white,
borderRadius: widget.borderRadius,
border: widget.border
),
padding: widget.padding,
child: Row(
children: <Widget>[
Expanded(
child: CupertinoTextField(
controller: widget.controller,
readOnly: widget.readOnly ?? false,
focusNode: widget.focusNode,
decoration: widget.showBottomBorder==true ? BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey.shade400,
width: 0.5,
),
),
) : BoxDecoration(),
placeholder: widget.placeholder,
placeholderStyle: TextStyle(
fontSize: widget.fontSize ?? 14,
color: CommonColor.withValue('#707488'),
),
style: TextStyle(
fontSize: widget.fontSize ?? 14,
color: widget.textColor ?? Colors.black,
),
prefix: widget.prefix,
clearButtonMode: widget.clearButtonMode,
keyboardType: widget.keyboardType,
maxLines: widget.maxLines,
maxLength: widget.maxLength,
onChanged: widget.onChanged,
inputFormatters: widget.inputFormatters,
obscureText: widget.obscureText,
textInputAction: widget.textInputAction ?? TextInputAction.done,
onSubmitted: widget.onSubmitted,
onTap: widget.onTap,
onEditingComplete: widget.onEditingComplete,
)),
if (widget.suffix != null) widget.suffix!,
],
),
);
}
}

View File

@ -71,6 +71,8 @@ flutter:
assets: assets:
- assets/common/ - assets/common/
- assets/imgs/ - assets/imgs/
- assets/pics/
- assets/mine/
# - 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