diff --git a/assets/common/loading.gif b/assets/common/loading.gif new file mode 100644 index 0000000..859a595 Binary files /dev/null and b/assets/common/loading.gif differ diff --git a/assets/imgs/box_button.png b/assets/imgs/box_button.png new file mode 100644 index 0000000..0bd9ac9 Binary files /dev/null and b/assets/imgs/box_button.png differ diff --git a/assets/imgs/box_close.png b/assets/imgs/box_close.png new file mode 100644 index 0000000..c68f4ca Binary files /dev/null and b/assets/imgs/box_close.png differ diff --git a/assets/imgs/box_gift.png b/assets/imgs/box_gift.png new file mode 100644 index 0000000..b51daa8 Binary files /dev/null and b/assets/imgs/box_gift.png differ diff --git a/assets/imgs/box_opened.png b/assets/imgs/box_opened.png new file mode 100644 index 0000000..b4b3218 Binary files /dev/null and b/assets/imgs/box_opened.png differ diff --git a/assets/mine/mine_ab.png b/assets/mine/mine_ab.png new file mode 100644 index 0000000..eb01d2f Binary files /dev/null and b/assets/mine/mine_ab.png differ diff --git a/assets/mine/mine_btn_bg.png b/assets/mine/mine_btn_bg.png new file mode 100644 index 0000000..8c9d314 Binary files /dev/null and b/assets/mine/mine_btn_bg.png differ diff --git a/assets/mine/mine_pp.png b/assets/mine/mine_pp.png new file mode 100644 index 0000000..8e964ad Binary files /dev/null and b/assets/mine/mine_pp.png differ diff --git a/assets/mine/mine_ua.png b/assets/mine/mine_ua.png new file mode 100644 index 0000000..0b99593 Binary files /dev/null and b/assets/mine/mine_ua.png differ diff --git a/lib/pages/home/cate_page.dart b/lib/pages/home/cate_page.dart index b9b600d..ff40f7c 100644 --- a/lib/pages/home/cate_page.dart +++ b/lib/pages/home/cate_page.dart @@ -4,11 +4,17 @@ import 'package:skywood/main/lib_file.dart'; import 'package:skywood/pages/home/players/page/drama_detail_page.dart'; import 'package:skywood/utils/models/drama/skywood_category.dart'; import 'package:skywood/utils/models/drama/skywood_drama.dart'; +import 'package:skywood/utils/tools/app_toast.dart'; import 'package:skywood/utils/tools/sky_time_tool.dart'; +import 'package:skywood/utils/tools/widgets/sky_app_bar.dart'; import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; class CatePage extends StatefulWidget { - const CatePage({super.key}); + const CatePage({super.key, + this.preCategory, + }); + + final SkywoodCategory? preCategory; @override State createState() => _CatePageState(); @@ -44,6 +50,14 @@ class _CatePageState extends State { }); if (categories.isNotEmpty) { + if (widget.preCategory != null) { + // 通过id获取_selectedIndex + setState(() { + _selectedIndex = _categories.indexWhere((element) => element.id == widget.preCategory!.id); + }); + _loadCategoryDetail(widget.preCategory!.id); + return; + } _loadCategoryDetail(categories.first.id); } } @@ -55,6 +69,7 @@ class _CatePageState extends State { _dramas.clear(); }); + AppToast.showLottieLoading(); List dramas = []; try { dramas = await PlayClient.getCategoriesDetail(categoryId: categoryId); @@ -62,6 +77,7 @@ class _CatePageState extends State { if (!mounted || _loadingCategoryId != categoryId) return; } + AppToast.dismiss(); if (!mounted || _loadingCategoryId != categoryId) return; setState(() { @@ -74,12 +90,14 @@ class _CatePageState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, + appBar: SkyAppBar( + title: 'Explore Genres', + titleColor: CommonColor.black, + ), body: SafeArea( - bottom: false, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildHeader(), Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -95,38 +113,10 @@ class _CatePageState extends State { ); } - Widget _buildHeader() { - return SizedBox( - height: 49.h, - child: Stack( - children: [ - Positioned( - left: 13.w, - top: 16.h, - child: HelpImg.asset( - ConstImg.back, - width: 24.w, - height: 24.w, - ).addInkWell(onTap: () => HelpNav.back()), - ), - Positioned( - left: 94.w, - top: 22.h, - child: SkyText( - text: 'Explore Genres', - color: CommonColor.black, - fontSize: 14, - fontWeight: FontWeight.w800, - ), - ), - ], - ), - ); - } Widget _buildCategoryList() { return Container( - width: 86.w, + width: 110.w, height: double.infinity, padding: EdgeInsets.only(top: 12.h, bottom: 18.h), decoration: BoxDecoration( @@ -159,31 +149,27 @@ class _CatePageState extends State { _loadCategoryDetail(category.id); }, child: SizedBox( - height: 47.h, + height: 58, child: Stack( alignment: Alignment.center, children: [ if (selected) Positioned( - left: 0, - top: 0, - bottom: 0, + left: 5, top: 16.5, child: HelpImg.asset( ConstImg.cateSelLeft, - width: 20.w, - height: 50.h, + width: 10, + height: 25, fit: BoxFit.fill, ), ), if (selected) Positioned( - right: 0, - top: 0, - bottom: 0, + right: 5, top: 16.5, child: HelpImg.asset( ConstImg.cateSelRight, - width: 20.w, - height: 50.h, + width: 10, + height: 25, fit: BoxFit.fill, ), ), @@ -209,9 +195,7 @@ class _CatePageState extends State { Widget _buildDramaList() { if (_loadingDramas) { - return const Center( - child: CircularProgressIndicator(color: CommonColor.primacyColor), - ); + return const SizedBox(); } if (_dramas.isEmpty) { @@ -243,15 +227,15 @@ class _DramaItem extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( - height: 80.h, + height: 104.h, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ HelpImg.networkRadius( drama.imageUrl, - width: 64.w, - height: 80.h, - fit: BoxFit.cover, + width: 80.w, + height: 104.h, + fit: BoxFit.fill, borderRadius: BorderRadius.circular(4.r), ), SizedBox(width: 8.w), @@ -264,7 +248,7 @@ class _DramaItem extends StatelessWidget { SkyText( text: drama.name, color: CommonColor.black, - fontSize: 11, + fontSize: 13, fontWeight: FontWeight.w700, maxLines: 2, lineHeight: 1.08, @@ -273,7 +257,7 @@ class _DramaItem extends StatelessWidget { SkyText( text: drama.description, color: Colors.black.withAlpha(92), - fontSize: 9, + fontSize: 11, maxLines: 2, lineHeight: 1.2, ), @@ -309,9 +293,9 @@ class _TipView extends StatelessWidget { return Row( mainAxisSize: MainAxisSize.min, children: [ - HelpImg.asset(icon, width: 8.w, height: 8.w), + HelpImg.asset(icon, width: 12.w, height: 12.w), SizedBox(width: 2.w), - SkyText(text: text, color: Colors.black.withAlpha(110), fontSize: 8), + SkyText(text: text, color: Colors.black.withAlpha(110), fontSize: 10), ], ); } diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 4ac45cf..f03273d 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -1,15 +1,23 @@ +import 'dart:math'; + +import 'package:easy_refresh/easy_refresh.dart'; 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/players/page/drama_detail_page.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/search/page/result_page.dart'; import 'package:skywood/pages/widgets/common_bg_page.dart'; +import 'package:skywood/pages/widgets/mystery_box_widget.dart'; +import 'package:skywood/utils/extend/gif_header.dart'; import 'package:skywood/utils/models/drama/skywood_category.dart'; +import 'package:skywood/utils/models/drama/skywood_drama.dart'; import 'package:skywood/utils/models/drama/skywood_home.dart'; +import 'package:skywood/utils/tools/app_toast.dart'; +import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -21,96 +29,126 @@ class HomePage extends StatefulWidget { } class _MainRootState extends State { - List _homeData = []; List _categories = []; + final EasyRefreshController _refreshController = EasyRefreshController( + controlFinishRefresh: true, + ); @override void initState() { super.initState(); - _getHomeData(); - _getDramaCate(); + _refreshHome(); } - void _getHomeData() { + Future _refreshHome() async { // 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}'); - } - setState(() { - _homeData = value; - }); + AppToast.showLottieLoading(); + final homeFuture = PlayClient.getHomeData(); + final categoriesFuture = PlayClient.getCategoriesList(); + final homeData = await homeFuture; + final categories = await categoriesFuture; + if (!mounted) return; + + for (SkywoodHome element in homeData) { + print('lele: ${element.moduleKey} : ${element.dataList.length}'); + } + + + _refreshController.finishRefresh(); + AppToast.dismiss(); + setState(() { + _homeData = homeData; + _categories = categories; }); } - void _getDramaCate() { - PlayClient.getCategoriesList().then((value) { - print('cateeeee: ${value.length}'); - setState(() { - _categories = value; - }); - }); - } - - @override Widget build(BuildContext context) { return CommonBgPage( child: SafeArea( - child: _homeData.isEmpty ? SizedBox() : CustomScrollView( - slivers: [ - _buildTopTwoImgs(), - _buildSpace(), - DailyQuestWidget( - dramaList: _homeData.firstWhere((element) => element.moduleKey == 'home_v3_recommand').dataList, - ), - _buildSpace(height: 10), - _buildTitleImg(ConstImg.homeTitleMystery), - _buildSpace(), - // 345 355 - _buildTitleImg( - ConstImg.homeOpenNowBox, - padding: EdgeInsets.symmetric(horizontal: 15.w), - height: 355.h, - ), - _buildSpace(height: 10), - _buildTitleImg(ConstImg.homeTitleHot), - _buildSpace(), - SliverPadding( - padding: EdgeInsets.symmetric(horizontal: 15.w), - sliver: HotCartridgeWidget( - dramaList: _homeData.firstWhere((element) => element.moduleKey == 'new_recommand').dataList, + child: _homeData.isEmpty + ? const SizedBox() + : EasyRefresh( + controller: _refreshController, + header: const GifHeader(), + onRefresh: _refreshHome, + child: _bodyWidget(), ), - ), - _buildSpace(height: 10), - _buildTitleImg(ConstImg.homeTitleWeekly), - SliverPadding( - padding: EdgeInsets.symmetric(horizontal: 15.w), - sliver: SliverToBoxAdapter( - child: RankListWidget( - dramaList: _homeData.firstWhere((element) => element.moduleKey == 'week_ranking').dataList, - ), - ), - ), - _buildTitleImg(ConstImg.homeTitleHotCate), - SliverPadding( - padding: EdgeInsets.only(left: 15.w, right: 15.w, top: 20.h), - sliver: SliverToBoxAdapter( - child: HomeCateWidget( - categories: _categories, - ), - ), - ), - _buildSpace(height: 20), - ], - ), ), ); } + Widget _bodyWidget() { + return CustomScrollView( + slivers: [ + _buildTopTwoImgs(), + _buildSpace(), + DailyQuestWidget( + dramaList: _homeData + .firstWhere((element) => element.moduleKey == 'home_v3_recommand') + .dataList, + ), + _buildSpace(height: 10), + _buildTitleImg(ConstImg.homeTitleMystery), + _buildSpace(), + // 345 355 + _buildTitleImg( + ConstImg.homeOpenNowBox, + padding: EdgeInsets.symmetric(horizontal: 15.w), + height: 355.h, + onTap: () { + // 随机获取一下视频 week_highest_recommend + List list = _homeData + .firstWhere((element) => element.moduleKey == 'week_highest_recommend') + .dataList; + int max = list.length; + int index = Random().nextInt(max); + HelpNav.showDialog(MysteryBoxWidget( + drama: list[index], + onTap: () { + HelpNav.to(DramaDetailPage(dramaBean: list[index])); + }, + ), barrierDismissible: true); + } + ), + _buildSpace(height: 10), + _buildTitleImg(ConstImg.homeTitleHot), + _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), + SliverPadding( + padding: EdgeInsets.symmetric(horizontal: 15.w), + sliver: SliverToBoxAdapter( + child: RankListWidget( + dramaList: _homeData + .firstWhere((element) => element.moduleKey == 'week_ranking') + .dataList, + ), + ), + ), + _buildTitleImg(ConstImg.homeTitleHotCate), + SliverPadding( + padding: EdgeInsets.only(left: 15.w, right: 15.w, top: 20.h), + sliver: SliverToBoxAdapter( + child: HomeCateWidget(categories: _categories), + ), + ), + _buildSpace(height: 20), + ], + ); + } + SliverToBoxAdapter _buildTopTwoImgs() { // 104-76 return SliverToBoxAdapter( @@ -121,7 +159,10 @@ class _MainRootState extends State { height: 204.h, padding: EdgeInsets.only(left: 25.w, top: 3.h, right: 25.w), child: HelpImg.asset(ConstImg.homeTopBg, fit: BoxFit.fill), - ), + ).addInkWell(onTap: () { + // 去到搜索页面 + HelpNav.to(ResultPage(keyword: '')); + }), Positioned( top: (204 - 28).h, height: 104.h, @@ -141,15 +182,20 @@ class _MainRootState extends State { String imgName, { double? height, double? width, + Function()? onTap, EdgeInsets? padding, }) { // 104-76 return SliverToBoxAdapter( - child: Container( - padding: padding ?? EdgeInsets.zero, - width: width ?? double.infinity, - height: height ?? 104.h, - child: HelpImg.asset(imgName, fit: BoxFit.fill), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Container( + padding: padding ?? EdgeInsets.zero, + width: width ?? double.infinity, + height: height ?? 104.h, + child: HelpImg.asset(imgName, fit: BoxFit.fill), + ), ), ); } diff --git a/lib/pages/home/players/page/drama_detail_page.dart b/lib/pages/home/players/page/drama_detail_page.dart index 11b6925..7253e59 100644 --- a/lib/pages/home/players/page/drama_detail_page.dart +++ b/lib/pages/home/players/page/drama_detail_page.dart @@ -6,6 +6,7 @@ import 'package:skywood/pages/home/players/widget/sky_player_view.dart'; import 'package:skywood/utils/models/drama/skywood_detail.dart'; import 'package:skywood/utils/models/drama/skywood_drama.dart'; import 'package:skywood/utils/models/drama/skywood_home.dart'; +import 'package:skywood/utils/tools/app_toast.dart'; import 'package:skywood/utils/tools/widgets/sky_app_bar.dart'; import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; @@ -34,10 +35,13 @@ class _DramaDetailPageState extends State { } void _getDetailDrama() { + print('currentEpisode===${widget.dramaBean.currentEpisode}'); + AppToast.showLottieLoading(); PlayClient.getVideoDetail( shortPlayId: widget.dramaBean.shortPlayId ?? 0, shortPlayVideoId: widget.dramaBean.shortPlayVideoId ?? 0 ).then((value) { + AppToast.dismiss(); setState(() { _detailBean = value; }); @@ -57,11 +61,10 @@ class _DramaDetailPageState extends State { children: [ SizedBox(width: double.infinity, height: double.infinity), - if (widget.dramaBean.videoUrl != null) + if (_detailBean != null) Positioned( bottom: 0, left: 0, right: 0, top: 0, child: SkyPlayerView( - videoUrl: widget.dramaBean.videoUrl!, detailBean: _detailBean, dramaBean: widget.dramaBean, ), diff --git a/lib/pages/home/players/page/drama_esp_page.dart b/lib/pages/home/players/page/drama_esp_page.dart index f7a0f81..574dffc 100644 --- a/lib/pages/home/players/page/drama_esp_page.dart +++ b/lib/pages/home/players/page/drama_esp_page.dart @@ -6,10 +6,15 @@ import 'package:skywood/utils/models/drama/skywood_episode.dart'; import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; class DramaEspPage extends StatefulWidget { - const DramaEspPage({super.key, required this.detail, this.currentEpisode}); + const DramaEspPage({super.key, + required this.detail, + this.currentEpisode, + this.onSelectedEpisode, + }); final SkywoodDetail detail; final int? currentEpisode; + final Function(int)? onSelectedEpisode; @override State createState() { @@ -182,6 +187,7 @@ class _DramaEspPageState extends State { setState(() { _selectedEpisode = episode.episode; }); + widget.onSelectedEpisode?.call(_selectedEpisode); }, child: Container( alignment: Alignment.center, diff --git a/lib/pages/home/players/widget/sky_player_view.dart b/lib/pages/home/players/widget/sky_player_view.dart index c6003c4..3412ad9 100644 --- a/lib/pages/home/players/widget/sky_player_view.dart +++ b/lib/pages/home/players/widget/sky_player_view.dart @@ -4,22 +4,25 @@ import 'package:skywood/main/lib_file.dart'; import 'package:skywood/pages/home/players/page/drama_esp_page.dart'; import 'package:skywood/utils/models/drama/skywood_detail.dart'; import 'package:skywood/utils/models/drama/skywood_drama.dart'; +import 'package:skywood/utils/models/drama/skywood_episode.dart'; import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; import 'package:video_player/video_player.dart'; /// 视频播放器 class SkyPlayerView extends StatefulWidget { - final String videoUrl; + // final String videoUrl; final bool autoPlay; final SkywoodDetail? detailBean; final SkywoodDrama dramaBean; + final int? currentEpisode; const SkyPlayerView({ super.key, - required this.videoUrl, + // required this.videoUrl, this.autoPlay = true, this.detailBean, required this.dramaBean, + this.currentEpisode, }); @override @@ -36,6 +39,7 @@ class _VideoPlayerWidgetState extends State { Duration _currentPosition = Duration.zero; Duration _totalDuration = Duration.zero; bool _isLiked = false; + SkywoodEpisode? _currentEpisode; @override void initState() { @@ -43,14 +47,14 @@ class _VideoPlayerWidgetState extends State { _initializePlayer(); } - @override - void didUpdateWidget(SkyPlayerView oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.videoUrl != widget.videoUrl) { - _disposeController(); - _initializePlayer(); - } - } + // @override + // void didUpdateWidget(SkyPlayerView oldWidget) { + // super.didUpdateWidget(oldWidget); + // if (oldWidget.videoUrl != widget.videoUrl) { + // _disposeController(); + // _initializePlayer(); + // } + // } Future _initializePlayer() async { setState(() { @@ -62,8 +66,17 @@ class _VideoPlayerWidgetState extends State { _isLiked = widget.dramaBean.isCollect ?? false; }); + int _esp = widget.currentEpisode ?? 0; + String videoUrl = ''; + if (widget.detailBean != null) { + if (widget.detailBean!.episodeList.isNotEmpty) { + videoUrl = widget.detailBean!.episodeList[_esp].videoUrl; + _currentEpisode = widget.detailBean!.episodeList[_esp]; + } + } + final controller = VideoPlayerController.networkUrl( - Uri.parse(widget.videoUrl), + Uri.parse(videoUrl), ); _controller = controller; _controllerCreated = true; @@ -181,8 +194,8 @@ class _VideoPlayerWidgetState extends State { Widget _buildLoadingView() { return Container( color: Colors.black, - child: const Center( - child: CircularProgressIndicator(color: Colors.white), + child: Center( + child: HelpImg.asset(ConstImg.loading, width: 110), ), ); } @@ -211,16 +224,17 @@ class _VideoPlayerWidgetState extends State { } void _collectionDrama() { + if (_currentEpisode == null) return; if (_isLiked) { - PlayClient.cancelCollect(shortPlayId: widget.dramaBean.shortPlayId??0).then((value) { + PlayClient.cancelCollect(shortPlayId: _currentEpisode!.shortPlayId).then((value) { setState(() { _isLiked = value; }); }); } else { PlayClient.collect( - shortPlayId: widget.dramaBean.shortPlayId??0, - videoId: widget.dramaBean.shortPlayVideoId ?? 0, + shortPlayId: _currentEpisode!.shortPlayId, + videoId: _currentEpisode!.shortPlayVideoId, ).then((value) { setState(() { _isLiked = value; @@ -231,7 +245,7 @@ class _VideoPlayerWidgetState extends State { Widget _buildBottomView() { SkywoodDetail detail = widget.detailBean!; - final int currentEpisode = _currentEpisode(detail); + final int currentEpisode = _getCurrentEpisode(detail); final int totalEpisode = _episodeTotal(detail); return Container( @@ -292,6 +306,14 @@ class _VideoPlayerWidgetState extends State { child: DramaEspPage( detail: detail, currentEpisode: currentEpisode, + onSelectedEpisode: (episode) { + + if (widget.detailBean != null) { + if (widget.detailBean!.episodeList.isNotEmpty) { + _currentEpisode = widget.detailBean!.episodeList[episode]; + } + } + }, ), ); }, @@ -339,7 +361,9 @@ class _VideoPlayerWidgetState extends State { child: SliderTheme( data: SliderThemeData( trackHeight: 3, // ✅ 进度条高度 3 - thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 1), + thumbShape: _ImageThumbShape( + imagePath: ConstImg.playTag + ), overlayShape: const RoundSliderOverlayShape(overlayRadius: 3), activeTrackColor: Colors.white, // ✅ 已播放 = 白色 inactiveTrackColor: Colors.white.withAlpha(126), // ✅ 未播放 = 灰色 @@ -379,7 +403,7 @@ class _VideoPlayerWidgetState extends State { return '$minutes:$seconds'; } - int _currentEpisode(SkywoodDetail detail) { + int _getCurrentEpisode(SkywoodDetail detail) { final int? episode = detail.videoInfo?.episode ?? detail.videoInfo?.currentEpisode ?? @@ -394,3 +418,75 @@ class _VideoPlayerWidgetState extends State { return detail.episodeList.length; } } + + + + + +class _ImageThumbShape extends SliderComponentShape { + final String imagePath; + final double imageSize; + + const _ImageThumbShape({ + required this.imagePath, + this.imageSize = 16, + }); + + @override + Size getPreferredSize(bool isEnabled, bool isDiscrete) { + return Size(imageSize, imageSize); + } + + @override + void paint( + PaintingContext context, + Offset center, { + required Animation activationAnimation, + required Animation enableAnimation, + required bool isDiscrete, + required TextPainter labelPainter, + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required TextDirection textDirection, + required double value, + required double textScaleFactor, + required Size sizeWithOverflow, + }) { + // 🔥 使用 Image 直接渲染图片 + final imageWidget = Image.asset( + imagePath, + width: imageSize, + height: imageSize, + ); + + // 使用 WidgetsBinding 确保图片已加载 + final image = imageWidget.image; + final stream = image.resolve(ImageConfiguration()); + + // 添加监听器,但只在图片加载完成后绘制 + stream.addListener(ImageStreamListener((imageInfo, _) { + final rect = Rect.fromCenter( + center: center, + width: imageSize, + height: imageSize, + ); + context.canvas.drawImageRect( + imageInfo.image, + Rect.fromLTWH( + 0, + 0, + imageInfo.image.width.toDouble(), + imageInfo.image.height.toDouble(), + ), + rect, + Paint(), + ); + }, onError: (error, stackTrace) { + // 🔥 如果图片加载失败,绘制一个默认圆点 + final paint = Paint() + ..color = Colors.white + ..style = PaintingStyle.fill; + context.canvas.drawCircle(center, imageSize / 2, paint); + })); + } +} \ No newline at end of file diff --git a/lib/pages/home/widgets/daily_quest_widget.dart b/lib/pages/home/widgets/daily_quest_widget.dart index 4120753..aa00fa0 100644 --- a/lib/pages/home/widgets/daily_quest_widget.dart +++ b/lib/pages/home/widgets/daily_quest_widget.dart @@ -57,7 +57,7 @@ class DailyQuestWidget extends StatelessWidget { onTap: () { HelpNav.to(DramaDetailPage(dramaBean: drama)); }, - child: HelpImg.network(drama.imageUrl, width: 95.w, height: 50.h, fit: BoxFit.cover), + child: HelpImg.network(drama.horizontallyImg, width: 95.w, height: 50.h, fit: BoxFit.cover), ); }) ), @@ -73,7 +73,7 @@ class DailyQuestWidget extends StatelessWidget { decoration: BoxDecoration( border: Border.all(color: CommonColor.black, width: 1), ), - child: HelpImg.network(drama.imageUrl, width: 300.w, height: 160.h, fit: BoxFit.cover), + child: HelpImg.network(drama.horizontallyImg, width: 300.w, height: 160.h, fit: BoxFit.cover), ), SizedBox(height: 4.h), Row( diff --git a/lib/pages/home/widgets/home_cate_widget.dart b/lib/pages/home/widgets/home_cate_widget.dart index f9a5472..574f5f0 100644 --- a/lib/pages/home/widgets/home_cate_widget.dart +++ b/lib/pages/home/widgets/home_cate_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:skywood/main/lib_file.dart'; +import 'package:skywood/pages/home/cate_page.dart'; import 'package:skywood/utils/models/drama/skywood_category.dart'; import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; @@ -44,7 +45,7 @@ class HomeCateWidget extends StatelessWidget { ) ], ).addInkWell(onTap: () { - // HelpNav.to(DramaDetailPage(dramaBean: drama)); + HelpNav.to(CatePage(preCategory: category)); }); } diff --git a/lib/pages/home/widgets/hot_cartridge_widget.dart b/lib/pages/home/widgets/hot_cartridge_widget.dart index 260ba36..422508c 100644 --- a/lib/pages/home/widgets/hot_cartridge_widget.dart +++ b/lib/pages/home/widgets/hot_cartridge_widget.dart @@ -18,8 +18,8 @@ class HotCartridgeWidget extends StatelessWidget { return SliverGrid.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, // 每行 2 列 - crossAxisSpacing: 12, // 列间距 - mainAxisSpacing: 5, // 行间距 + crossAxisSpacing: 10, // 列间距 + mainAxisSpacing: 10, // 行间距 childAspectRatio: 110 / 174, // 宽高比 ), itemBuilder: (context, index) { diff --git a/lib/pages/home/widgets/rank_list_widget.dart b/lib/pages/home/widgets/rank_list_widget.dart index d3d9f05..e316488 100644 --- a/lib/pages/home/widgets/rank_list_widget.dart +++ b/lib/pages/home/widgets/rank_list_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:skywood/main/lib_file.dart'; +import 'package:skywood/pages/home/players/page/drama_detail_page.dart'; import 'package:skywood/pages/home/widgets/like_num_widget.dart'; import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; @@ -61,7 +62,9 @@ class RankListWidget extends StatelessWidget { LikeNumWidget(watchTotal: drama.watchTotal) ], ), - ); + ).addInkWell(onTap: () { + HelpNav.to(DramaDetailPage(dramaBean: drama)); + }); } Widget _rankIndex(int index) { diff --git a/lib/pages/mine/mine_page.dart b/lib/pages/mine/mine_page.dart index d0a966b..e43f2c6 100644 --- a/lib/pages/mine/mine_page.dart +++ b/lib/pages/mine/mine_page.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:skywood/global/client/user_client.dart'; +import 'package:skywood/global/request/api_const.dart'; import 'package:skywood/main/lib_file.dart'; +import 'package:skywood/pages/mine/page/about_us_page.dart'; +import 'package:skywood/pages/mine/page/sky_web_page.dart'; import 'package:skywood/pages/mine/widget/mine_section_view.dart'; import 'package:skywood/pages/widgets/pixel_widgets.dart'; import 'package:skywood/utils/models/user/skywood_user.dart'; @@ -23,6 +26,40 @@ class _MainRootState extends State { SkywoodUser? _userInfo; + List _buildSections() { + final menuData = [ + { + 'title': 'Privacy Policy', 'img': ConstImg.minePp, + 'action': () { + HelpNav.to(SkyWebPage(params: WebLinkParams( + title: 'Privacy Policy', + urlString: ApiConst.webPrivacy, + ))); + } + }, + { + 'title': 'User Agreement', 'img': ConstImg.mineUa, + 'action': () { + HelpNav.to(SkyWebPage(params: WebLinkParams( + title: 'User Agreement', + urlString: ApiConst.webAgreement, + ))); + } + }, + { + 'title': 'About us', 'img': ConstImg.mineAb, + 'action': () { + HelpNav.to(AboutUsPage()); + } + } + ]; + return menuData.map((ele) => _buildMenuItem( + ele['title'] as String, + imgName: ele['img'] as String, + onTap: ele['action'] as void Function(), + )).toList(); + } + @override void initState() { super.initState(); @@ -54,7 +91,22 @@ class _MainRootState extends State { child: _userInfo==null ? null : _showUserInfo(), ), const SizedBox(height: 6), - MineSectionView(title: 'GAME MENU', imgName: ConstImg.sectionBg1), + MineSectionView(title: 'GAME MENU', imgName: ConstImg.sectionBg1, + paddingSpace: 10, + child: SizedBox( + height: 200, + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.0, + ), + itemBuilder: (context, index) => _buildSections()[index], + itemCount: _buildSections().length, + ), + ), + ), ], ), ) @@ -106,384 +158,28 @@ class _MainRootState extends State { ); } - Widget _buildHeader() { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 15.w), - child: Column( - children: [ - Row( + Widget _buildMenuItem(String title, {required String imgName, Function()? onTap}) { + double _w = (AppScreen.screenWidth - 30 - 10) / 4 - 10; + return Stack( + children: [ + HelpImg.asset(ConstImg.mineBtnBg, width: _w, fit: BoxFit.fill), + Positioned( + left: 5, top: 5, right: 5, + child: Column( children: [ - _DecorIcon(icon: Icons.forest, color: const Color(0xFFFF5454)), - SizedBox(width: 10.w), - Expanded( - child: Container( - height: 60.h, - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0xFF16ED1E), - border: Border.all(color: CommonColor.black, width: 1.5.w), - boxShadow: const [ - BoxShadow(color: Color(0xFF1C1E1F), offset: Offset(3, 3)), - ], - ), - child: RichText( - text: TextSpan( - style: TextStyle( - fontSize: 24.sp, - fontWeight: FontWeight.w900, - height: 1, - shadows: const [ - Shadow( - color: Color(0xFF1C1E1F), - offset: Offset(2, 2), - ), - ], - ), - children: const [ - TextSpan( - text: 'Profile ', - style: TextStyle(color: Color(0xFFFFDC4C)), - ), - TextSpan( - text: 'Menu', - style: TextStyle(color: Colors.white), - ), - ], - ), - ), - ), + HelpImg.asset(imgName, width: _w/2.0), + SkyText(text: title, fontSize: 9, color: Colors.white, + maxLines: 2, + textAlign: TextAlign.center, ), - SizedBox(width: 10.w), - _DecorIcon(icon: Icons.live_tv, color: const Color(0xFF8965FB)), ], ), - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(7, (index) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 3.w), - child: Icon( - index < 4 ? Icons.favorite : Icons.question_mark, - size: index < 4 ? 18.w : 14.w, - color: index.isEven - ? const Color(0xFF1AF23A) - : const Color(0xFF653CFA), - ), - ); - }), - ), - ], - ), - ); - } - - Widget _buildPlayerCard() { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 15.w), - child: PixelPanel( - color: CommonColor.primacyColor, - shadowColor: CommonColor.black, - padding: EdgeInsets.zero, - child: Column( - children: [ - const PixelSectionLabel(label: 'Player Card'), - Padding( - padding: EdgeInsets.all(16.w), - child: Row( - children: [ - Container( - width: 68.w, - height: 68.w, - padding: EdgeInsets.all(4.w), - decoration: BoxDecoration( - color: const Color(0xFFE9C7FF), - border: Border.all(color: CommonColor.black, width: 1.w), - ), - child: Icon( - Icons.person_pin, - size: 42.w, - color: const Color(0xFF653CFA), - ), - ), - SizedBox(width: 15.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Vistor', - style: TextStyle( - color: Colors.white, - fontSize: 20.sp, - fontWeight: FontWeight.w700, - height: 1, - ), - ), - SizedBox(height: 12.h), - Text( - 'ID: 32432233341', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.72), - fontSize: 10.sp, - height: 1, - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildWalletShop() { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 15.w), - child: Row( - children: [ - Expanded( - child: _SmallPanel( - label: 'Treasure Wallet', - child: Row( - children: [ - Icon( - Icons.inventory_2, - size: 70.w, - color: const Color(0xFFFFDC4C), - ), - SizedBox(width: 6.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _WalletValue(label: 'Coins', value: '15,890'), - Divider(height: 8.h, color: Colors.white54), - _WalletValue(label: 'Bonus', value: '20,000'), - ], - ), - ), - ], - ), - ), - ), - SizedBox(width: 10.w), - Expanded( - child: _SmallPanel( - label: 'Item Shop', - child: Row( - children: [ - Icon( - Icons.storefront, - size: 66.w, - color: const Color(0xFFFF8DB8), - ), - SizedBox(width: 7.w), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Get More\nCoins', - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - fontWeight: FontWeight.w600, - height: 1.1, - ), - ), - SizedBox(height: 14.h), - const PixelButton( - label: 'Store', - width: 70, - height: 20, - color: Color(0xFFFF88CF), - ), - ], - ), - ), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildCheckpoint() { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 15.w), - child: PixelPanel( - color: CommonColor.primacyColor, - padding: EdgeInsets.zero, - shadowColor: CommonColor.black, - child: Column( - children: [ - const PixelSectionLabel(label: 'Daily Checkpoint'), - Padding( - padding: EdgeInsets.fromLTRB(16.w, 10.h, 10.w, 10.h), - child: Row( - children: [ - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate(7, (index) { - return _DayBox(day: index + 1, active: index == 0); - }), - ), - ), - SizedBox(width: 12.w), - const PixelButton( - label: 'Check In', - width: 70, - height: 22, - color: Color(0xFFFF88CF), - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildGameMenu() { - final items = const [ - _MenuItem('Log In', Icons.login), - _MenuItem('Wallet', Icons.account_balance_wallet), - _MenuItem('Language', Icons.public), - _MenuItem('Feedback', Icons.mail), - _MenuItem('Settings', Icons.settings), - _MenuItem('About Us', Icons.info), - ]; - - return Padding( - padding: EdgeInsets.symmetric(horizontal: 15.w), - child: PixelPanel( - color: CommonColor.primacyColor, - padding: EdgeInsets.zero, - shadowColor: CommonColor.black, - child: Column( - children: [ - const PixelSectionLabel(label: 'Game Menu'), - Padding( - padding: EdgeInsets.all(10.w), - child: GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 5, - mainAxisSpacing: 8.h, - crossAxisSpacing: 6.w, - childAspectRatio: 1, - ), - itemCount: items.length, - itemBuilder: (context, index) => _MenuTile(item: items[index]), - ), - ), - ], - ), - ), - ); - } -} - -class _SmallPanel extends StatelessWidget { - const _SmallPanel({required this.label, required this.child}); - - final String label; - final Widget child; - - @override - Widget build(BuildContext context) { - return PixelPanel( - color: CommonColor.primacyColor, - shadowColor: CommonColor.black, - padding: EdgeInsets.zero, - child: SizedBox( - height: 134.h, - child: Column( - children: [ - PixelSectionLabel(label: label), - Expanded( - child: Padding(padding: EdgeInsets.all(8.w), child: child), - ), - ], - ), - ), - ); - } -} - -class _WalletValue extends StatelessWidget { - const _WalletValue({required this.label, required this.value}); - - final String label; - final String value; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: TextStyle(color: Colors.white70, fontSize: 9.sp, height: 1), - ), - SizedBox(height: 2.h), - Text( - value, - style: TextStyle( - color: Colors.white, - fontSize: 15.sp, - fontWeight: FontWeight.w900, - height: 1, - ), - ), + ) ], - ); + ).addInkWell(onTap: onTap); } } -class _DayBox extends StatelessWidget { - const _DayBox({required this.day, required this.active}); - - final int day; - final bool active; - - @override - Widget build(BuildContext context) { - return Container( - width: 28.w, - height: 34.h, - decoration: BoxDecoration( - color: active ? const Color(0xFF91F473) : Colors.white, - border: Border.all(color: CommonColor.black, width: 1.w), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Day', style: TextStyle(fontSize: 7.sp, height: 1)), - SizedBox(height: 4.h), - Text( - '$day', - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w800, - height: 1, - ), - ), - ], - ), - ); - } -} class _MenuTile extends StatelessWidget { const _MenuTile({required this.item}); diff --git a/lib/pages/mine/page/about_us_page.dart b/lib/pages/mine/page/about_us_page.dart new file mode 100644 index 0000000..37898c9 --- /dev/null +++ b/lib/pages/mine/page/about_us_page.dart @@ -0,0 +1,65 @@ + +import 'package:flutter/material.dart'; +import 'package:skywood/global/request/api_const.dart'; +import 'package:skywood/utils/tools/sky_device_info.dart'; +import 'package:skywood/utils/tools/widgets/sky_app_bar.dart'; +import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AboutUsPage extends StatelessWidget { + + const AboutUsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: SkyAppBar( + title: 'About Us', + ), + body: SizedBox( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 100), + SkyText(text: ApiConst.appName, + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + const SizedBox(height: 5), + SkyText(text: 'V ${SkyDeviceInfo.instance.appVersion}', + fontSize: 12, + color: Colors.grey, + ), + const SizedBox(height: 20), + Container( + height: 46, + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 4), + child: Row( + children: [ + + ], + ), + ) + ], + ), + ), + ); + } + + Future openUrl(String url) async { + final encodedUrl = Uri.encodeFull(url); + try { + final bool launched = await launchUrl( + Uri.parse(encodedUrl), + mode: LaunchMode.externalApplication, + ); + if (!launched) { + print('无法打开链接: $encodedUrl'); + } + } catch (e) { + print('打开链接异常: $e'); + } + } +} \ No newline at end of file diff --git a/lib/pages/mine/page/sky_web_page.dart b/lib/pages/mine/page/sky_web_page.dart new file mode 100644 index 0000000..f1d6c71 --- /dev/null +++ b/lib/pages/mine/page/sky_web_page.dart @@ -0,0 +1,306 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:loading_animation_widget/loading_animation_widget.dart'; +import 'package:skywood/utils/tools/widgets/sky_app_bar.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +/// WebView 页面参数 +class WebLinkParams { + final String title; + final String urlString; + final bool showAppbar; + + const WebLinkParams({ + this.title = 'Web View', + required this.urlString, + this.showAppbar = true, + }); +} + +/// WebView 页面 +class SkyWebPage extends StatefulWidget { + final WebLinkParams params; + + const SkyWebPage({ + super.key, + required this.params, + }); + + @override + State createState() => _WebLinkViewState(); +} + +class _WebLinkViewState extends State { + late WebViewController _webViewController; + int _loadingProgress = 0; + bool _isLoading = true; + bool _hasError = false; + String _errorMessage = ''; + String _urlString = ''; + + @override + void initState() { + super.initState(); + _initializeParameters(); + _initializeWebView(); + _loadUrl(); + } + + void _initializeParameters() { + _urlString = _validateAndFixUrl(widget.params.urlString); + } + + void _initializeWebView() { + _webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00FF0000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + setState(() { + _loadingProgress = progress; + }); + }, + onPageStarted: (String url) { + setState(() { + _isLoading = true; + _hasError = false; + _loadingProgress = 0; + }); + }, + onPageFinished: (String url) async { + setState(() { + _isLoading = false; + _loadingProgress = 100; + }); + + // 修改网页背景为透明,文字为白色 + await _webViewController.runJavaScript(''' + document.body.style.background = 'none'; + document.body.style.color = '#fff'; + + // 修改标题样式 + var titleElements = document.getElementsByClassName('title'); + for (var i = 0; i < titleElements.length; i++) { + titleElements[i].style.display = 'none'; + } + + // 修改文本背景为白色 + var bgElements = document.getElementsByClassName('bg-fff'); + for (var i = 0; i < bgElements.length; i++) { + bgElements[i].style.background = 'none'; + bgElements[i].style.paddingTop = '0px'; + } + + // 修改所有文本元素为白色 + var allElements = document.getElementsByTagName('*'); + for (var i = 0; i < allElements.length; i++) { + allElements[i].style.color = '#fff'; + } + + // 修改链接颜色 + var links = document.getElementsByTagName('a'); + for (var i = 0; i < links.length; i++) { + links[i].style.color = '#87CEEB'; + } + '''); + }, + onHttpError: (HttpResponseError error) { + setState(() { + _hasError = true; + _errorMessage = 'HTTP Error: ${error.response?.statusCode ?? "Unknown"}'; + _isLoading = false; + }); + }, + onWebResourceError: (WebResourceError error) { + setState(() { + _hasError = true; + _errorMessage = error.description; + _isLoading = false; + }); + }, + onNavigationRequest: (NavigationRequest request) { + return NavigationDecision.navigate; + }, + ), + ); + } + + void _loadUrl() { + if (_urlString.isEmpty) { + setState(() { + _hasError = true; + _errorMessage = 'Invalid URL'; + _isLoading = false; + }); + return; + } + + try { + final uri = Uri.parse(_urlString); + _webViewController.loadRequest(uri); + } catch (e) { + setState(() { + _hasError = true; + _errorMessage = 'Failed to load URL: $e'; + _isLoading = false; + }); + } + } + + String _validateAndFixUrl(String url) { + if (url.isEmpty) { + return ''; + } + + url = url.trim(); + + // Already has a valid scheme + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + + // Looks like a domain (has a dot and no spaces) + if (url.contains('.') && !url.contains(' ')) { + return 'https://$url'; + } + + // Treat as search query + return 'https://www.google.com/search?q=${Uri.encodeComponent(url)}'; + } + + void _reload() { + setState(() { + _hasError = false; + _isLoading = true; + _loadingProgress = 0; + _errorMessage = ''; + }); + _initializeWebView(); + _loadUrl(); + } + + void _goBack() { + _webViewController.goBack(); + } + + void _goForward() { + _webViewController.goForward(); + } + + @override + void dispose() { + _webViewController.clearCache(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: widget.params.showAppbar + ? _buildAppBar() + : null, + body: _buildBody(), + ); + } + + PreferredSizeWidget _buildAppBar() { + return SkyAppBar( + backgroundColor: Colors.transparent, + title: widget.params.title, + titleColor: Colors.white, + actions: [ + IconButton( + icon: const Icon(Icons.refresh), + onPressed: _reload, + ), + ], + ); + } + + Widget _buildBody() { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF1A0B2E), + Color(0xFF2D1B4E), + ], + ), + ), + child: SafeArea( + child: Column( + children: [ + // 加载进度条(可选) + if (_isLoading) + LinearProgressIndicator( + value: _loadingProgress / 100, + backgroundColor: Colors.white.withOpacity(0.2), + valueColor: const AlwaysStoppedAnimation( + Color(0xFFFB98E0), + ), + ), + Expanded( + child: _buildContent(), + ), + ], + ), + ), + ); + } + + Widget _buildContent() { + if (_isLoading) { + return Center( + child: LoadingAnimationWidget.newtonCradle( + color: const Color(0xFFFB98E0), + size: 96.w, + ), + ); + } + + if (_hasError) { + return _buildErrorWidget(); + } + + return WebViewWidget( + controller: _webViewController, + ); + } + + Widget _buildErrorWidget() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 64.w, + color: Colors.white.withOpacity(0.6), + ), + SizedBox(height: 16.h), + Text( + _errorMessage, + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 16.sp, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 24.h), + ElevatedButton( + onPressed: _reload, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFFB98E0), + foregroundColor: Colors.white, + ), + child: const Text('重试'), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/search/page/result_page.dart b/lib/pages/search/page/result_page.dart index 7a360b6..98a840f 100644 --- a/lib/pages/search/page/result_page.dart +++ b/lib/pages/search/page/result_page.dart @@ -9,6 +9,7 @@ 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'; +import '../widget/search_util.dart'; class ResultPage extends StatefulWidget { @@ -26,7 +27,6 @@ class ResultPage extends StatefulWidget { class _ResultPageState extends State { - List _searchHistory = []; final _searchController = TextEditingController(); final _searchFocus = FocusNode(); List _searchResult = []; @@ -39,9 +39,13 @@ class _ResultPageState extends State { _search(); } - void _search() { + void _search() async { String keyword = _searchController.text; - if (keyword.trim().isEmpty) return; + if (keyword.trim().isEmpty) { + _searchFocus.requestFocus(); + return; + } + await SearchUtil.addSearch(keyword); PlayClient.search(keyword: keyword).then((valuer) { setState(() { _searchResult = valuer; @@ -64,10 +68,9 @@ class _ResultPageState extends State { 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); + _search(); }, ), ), diff --git a/lib/pages/search/page/search_page.dart b/lib/pages/search/page/search_page.dart index 29ec4b8..c572596 100644 --- a/lib/pages/search/page/search_page.dart +++ b/lib/pages/search/page/search_page.dart @@ -10,6 +10,7 @@ import 'package:skywood/utils/models/drama/skywood_drama.dart'; import 'package:skywood/utils/tools/help_nav.dart'; import 'package:skywood/utils/tools/sky_time_tool.dart'; import 'package:skywood/utils/tools/widgets/sky_app_bar.dart'; +import 'package:skywood/utils/tools/widgets/sky_common_widget.dart'; import 'package:skywood/utils/tools/widgets/sky_text_field.dart'; class SearchPage extends StatefulWidget { @@ -27,16 +28,6 @@ class _MainRootState extends State { final _searchController = TextEditingController(); final _searchFocus = FocusNode(); - final List _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', - ]; List _searchHots = []; Future _loadSearchHistory() async { @@ -103,7 +94,8 @@ class _MainRootState extends State { body: CustomScrollView( slivers: [ SliverToBoxAdapter(child: _buildHistory()), - SliverToBoxAdapter(child: SizedBox(height: 30.h)), + if (_searchHistory.isNotEmpty) + SliverToBoxAdapter(child: SizedBox(height: 30.h)), SliverToBoxAdapter(child: _buildTitle('Trending Now')), SliverToBoxAdapter(child: SizedBox(height: 10.h)), SliverToBoxAdapter(child: _buildTrendingList()), @@ -115,9 +107,12 @@ class _MainRootState extends State { Widget _buildHistory() { - return SearchView( + return _searchHistory.isEmpty ? SizedBox() : SearchView( searchHistory: _searchHistory, onTapClear: () { + // HelpNav.showDialog(ComConfirmWidget( + // title: 'Clear Search History?', + // )); _searchHistory.clear(); setState(() {}); }, diff --git a/lib/pages/widgets/mystery_box_widget.dart b/lib/pages/widgets/mystery_box_widget.dart new file mode 100644 index 0000000..8b5ac84 --- /dev/null +++ b/lib/pages/widgets/mystery_box_widget.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:skywood/main/lib_file.dart'; +import 'package:skywood/pages/home/players/page/drama_detail_page.dart'; +import 'package:skywood/utils/models/drama/skywood_drama.dart'; + +class MysteryBoxWidget extends StatelessWidget { + + const MysteryBoxWidget({super.key, + required this.drama, + this.onTap, + }); + + final SkywoodDrama drama; + final Function()? onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: AppScreen.screenWidth, height: 400, + child: Column( + children: [ + HelpImg.asset(ConstImg.boxOpened, width: 179, height: 24), + const SizedBox(height: 20), + Stack( + children: [ + HelpImg.asset(ConstImg.boxGift, width: AppScreen.screenWidth), + // 015 140 00EE02 + Positioned( + top: 0, left: 135, right: 135, + child: Container( + width: 105.w, height: 140.w, + decoration: BoxDecoration( + border: Border.all(width: 1, color: CommonColor.withValue('00EE02')) + ), + child: HelpImg.network(drama.imageUrl, width: 105.w, fit: BoxFit.fill), + ), + ) + ], + ), + HelpImg.asset(ConstImg.boxBtn, width: 196).addInkWell( + onTap: () { + HelpNav.back(); + onTap?.call(); + } + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/utils/extend/gif_header.dart b/lib/utils/extend/gif_header.dart new file mode 100644 index 0000000..23db78b --- /dev/null +++ b/lib/utils/extend/gif_header.dart @@ -0,0 +1,37 @@ +import 'package:easy_refresh/easy_refresh.dart'; +import 'package:flutter/material.dart'; +import 'package:skywood/main/lib_file.dart'; + +/// 自定义 GIF Header +class GifHeader extends Header { + const GifHeader({ + super.triggerOffset = 80, + super.clamping = false, + this.height = 80, + this.size = 74, + }); + + final double height; + final double size; + + @override + Widget build(BuildContext context, IndicatorState state) { + if (state.mode == IndicatorMode.inactive || + state.mode == IndicatorMode.processed || + state.mode == IndicatorMode.done) { + return const SizedBox.shrink(); + } + + return SizedBox( + height: height, + child: Center( + child: Image.asset( + ConstImg.loading, + width: size, + height: size, + fit: BoxFit.contain, + ), + ), + ); + } +} diff --git a/lib/utils/tools/app_toast.dart b/lib/utils/tools/app_toast.dart index 09547c6..74ca5d7 100644 --- a/lib/utils/tools/app_toast.dart +++ b/lib/utils/tools/app_toast.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:skywood/main/lib_file.dart'; // import 'package:lottie/lottie.dart'; // 加载数据的函数类型 @@ -83,26 +84,22 @@ class AppToast { } /// 显示 Lottie 加载提示 - // static Future showLottieLoading({ - // Duration? displayTime, - // }) async { - // await dismiss(); - // return SmartDialog.showLoading( - // displayTime: displayTime ?? const Duration(seconds: 5), - // maskColor: Colors.transparent, - // builder: (_) { - // return SizedBox( - // width: 120, - // height: 120, - // child: Lottie.asset( - // 'assets/bagua.json', - // fit: BoxFit.contain, - // repeat: true, - // ), - // ); - // }, - // ); - // } + static Future showLottieLoading({ + Duration? displayTime, + }) async { + await dismiss(); + return SmartDialog.showLoading( + displayTime: displayTime ?? const Duration(seconds: 5), + maskColor: Colors.transparent, + builder: (_) { + return SizedBox( + width: 120, + height: 120, + child: Image.asset(ConstImg.loading, fit: BoxFit.fill), + ); + }, + ); + } /// 显示加载提示 static Future showLoading({ diff --git a/lib/utils/tools/help_img.dart b/lib/utils/tools/help_img.dart index dc4f988..ce1e59c 100644 --- a/lib/utils/tools/help_img.dart +++ b/lib/utils/tools/help_img.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -// import 'package:lottie/lottie.dart'; // import 'package:flutter_svg/flutter_svg.dart'; class ConstImg { @@ -16,6 +15,8 @@ class ConstImg { static const String tabLikeUnselect = "assets/common/tab_like_unselect.png"; static const String tabMineSelected = "assets/common/tab_mine_selected.png"; static const String tabMineUnselect = "assets/common/tab_mine_unselect.png"; + // loading + static const String loading = "assets/common/loading.gif"; static const String homeBg = "assets/imgs/home_bg.png"; // home_card_bg @@ -42,6 +43,11 @@ class ConstImg { 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"; + // box cl gift opened + static const String boxBtn = "assets/imgs/box_button.png"; + static const String boxClose = "assets/imgs/box_close.png"; + static const String boxOpened = "assets/imgs/box_opened.png"; + static const String boxGift = "assets/imgs/box_gift.png"; /// pics static const String delete = "assets/pics/delete.png"; @@ -80,7 +86,11 @@ class ConstImg { static const String sectionBg2 = "assets/mine/section_bg2.png"; static const String sectionBg3 = "assets/mine/section_bg3.png"; static const String defAvatar = "assets/mine/def_avatar.png"; - + // mine_btn_bg + static const String mineBtnBg = "assets/mine/mine_btn_bg.png"; + static const String mineAb = "assets/mine/mine_ab.png"; + static const String minePp = "assets/mine/mine_pp.png"; + static const String mineUa = "assets/mine/mine_ua.png"; // nodata diff --git a/lib/utils/tools/widgets/sky_app_bar.dart b/lib/utils/tools/widgets/sky_app_bar.dart index 8b422e6..893a701 100644 --- a/lib/utils/tools/widgets/sky_app_bar.dart +++ b/lib/utils/tools/widgets/sky_app_bar.dart @@ -20,9 +20,11 @@ class SkyAppBar extends StatelessWidget implements PreferredSizeWidget { this.elevation = 0, this.bottom, this.toolbarHeight = kToolbarHeight, + this.titleColor, }); final String? title; + final Color? titleColor; final Widget? titleWidget; final Widget? leading; final List? actions; @@ -59,7 +61,7 @@ class SkyAppBar extends StatelessWidget implements PreferredSizeWidget { return AppBar( title: titleWidget ?? (title != null - ? SkyText(text: title!, fontSize: 18, fontWeight: FontWeight.bold) + ? SkyText(text: title!, fontSize: 18, fontWeight: FontWeight.bold, color: titleColor) : null), leading: resolvedLeading, actions: actions,