mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
mod: 无障碍语义适配
This commit is contained in:
@@ -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') {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -31,6 +31,7 @@ class StatDanMu extends StatelessWidget {
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: color,
|
||||
),
|
||||
semanticsLabel: '${Utils.numFormat(danmu!)}条弹幕',
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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" ? "浏览" : "播放"}',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -38,7 +38,10 @@ class VideoCardH extends StatelessWidget {
|
||||
final int aid = videoItem.aid;
|
||||
final String bvid = videoItem.bvid;
|
||||
final String heroTag = Utils.makeHeroTag(aid);
|
||||
return GestureDetector(
|
||||
return Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
excludeSemantics: true,
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
if (longPress != null) {
|
||||
longPress!();
|
||||
@@ -52,8 +55,8 @@ class VideoCardH extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
try {
|
||||
final int cid =
|
||||
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
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) {
|
||||
@@ -124,7 +127,7 @@ class VideoCardH extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -124,7 +124,10 @@ class VideoCardV extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||
return Card(
|
||||
return Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
excludeSemantics: true,
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
@@ -165,6 +168,8 @@ class VideoCardV extends StatelessWidget {
|
||||
size: 'small',
|
||||
type: 'gray',
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
// semanticsLabel:
|
||||
// '时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}',
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -174,7 +179,7 @@ class VideoCardV extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,8 +297,10 @@ class VideoStat extends StatelessWidget {
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.stat.view,
|
||||
goto: videoItem.goto,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (videoItem.goto != 'picture')
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: videoItem.stat.danmu,
|
||||
|
||||
@@ -148,6 +148,7 @@ class MyApp extends StatelessWidget {
|
||||
// 图片缓存
|
||||
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
|
||||
return GetMaterialApp(
|
||||
// showSemanticsDebugger: true,
|
||||
title: 'PiliPalaX',
|
||||
theme: ThemeData(
|
||||
// fontFamily: 'HarmonyOS',
|
||||
|
||||
@@ -826,15 +826,15 @@ class Like {
|
||||
|
||||
class Stat {
|
||||
Stat({
|
||||
this.danmaku,
|
||||
this.danmu,
|
||||
this.play,
|
||||
});
|
||||
|
||||
String? danmaku;
|
||||
String? danmu;
|
||||
String? play;
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
danmaku = json['danmaku'];
|
||||
danmu = json['danmaku'];
|
||||
play = json['play'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,15 +134,15 @@ class VListItemModel {
|
||||
class Stat {
|
||||
Stat({
|
||||
this.view,
|
||||
this.danmaku,
|
||||
this.danmu,
|
||||
});
|
||||
|
||||
int? view;
|
||||
int? danmaku;
|
||||
int? danmu;
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
view = json["play"];
|
||||
danmaku = json['video_review'];
|
||||
danmu = json['video_review'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, dynamic> json) {
|
||||
aid = json["aid"];
|
||||
view = json["view"];
|
||||
danmaku = json['danmaku'];
|
||||
danmu = json['danmaku'];
|
||||
reply = json["reply"];
|
||||
favorite = json["favorite"];
|
||||
coin = json['coin'];
|
||||
|
||||
@@ -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<String, dynamic> json) {
|
||||
view = json['play'];
|
||||
danmaku = json['danmaku'];
|
||||
danmu = json['danmaku'];
|
||||
favorite = json['favorite'];
|
||||
reply = json['review'];
|
||||
like = json['like'];
|
||||
|
||||
@@ -107,14 +107,14 @@ class FavDetailItemData {
|
||||
class Stat {
|
||||
Stat({
|
||||
this.view,
|
||||
this.danmaku,
|
||||
this.danmu,
|
||||
});
|
||||
|
||||
int? view;
|
||||
int? danmaku;
|
||||
int? danmu;
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
view = json['play'];
|
||||
danmaku = json['danmaku'];
|
||||
danmu = json['danmaku'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic> 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;
|
||||
|
||||
@@ -50,9 +50,10 @@ class _AboutPageState extends State<AboutPage> {
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 150),
|
||||
child: ExcludeSemantics(
|
||||
child: Image.asset(
|
||||
'assets/images/logo/logo_android_2.png',
|
||||
),
|
||||
)),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('PiliPalaX',
|
||||
@@ -65,6 +66,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
'使用Flutter开发的哔哩哔哩第三方客户端',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
semanticsLabel: '与你一起,发现不一样的世界',
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
@@ -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(
|
||||
|
||||
@@ -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<BangumiInfo> {
|
||||
src: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!,
|
||||
semanticsLabel: '封面',
|
||||
),
|
||||
if (bangumiItem != null &&
|
||||
bangumiItem!.rating != null)
|
||||
@@ -235,6 +237,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: '收藏',
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
@@ -394,18 +397,19 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1.25,
|
||||
children: <Widget>[
|
||||
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<BangumiInfo> {
|
||||
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<BangumiInfo> {
|
||||
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<BangumiInfo> {
|
||||
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']!)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -98,6 +98,7 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_futureBuilderFutureFollow =
|
||||
|
||||
@@ -95,6 +95,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '关闭',
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
|
||||
@@ -25,12 +25,15 @@ class _ActionPanelState extends State<ActionPanel> {
|
||||
late ModuleStatModel stat;
|
||||
bool isProcessing = false;
|
||||
void Function()? handleState(Future Function() action) {
|
||||
return isProcessing ? null : () async {
|
||||
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<ActionPanel> {
|
||||
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<ActionPanel> {
|
||||
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<ActionPanel> {
|
||||
: 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<ActionPanel> {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
stat.like!.count ?? '点赞',
|
||||
stat.like!.count ?? '',
|
||||
key: ValueKey<String>(stat.like!.count ?? '点赞'),
|
||||
style: TextStyle(
|
||||
color: stat.like!.status! ? primary : color,
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -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 + '条弹幕')
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -78,10 +78,11 @@ class _EmotePanelState extends State<EmotePanel>
|
||||
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!,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -49,6 +49,7 @@ class _FavPageState extends State<FavPage> {
|
||||
onPressed: () => Get.toNamed(
|
||||
'/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
tooltip: '搜索',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
|
||||
@@ -96,6 +96,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: '搜索',
|
||||
onPressed: () =>
|
||||
Get.toNamed('/favSearch?searchType=0&mediaId=$mediaId'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
|
||||
@@ -209,6 +209,7 @@ class VideoContent extends StatelessWidget {
|
||||
right: 0,
|
||||
bottom: -4,
|
||||
child: IconButton(
|
||||
tooltip: '取消收藏',
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
|
||||
@@ -50,6 +50,7 @@ class _FavSearchPageState extends State<FavSearchPage> {
|
||||
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<FavSearchPage> {
|
||||
hintText: _favSearchCtr.hintText,
|
||||
border: InputBorder.none,
|
||||
suffixIcon: IconButton(
|
||||
tooltip: '清空',
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
size: 22,
|
||||
|
||||
@@ -41,6 +41,7 @@ class _FollowPageState extends State<FollowPage> {
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed('/followSearch?mid=$mid'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
tooltip: '搜索'
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
|
||||
@@ -50,6 +50,7 @@ class _FollowSearchPageState extends State<FollowSearchPage> {
|
||||
titleSpacing: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: '搜索',
|
||||
onPressed: reRequest,
|
||||
icon: const Icon(CupertinoIcons.search, size: 22),
|
||||
),
|
||||
@@ -65,6 +66,7 @@ class _FollowSearchPageState extends State<FollowSearchPage> {
|
||||
hintText: _followSearchController.hintText,
|
||||
border: InputBorder.none,
|
||||
suffixIcon: IconButton(
|
||||
tooltip: '清空',
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
size: 22,
|
||||
|
||||
@@ -76,6 +76,7 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: '搜索',
|
||||
onPressed: () => Get.toNamed('/historySearch'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
@@ -129,6 +130,7 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
leading: IconButton(
|
||||
tooltip: '取消',
|
||||
onPressed: () {
|
||||
_historyController.enableMultiple.value = false;
|
||||
for (var item in _historyController.historyList) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -50,6 +50,7 @@ class _HistorySearchPageState extends State<HistorySearchPage> {
|
||||
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<HistorySearchPage> {
|
||||
hintText: _historySearchCtr.hintText,
|
||||
border: InputBorder.none,
|
||||
suffixIcon: IconButton(
|
||||
tooltip: '清空',
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
size: 22,
|
||||
|
||||
@@ -220,13 +220,18 @@ 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(
|
||||
Semantics(
|
||||
label: "我的",
|
||||
child: Obx(
|
||||
() => userLogin.value
|
||||
? Stack(
|
||||
children: [
|
||||
@@ -254,7 +259,7 @@ class UserInfoWidget extends StatelessWidget {
|
||||
],
|
||||
)
|
||||
: 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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -136,6 +136,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
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<HtmlRenderPage>
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
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)),
|
||||
|
||||
@@ -89,6 +89,7 @@ class _BottomControlState extends State<BottomControl> {
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: '画中画',
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
@@ -114,6 +115,7 @@ class _BottomControlState extends State<BottomControl> {
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
ComBtn(
|
||||
tooltip: '全屏切换',
|
||||
icon: const Icon(
|
||||
Icons.fullscreen,
|
||||
size: 20,
|
||||
|
||||
@@ -25,6 +25,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
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<LoginPage> {
|
||||
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<LoginPage> {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
tooltip: '切换至验证码登录',
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith(
|
||||
@@ -265,6 +268,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
tooltip: '切换至密码登录',
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith(
|
||||
|
||||
@@ -149,6 +149,7 @@ class _MediaPageState extends State<MediaPage>
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture = mediaController.queryFavFolder();
|
||||
@@ -189,6 +190,7 @@ class _MediaPageState extends State<MediaPage>
|
||||
right: 14, bottom: 35),
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
tooltip: '查看更多',
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
|
||||
@@ -104,6 +104,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
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<MemberPage>
|
||||
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<MemberPage>
|
||||
_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<MemberPage>
|
||||
_memberController.memberInfo.value.vip!
|
||||
.label!['img_label_uri_hans_static'],
|
||||
height: 20,
|
||||
semanticLabel: _memberController.memberInfo.value.vip!.label!['text'],
|
||||
),
|
||||
]
|
||||
],
|
||||
|
||||
@@ -147,7 +147,9 @@ class ProfilePanel extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
InkWell(
|
||||
onTap: null,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
!loadingStatus
|
||||
@@ -168,7 +170,7 @@ class ProfilePanel extends StatelessWidget {
|
||||
.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'
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -52,6 +52,7 @@ class _MemberSearchPageState extends State<MemberSearchPage>
|
||||
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<MemberSearchPage>
|
||||
hintText: _memberSearchCtr.hintText,
|
||||
border: InputBorder.none,
|
||||
suffixIcon: IconButton(
|
||||
tooltip: '清空',
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
size: 22,
|
||||
|
||||
@@ -44,8 +44,8 @@ class _MinePageState extends State<MinePage> {
|
||||
toolbarHeight: kTextTabBarHeight + 20,
|
||||
backgroundColor: Colors.transparent,
|
||||
centerTitle: false,
|
||||
title: //logo
|
||||
Row(
|
||||
title: ExcludeSemantics(
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logo/logo_android_2.png',
|
||||
@@ -58,32 +58,40 @@ class _MinePageState extends State<MinePage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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<MinePage> {
|
||||
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<MinePage> {
|
||||
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<MinePage> {
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontSize: 12,
|
||||
),
|
||||
semanticsLabel:
|
||||
'当前经验${levelInfo.currentExp!},升级需要${levelInfo.nextExp!}',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -64,8 +64,11 @@ class SSearchController extends GetxController {
|
||||
void submit() {
|
||||
// ignore: unrelated_type_equality_checks
|
||||
if (searchKeyWord == '') {
|
||||
if (hintText == ''){
|
||||
return;
|
||||
}
|
||||
searchKeyWord.value = hintText;
|
||||
}
|
||||
List arr = historyCacheList.where((e) => e != searchKeyWord.value).toList();
|
||||
arr.insert(0, searchKeyWord.value);
|
||||
historyCacheList = arr;
|
||||
|
||||
@@ -53,6 +53,7 @@ class _SearchPageState extends State<SearchPage> 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<SearchPage> with RouteAware {
|
||||
hintText: _searchController.hintText,
|
||||
border: InputBorder.none,
|
||||
suffixIcon: IconButton(
|
||||
tooltip: '清空',
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
size: 22,
|
||||
|
||||
@@ -87,6 +87,7 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: IconButton(
|
||||
tooltip: '筛选',
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
|
||||
@@ -87,7 +87,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
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<VideoInfo> 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,6 +335,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 6,
|
||||
child: Semantics(
|
||||
label: 'AI总结',
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final res =
|
||||
@@ -343,9 +345,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
showAiBottomSheet();
|
||||
}
|
||||
},
|
||||
child:
|
||||
Image.asset('assets/images/ai.png', height: 22),
|
||||
),
|
||||
child: Image.asset('assets/images/ai.png',
|
||||
height: 22),
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
@@ -406,11 +408,15 @@ class _VideoInfoState extends State<VideoInfo> 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<VideoInfo> 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<VideoInfo> 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<VideoInfo> 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<VideoInfo> 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!)
|
||||
: '分享'),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
Icon(
|
||||
selectStatus
|
||||
? Icon(selectIcon!.icon!,
|
||||
size: 18, color: Theme.of(context).colorScheme.primary)
|
||||
: Icon(icon!.icon!,
|
||||
size: 18, color: Theme.of(context).colorScheme.outline),
|
||||
? 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: "",
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class _FavPanelState extends State<FavPanel> {
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
tooltip: '关闭',
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.close_outlined)),
|
||||
title:
|
||||
|
||||
@@ -61,6 +61,7 @@ class _GroupPanelState extends State<GroupPanel> {
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
tooltip: '关闭',
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.close_outlined)),
|
||||
title:
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -115,6 +115,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
.titleMedium,
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '关闭',
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
|
||||
@@ -210,6 +210,8 @@ class ReplyItem extends StatelessWidget {
|
||||
// title
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4),
|
||||
child: Semantics(
|
||||
label: replyItem?.content?.message ?? "",
|
||||
child: Text.rich(
|
||||
style: const TextStyle(height: 1.75),
|
||||
maxLines:
|
||||
@@ -231,7 +233,7 @@ class ReplyItem extends StatelessWidget {
|
||||
buildContent(context, replyItem!, replyReply, null),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
// 操作区域
|
||||
bottonAction(context, replyItem!.replyControl),
|
||||
|
||||
@@ -76,6 +76,7 @@ class _ZanButtonState extends State<ZanButton> {
|
||||
: FontAwesomeIcons.thumbsUp,
|
||||
size: 16,
|
||||
color: widget.replyItem!.action == 1 ? primary : color,
|
||||
semanticLabel: widget.replyItem!.action == 1 ? '已赞' : '点赞',
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
AnimatedSwitcher(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -192,6 +192,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ToolbarIconButton(
|
||||
tooltip: '输入',
|
||||
onPressed: () {
|
||||
if (toolbarType == 'emote') {
|
||||
setState(() {
|
||||
@@ -206,6 +207,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
ToolbarIconButton(
|
||||
tooltip: '表情',
|
||||
onPressed: () {
|
||||
if (toolbarType == 'input') {
|
||||
setState(() {
|
||||
|
||||
@@ -85,6 +85,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
children: <Widget>[
|
||||
const Text('评论详情'),
|
||||
IconButton(
|
||||
tooltip: '关闭',
|
||||
icon: const Icon(Icons.close, size: 20),
|
||||
onPressed: () {
|
||||
_videoReplyReplyController.currentPage = 0;
|
||||
|
||||
@@ -1027,6 +1027,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
children: [
|
||||
// SizedBox(width: MediaQuery.of(context).padding.left,),
|
||||
ComBtn(
|
||||
tooltip: '上一页',
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.arrowLeft,
|
||||
size: 15,
|
||||
@@ -1048,7 +1049,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
},
|
||||
),
|
||||
SizedBox(width: buttonSpace),
|
||||
if ((videoIntroController.videoDetail.value.title != null) && (isFullScreen ||
|
||||
if ((videoIntroController.videoDetail.value.title != null) &&
|
||||
(isFullScreen ||
|
||||
(!isFullScreen && isLandscape && !horizontalScreen))) ...[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1090,6 +1092,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
)
|
||||
] else ...[
|
||||
ComBtn(
|
||||
tooltip: '返回主页',
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.house,
|
||||
size: 15,
|
||||
@@ -1118,12 +1121,13 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
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<HeaderControl> {
|
||||
height: 34,
|
||||
child: Obx(
|
||||
() => IconButton(
|
||||
tooltip: "${_.isOpenDanmu.value ? '关闭' : '开启'}弹幕",
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
@@ -1143,8 +1148,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
},
|
||||
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<HeaderControl> {
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: '画中画',
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
@@ -1182,6 +1188,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
SizedBox(width: buttonSpace),
|
||||
],
|
||||
ComBtn(
|
||||
tooltip: '更多设置',
|
||||
icon: const Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 18,
|
||||
|
||||
@@ -27,6 +27,7 @@ class _WebviewPageState extends State<WebviewPage> {
|
||||
actions: [
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
_webviewController.controller.reload();
|
||||
},
|
||||
@@ -34,6 +35,7 @@ class _WebviewPageState extends State<WebviewPage> {
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '用外部浏览器打开',
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(_webviewController.url));
|
||||
},
|
||||
|
||||
@@ -90,6 +90,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
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<WhisperDetailPage>
|
||||
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<WhisperDetailPage>
|
||||
// ),
|
||||
// ),
|
||||
IconButton(
|
||||
tooltip: '表情',
|
||||
onPressed: () {
|
||||
// if (toolbarType == 'input') {
|
||||
// setState(() {
|
||||
@@ -220,6 +223,8 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(40.0),
|
||||
),
|
||||
child: Semantics(
|
||||
label: '私信输入框(开发中)',
|
||||
child: TextField(
|
||||
readOnly: true,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
@@ -232,10 +237,11 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 12.0), // 内边距
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '发送',
|
||||
// onPressed: _whisperDetailController.sendMsg,
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
|
||||
@@ -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,6 +48,10 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 7, right: 7, bottom: 6),
|
||||
child: Semantics(
|
||||
// label: '${(value / max * 100).round()}%',
|
||||
value: '${(value / max * 100).round()}%',
|
||||
// enabled: false,
|
||||
child: ProgressBar(
|
||||
progress: Duration(seconds: value),
|
||||
buffered: Duration(seconds: buffer),
|
||||
@@ -57,6 +68,17 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
_.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) {
|
||||
@@ -64,8 +86,11 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
_.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),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -67,6 +67,9 @@ class PlayOrPauseButtonState extends State<PlayOrPauseButton>
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: widget.controller!.videoPlayerController!.state.playing
|
||||
? '暂停'
|
||||
: '播放',
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
|
||||
@@ -45,6 +45,76 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static String durationReadFormat(String duration) {
|
||||
List<String> 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<int> generateRandomBytes(int minLength, int maxLength) {
|
||||
return List<int>.generate(
|
||||
random.nextInt(maxLength-minLength+1), (_) => random.nextInt(0x60) + 0x20
|
||||
);
|
||||
return List<int>.generate(random.nextInt(maxLength - minLength + 1),
|
||||
(_) => random.nextInt(0x60) + 0x20);
|
||||
}
|
||||
|
||||
static String base64EncodeRandomString(int minLength, int maxLength) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user