From f8e6ec00f9e9e8abeba5dd8f5c17dabb9884bd4b Mon Sep 17 00:00:00 2001 From: orz12 Date: Thu, 29 Feb 2024 21:00:53 +0800 Subject: [PATCH] =?UTF-8?q?mod:=20=E6=97=A0=E9=9A=9C=E7=A2=8D=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/badge.dart | 3 + lib/common/widgets/network_img_layer.dart | 13 +- lib/common/widgets/overlay_pop.dart | 1 + lib/common/widgets/stat/danmu.dart | 1 + lib/common/widgets/stat/view.dart | 9 +- lib/common/widgets/video_card_h.dart | 176 +++++++++--------- lib/common/widgets/video_card_v.dart | 117 ++++++------ lib/main.dart | 1 + lib/models/dynamics/result.dart | 6 +- lib/models/member/archive.dart | 6 +- lib/models/model_hot_video_item.dart | 6 +- lib/models/search/result.dart | 6 +- lib/models/user/fav_detail.dart | 6 +- lib/models/video_detail_res.dart | 8 +- lib/pages/about/index.dart | 11 +- lib/pages/bangumi/introduction/view.dart | 34 ++-- lib/pages/bangumi/view.dart | 1 + lib/pages/bangumi/widgets/bangumi_panel.dart | 1 + lib/pages/dynamics/widgets/action_panel.dart | 22 ++- lib/pages/dynamics/widgets/author_panel.dart | 2 + lib/pages/dynamics/widgets/video_panel.dart | 3 +- lib/pages/emote/view.dart | 5 +- lib/pages/fav/view.dart | 1 + lib/pages/fav_detail/view.dart | 1 + .../fav_detail/widget/fav_video_card.dart | 1 + lib/pages/fav_search/view.dart | 2 + lib/pages/follow/view.dart | 1 + lib/pages/follow_search/view.dart | 2 + lib/pages/history/view.dart | 2 + lib/pages/history/widgets/item.dart | 1 + lib/pages/history_search/view.dart | 2 + lib/pages/home/view.dart | 63 ++++--- lib/pages/home/widgets/app_bar.dart | 3 + lib/pages/html/view.dart | 31 +++ .../live_room/widgets/bottom_control.dart | 2 + lib/pages/login/view.dart | 4 + lib/pages/media/view.dart | 2 + lib/pages/member/view.dart | 6 + lib/pages/member/widgets/profile.dart | 49 ++--- lib/pages/member/widgets/seasons.dart | 1 + lib/pages/member_search/view.dart | 2 + lib/pages/mine/view.dart | 51 +++-- lib/pages/search/controller.dart | 5 +- lib/pages/search/view.dart | 2 + .../search_panel/widgets/video_panel.dart | 1 + lib/pages/video/detail/introduction/view.dart | 55 +++--- .../introduction/widgets/action_item.dart | 24 ++- .../introduction/widgets/fav_panel.dart | 1 + .../introduction/widgets/group_panel.dart | 1 + .../introduction/widgets/intro_detail.dart | 2 +- .../detail/introduction/widgets/page.dart | 1 + .../detail/reply/widgets/reply_item.dart | 46 ++--- lib/pages/video/detail/reply/widgets/zan.dart | 1 + .../detail/reply_new/toolbar_icon_button.dart | 3 + lib/pages/video/detail/reply_new/view.dart | 2 + lib/pages/video/detail/reply_reply/view.dart | 1 + .../video/detail/widgets/header_control.dart | 17 +- lib/pages/webview/view.dart | 2 + lib/pages/whisper_detail/view.dart | 34 ++-- .../pl_player/widgets/bottom_control.dart | 116 +++++++----- lib/plugin/pl_player/widgets/common_btn.dart | 3 + .../pl_player/widgets/play_pause_btn.dart | 3 + lib/utils/utils.dart | 78 +++++++- pubspec.lock | 8 - pubspec.yaml | 2 - 65 files changed, 683 insertions(+), 390 deletions(-) diff --git a/lib/common/widgets/badge.dart b/lib/common/widgets/badge.dart index a8f2fc67..2ac6486b 100644 --- a/lib/common/widgets/badge.dart +++ b/lib/common/widgets/badge.dart @@ -10,6 +10,7 @@ class PBadge extends StatelessWidget { final String? size; final String? stack; final double? fs; + final String? semanticsLabel; const PBadge({ super.key, @@ -22,6 +23,7 @@ class PBadge extends StatelessWidget { this.size = 'medium', this.stack = 'position', this.fs = 11, + this.semanticsLabel, }); @override @@ -68,6 +70,7 @@ class PBadge extends StatelessWidget { child: Text( text!, style: TextStyle(fontSize: fs ?? fontSize, color: color), + semanticsLabel: semanticsLabel, ), ); if (stack == 'position') { diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index 893dd66f..9a5444d7 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -1,5 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:hive/hive.dart'; import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/global_data.dart'; @@ -20,6 +21,7 @@ class NetworkImgLayer extends StatelessWidget { // 图片质量 默认1% this.quality, this.origAspectRatio, + this.semanticsLabel, }); final String? src; @@ -30,6 +32,7 @@ class NetworkImgLayer extends StatelessWidget { final Duration? fadeInDuration; final int? quality; final double? origAspectRatio; + final String? semanticsLabel; @override Widget build(BuildContext context) { @@ -49,8 +52,7 @@ class NetworkImgLayer extends StatelessWidget { memCacheWidth = width.cacheSize(context); // memCacheHeight = height.cacheSize(context); } - - return src != '' && src != null + Widget res = src != '' && src != null ? ClipRRect( clipBehavior: Clip.antiAlias, borderRadius: BorderRadius.circular( @@ -79,6 +81,13 @@ class NetworkImgLayer extends StatelessWidget { ), ) : placeholder(context); + if (semanticsLabel != null) { + return Semantics( + label: semanticsLabel, + child: res, + ); + } + return res; } Widget placeholder(BuildContext context) { diff --git a/lib/common/widgets/overlay_pop.dart b/lib/common/widgets/overlay_pop.dart index fe9b9377..fd03c98d 100644 --- a/lib/common/widgets/overlay_pop.dart +++ b/lib/common/widgets/overlay_pop.dart @@ -41,6 +41,7 @@ class OverlayPop extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(20))), child: IconButton( + tooltip: '关闭', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), diff --git a/lib/common/widgets/stat/danmu.dart b/lib/common/widgets/stat/danmu.dart index 7c28f5ee..2d4b4689 100644 --- a/lib/common/widgets/stat/danmu.dart +++ b/lib/common/widgets/stat/danmu.dart @@ -31,6 +31,7 @@ class StatDanMu extends StatelessWidget { fontSize: size == 'medium' ? 12 : 11, color: color, ), + semanticsLabel: '${Utils.numFormat(danmu!)}条弹幕', ) ], ); diff --git a/lib/common/widgets/stat/view.dart b/lib/common/widgets/stat/view.dart index d0b05e19..b68a6d97 100644 --- a/lib/common/widgets/stat/view.dart +++ b/lib/common/widgets/stat/view.dart @@ -5,8 +5,9 @@ class StatView extends StatelessWidget { final String? theme; final dynamic view; final String? size; + final String? goto; - const StatView({Key? key, this.theme, this.view, this.size}) + const StatView({Key? key, this.theme, this.view, this.size, this.goto}) : super(key: key); @override @@ -20,7 +21,9 @@ class StatView extends StatelessWidget { return Row( children: [ Icon( - Icons.play_circle_outlined, + goto == 'picture' + ? Icons.remove_red_eye_outlined + : Icons.play_circle_outlined, size: 13, color: color, ), @@ -31,6 +34,8 @@ class StatView extends StatelessWidget { fontSize: size == 'medium' ? 12 : 11, color: color, ), + semanticsLabel: + '${Utils.numFormat(view!)}次${goto == "picture" ? "浏览" : "播放"}', ), ], ); diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index a94612e4..a53080f1 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -38,93 +38,96 @@ class VideoCardH extends StatelessWidget { final int aid = videoItem.aid; final String bvid = videoItem.bvid; final String heroTag = Utils.makeHeroTag(aid); - return GestureDetector( - onLongPress: () { - if (longPress != null) { - longPress!(); - } - }, - // onLongPressEnd: (details) { - // if (longPressEnd != null) { - // longPressEnd!(); - // } - // }, - child: InkWell( - onTap: () async { - try { - final int cid = - videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); - Get.toNamed('/video?bvid=$bvid&cid=$cid', - arguments: {'videoItem': videoItem, 'heroTag': heroTag}); - } catch (err) { - SmartDialog.showToast(err.toString()); - } - }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints boxConstraints) { - final double width = (boxConstraints.maxWidth - - StyleString.cardSpace * - 6 / - MediaQuery.textScalerOf(context).scale(1.0)) / - 2; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (BuildContext context, - BoxConstraints boxConstraints) { - final double maxWidth = boxConstraints.maxWidth; - final double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: videoItem.pic as String, - width: maxWidth, - height: maxHeight, - ), - ), - PBadge( - text: Utils.timeFormat(videoItem.duration!), - right: 6.0, - bottom: 6.0, - type: 'gray', - ), - // if (videoItem.rcmdReason != null && - // videoItem.rcmdReason.content != '') - // pBadge(videoItem.rcmdReason.content, context, - // 6.0, 6.0, null, null), - ], - ); - }, - ), - ), - VideoContent( - videoItem: videoItem, - source: source, - showOwner: showOwner, - showView: showView, - showDanmaku: showDanmaku, - showPubdate: showPubdate, - ) - ], - ), - ); + return Semantics( + label: Utils.videoItemSemantics(videoItem), + excludeSemantics: true, + child: GestureDetector( + onLongPress: () { + if (longPress != null) { + longPress!(); + } + }, + // onLongPressEnd: (details) { + // if (longPressEnd != null) { + // longPressEnd!(); + // } + // }, + child: InkWell( + onTap: () async { + try { + final int cid = videoItem.cid ?? + await SearchHttp.ab2c(aid: aid, bvid: bvid); + Get.toNamed('/video?bvid=$bvid&cid=$cid', + arguments: {'videoItem': videoItem, 'heroTag': heroTag}); + } catch (err) { + SmartDialog.showToast(err.toString()); + } }, + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 5, StyleString.safeSpace, 5), + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints boxConstraints) { + final double width = (boxConstraints.maxWidth - + StyleString.cardSpace * + 6 / + MediaQuery.textScalerOf(context).scale(1.0)) / + 2; + return Container( + constraints: const BoxConstraints(minHeight: 88), + height: width / StyleString.aspectRatio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (BuildContext context, + BoxConstraints boxConstraints) { + final double maxWidth = boxConstraints.maxWidth; + final double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: videoItem.pic as String, + width: maxWidth, + height: maxHeight, + ), + ), + PBadge( + text: Utils.timeFormat(videoItem.duration!), + right: 6.0, + bottom: 6.0, + type: 'gray', + ), + // if (videoItem.rcmdReason != null && + // videoItem.rcmdReason.content != '') + // pBadge(videoItem.rcmdReason.content, context, + // 6.0, 6.0, null, null), + ], + ); + }, + ), + ), + VideoContent( + videoItem: videoItem, + source: source, + showOwner: showOwner, + showView: showView, + showDanmaku: showDanmaku, + showPubdate: showPubdate, + ) + ], + ), + ); + }, + ), + ), ), - ), - ), - ); + )); } } @@ -185,6 +188,7 @@ class VideoContent extends StatelessWidget { ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface, ), + semanticsLabel: i['text'] as String, ), ] ], @@ -235,7 +239,7 @@ class VideoContent extends StatelessWidget { if (showDanmaku) StatDanMu( theme: 'gray', - danmu: videoItem.stat.danmaku as int, + danmu: videoItem.stat.danmu as int, ), const Spacer(), if (source == 'normal') diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 090182df..5d68f61d 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -124,57 +124,62 @@ class VideoCardV extends StatelessWidget { @override Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(videoItem.id); - return Card( - elevation: 0, - clipBehavior: Clip.hardEdge, - margin: EdgeInsets.zero, - child: GestureDetector( - onLongPress: () { - if (longPress != null) { - longPress!(); - } - }, - // onLongPressEnd: (details) { - // if (longPressEnd != null) { - // longPressEnd!(); - // } - // }, - child: InkWell( - onTap: () async => onPushDetail(heroTag), - child: Column( - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder(builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: videoItem.pic, - width: maxWidth, - height: maxHeight, - ), - ), - if (videoItem.duration > 0) - PBadge( - bottom: 6, - right: 7, - size: 'small', - type: 'gray', - text: Utils.timeFormat(videoItem.duration), - ) - ], - ); - }), + return Semantics( + label: Utils.videoItemSemantics(videoItem), + excludeSemantics: true, + child: Card( + elevation: 0, + clipBehavior: Clip.hardEdge, + margin: EdgeInsets.zero, + child: GestureDetector( + onLongPress: () { + if (longPress != null) { + longPress!(); + } + }, + // onLongPressEnd: (details) { + // if (longPressEnd != null) { + // longPressEnd!(); + // } + // }, + child: InkWell( + onTap: () async => onPushDetail(heroTag), + child: Column( + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder(builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: videoItem.pic, + width: maxWidth, + height: maxHeight, + ), + ), + if (videoItem.duration > 0) + PBadge( + bottom: 6, + right: 7, + size: 'small', + type: 'gray', + text: Utils.timeFormat(videoItem.duration), + // semanticsLabel: + // '时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}', + ) + ], + ); + }), + ), + VideoContent(videoItem: videoItem) + ], ), - VideoContent(videoItem: videoItem) - ], - ), - ), - ), + ), + )), ); } } @@ -195,6 +200,7 @@ class VideoContent extends StatelessWidget { children: [ Expanded( child: Text(videoItem.title, + // semanticsLabel: "${videoItem.title}", maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( @@ -248,6 +254,7 @@ class VideoContent extends StatelessWidget { flex: 1, child: Text( videoItem.owner.name, + // semanticsLabel: "Up主:${videoItem.owner.name}", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -290,12 +297,14 @@ class VideoStat extends StatelessWidget { StatView( theme: 'gray', view: videoItem.stat.view, + goto: videoItem.goto, ), const SizedBox(width: 8), - StatDanMu( - theme: 'gray', - danmu: videoItem.stat.danmu, - ), + if (videoItem.goto != 'picture') + StatDanMu( + theme: 'gray', + danmu: videoItem.stat.danmu, + ), if (videoItem is RecVideoItemModel) ...[ const Spacer(), RichText( diff --git a/lib/main.dart b/lib/main.dart index 1cb5d0f7..3cff0420 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -148,6 +148,7 @@ class MyApp extends StatelessWidget { // 图片缓存 // PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20; return GetMaterialApp( + // showSemanticsDebugger: true, title: 'PiliPalaX', theme: ThemeData( // fontFamily: 'HarmonyOS', diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index 2f7c2d40..a7b92a62 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -826,15 +826,15 @@ class Like { class Stat { Stat({ - this.danmaku, + this.danmu, this.play, }); - String? danmaku; + String? danmu; String? play; Stat.fromJson(Map json) { - danmaku = json['danmaku']; + danmu = json['danmaku']; play = json['play']; } } diff --git a/lib/models/member/archive.dart b/lib/models/member/archive.dart index d735ab7c..d78cf956 100644 --- a/lib/models/member/archive.dart +++ b/lib/models/member/archive.dart @@ -134,15 +134,15 @@ class VListItemModel { class Stat { Stat({ this.view, - this.danmaku, + this.danmu, }); int? view; - int? danmaku; + int? danmu; Stat.fromJson(Map json) { view = json["play"]; - danmaku = json['video_review']; + danmu = json['video_review']; } } diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index db331a4c..650165e1 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -90,7 +90,7 @@ class Stat { Stat({ this.aid, this.view, - this.danmaku, + this.danmu, this.reply, this.favorite, this.coin, @@ -105,7 +105,7 @@ class Stat { int? aid; int? view; - int? danmaku; + int? danmu; int? reply; int? favorite; int? coin; @@ -120,7 +120,7 @@ class Stat { Stat.fromJson(Map json) { aid = json["aid"]; view = json["view"]; - danmaku = json['danmaku']; + danmu = json['danmaku']; reply = json["reply"]; favorite = json["favorite"]; coin = json['coin']; diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index 58ac0c23..5732333f 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -98,7 +98,7 @@ class SearchVideoItemModel { class Stat { Stat({ this.view, - this.danmaku, + this.danmu, this.favorite, this.reply, this.like, @@ -107,7 +107,7 @@ class Stat { // 播放量 int? view; // 弹幕数 - int? danmaku; + int? danmu; // 收藏数 int? favorite; // 评论数 @@ -117,7 +117,7 @@ class Stat { Stat.fromJson(Map json) { view = json['play']; - danmaku = json['danmaku']; + danmu = json['danmaku']; favorite = json['favorite']; reply = json['review']; like = json['like']; diff --git a/lib/models/user/fav_detail.dart b/lib/models/user/fav_detail.dart index cedc75e9..f9de284f 100644 --- a/lib/models/user/fav_detail.dart +++ b/lib/models/user/fav_detail.dart @@ -107,14 +107,14 @@ class FavDetailItemData { class Stat { Stat({ this.view, - this.danmaku, + this.danmu, }); int? view; - int? danmaku; + int? danmu; Stat.fromJson(Map json) { view = json['play']; - danmaku = json['danmaku']; + danmu = json['danmaku']; } } diff --git a/lib/models/video_detail_res.dart b/lib/models/video_detail_res.dart index 787df19c..8ec1e201 100644 --- a/lib/models/video_detail_res.dart +++ b/lib/models/video_detail_res.dart @@ -427,7 +427,7 @@ class Part { class Stat { int? aid; int? view; - int? danmaku; + int? danmu; int? reply; int? favorite; int? coin; @@ -442,7 +442,7 @@ class Stat { Stat({ this.aid, this.view, - this.danmaku, + this.danmu, this.reply, this.favorite, this.coin, @@ -462,7 +462,7 @@ class Stat { Stat.fromJson(Map json) { aid = json["aid"]; view = json["view"]; - danmaku = json["danmaku"]; + danmu = json["danmaku"]; reply = json["reply"]; favorite = json["favorite"]; coin = json["coin"]; @@ -480,7 +480,7 @@ class Stat { data["aid"] = aid; data["view"] = view; - data["danmaku"] = danmaku; + data["danmaku"] = danmu; data["reply"] = reply; data["favorite"] = favorite; data["coin"] = coin; diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 6df28042..88c3dceb 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -50,9 +50,10 @@ class _AboutPageState extends State { children: [ ConstrainedBox( constraints: const BoxConstraints(maxHeight: 150), - child: Image.asset( + child: ExcludeSemantics( + child: Image.asset( 'assets/images/logo/logo_android_2.png', - ), + )), ), ListTile( title: Text('PiliPalaX', @@ -65,6 +66,7 @@ class _AboutPageState extends State { '使用Flutter开发的哔哩哔哩第三方客户端', textAlign: TextAlign.center, style: TextStyle(color: Theme.of(context).colorScheme.outline), + semanticsLabel: '与你一起,发现不一样的世界', ), ), Obx( @@ -156,7 +158,7 @@ class _AboutPageState extends State { var cleanStatus = await CacheManage().clearCacheAll(); if (cleanStatus) { getCacheSize(); - SmartDialog.showToast('清除成功'); + SmartDialog.showToast('清除成功'); } }, title: const Text('清除缓存'), @@ -207,7 +209,7 @@ class AboutController extends GetxController { String buildNumber = currentInfo.buildNumber; //if is android if (Platform.isAndroid) { - buildNumber = buildNumber.substring(0,buildNumber.length - 1); + buildNumber = buildNumber.substring(0, buildNumber.length - 1); } currentVersion.value = "${currentInfo.version}+$buildNumber"; } @@ -265,6 +267,7 @@ class AboutController extends GetxController { ), ); } + // 问题反馈 feedback() { launchUrl( diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index fb7b7595..70c54dc5 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -15,6 +15,7 @@ import 'package:PiliPalaX/pages/video/detail/introduction/widgets/action_row_ite import 'package:PiliPalaX/pages/video/detail/introduction/widgets/fav_panel.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; +import '../../../utils/utils.dart'; import 'controller.dart'; import 'widgets/intro_detail.dart'; @@ -192,6 +193,7 @@ class _BangumiInfoState extends State { src: !widget.loadingStatus ? widget.bangumiDetail!.cover! : bangumiItem!.cover!, + semanticsLabel: '封面', ), if (bangumiItem != null && bangumiItem!.rating != null) @@ -235,6 +237,7 @@ class _BangumiInfoState extends State { width: 34, height: 34, child: IconButton( + tooltip: '收藏', style: ButtonStyle( padding: MaterialStateProperty.all( EdgeInsets.zero), @@ -394,18 +397,19 @@ class _BangumiInfoState extends State { crossAxisCount: 5, childAspectRatio: 1.25, children: [ - Obx( - () => ActionItem( + Obx(() => ActionItem( icon: const Icon(FontAwesomeIcons.thumbsUp), selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp), onTap: handleState(bangumiIntroController.actionLikeVideo), selectStatus: bangumiIntroController.hasLike.value, loadingStatus: false, + semanticsLabel: '点赞', text: !widget.loadingStatus - ? widget.bangumiDetail!.stat!['likes']!.toString() - : bangumiItem!.stat!['likes']!.toString()), - ), + ? Utils.numFormat( + widget.bangumiDetail!.stat!['likes']!) + : Utils.numFormat(bangumiItem!.stat!['likes']!), + )), Obx( () => ActionItem( icon: const Icon(FontAwesomeIcons.b), @@ -414,9 +418,10 @@ class _BangumiInfoState extends State { handleState(bangumiIntroController.actionCoinVideo), selectStatus: bangumiIntroController.hasCoin.value, loadingStatus: false, + semanticsLabel: '投币', text: !widget.loadingStatus - ? widget.bangumiDetail!.stat!['coins']!.toString() - : bangumiItem!.stat!['coins']!.toString()), + ? Utils.numFormat(widget.bangumiDetail!.stat!['coins']!) + : Utils.numFormat(bangumiItem!.stat!['coins']!)), ), Obx( () => ActionItem( @@ -425,9 +430,10 @@ class _BangumiInfoState extends State { onTap: () => showFavBottomSheet(), selectStatus: bangumiIntroController.hasFav.value, loadingStatus: false, + semanticsLabel: '收藏', text: !widget.loadingStatus - ? widget.bangumiDetail!.stat!['favorite']!.toString() - : bangumiItem!.stat!['favorite']!.toString()), + ? Utils.numFormat(widget.bangumiDetail!.stat!['favorite']!) + : Utils.numFormat(bangumiItem!.stat!['favorite']!)), ), ActionItem( icon: const Icon(FontAwesomeIcons.comment), @@ -435,18 +441,20 @@ class _BangumiInfoState extends State { onTap: () => videoDetailCtr.tabCtr.animateTo(1), selectStatus: false, loadingStatus: false, + semanticsLabel: '评论', text: !widget.loadingStatus - ? widget.bangumiDetail!.stat!['reply']!.toString() - : bangumiItem!.stat!['reply']!.toString(), + ? Utils.numFormat(widget.bangumiDetail!.stat!['reply']!) + : Utils.numFormat(bangumiItem!.stat!['reply']!), ), ActionItem( icon: const Icon(FontAwesomeIcons.shareFromSquare), onTap: () => bangumiIntroController.actionShareVideo(), selectStatus: false, loadingStatus: false, + semanticsLabel: '转发', text: !widget.loadingStatus - ? widget.bangumiDetail!.stat!['share']!.toString() - : bangumiItem!.stat!['share']!.toString()), + ? Utils.numFormat(widget.bangumiDetail!.stat!['share']!) + : Utils.numFormat(bangumiItem!.stat!['share']!)), ], ), ), diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index dc8b7cbd..59237126 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -98,6 +98,7 @@ class _BangumiPageState extends State style: Theme.of(context).textTheme.titleMedium, ), IconButton( + tooltip: '刷新', onPressed: () { setState(() { _futureBuilderFutureFollow = diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart index 2b667a16..a6ca719f 100644 --- a/lib/pages/bangumi/widgets/bangumi_panel.dart +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -95,6 +95,7 @@ class _BangumiPanelState extends State { style: Theme.of(context).textTheme.titleMedium, ), IconButton( + tooltip: '关闭', icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index 37d29829..bb937bf4 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -25,12 +25,15 @@ class _ActionPanelState extends State { late ModuleStatModel stat; bool isProcessing = false; void Function()? handleState(Future Function() action) { - return isProcessing ? null : () async { - setState(() => isProcessing = true); - await action(); - setState(() => isProcessing = false); - }; + return isProcessing + ? null + : () async { + setState(() => isProcessing = true); + await action(); + setState(() => isProcessing = false); + }; } + @override void initState() { super.initState(); @@ -83,12 +86,13 @@ class _ActionPanelState extends State { icon: const Icon( FontAwesomeIcons.shareFromSquare, size: 16, + semanticLabel: "转发", ), style: TextButton.styleFrom( padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), foregroundColor: Theme.of(context).colorScheme.outline, ), - label: Text(stat.forward!.count ?? '转发'), + label: Text(stat.forward!.count ?? ''), ), ), Expanded( @@ -99,12 +103,13 @@ class _ActionPanelState extends State { icon: const Icon( FontAwesomeIcons.comment, size: 16, + semanticLabel: "评论", ), style: TextButton.styleFrom( padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), foregroundColor: Theme.of(context).colorScheme.outline, ), - label: Text(stat.comment!.count ?? '评论'), + label: Text(stat.comment!.count ?? ''), ), ), Expanded( @@ -117,6 +122,7 @@ class _ActionPanelState extends State { : FontAwesomeIcons.thumbsUp, size: 16, color: stat.like!.status! ? primary : color, + semanticLabel: stat.like!.status! ? "已赞": "点赞", ), style: TextButton.styleFrom( padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), @@ -128,7 +134,7 @@ class _ActionPanelState extends State { return ScaleTransition(scale: animation, child: child); }, child: Text( - stat.like!.count ?? '点赞', + stat.like!.count ?? '', key: ValueKey(stat.like!.count ?? '点赞'), style: TextStyle( color: stat.like!.status! ? primary : color, diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index d2028f71..7c092a45 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -48,6 +48,7 @@ class AuthorPanel extends StatelessWidget { children: [ Text( item.modules.moduleAuthor.name, + // semanticsLabel: "Up主:${item.modules.moduleAuthor.name}", style: TextStyle( color: item.modules.moduleAuthor!.vip != null && item.modules.moduleAuthor!.vip['status'] > 0 @@ -81,6 +82,7 @@ class AuthorPanel extends StatelessWidget { width: 32, height: 32, child: IconButton( + tooltip: '更多', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart index a89396b4..42d59936 100644 --- a/lib/pages/dynamics/widgets/video_panel.dart +++ b/lib/pages/dynamics/widgets/video_panel.dart @@ -87,6 +87,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) { width: width, height: width / StyleString.aspectRatio, src: content.cover, + semanticsLabel: content.title, ), ), if (content.badge != null && type == 'pgc') @@ -133,7 +134,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) { const SizedBox(width: 10), Text(content.stat.play + '次围观'), const SizedBox(width: 10), - Text(content.stat.danmaku + '条弹幕') + Text(content.stat.danmu + '条弹幕') ], ), ), diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart index 51a20728..6ec6e87b 100644 --- a/lib/pages/emote/view.dart +++ b/lib/pages/emote/view.dart @@ -78,10 +78,11 @@ class _EmotePanelState extends State overflow: TextOverflow.clip, maxLines: 1, ) - : Image.network( - e.emote![index].url!, + : NetworkImgLayer( + src: e.emote![index].url!, width: size * 38, height: size * 38, + semanticsLabel: e.emote![index].text!, ), ), ), diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 71c7937d..0b308cc9 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -49,6 +49,7 @@ class _FavPageState extends State { onPressed: () => Get.toNamed( '/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'), icon: const Icon(Icons.search_outlined), + tooltip: '搜索', ), const SizedBox(width: 6), ], diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 60ea7744..045c63db 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -96,6 +96,7 @@ class _FavDetailPageState extends State { ), actions: [ IconButton( + tooltip: '搜索', onPressed: () => Get.toNamed('/favSearch?searchType=0&mediaId=$mediaId'), icon: const Icon(Icons.search_outlined), diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index c8fb8058..f152f498 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -209,6 +209,7 @@ class VideoContent extends StatelessWidget { right: 0, bottom: -4, child: IconButton( + tooltip: '取消收藏', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), diff --git a/lib/pages/fav_search/view.dart b/lib/pages/fav_search/view.dart index 4107e138..445693d8 100644 --- a/lib/pages/fav_search/view.dart +++ b/lib/pages/fav_search/view.dart @@ -50,6 +50,7 @@ class _FavSearchPageState extends State { titleSpacing: 0, actions: [ IconButton( + tooltip: '搜索', onPressed: () => _favSearchCtr.submit(), icon: const Icon(Icons.search_outlined, size: 22)), const SizedBox(width: 10) @@ -65,6 +66,7 @@ class _FavSearchPageState extends State { hintText: _favSearchCtr.hintText, border: InputBorder.none, suffixIcon: IconButton( + tooltip: '清空', icon: Icon( Icons.clear, size: 22, diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart index 9633e7f0..ea69e159 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -41,6 +41,7 @@ class _FollowPageState extends State { IconButton( onPressed: () => Get.toNamed('/followSearch?mid=$mid'), icon: const Icon(Icons.search_outlined), + tooltip: '搜索' ), PopupMenuButton( icon: const Icon(Icons.more_vert), diff --git a/lib/pages/follow_search/view.dart b/lib/pages/follow_search/view.dart index fad06097..8fbc3565 100644 --- a/lib/pages/follow_search/view.dart +++ b/lib/pages/follow_search/view.dart @@ -50,6 +50,7 @@ class _FollowSearchPageState extends State { titleSpacing: 0, actions: [ IconButton( + tooltip: '搜索', onPressed: reRequest, icon: const Icon(CupertinoIcons.search, size: 22), ), @@ -65,6 +66,7 @@ class _FollowSearchPageState extends State { hintText: _followSearchController.hintText, border: InputBorder.none, suffixIcon: IconButton( + tooltip: '清空', icon: Icon( Icons.clear, size: 22, diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 58d89b98..0986ed25 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -76,6 +76,7 @@ class _HistoryPageState extends State { ), actions: [ IconButton( + tooltip: '搜索', onPressed: () => Get.toNamed('/historySearch'), icon: const Icon(Icons.search_outlined), ), @@ -129,6 +130,7 @@ class _HistoryPageState extends State { titleSpacing: 0, centerTitle: false, leading: IconButton( + tooltip: '取消', onPressed: () { _historyController.enableMultiple.value = false; for (var item in _historyController.historyList) { diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index a6a7d2c2..3bcfef9e 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -230,6 +230,7 @@ class HistoryItem extends StatelessWidget { const Duration(milliseconds: 250), curve: Curves.easeInOut, child: IconButton( + tooltip: '取消选择', style: ButtonStyle( padding: MaterialStateProperty.all( EdgeInsets.zero), diff --git a/lib/pages/history_search/view.dart b/lib/pages/history_search/view.dart index 7f4cbca5..c6a2a727 100644 --- a/lib/pages/history_search/view.dart +++ b/lib/pages/history_search/view.dart @@ -50,6 +50,7 @@ class _HistorySearchPageState extends State { titleSpacing: 0, actions: [ IconButton( + tooltip: '搜索', onPressed: () => _historySearchCtr.submit(), icon: const Icon(Icons.search_outlined, size: 22)), const SizedBox(width: 10) @@ -65,6 +66,7 @@ class _HistorySearchPageState extends State { hintText: _historySearchCtr.hintText, border: InputBorder.none, suffixIcon: IconButton( + tooltip: '清空', icon: Icon( Icons.clear, size: 22, diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index e42a0ee1..8ca396cf 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -220,41 +220,46 @@ class UserInfoWidget extends StatelessWidget { const SizedBox(width: 4), ClipRect( child: IconButton( + tooltip: '消息', onPressed: () => Get.toNamed('/whisper'), - icon: const Icon(Icons.notifications_none), + icon: const Icon( + Icons.notifications_none, + ), ), ) ], const SizedBox(width: 8), - Obx( - () => userLogin.value - ? Stack( - children: [ - NetworkImgLayer( - type: 'avatar', - width: 34, - height: 34, - src: userFace, - ), - Positioned.fill( - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => callback?.call(), - splashColor: Theme.of(context) - .colorScheme - .primaryContainer - .withOpacity(0.3), - borderRadius: const BorderRadius.all( - Radius.circular(50), - ), + Semantics( + label: "我的", + child: Obx( + () => userLogin.value + ? Stack( + children: [ + NetworkImgLayer( + type: 'avatar', + width: 34, + height: 34, + src: userFace, ), - ), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => callback?.call(), + splashColor: Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.3), + borderRadius: const BorderRadius.all( + Radius.circular(50), + ), + ), + ), + ) + ], ) - ], - ) - : DefaultUser(callback: () => callback!()), - ), + : DefaultUser(callback: () => callback!()), + )), ], ); } @@ -270,6 +275,7 @@ class DefaultUser extends StatelessWidget { width: 38, height: 38, child: IconButton( + tooltip: '默认用户头像', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), backgroundColor: MaterialStateProperty.resolveWith((states) { @@ -409,6 +415,7 @@ class SearchBar extends StatelessWidget { Icon( Icons.search_outlined, color: colorScheme.onSecondaryContainer, + semanticLabel: '搜索', ), const SizedBox(width: 10), Expanded( diff --git a/lib/pages/home/widgets/app_bar.dart b/lib/pages/home/widgets/app_bar.dart index 0bc96a13..7a8ee2d9 100644 --- a/lib/pages/home/widgets/app_bar.dart +++ b/lib/pages/home/widgets/app_bar.dart @@ -43,6 +43,7 @@ class HomeAppBar extends StatelessWidget { Hero( tag: 'searchTag', child: IconButton( + tooltip: '搜索', onPressed: () { Get.toNamed('/search'); }, @@ -72,11 +73,13 @@ class HomeAppBar extends StatelessWidget { width: 32, height: 32, src: userInfo.face, + semanticsLabel: '我的', ), ), const SizedBox(width: 10), ] else ...[ IconButton( + tooltip: '登录', onPressed: () => showModalBottomSheet( context: context, builder: (_) => const SizedBox( diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index cf87be95..f8280343 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -136,6 +136,7 @@ class _HtmlRenderPageState extends State actions: [ const SizedBox(width: 4), IconButton( + tooltip: '用内置浏览器打开', onPressed: () { Get.toNamed('/webview', parameters: { 'url': url.startsWith('http') ? url : 'https:$url', @@ -148,6 +149,36 @@ class _HtmlRenderPageState extends State PopupMenuButton( icon: const Icon(Icons.more_vert), itemBuilder: (BuildContext context) => [ + PopupMenuItem( + onTap: () => { + _htmlRenderCtr.reqHtml(id), + }, + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.refresh, size: 19), + SizedBox(width: 10), + Text('刷新'), + ], + ), + ), + PopupMenuItem( + onTap: () => { + Get.toNamed('/webview', parameters: { + 'url': url.startsWith('http') ? url : 'https:$url', + 'type': 'url', + 'pageTitle': title, + }), + }, + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.open_in_new, size: 19), + SizedBox(width: 10), + Text('内置浏览器打开'), + ], + ), + ), PopupMenuItem( onTap: () => { Clipboard.setData(ClipboardData(text: url)), diff --git a/lib/pages/live_room/widgets/bottom_control.dart b/lib/pages/live_room/widgets/bottom_control.dart index baf52bea..d76da720 100644 --- a/lib/pages/live_room/widgets/bottom_control.dart +++ b/lib/pages/live_room/widgets/bottom_control.dart @@ -89,6 +89,7 @@ class _BottomControlState extends State { width: 34, height: 34, child: IconButton( + tooltip: '画中画', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), @@ -114,6 +115,7 @@ class _BottomControlState extends State { const SizedBox(width: 4), ], ComBtn( + tooltip: '全屏切换', icon: const Icon( Icons.fullscreen, size: 20, diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index 6521e9d9..bd696fec 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -25,6 +25,7 @@ class _LoginPageState extends State { leading: Obx( () => _loginPageCtr.currentIndex.value == 0 ? IconButton( + tooltip: '关闭', onPressed: () async { _loginPageCtr.mobTextFieldNode.unfocus(); await Future.delayed(const Duration(milliseconds: 200)); @@ -33,6 +34,7 @@ class _LoginPageState extends State { icon: const Icon(Icons.close_outlined), ) : IconButton( + tooltip: '返回', onPressed: () => _loginPageCtr.previousPage(), icon: const Icon(Icons.arrow_back), ), @@ -174,6 +176,7 @@ class _LoginPageState extends State { ), const SizedBox(width: 4), IconButton( + tooltip: '切换至验证码登录', style: ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith( @@ -265,6 +268,7 @@ class _LoginPageState extends State { ), const SizedBox(width: 4), IconButton( + tooltip: '切换至密码登录', style: ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith( diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index c757c246..f5a22f91 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -149,6 +149,7 @@ class _MediaPageState extends State ), ), trailing: IconButton( + tooltip: '刷新', onPressed: () { setState(() { _futureBuilderFuture = mediaController.queryFavFolder(); @@ -189,6 +190,7 @@ class _MediaPageState extends State right: 14, bottom: 35), child: Center( child: IconButton( + tooltip: '查看更多', style: ButtonStyle( padding: MaterialStateProperty.all( EdgeInsets.zero), diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 79b58136..d77f5198 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -104,6 +104,7 @@ class _MemberPageState extends State ), actions: [ IconButton( + tooltip: '搜索', onPressed: () => Get.toNamed( '/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'), icon: const Icon(Icons.search_outlined), @@ -310,17 +311,20 @@ class _MemberPageState extends State FontAwesomeIcons.venus, size: 14, color: Colors.pink, + semanticLabel: _memberController.memberInfo.value.sex, ), if (_memberController.memberInfo.value.sex == '男') const Icon( FontAwesomeIcons.mars, size: 14, color: Colors.blue, + semanticLabel: _memberController.memberInfo.value.sex, ), const SizedBox(width: 4), Image.asset( 'assets/images/lv/lv${_memberController.memberInfo.value.level}.png', height: 11, + semanticLabel: '等级${_memberController.memberInfo.value.level}', ), const SizedBox(width: 6), if (_memberController @@ -333,6 +337,7 @@ class _MemberPageState extends State _memberController.memberInfo.value.vip! .label!['img_label_uri_hans'], height: 20, + semanticLabel: _memberController.memberInfo.value.vip!.label!['text'], ), ] else if (_memberController .memberInfo.value.vip!.status == @@ -344,6 +349,7 @@ class _MemberPageState extends State _memberController.memberInfo.value.vip! .label!['img_label_uri_hans_static'], height: 20, + semanticLabel: _memberController.memberInfo.value.vip!.label!['text'], ), ] ], diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index 7ae66214..ab363875 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -147,28 +147,30 @@ class ProfilePanel extends StatelessWidget { ], ), ), - Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['likes'] != null - ? Utils.numFormat( - ctr.userStat!['likes'], - ) - : '-' - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold)), - Text( - '获赞', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), + InkWell( + onTap: null, + child: Column( + children: [ + Text( + !loadingStatus + ? ctr.userStat!['likes'] != null + ? Utils.numFormat( + ctr.userStat!['likes'], + ) + : '-' + : '-', + style: const TextStyle( + fontWeight: FontWeight.bold)), + Text( + '获赞', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelMedium! + .fontSize), + ) + ], + )), ], ), ), @@ -221,8 +223,7 @@ class ProfilePanel extends StatelessWidget { TextButton( onPressed: () { Get.toNamed('/webview', parameters: { - 'url': - 'https://account.bilibili.com/account/home', + 'url': 'https://account.bilibili.com/account/home', 'pageTitle': '编辑资料(建议浏览器打开)', 'type': 'url' }); diff --git a/lib/pages/member/widgets/seasons.dart b/lib/pages/member/widgets/seasons.dart index 51b013e2..1e6f4b12 100644 --- a/lib/pages/member/widgets/seasons.dart +++ b/lib/pages/member/widgets/seasons.dart @@ -43,6 +43,7 @@ class MemberSeasonsPanel extends StatelessWidget { width: 35, height: 35, child: IconButton( + tooltip: '前往', onPressed: () => Get.toNamed( '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'), style: ButtonStyle( diff --git a/lib/pages/member_search/view.dart b/lib/pages/member_search/view.dart index d7fa40f1..3cd7c9aa 100644 --- a/lib/pages/member_search/view.dart +++ b/lib/pages/member_search/view.dart @@ -52,6 +52,7 @@ class _MemberSearchPageState extends State titleSpacing: 0, actions: [ IconButton( + tooltip: '搜索', onPressed: () => _memberSearchCtr.submit(), icon: const Icon(CupertinoIcons.search, size: 22)), const SizedBox(width: 10) @@ -67,6 +68,7 @@ class _MemberSearchPageState extends State hintText: _memberSearchCtr.hintText, border: InputBorder.none, suffixIcon: IconButton( + tooltip: '清空', icon: Icon( Icons.clear, size: 22, diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index a1e76573..e91436ea 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -44,46 +44,54 @@ class _MinePageState extends State { toolbarHeight: kTextTabBarHeight + 20, backgroundColor: Colors.transparent, centerTitle: false, - title: //logo - Row( - children: [ - Image.asset( - 'assets/images/logo/logo_android_2.png', - width: 40, - ), - const SizedBox(width: 5), - Text( - 'PiliPalaX', - style: Theme.of(context).textTheme.titleMedium, - ), - ], + title: ExcludeSemantics( + child: Row( + children: [ + Image.asset( + 'assets/images/logo/logo_android_2.png', + width: 40, + ), + const SizedBox(width: 5), + Text( + 'PiliPalaX', + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), ), actions: [ IconButton( + tooltip: "${MineController.anonymity ? '退出' : '进入'}无痕模式", onPressed: () { MineController.onChangeAnonymity(context); setState(() {}); }, icon: Icon( MineController.anonymity - ? Icons.visibility_off - : Icons.visibility, + ? CupertinoIcons.checkmark_shield + : CupertinoIcons.shield_slash, size: 22, ), ), IconButton( - onPressed: () => mineController.onChangeTheme(), + tooltip: + '切换至${mineController.themeType.value == ThemeType.dark ? '浅色' : '深色'}主题', + onPressed: () { + mineController.onChangeTheme(); + setState(() {}); + }, icon: Icon( mineController.themeType.value == ThemeType.dark - ? Icons.light_mode - : Icons.mode_night, + ? CupertinoIcons.moon + : CupertinoIcons.sun_min, size: 22, ), ), IconButton( + tooltip: '设置', onPressed: () => Get.toNamed('/setting', preventDuplicates: false), icon: const Icon( - Icons.settings, + CupertinoIcons.gear, ), ), const SizedBox(width: 10), @@ -140,6 +148,7 @@ class _MinePageState extends State { child: _mineController.userInfo.value.face != null ? NetworkImgLayer( src: _mineController.userInfo.value.face, + semanticsLabel: '头像', width: 85, height: 85) : Image.asset('assets/images/noface.jpeg'), @@ -159,6 +168,8 @@ class _MinePageState extends State { Image.asset( 'assets/images/lv/lv${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}.png', height: 10, + semanticLabel: + '等级:${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}', ), ], ), @@ -207,6 +218,8 @@ class _MinePageState extends State { color: Theme.of(context).colorScheme.onPrimary, fontSize: 12, ), + semanticsLabel: + '当前经验${levelInfo.currentExp!},升级需要${levelInfo.nextExp!}', ), ), ), diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 08998c0f..dd623bd0 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -64,7 +64,10 @@ class SSearchController extends GetxController { void submit() { // ignore: unrelated_type_equality_checks if (searchKeyWord == '') { - return; + if (hintText == ''){ + return; + } + searchKeyWord.value = hintText; } List arr = historyCacheList.where((e) => e != searchKeyWord.value).toList(); arr.insert(0, searchKeyWord.value); diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 6a390f1f..2cc93f3e 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -53,6 +53,7 @@ class _SearchPageState extends State with RouteAware { titleSpacing: 0, actions: [ IconButton( + tooltip: '搜索', onPressed: () => _searchController.submit(), icon: const Icon(CupertinoIcons.search, size: 22), ), @@ -69,6 +70,7 @@ class _SearchPageState extends State with RouteAware { hintText: _searchController.hintText, border: InputBorder.none, suffixIcon: IconButton( + tooltip: '清空', icon: Icon( Icons.clear, size: 22, diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index ee5e4ecd..9cb115e4 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -87,6 +87,7 @@ class SearchVideoPanel extends StatelessWidget { width: 32, height: 32, child: IconButton( + tooltip: '筛选', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 63f9d651..e5cba260 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -87,7 +87,7 @@ class _VideoIntroPanelState extends State errMsg: snapshot.data['msg'], btnText: snapshot.data['code'] == -404 || snapshot.data['code'] == 62002 - ? '返回上一页' + ? '上一页' : null, fn: () => Get.back(), ); @@ -285,8 +285,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { StatDanMu( theme: 'gray', danmu: !loadingStatus - ? widget.videoDetail!.stat!.danmaku - : videoItem['stat'].danmaku, + ? widget.videoDetail!.stat!.danmu + : videoItem['stat'].danmu, size: 'medium', ), const SizedBox(width: 10), @@ -335,17 +335,19 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Positioned( right: 10, top: 6, - child: GestureDetector( - onTap: () async { - final res = - await videoIntroController.aiConclusion(); - if (res['status']) { - showAiBottomSheet(); - } - }, - child: - Image.asset('assets/images/ai.png', height: 22), - ), + child: Semantics( + label: 'AI总结', + child: GestureDetector( + onTap: () async { + final res = + await videoIntroController.aiConclusion(); + if (res['status']) { + showAiBottomSheet(); + } + }, + child: Image.asset('assets/images/ai.png', + height: 22), + )), ) ], ), @@ -406,11 +408,15 @@ class _VideoInfoState extends State with TickerProviderStateMixin { fadeOutDuration: Duration.zero, ), const SizedBox(width: 10), - Text(owner.name, - style: const TextStyle(fontSize: 13)), + Text( + owner.name, + style: const TextStyle(fontSize: 13), + // semanticsLabel: "Up主:${owner.name}", + ), const SizedBox(width: 6), Text( follower, + semanticsLabel: "粉丝数:$follower", style: TextStyle( fontSize: t.textTheme.labelSmall!.fontSize, color: outline, @@ -498,8 +504,9 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onTap: handleState(videoIntroController.actionLikeVideo), selectStatus: videoIntroController.hasLike.value, loadingStatus: loadingStatus, + semanticsLabel: '点赞', text: !loadingStatus - ? widget.videoDetail!.stat!.like!.toString() + ? Utils.numFormat(widget.videoDetail!.stat!.like!) : '-'), ), // ActionItem( @@ -515,8 +522,9 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onTap: handleState(videoIntroController.actionCoinVideo), selectStatus: videoIntroController.hasCoin.value, loadingStatus: loadingStatus, + semanticsLabel: '投币', text: !loadingStatus - ? widget.videoDetail!.stat!.coin!.toString() + ? Utils.numFormat(widget.videoDetail!.stat!.coin!) : '-'), ), Obx( @@ -527,8 +535,9 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onLongPress: () => showFavBottomSheet(type: 'longPress'), selectStatus: videoIntroController.hasFav.value, loadingStatus: loadingStatus, + semanticsLabel: '收藏', text: !loadingStatus - ? widget.videoDetail!.stat!.favorite!.toString() + ? Utils.numFormat(widget.videoDetail!.stat!.favorite!) : '-'), ), ActionItem( @@ -536,15 +545,19 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onTap: () => videoDetailCtr.tabCtr.animateTo(1), selectStatus: false, loadingStatus: loadingStatus, + semanticsLabel: '评论', text: !loadingStatus - ? widget.videoDetail!.stat!.reply!.toString() + ? Utils.numFormat(widget.videoDetail!.stat!.reply!) : '评论'), ActionItem( icon: const Icon(FontAwesomeIcons.shareFromSquare), onTap: () => videoIntroController.actionShareVideo(), selectStatus: false, loadingStatus: loadingStatus, - text: '分享'), + semanticsLabel: '分享', + text: !loadingStatus + ? Utils.numFormat(widget.videoDetail!.stat!.share!) + : '分享'), ], ), ); diff --git a/lib/pages/video/detail/introduction/widgets/action_item.dart b/lib/pages/video/detail/introduction/widgets/action_item.dart index 40bb4a85..a036101d 100644 --- a/lib/pages/video/detail/introduction/widgets/action_item.dart +++ b/lib/pages/video/detail/introduction/widgets/action_item.dart @@ -10,6 +10,7 @@ class ActionItem extends StatelessWidget { final bool? loadingStatus; final String? text; final bool selectStatus; + final String semanticsLabel; const ActionItem({ Key? key, @@ -20,11 +21,15 @@ class ActionItem extends StatelessWidget { this.loadingStatus, this.text, this.selectStatus = false, + required this.semanticsLabel, }) : super(key: key); @override Widget build(BuildContext context) { - return InkWell( + return Semantics( + label: (text ?? "") + (selectStatus ? "已" :"") + semanticsLabel, + child: + InkWell( onTap: () => { feedBack(), onTap!(), @@ -37,11 +42,15 @@ class ActionItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 4), - selectStatus - ? Icon(selectIcon!.icon!, - size: 18, color: Theme.of(context).colorScheme.primary) - : Icon(icon!.icon!, - size: 18, color: Theme.of(context).colorScheme.outline), + Icon( + selectStatus + ? selectIcon!.icon! + : icon!.icon!, + size: 18, + color: selectStatus + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), const SizedBox(height: 6), AnimatedOpacity( opacity: loadingStatus! ? 0 : 1, @@ -59,11 +68,12 @@ class ActionItem extends StatelessWidget { ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.outline, fontSize: Theme.of(context).textTheme.labelSmall!.fontSize), + semanticsLabel: "", ), ), ), ], ), - ); + )); } } diff --git a/lib/pages/video/detail/introduction/widgets/fav_panel.dart b/lib/pages/video/detail/introduction/widgets/fav_panel.dart index bd965231..eb8d57c0 100644 --- a/lib/pages/video/detail/introduction/widgets/fav_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/fav_panel.dart @@ -33,6 +33,7 @@ class _FavPanelState extends State { centerTitle: false, elevation: 0, leading: IconButton( + tooltip: '关闭', onPressed: () => Get.back(), icon: const Icon(Icons.close_outlined)), title: diff --git a/lib/pages/video/detail/introduction/widgets/group_panel.dart b/lib/pages/video/detail/introduction/widgets/group_panel.dart index 8e51434e..c19846c0 100644 --- a/lib/pages/video/detail/introduction/widgets/group_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/group_panel.dart @@ -61,6 +61,7 @@ class _GroupPanelState extends State { centerTitle: false, elevation: 0, leading: IconButton( + tooltip: '关闭', onPressed: () => Get.back(), icon: const Icon(Icons.close_outlined)), title: diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index c31b4973..26155743 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -61,7 +61,7 @@ class IntroDetail extends StatelessWidget { const SizedBox(width: 10), StatDanMu( theme: 'gray', - danmu: videoDetail!.stat!.danmaku, + danmu: videoDetail!.stat!.danmu, size: 'medium', ), const SizedBox(width: 10), diff --git a/lib/pages/video/detail/introduction/widgets/page.dart b/lib/pages/video/detail/introduction/widgets/page.dart index 0042d465..ab0172e5 100644 --- a/lib/pages/video/detail/introduction/widgets/page.dart +++ b/lib/pages/video/detail/introduction/widgets/page.dart @@ -115,6 +115,7 @@ class _PagesPanelState extends State { .titleMedium, ), IconButton( + tooltip: '关闭', icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 53d8373d..ea8f379b 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -210,28 +210,30 @@ class ReplyItem extends StatelessWidget { // title Container( margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4), - child: Text.rich( - style: const TextStyle(height: 1.75), - maxLines: - replyItem!.content!.isText! && replyLevel == '1' ? 3 : 999, - overflow: TextOverflow.ellipsis, - TextSpan( - children: [ - if (replyItem!.isTop!) - const WidgetSpan( - alignment: PlaceholderAlignment.top, - child: PBadge( - text: 'TOP', - size: 'small', - stack: 'normal', - type: 'line', - fs: 9, - ), - ), - buildContent(context, replyItem!, replyReply, null), - ], - ), - ), + child: Semantics( + label: replyItem?.content?.message ?? "", + child: Text.rich( + style: const TextStyle(height: 1.75), + maxLines: + replyItem!.content!.isText! && replyLevel == '1' ? 3 : 999, + overflow: TextOverflow.ellipsis, + TextSpan( + children: [ + if (replyItem!.isTop!) + const WidgetSpan( + alignment: PlaceholderAlignment.top, + child: PBadge( + text: 'TOP', + size: 'small', + stack: 'normal', + type: 'line', + fs: 9, + ), + ), + buildContent(context, replyItem!, replyReply, null), + ], + ), + )), ), // 操作区域 bottonAction(context, replyItem!.replyControl), diff --git a/lib/pages/video/detail/reply/widgets/zan.dart b/lib/pages/video/detail/reply/widgets/zan.dart index d9252731..923416c8 100644 --- a/lib/pages/video/detail/reply/widgets/zan.dart +++ b/lib/pages/video/detail/reply/widgets/zan.dart @@ -76,6 +76,7 @@ class _ZanButtonState extends State { : FontAwesomeIcons.thumbsUp, size: 16, color: widget.replyItem!.action == 1 ? primary : color, + semanticLabel: widget.replyItem!.action == 1 ? '已赞' : '点赞', ), const SizedBox(width: 4), AnimatedSwitcher( diff --git a/lib/pages/video/detail/reply_new/toolbar_icon_button.dart b/lib/pages/video/detail/reply_new/toolbar_icon_button.dart index c4390796..047439b1 100644 --- a/lib/pages/video/detail/reply_new/toolbar_icon_button.dart +++ b/lib/pages/video/detail/reply_new/toolbar_icon_button.dart @@ -5,6 +5,7 @@ class ToolbarIconButton extends StatelessWidget { final Icon icon; final String toolbarType; final bool selected; + final String tooltip; const ToolbarIconButton({ super.key, @@ -12,6 +13,7 @@ class ToolbarIconButton extends StatelessWidget { required this.icon, required this.toolbarType, required this.selected, + required this.tooltip, }); @override @@ -20,6 +22,7 @@ class ToolbarIconButton extends StatelessWidget { width: 36, height: 36, child: IconButton( + tooltip: tooltip, onPressed: onPressed, icon: icon, highlightColor: Theme.of(context).colorScheme.secondaryContainer, diff --git a/lib/pages/video/detail/reply_new/view.dart b/lib/pages/video/detail/reply_new/view.dart index 36567718..13a475d0 100644 --- a/lib/pages/video/detail/reply_new/view.dart +++ b/lib/pages/video/detail/reply_new/view.dart @@ -192,6 +192,7 @@ class _VideoReplyNewDialogState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ToolbarIconButton( + tooltip: '输入', onPressed: () { if (toolbarType == 'emote') { setState(() { @@ -206,6 +207,7 @@ class _VideoReplyNewDialogState extends State ), const SizedBox(width: 20), ToolbarIconButton( + tooltip: '表情', onPressed: () { if (toolbarType == 'input') { setState(() { diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index 59b4479c..75fc11b2 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -85,6 +85,7 @@ class _VideoReplyReplyPanelState extends State { children: [ const Text('评论详情'), IconButton( + tooltip: '关闭', icon: const Icon(Icons.close, size: 20), onPressed: () { _videoReplyReplyController.currentPage = 0; diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index d07b793d..4afa97e5 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -1027,6 +1027,7 @@ class _HeaderControlState extends State { children: [ // SizedBox(width: MediaQuery.of(context).padding.left,), ComBtn( + tooltip: '上一页', icon: const Icon( FontAwesomeIcons.arrowLeft, size: 15, @@ -1048,8 +1049,9 @@ class _HeaderControlState extends State { }, ), SizedBox(width: buttonSpace), - if ((videoIntroController.videoDetail.value.title != null) && (isFullScreen || - (!isFullScreen && isLandscape && !horizontalScreen))) ...[ + if ((videoIntroController.videoDetail.value.title != null) && + (isFullScreen || + (!isFullScreen && isLandscape && !horizontalScreen))) ...[ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1090,6 +1092,7 @@ class _HeaderControlState extends State { ) ] else ...[ ComBtn( + tooltip: '返回主页', icon: const Icon( FontAwesomeIcons.house, size: 15, @@ -1118,12 +1121,13 @@ class _HeaderControlState extends State { width: 34, height: 34, child: IconButton( + tooltip: '发弹幕', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), onPressed: () => showShootDanmakuSheet(), icon: const Icon( - Icons.add_card_outlined, + Icons.add_comment_outlined, size: 19, color: Colors.white, ), @@ -1135,6 +1139,7 @@ class _HeaderControlState extends State { height: 34, child: Obx( () => IconButton( + tooltip: "${_.isOpenDanmu.value ? '关闭' : '开启'}弹幕", style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), @@ -1143,8 +1148,8 @@ class _HeaderControlState extends State { }, icon: Icon( _.isOpenDanmu.value - ? Icons.subtitles_outlined - : Icons.subtitles_off_outlined, + ? Icons.comment_outlined + : Icons.comments_disabled_outlined, size: 19, color: Colors.white, ), @@ -1157,6 +1162,7 @@ class _HeaderControlState extends State { width: 34, height: 34, child: IconButton( + tooltip: '画中画', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), @@ -1182,6 +1188,7 @@ class _HeaderControlState extends State { SizedBox(width: buttonSpace), ], ComBtn( + tooltip: '更多设置', icon: const Icon( Icons.more_vert_outlined, size: 18, diff --git a/lib/pages/webview/view.dart b/lib/pages/webview/view.dart index 8edd2189..c448c4a7 100644 --- a/lib/pages/webview/view.dart +++ b/lib/pages/webview/view.dart @@ -27,6 +27,7 @@ class _WebviewPageState extends State { actions: [ const SizedBox(width: 4), IconButton( + tooltip: '刷新', onPressed: () { _webviewController.controller.reload(); }, @@ -34,6 +35,7 @@ class _WebviewPageState extends State { color: Theme.of(context).colorScheme.primary), ), IconButton( + tooltip: '用外部浏览器打开', onPressed: () { launchUrl(Uri.parse(_webviewController.url)); }, diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index 2b632c02..bf8a8c4f 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -90,6 +90,7 @@ class _WhisperDetailPageState extends State width: 34, height: 34, child: IconButton( + tooltip: '返回', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), backgroundColor: MaterialStateProperty.resolveWith( @@ -160,7 +161,8 @@ class _WhisperDetailPageState extends State reverse: true, itemBuilder: (_, int i) { return ChatItem( - item: messageList[i], e_infos: _whisperDetailController.eInfos); + item: messageList[i], + e_infos: _whisperDetailController.eInfos); }, ); }), @@ -197,6 +199,7 @@ class _WhisperDetailPageState extends State // ), // ), IconButton( + tooltip: '表情', onPressed: () { // if (toolbarType == 'input') { // setState(() { @@ -220,22 +223,25 @@ class _WhisperDetailPageState extends State .withOpacity(0.08), borderRadius: BorderRadius.circular(40.0), ), - child: TextField( - readOnly: true, - style: Theme.of(context).textTheme.titleMedium, - controller: _replyContentController, - autofocus: false, - focusNode: replyContentFocusNode, - decoration: const InputDecoration( - border: InputBorder.none, // 移除默认边框 - hintText: '开发中 ...', // 提示文本 - contentPadding: EdgeInsets.symmetric( - horizontal: 16.0, vertical: 12.0), // 内边距 - ), - ), + child: Semantics( + label: '私信输入框(开发中)', + child: TextField( + readOnly: true, + style: Theme.of(context).textTheme.titleMedium, + controller: _replyContentController, + autofocus: false, + focusNode: replyContentFocusNode, + decoration: const InputDecoration( + border: InputBorder.none, // 移除默认边框 + hintText: '开发中 ...', // 提示文本 + contentPadding: EdgeInsets.symmetric( + horizontal: 16.0, vertical: 12.0), // 内边距 + ), + )), ), ), IconButton( + tooltip: '发送', // onPressed: _whisperDetailController.sendMsg, onPressed: null, icon: Icon( diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index 380312a2..7b351ff6 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -1,11 +1,16 @@ -import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:nil/nil.dart'; import 'package:PiliPalaX/plugin/pl_player/index.dart'; import 'package:PiliPalaX/plugin/pl_player/widgets/play_pause_btn.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; +import '../../../common/widgets/audio_video_progress_bar.dart'; +import '../../../utils/utils.dart'; + class BottomControl extends StatelessWidget implements PreferredSizeWidget { final PlPlayerController? controller; final Function? triggerFullScreen; @@ -23,7 +28,9 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { color: Colors.white, fontSize: 12, ); - + //阅读器限制 + Timer? _accessibilityDebounce; + double _lastAnnouncedValue = -1; return Container( color: Colors.transparent, height: 90, @@ -41,31 +48,49 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { } return Padding( padding: const EdgeInsets.only(left: 7, right: 7, bottom: 6), - child: ProgressBar( - progress: Duration(seconds: value), - buffered: Duration(seconds: buffer), - total: Duration(seconds: max), - progressBarColor: colorTheme, - baseBarColor: Colors.white.withOpacity(0.2), - bufferedBarColor: colorTheme.withOpacity(0.4), - timeLabelLocation: TimeLabelLocation.none, - thumbColor: colorTheme, - barHeight: 3.5, - thumbRadius: 7, - onDragStart: (duration) { - feedBack(); - _.onChangedSliderStart(); - }, - onDragUpdate: (duration) { - _.onUpdatedSliderProgress(duration.timeStamp); - }, - onSeek: (duration) { - _.onChangedSliderEnd(); - _.onChangedSlider(duration.inSeconds.toDouble()); - _.seekTo(Duration(seconds: duration.inSeconds), - type: 'slider'); - }, - ), + child: Semantics( + // label: '${(value / max * 100).round()}%', + value: '${(value / max * 100).round()}%', + // enabled: false, + child: ProgressBar( + progress: Duration(seconds: value), + buffered: Duration(seconds: buffer), + total: Duration(seconds: max), + progressBarColor: colorTheme, + baseBarColor: Colors.white.withOpacity(0.2), + bufferedBarColor: colorTheme.withOpacity(0.4), + timeLabelLocation: TimeLabelLocation.none, + thumbColor: colorTheme, + barHeight: 3.5, + thumbRadius: 7, + onDragStart: (duration) { + feedBack(); + _.onChangedSliderStart(); + }, + onDragUpdate: (duration) { + double newProgress = duration.timeStamp.inSeconds / max; + if ((newProgress - _lastAnnouncedValue).abs() > 0.02) { + _accessibilityDebounce?.cancel(); + _accessibilityDebounce = + Timer(const Duration(milliseconds: 200), () { + SemanticsService.announce( + "${(newProgress * 100).round()}%", + TextDirection.ltr); + _lastAnnouncedValue = newProgress; + }); + } + _.onUpdatedSliderProgress(duration.timeStamp); + }, + onSeek: (duration) { + _.onChangedSliderEnd(); + _.onChangedSlider(duration.inSeconds.toDouble()); + _.seekTo(Duration(seconds: duration.inSeconds), + type: 'slider'); + SemanticsService.announce( + "${(duration.inSeconds / max * 100).round()}%", + TextDirection.ltr); + }, + )), ); }, ), @@ -80,25 +105,26 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { // 播放时间 Obx(() { return Text( - _.durationSeconds.value >= 3600 - ? printDurationWithHours( - Duration(seconds: _.positionSeconds.value)) - : printDuration( - Duration(seconds: _.positionSeconds.value)), + Utils.timeFormat(_.positionSeconds.value), style: textStyle, + semanticsLabel: + '已播放${Utils.durationReadFormat(Utils.timeFormat(_.positionSeconds.value))}', ); }), const SizedBox(width: 2), - const Text('/', style: textStyle), + const ExcludeSemantics( + child: Text( + '/', + style: textStyle, + ), + ), const SizedBox(width: 2), Obx( () => Text( - _.durationSeconds.value >= 3600 - ? printDurationWithHours( - Duration(seconds: _.durationSeconds.value)) - : printDuration( - Duration(seconds: _.durationSeconds.value)), + Utils.timeFormat(_.durationSeconds.value), style: textStyle, + semanticsLabel: + '共${Utils.durationReadFormat(Utils.timeFormat(_.durationSeconds.value))}', ), ), const Spacer(), @@ -127,6 +153,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { width: 45, height: 30, child: IconButton( + tooltip: '字幕', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), @@ -151,6 +178,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { () => Text( '${_.playbackSpeed}X', style: const TextStyle(color: Colors.white, fontSize: 13), + semanticsLabel: '${_.playbackSpeed}倍速', ), ), ), @@ -159,16 +187,18 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { SizedBox( width: 45, height: 30, - child: ComBtn( - icon: Obx(() => Icon( + child: Obx(() => ComBtn( + tooltip: _.isFullScreen.value ? '退出全屏' : '全屏', + icon: Icon( _.isFullScreen.value ? Icons.fullscreen_exit : Icons.fullscreen, size: 19, color: Colors.white, - )), - fuc: () => triggerFullScreen!(status: !_.isFullScreen.value), - ), + ), + fuc: () => + triggerFullScreen!(status: !_.isFullScreen.value), + )), ), ], ), diff --git a/lib/plugin/pl_player/widgets/common_btn.dart b/lib/plugin/pl_player/widgets/common_btn.dart index 5f33311c..18ffa228 100644 --- a/lib/plugin/pl_player/widgets/common_btn.dart +++ b/lib/plugin/pl_player/widgets/common_btn.dart @@ -3,10 +3,12 @@ import 'package:flutter/material.dart'; class ComBtn extends StatelessWidget { final Widget? icon; final Function? fuc; + final String tooltip; const ComBtn({ this.icon, this.fuc, + required this.tooltip, super.key, }); @@ -16,6 +18,7 @@ class ComBtn extends StatelessWidget { width: 34, height: 34, child: IconButton( + tooltip: tooltip, style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), diff --git a/lib/plugin/pl_player/widgets/play_pause_btn.dart b/lib/plugin/pl_player/widgets/play_pause_btn.dart index 0ae57e54..1ac942f2 100644 --- a/lib/plugin/pl_player/widgets/play_pause_btn.dart +++ b/lib/plugin/pl_player/widgets/play_pause_btn.dart @@ -67,6 +67,9 @@ class PlayOrPauseButtonState extends State width: 34, height: 34, child: IconButton( + tooltip: widget.controller!.videoPlayerController!.state.playing + ? '暂停' + : '播放', style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 72a6a1fe..36ee8086 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -45,6 +45,76 @@ class Utils { } } + static String durationReadFormat(String duration) { + List durationParts = duration.split(':'); + + if (durationParts.length == 3) { + if (durationParts[0] != '00') { + return '${int.parse(durationParts[0])}小时${durationParts[1]}分钟${durationParts[2]}秒'; + } + durationParts.removeAt(0); + } + if (durationParts.length == 2) { + if (durationParts[0] != '00') { + return '${int.parse(durationParts[0])}分钟${durationParts[1]}秒'; + } + durationParts.removeAt(0); + } + return '${int.parse(durationParts[0])}秒'; + } + + static String videoItemSemantics(dynamic videoItem) { + String semanticsLabel = ""; + bool emptyStatCheck(dynamic stat) { + return stat == null || + stat == '' || + stat == 0 || + stat == '0' || + stat == '-'; + } + + if (videoItem.runtimeType.toString() == "RecVideoItemAppModel") { + if (videoItem.goto == 'picture') { + semanticsLabel += '动态,'; + } else if (videoItem.goto == 'bangumi') { + semanticsLabel += '番剧,'; + } + } + semanticsLabel += '${videoItem.title}'; + if (!emptyStatCheck(videoItem.stat.view)) { + semanticsLabel += ',${Utils.numFormat(videoItem.stat.view)}'; + semanticsLabel += + (videoItem.runtimeType.toString() == "RecVideoItemAppModel" && + videoItem.goto == 'picture') + ? '浏览' + : '播放'; + } + if (!emptyStatCheck(videoItem.stat.danmu)) { + semanticsLabel += ',${Utils.numFormat(videoItem.stat.danmu)}弹幕'; + } + if (videoItem.rcmdReason != null && videoItem.rcmdReason.content != '') { + semanticsLabel += ',${videoItem.rcmdReason.content}'; + } + if (!emptyStatCheck(videoItem.duration)) { + semanticsLabel += + ',时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}'; + } + if (videoItem.runtimeType.toString() != "RecVideoItemAppModel" && + videoItem.pubdate != null) { + semanticsLabel += + ',${Utils.dateFormat(videoItem.pubdate!, formatType: 'day')}'; + } + if (videoItem.owner.name != '') { + semanticsLabel += ',Up主:${videoItem.owner.name}'; + } + if (videoItem.runtimeType.toString() == "RecVideoItemAppModel" || + videoItem.runtimeType.toString() == "RecVideoItemModel" && + videoItem.isFollowed == 1) { + semanticsLabel += ',已关注'; + } + return semanticsLabel; + } + static String timeFormat(dynamic time) { // 1小时内 if (time is String && time.contains(':')) { @@ -214,7 +284,8 @@ class Utils { closestNumber = number; } } - } catch (_) {} finally { + } catch (_) { + } finally { closestNumber ??= numbers.last; } return closestNumber; @@ -347,9 +418,8 @@ class Utils { } static List generateRandomBytes(int minLength, int maxLength) { - return List.generate( - random.nextInt(maxLength-minLength+1), (_) => random.nextInt(0x60) + 0x20 - ); + return List.generate(random.nextInt(maxLength - minLength + 1), + (_) => random.nextInt(0x60) + 0x20); } static String base64EncodeRandomString(int minLength, int maxLength) { diff --git a/pubspec.lock b/pubspec.lock index d9d4baa7..bdd7ffbc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,14 +97,6 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "0.1.18" - audio_video_progress_bar: - dependency: "direct main" - description: - name: audio_video_progress_bar - sha256: "3384875247cdbea748bd9ae8330631cd06a6cabfcda4945d45c9b406da92bc66" - url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" - source: hosted - version: "2.0.1" auto_orientation: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9302d1eb..70d29504 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,8 +98,6 @@ dependencies: screen_brightness: ^0.2.2+1 wakelock_plus: ^1.1.1 universal_platform: ^1.0.0+1 - # 进度条 - audio_video_progress_bar: ^2.0.1 auto_orientation: ^2.3.1 protobuf: ^3.0.0 animations: ^2.0.8