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/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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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';
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
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: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: [
|
||||||
|
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(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildTabs(),
|
Padding(
|
||||||
SizedBox(height: 20.h),
|
padding: const EdgeInsets.only(left: 10, top: 10),
|
||||||
|
child: LikeHeader(
|
||||||
|
onSelectedIndex: (index) {
|
||||||
|
_pageController.jumpToPage(index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 主页面
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AnimatedSwitcher(
|
child: PageView(
|
||||||
duration: const Duration(milliseconds: 180),
|
controller: _pageController,
|
||||||
child: _tabIndex == 0
|
children: [
|
||||||
? _buildCollection()
|
AppKeepAlive(child: CollectionPage()),
|
||||||
: _buildPlayLog(),
|
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
|
// 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";
|
||||||
|
|||||||
@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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:
|
||||||
- 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
|
||||||
|
|||||||