feat: UI pages
BIN
assets/imgs/home_rank_bg.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/imgs/hot_bg1.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/imgs/hot_bg2.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/imgs/purple_bg.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/imgs/red_bg.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/imgs/yellow_bg.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
assets/pics/back.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/pics/collection_bg.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/pics/continue_watch.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/pics/delete.png
Normal file
|
After Width: | Height: | Size: 659 B |
BIN
assets/pics/hot.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/pics/like_bg.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/pics/like_selected_bg.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
assets/pics/like_unselect_bg.png
Normal file
|
After Width: | Height: | Size: 905 B |
BIN
assets/pics/look.png
Normal file
|
After Width: | Height: | Size: 527 B |
BIN
assets/pics/playlog_bg.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/pics/search_icon.png
Normal file
|
After Width: | Height: | Size: 706 B |
BIN
assets/pics/star.png
Normal file
|
After Width: | Height: | Size: 527 B |
@ -1,6 +1,8 @@
|
||||
|
||||
import 'package:skywood/global/request/api_const.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';
|
||||
|
||||
class PlayClient {
|
||||
@ -18,4 +20,22 @@ class PlayClient {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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/help_img.dart';
|
||||
export 'package:skywood/utils/tools/help_nav.dart';
|
||||
|
||||
export 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
@ -3,7 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:skywood/pages/home/home_page.dart';
|
||||
import 'package:skywood/pages/like/like_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';
|
||||
|
||||
class MainRoot extends StatefulWidget {
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:skywood/global/client/play_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/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/utils/models/drama/skywood_category.dart';
|
||||
import 'package:skywood/utils/models/drama/skywood_home.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
@ -18,22 +23,19 @@ class HomePage extends StatefulWidget {
|
||||
class _MainRootState extends State<HomePage> {
|
||||
|
||||
List<SkywoodHome> _homeData = [];
|
||||
List<SkywoodCategory> _categories = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_getHomeData();
|
||||
// _getHomeData();
|
||||
// _getDramaCate();
|
||||
}
|
||||
|
||||
void _getHomeData() {
|
||||
// home_v3_recommand
|
||||
// new_recommand
|
||||
// week_ranking
|
||||
// week_highest_recommend
|
||||
// marquee
|
||||
// home_banner
|
||||
// get_details_recommand
|
||||
// home_v3_recommand new_recommand week_ranking week_highest_recommend
|
||||
// marquee home_banner get_details_recommand
|
||||
PlayClient.getHomeData().then((value) {
|
||||
for (SkywoodHome element in value) {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
@ -67,11 +78,31 @@ class _MainRootState extends State<HomePage> {
|
||||
),
|
||||
_buildSpace(height: 10),
|
||||
_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),
|
||||
_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),
|
||||
_buildSpace(height: 300),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 15.w),
|
||||
sliver: HomeCateWidget(
|
||||
categories: _categories,
|
||||
),
|
||||
),
|
||||
_buildSpace(height: 100),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -20,7 +20,8 @@ class DailyQuestWidget extends StatelessWidget {
|
||||
double height = 281.h;
|
||||
double width = 345.w;
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 15.w),
|
||||
width: width,
|
||||
height: height,
|
||||
child: Stack(
|
||||
@ -66,6 +67,7 @@ class DailyQuestWidget extends StatelessWidget {
|
||||
),
|
||||
child: HelpImg.network(drama.imageUrl, width: 300.w, height: 160.h, fit: BoxFit.cover),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -86,7 +88,8 @@ class DailyQuestWidget extends StatelessWidget {
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
SizedBox(height: 3.h),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
35
lib/pages/home/widgets/home_cate_widget.dart
Normal 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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/pages/home/widgets/hot_cartridge_widget.dart
Normal 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)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
33
lib/pages/home/widgets/like_num_widget.dart
Normal 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,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
83
lib/pages/home/widgets/rank_list_widget.dart
Normal 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');
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,11 @@
|
||||
import 'package:flutter/material.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/utils/extend/app_keep_alive.dart';
|
||||
|
||||
class LikePage extends StatefulWidget {
|
||||
const LikePage({super.key});
|
||||
@ -13,6 +18,7 @@ class LikePage extends StatefulWidget {
|
||||
|
||||
class _MainRootState extends State<LikePage> {
|
||||
int _tabIndex = 0;
|
||||
final _pageController = PageController(initialPage: 0);
|
||||
|
||||
final List<String> _collectionTitles = const [
|
||||
'Undercover Play',
|
||||
@ -25,32 +31,44 @@ class _MainRootState extends State<LikePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: PixelPageBackground(
|
||||
child: SafeArea(
|
||||
return CommonBgPage(
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(15.w, 44.h, 15.w, 0),
|
||||
child: PixelPanel(
|
||||
padding: EdgeInsets.fromLTRB(10.w, 18.h, 10.w, 22.h),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildTabs(),
|
||||
SizedBox(height: 20.h),
|
||||
Expanded(
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
child: _tabIndex == 0
|
||||
? _buildCollection()
|
||||
: _buildPlayLog(),
|
||||
),
|
||||
padding: EdgeInsets.only(left: 15.w, right: 15.w, top: 20.h, bottom: 10.h+50.h),
|
||||
child: Stack(
|
||||
children: [
|
||||
HelpImg.asset(ConstImg.likeBg, width: AppScreen.screenWidth - 30.w, height: 700.h,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10, top: 10),
|
||||
child: LikeHeader(
|
||||
onSelectedIndex: (index) {
|
||||
_pageController.jumpToPage(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
// 主页面
|
||||
Expanded(
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
children: [
|
||||
AppKeepAlive(child: CollectionPage()),
|
||||
AppKeepAlive(child: PlayLogPage())
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
41
lib/pages/like/pages/collection_page.dart
Normal 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,),
|
||||
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
92
lib/pages/like/pages/play_log_page.dart
Normal 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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
79
lib/pages/like/widget/like_header.dart
Normal 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;
|
||||
});
|
||||
},);
|
||||
}
|
||||
}
|
||||
155
lib/pages/search/page/result_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
209
lib/pages/search/page/search_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
63
lib/pages/search/widget/search_util.dart
Normal file
@ -0,0 +1,63 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../../utils/tools/app_sp.dart';
|
||||
|
||||
/// 本地搜索历史(SharedPreferences),key 与 [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);
|
||||
}
|
||||
}
|
||||
89
lib/pages/search/widget/search_view.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,33 @@ class ConstImg {
|
||||
// splash_page
|
||||
static const String splashPage = "assets/imgs/splash_page.png";
|
||||
// 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
|
||||
static const String noData = "assets/images/nodata.png";
|
||||
|
||||
@ -43,11 +43,11 @@ class SkyTimeTool {
|
||||
if (value < 1000) {
|
||||
return addSeparator(value.toStringAsFixed(0));
|
||||
} else if (value < 1000000) {
|
||||
final formatted = (value / 1000).toStringAsFixed(decimalDigits);
|
||||
return '${removeTrailingZeros(formatted)}K';
|
||||
final formatted = (value / 10000).toStringAsFixed(decimalDigits);
|
||||
return '${removeTrailingZeros(formatted)}w';
|
||||
} else {
|
||||
final formatted = (value / 1000000).toStringAsFixed(decimalDigits);
|
||||
return '${removeTrailingZeros(formatted)}M';
|
||||
return '${removeTrailingZeros(formatted)}m';
|
||||
}
|
||||
}
|
||||
}
|
||||
76
lib/utils/tools/widgets/sky_app_bar.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
131
lib/utils/tools/widgets/sky_text_field.dart
Normal 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!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -71,6 +71,8 @@ flutter:
|
||||
assets:
|
||||
- assets/common/
|
||||
- assets/imgs/
|
||||
- assets/pics/
|
||||
- assets/mine/
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
|
||||