mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-11 05:11:34 +08:00
@@ -266,7 +266,7 @@ class VideoCustomActions {
|
||||
MineController.anonymity.value
|
||||
? const Icon(MdiIcons.incognitoOff, size: 16)
|
||||
: const Icon(MdiIcons.incognito, size: 16),
|
||||
() => MineController.onChangeAnonymity(context),
|
||||
MineController.onChangeAnonymity,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ class _ArticlePageState extends State<ArticlePage>
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => _articleCtr.queryBySort(),
|
||||
onPressed: _articleCtr.queryBySort,
|
||||
icon: const Icon(Icons.sort, size: 16),
|
||||
label: Obx(
|
||||
() => Text(
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'package:PiliPlus/models_new/fav/fav_folder/data.dart';
|
||||
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
|
||||
import 'package:PiliPlus/models_new/video/video_tag/data.dart';
|
||||
import 'package:PiliPlus/services/account_service.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
abstract class CommonIntroController extends GetxController {
|
||||
@@ -25,13 +23,4 @@ abstract class CommonIntroController extends GetxController {
|
||||
Future queryVideoInFolder();
|
||||
|
||||
Future<void> actionFavVideo();
|
||||
|
||||
void onChoose(bool checkValue, int index) {
|
||||
feedBack();
|
||||
FavFolderInfo item = favFolderData.value.list![index];
|
||||
item
|
||||
..favState = checkValue ? 1 : 0
|
||||
..mediaCount = checkValue ? item.mediaCount + 1 : item.mediaCount - 1;
|
||||
favFolderData.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +125,9 @@ class _UpPanelState extends State<UpPanel> {
|
||||
}
|
||||
|
||||
void _onSelect(UserItem data) {
|
||||
widget.dynamicsController.currentMid = data.mid;
|
||||
widget.dynamicsController.onSelectUp(data.mid);
|
||||
widget.dynamicsController
|
||||
..currentMid = data.mid
|
||||
..onSelectUp(data.mid);
|
||||
|
||||
data.hasUpdate = false;
|
||||
|
||||
|
||||
@@ -138,28 +138,40 @@ class _VotePanelState extends State<VotePanel> {
|
||||
];
|
||||
|
||||
Widget _buildOptions(int index) {
|
||||
final opt = _voteInfo.options[index];
|
||||
final selected = groupValue.contains(opt.optidx);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: PercentageChip(
|
||||
label: opt.optdesc!,
|
||||
percentage: _showPercentage ? _percentage[index] : null,
|
||||
selected: selected,
|
||||
onSelected: !_enabled || (groupValue.length >= _maxCnt && !selected)
|
||||
? null
|
||||
: (value) => _onSelected(value, opt.optidx!),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final opt = _voteInfo.options[index];
|
||||
final selected = groupValue.contains(opt.optidx);
|
||||
return PercentageChip(
|
||||
label: opt.optdesc!,
|
||||
percentage: _showPercentage ? _percentage[index] : null,
|
||||
selected: selected,
|
||||
onSelected: !_enabled || (groupValue.length >= _maxCnt && !selected)
|
||||
? null
|
||||
: (value) => _onSelected(context, value, opt.optidx!),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelected(bool value, int optidx) {
|
||||
void _onSelected(BuildContext context, bool value, int optidx) {
|
||||
bool isMax = groupValue.length >= _maxCnt;
|
||||
if (value) {
|
||||
groupValue.add(optidx);
|
||||
} else {
|
||||
groupValue.remove(optidx);
|
||||
}
|
||||
setState(() {});
|
||||
if ((isMax &&
|
||||
_maxCnt != _voteInfo.options.length &&
|
||||
groupValue.length < _maxCnt) ||
|
||||
(groupValue.length >= _maxCnt && _maxCnt < _voteInfo.options.length)) {
|
||||
setState(() {});
|
||||
} else {
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildContext() {
|
||||
|
||||
@@ -670,7 +670,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => _controller.queryBySort(),
|
||||
onPressed: _controller.queryBySort,
|
||||
icon: Icon(
|
||||
Icons.sort,
|
||||
size: 16,
|
||||
|
||||
@@ -83,12 +83,12 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
||||
privacy: _isPublic ? 0 : 1,
|
||||
cover: _cover ?? '',
|
||||
intro: _introController.text,
|
||||
).then((data) {
|
||||
if (data['status']) {
|
||||
Get.back(result: data['data']);
|
||||
).then((res) {
|
||||
if (res['status']) {
|
||||
Get.back(result: res['data']);
|
||||
SmartDialog.showToast('${_mediaId != null ? '编辑' : '创建'}成功');
|
||||
} else {
|
||||
SmartDialog.showToast(data['msg']);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -117,7 +117,7 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickImg(ThemeData theme) async {
|
||||
Future<void> _pickImg(BuildContext context, ThemeData theme) async {
|
||||
try {
|
||||
XFile? pickedFile = await _imagePicker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
@@ -154,12 +154,12 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
||||
path: croppedFile.path,
|
||||
bucket: 'medialist',
|
||||
dir: 'cover',
|
||||
).then((data) {
|
||||
if (data['status']) {
|
||||
_cover = data['data']['location'];
|
||||
setState(() {});
|
||||
).then((res) {
|
||||
if (res['status']) {
|
||||
_cover = res['data']['location'];
|
||||
(context as Element).markNeedsBuild();
|
||||
} else {
|
||||
SmartDialog.showToast(data['msg']);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -175,87 +175,91 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
||||
child: Column(
|
||||
children: [
|
||||
if (_attr == null || !FavUtil.isDefaultFav(_attr!)) ...[
|
||||
ListTile(
|
||||
tileColor: theme.colorScheme.onInverseSurface,
|
||||
onTap: () {
|
||||
EasyThrottle.throttle(
|
||||
'imagePicker', const Duration(milliseconds: 500), () {
|
||||
if (_cover?.isNotEmpty == true) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding:
|
||||
const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_pickImg(theme);
|
||||
},
|
||||
title: const Text(
|
||||
'替换封面',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return ListTile(
|
||||
tileColor: theme.colorScheme.onInverseSurface,
|
||||
onTap: () {
|
||||
EasyThrottle.throttle(
|
||||
'imagePicker', const Duration(milliseconds: 500), () {
|
||||
if (_cover?.isNotEmpty == true) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding:
|
||||
const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_pickImg(context, theme);
|
||||
},
|
||||
title: const Text(
|
||||
'替换封面',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_cover = null;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
title: const Text(
|
||||
'移除封面',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_cover = null;
|
||||
setState(() {});
|
||||
},
|
||||
title: const Text(
|
||||
'移除封面',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
_pickImg(theme);
|
||||
}
|
||||
});
|
||||
},
|
||||
leading: Text(
|
||||
'封面',
|
||||
style: leadingStyle,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_cover?.isNotEmpty == true)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(6)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: ImageUtil.thumbnailUrl(_cover!),
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxHeight * 16 / 9,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_right,
|
||||
color: theme.colorScheme.outline,
|
||||
} else {
|
||||
_pickImg(context, theme);
|
||||
}
|
||||
});
|
||||
},
|
||||
leading: Text(
|
||||
'封面',
|
||||
style: leadingStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
spacing: 10,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_cover?.isNotEmpty == true)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(6)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: ImageUtil.thumbnailUrl(_cover!),
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxHeight * 16 / 9,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_right,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
@@ -368,26 +372,30 @@ class _CreateFavPageState extends State<CreateFavPage> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
ListTile(
|
||||
onTap: () => setState(() {
|
||||
_isPublic = !_isPublic;
|
||||
}),
|
||||
tileColor: theme.colorScheme.onInverseSurface,
|
||||
leading: Text(
|
||||
'公开',
|
||||
style: leadingStyle,
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
value: _isPublic,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isPublic = value;
|
||||
});
|
||||
}),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
void onTap() {
|
||||
_isPublic = !_isPublic;
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
tileColor: theme.colorScheme.onInverseSurface,
|
||||
leading: Text(
|
||||
'公开',
|
||||
style: leadingStyle,
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
value: _isPublic,
|
||||
onChanged: (value) => onTap(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
@@ -215,12 +215,12 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
context: context,
|
||||
title: '确定删除该收藏夹?',
|
||||
onConfirm: () =>
|
||||
FavHttp.deleteFolder(mediaIds: [mediaId]).then((data) {
|
||||
if (data['status']) {
|
||||
FavHttp.deleteFolder(mediaIds: [mediaId]).then((res) {
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('删除成功');
|
||||
Get.back(result: true);
|
||||
} else {
|
||||
SmartDialog.showToast(data['msg']);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -52,26 +52,37 @@ class _FavPanelState extends State<FavPanel> {
|
||||
FavFolderInfo item = widget.ctr.favFolderData.value.list![index];
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: ListTile(
|
||||
onTap: () => setState(
|
||||
() => widget.ctr.onChoose(item.favState != 1, index)),
|
||||
dense: true,
|
||||
leading: FavUtil.isPublicFav(item.attr)
|
||||
? const Icon(Icons.folder_outlined)
|
||||
: const Icon(Icons.lock_outline),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(item.title),
|
||||
subtitle: Text(
|
||||
'${item.mediaCount}个内容 . ${FavUtil.isPublicFavText(item.attr)}',
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: item.favState == 1,
|
||||
onChanged: (bool? checkValue) =>
|
||||
setState(() => widget.ctr.onChoose(checkValue!, index)),
|
||||
),
|
||||
),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
void onTap() {
|
||||
bool isChecked = item.favState == 1;
|
||||
item
|
||||
..favState = isChecked ? 0 : 1
|
||||
..mediaCount =
|
||||
isChecked ? item.mediaCount - 1 : item.mediaCount + 1;
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
dense: true,
|
||||
leading: FavUtil.isPublicFav(item.attr)
|
||||
? const Icon(Icons.folder_outlined)
|
||||
: const Icon(Icons.lock_outline),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(item.title),
|
||||
subtitle: Text(
|
||||
'${item.mediaCount}个内容 . ${FavUtil.isPublicFavText(item.attr)}',
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: item.favState == 1,
|
||||
onChanged: (bool? checkValue) => onTap(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -136,7 +147,7 @@ class _FavPanelState extends State<FavPanel> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
onPressed: Get.back,
|
||||
style: TextButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
|
||||
@@ -25,7 +25,7 @@ class GroupPanel extends StatefulWidget {
|
||||
class _GroupPanelState extends State<GroupPanel> {
|
||||
LoadingState<List<MemberTagItemModel>> loadingState =
|
||||
LoadingState<List<MemberTagItemModel>>.loading();
|
||||
bool showDefaultBtn = true;
|
||||
RxBool showDefaultBtn = true.obs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -43,7 +43,7 @@ class _GroupPanelState extends State<GroupPanel> {
|
||||
..map((item) {
|
||||
return item.checked = widget.tags?.contains(item.tagid) == true;
|
||||
}).toList();
|
||||
showDefaultBtn = !tagsList.any((e) => e.checked == true);
|
||||
showDefaultBtn.value = !tagsList.any((e) => e.checked == true);
|
||||
loadingState = Success(tagsList);
|
||||
} else {
|
||||
loadingState = Error(res['msg']);
|
||||
@@ -89,28 +89,32 @@ class _GroupPanelState extends State<GroupPanel> {
|
||||
final item = response[index];
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
item.checked = !item.checked!;
|
||||
showDefaultBtn = !response.any((e) => e.checked == true);
|
||||
setState(() {});
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
void onTap() {
|
||||
item.checked = !item.checked!;
|
||||
(context as Element).markNeedsBuild();
|
||||
showDefaultBtn.value =
|
||||
!response.any((e) => e.checked == true);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
dense: true,
|
||||
leading: const Icon(Icons.group_outlined),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(item.name ?? ''),
|
||||
subtitle:
|
||||
item.tip?.isNotEmpty == true ? Text(item.tip!) : null,
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: item.checked,
|
||||
onChanged: (bool? checkValue) => onTap(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.group_outlined),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(item.name ?? ''),
|
||||
subtitle: item.tip?.isNotEmpty == true ? Text(item.tip!) : null,
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: item.checked,
|
||||
onChanged: (bool? checkValue) {
|
||||
item.checked = checkValue;
|
||||
showDefaultBtn = !response.any((e) => e.checked == true);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -152,13 +156,13 @@ class _GroupPanelState extends State<GroupPanel> {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => onSave(),
|
||||
onPressed: onSave,
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 30, right: 30),
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
child: Text(showDefaultBtn ? '保存至默认分组' : '保存'),
|
||||
child: Obx(() => Text(showDefaultBtn.value ? '保存至默认分组' : '保存')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -32,10 +32,14 @@ class _LoginPageState extends State<LoginPage> {
|
||||
const SizedBox(height: 20),
|
||||
const Text('使用 bilibili 官方 App 扫码登录'),
|
||||
const SizedBox(height: 20),
|
||||
Obx(() => Text('剩余有效时间: ${_loginPageCtr.qrCodeLeftTime} 秒',
|
||||
Obx(
|
||||
() => Text(
|
||||
'剩余有效时间: ${_loginPageCtr.qrCodeLeftTime} 秒',
|
||||
style: TextStyle(
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
color: theme.colorScheme.primaryFixedDim))),
|
||||
color: theme.colorScheme.primaryFixedDim),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -107,29 +111,37 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Obx(() => Text(
|
||||
_loginPageCtr.statusQRCode.value,
|
||||
style: TextStyle(color: theme.colorScheme.secondaryFixedDim),
|
||||
)),
|
||||
Obx(() => GestureDetector(
|
||||
onTap: () => Utils.copyText(
|
||||
_loginPageCtr.codeInfo['data']?['url'] ?? '',
|
||||
toastText: '已复制到剪贴板,可粘贴至已登录的app私信处发送,然后点击已发送的链接打开'),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||
child: Text(_loginPageCtr.codeInfo['data']?['url'] ?? "",
|
||||
style: theme.textTheme.labelSmall!.copyWith(
|
||||
color: theme.colorScheme.onSurface
|
||||
.withValues(alpha: 0.4))),
|
||||
),
|
||||
)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text('请务必在 PiliPlus 开源仓库等可信渠道下载安装。',
|
||||
Obx(
|
||||
() => Text(
|
||||
_loginPageCtr.statusQRCode.value,
|
||||
style: TextStyle(color: theme.colorScheme.secondaryFixedDim),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => GestureDetector(
|
||||
onTap: () => Utils.copyText(
|
||||
_loginPageCtr.codeInfo['data']?['url'] ?? '',
|
||||
toastText: '已复制到剪贴板,可粘贴至已登录的app私信处发送,然后点击已发送的链接打开'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||
child: Text(
|
||||
_loginPageCtr.codeInfo['data']?['url'] ?? "",
|
||||
style: theme.textTheme.labelSmall!.copyWith(
|
||||
color:
|
||||
theme.colorScheme.onSurface.withValues(alpha: 0.4)))),
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
'请务必在 PiliPlus 开源仓库等可信渠道下载安装。',
|
||||
style: theme.textTheme.labelSmall!.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -247,8 +259,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||
const EdgeInsets.fromLTRB(0.0, 2.0, 0.0, 16.0),
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(25, 0, 25, 10),
|
||||
child: Text("试试扫码、手机号登录,或选择")),
|
||||
padding: EdgeInsets.fromLTRB(25, 0, 25, 10),
|
||||
child: Text("试试扫码、手机号登录,或选择"),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'找回密码(手机版)',
|
||||
@@ -303,20 +316,25 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
'根据 bilibili 官方登录接口规范,密码将在本地加盐、加密后传输。\n'
|
||||
'盐与公钥均由官方提供;以 RSA/ECB/PKCS1Padding 方式加密。\n'
|
||||
'账号密码仅用于该登录接口,不予保存;本地仅存储登录凭证。\n'
|
||||
'请务必在 PiliPlus 开源仓库等可信渠道下载安装。',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.labelSmall!.copyWith(
|
||||
color:
|
||||
theme.colorScheme.onSurface.withValues(alpha: 0.4)))),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
'根据 bilibili 官方登录接口规范,密码将在本地加盐、加密后传输。\n'
|
||||
'盐与公钥均由官方提供;以 RSA/ECB/PKCS1Padding 方式加密。\n'
|
||||
'账号密码仅用于该登录接口,不予保存;本地仅存储登录凭证。\n'
|
||||
'请务必在 PiliPlus 开源仓库等可信渠道下载安装。',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.labelSmall!.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
late final List<Map<String, dynamic>> internationalDialingPrefix =
|
||||
Constants.internationalDialingPrefix;
|
||||
|
||||
Widget loginBySmS(ThemeData theme) {
|
||||
return Column(
|
||||
children: [
|
||||
@@ -324,55 +342,63 @@ class _LoginPageState extends State<LoginPage> {
|
||||
const Text('使用手机短信验证码登录'),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: DecoratedBox(
|
||||
decoration: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.4)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: DecoratedBox(
|
||||
decoration: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.4),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
Icon(
|
||||
Icons.phone,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return PopupMenuButton<Map<String, dynamic>>(
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: '选择国际冠码,'
|
||||
'当前为${_loginPageCtr.selectedCountryCodeId['cname']},'
|
||||
'+${_loginPageCtr.selectedCountryCodeId['country_id']}',
|
||||
onSelected: (Map<String, dynamic> type) {},
|
||||
itemBuilder: (_) => internationalDialingPrefix
|
||||
.map((Map<String, dynamic> item) {
|
||||
return PopupMenuItem<Map<String, dynamic>>(
|
||||
onTap: () {
|
||||
_loginPageCtr.selectedCountryCodeId = item;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
value: item,
|
||||
child: Row(children: [
|
||||
Text(item['cname']),
|
||||
const Spacer(),
|
||||
Text("+${item['country_id']}")
|
||||
]),
|
||||
);
|
||||
}).toList(),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.phone,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"+${_loginPageCtr.selectedCountryCodeId['country_id']}"),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: VerticalDivider(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
PopupMenuButton<Map<String, dynamic>>(
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: '选择国际冠码,'
|
||||
'当前为${_loginPageCtr.selectedCountryCodeId['cname']},'
|
||||
'+${_loginPageCtr.selectedCountryCodeId['country_id']}',
|
||||
//position: PopupMenuPosition.under,
|
||||
onSelected: (Map<String, dynamic> type) {},
|
||||
itemBuilder: (BuildContext context) => Constants
|
||||
.internationalDialingPrefix
|
||||
.map((Map<String, dynamic> item) {
|
||||
return PopupMenuItem<Map<String, dynamic>>(
|
||||
onTap: () => setState(() {
|
||||
_loginPageCtr.selectedCountryCodeId = item;
|
||||
}),
|
||||
value: item,
|
||||
child: Row(children: [
|
||||
Text(item['cname']),
|
||||
const Spacer(),
|
||||
Text("+${item['country_id']}")
|
||||
]),
|
||||
);
|
||||
}).toList(),
|
||||
child: Text(
|
||||
"+${_loginPageCtr.selectedCountryCodeId['country_id']}"),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: VerticalDivider(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _loginPageCtr.telTextController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
@@ -386,45 +412,51 @@ class _LoginPageState extends State<LoginPage> {
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: DecoratedBox(
|
||||
decoration: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _loginPageCtr.smsCodeTextController,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.sms_outlined),
|
||||
border: InputBorder.none,
|
||||
labelText: '验证码',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() => TextButton.icon(
|
||||
onPressed: _loginPageCtr.smsSendCooldown > 0
|
||||
? null
|
||||
: _loginPageCtr.sendSmsCode,
|
||||
icon: const Icon(Icons.send),
|
||||
label: Text(_loginPageCtr.smsSendCooldown > 0
|
||||
? '等待${_loginPageCtr.smsSendCooldown}秒'
|
||||
: '获取验证码'),
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: DecoratedBox(
|
||||
decoration: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.4),
|
||||
),
|
||||
)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _loginPageCtr.smsCodeTextController,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.sms_outlined),
|
||||
border: InputBorder.none,
|
||||
labelText: '验证码',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => TextButton.icon(
|
||||
onPressed: _loginPageCtr.smsSendCooldown > 0
|
||||
? null
|
||||
: _loginPageCtr.sendSmsCode,
|
||||
icon: const Icon(Icons.send),
|
||||
label: Text(_loginPageCtr.smsSendCooldown > 0
|
||||
? '等待${_loginPageCtr.smsSendCooldown}秒'
|
||||
: '获取验证码'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _loginPageCtr.loginBySmsCode,
|
||||
@@ -433,15 +465,17 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
'手机号仅用于 bilibili 官方发送验证码与登录接口,不予保存;\n'
|
||||
'本地仅存储登录凭证。\n'
|
||||
'请务必在 PiliPlus 开源仓库等可信渠道下载安装。',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.labelSmall!.copyWith(
|
||||
color:
|
||||
theme.colorScheme.onSurface.withValues(alpha: 0.4)))),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
'手机号仅用于 bilibili 官方发送验证码与登录接口,不予保存;\n'
|
||||
'本地仅存储登录凭证。\n'
|
||||
'请务必在 PiliPlus 开源仓库等可信渠道下载安装。',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.labelSmall!.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ class _MatchInfoPageState extends State<MatchInfoPage> {
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => _controller.queryBySort(),
|
||||
onPressed: _controller.queryBySort,
|
||||
icon: Icon(
|
||||
Icons.sort,
|
||||
size: 16,
|
||||
|
||||
@@ -83,7 +83,7 @@ class MineController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
static void onChangeAnonymity(BuildContext context) {
|
||||
static void onChangeAnonymity() {
|
||||
if (Accounts.account.isEmpty) {
|
||||
SmartDialog.showToast('请先登录');
|
||||
return;
|
||||
|
||||
@@ -33,20 +33,21 @@ class _MinePageState extends State<MinePage> {
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
iconSize: 40.0,
|
||||
padding: const EdgeInsets.all(8),
|
||||
style: const ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
tooltip: "${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
|
||||
onPressed: () {
|
||||
MineController.onChangeAnonymity(context);
|
||||
setState(() {});
|
||||
Obx(
|
||||
() {
|
||||
return IconButton(
|
||||
iconSize: 40.0,
|
||||
padding: const EdgeInsets.all(8),
|
||||
style: const ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
tooltip: "${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
|
||||
onPressed: MineController.onChangeAnonymity,
|
||||
icon: MineController.anonymity.value
|
||||
? const Icon(MdiIcons.incognito, size: 24)
|
||||
: const Icon(MdiIcons.incognitoOff, size: 24),
|
||||
);
|
||||
},
|
||||
icon: MineController.anonymity.value
|
||||
? const Icon(MdiIcons.incognito, size: 24)
|
||||
: const Icon(MdiIcons.incognitoOff, size: 24),
|
||||
),
|
||||
Obx(
|
||||
() {
|
||||
|
||||
@@ -20,31 +20,31 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
late double longPressSpeedDefault;
|
||||
late List<double> speedList;
|
||||
late bool enableAutoLongPressSpeed;
|
||||
List<Map<dynamic, dynamic>> sheetMenu = [
|
||||
{
|
||||
'id': 1,
|
||||
'title': '设置为默认倍速',
|
||||
'leading': const Icon(
|
||||
List<({int id, String title, Icon icon})> sheetMenu = [
|
||||
(
|
||||
id: 1,
|
||||
title: '设置为默认倍速',
|
||||
icon: const Icon(
|
||||
Icons.speed,
|
||||
size: 21,
|
||||
),
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'title': '设置为默认长按倍速',
|
||||
'leading': const Icon(
|
||||
),
|
||||
(
|
||||
id: 2,
|
||||
title: '设置为默认长按倍速',
|
||||
icon: const Icon(
|
||||
Icons.speed_sharp,
|
||||
size: 21,
|
||||
),
|
||||
},
|
||||
{
|
||||
'id': -1,
|
||||
'title': '删除该项',
|
||||
'leading': const Icon(
|
||||
),
|
||||
(
|
||||
id: -1,
|
||||
title: '删除该项',
|
||||
icon: const Icon(
|
||||
Icons.delete_outline,
|
||||
size: 21,
|
||||
),
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
Box get video => GStorage.video;
|
||||
@@ -62,13 +62,6 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
speedList = GStorage.speedList;
|
||||
enableAutoLongPressSpeed = GStorage.setting
|
||||
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
|
||||
if (enableAutoLongPressSpeed) {
|
||||
Map newItem = sheetMenu[1];
|
||||
newItem['show'] = false;
|
||||
setState(() {
|
||||
sheetMenu[1] = newItem;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 添加自定义倍速
|
||||
@@ -147,16 +140,18 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
const SizedBox(height: 10),
|
||||
...sheetMenu.map(
|
||||
(item) => ListTile(
|
||||
enabled:
|
||||
enableAutoLongPressSpeed && item.id == 2 ? false : true,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
menuAction(index, item['id']);
|
||||
Get.back();
|
||||
menuAction(index, item.id);
|
||||
},
|
||||
minLeadingWidth: 0,
|
||||
iconColor: theme.colorScheme.onSurface,
|
||||
leading: item['leading'],
|
||||
leading: item.icon,
|
||||
title: Text(
|
||||
item['title'],
|
||||
style: theme.textTheme.titleSmall,
|
||||
item.title,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -233,14 +228,8 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
subtitle: '根据默认倍速长按时自动双倍',
|
||||
setKey: SettingBoxKey.enableAutoLongPressSpeed,
|
||||
defaultVal: enableAutoLongPressSpeed,
|
||||
onChanged: (val) {
|
||||
Map newItem = sheetMenu[1];
|
||||
val ? newItem['show'] = false : newItem['show'] = true;
|
||||
setState(() {
|
||||
sheetMenu[1] = newItem;
|
||||
enableAutoLongPressSpeed = val;
|
||||
});
|
||||
},
|
||||
onChanged: (val) =>
|
||||
setState(() => enableAutoLongPressSpeed = val),
|
||||
),
|
||||
if (!enableAutoLongPressSpeed)
|
||||
ListTile(
|
||||
|
||||
@@ -1480,7 +1480,7 @@ List<SettingsModel> get privacySettings => [
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.normal,
|
||||
onTap: (setState) {
|
||||
MineController.onChangeAnonymity(Get.context!);
|
||||
MineController.onChangeAnonymity();
|
||||
setState();
|
||||
},
|
||||
leading: const Icon(Icons.privacy_tip_outlined),
|
||||
|
||||
@@ -74,59 +74,63 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
|
||||
Widget _blockLimitItem(
|
||||
ThemeData theme, TextStyle titleStyle, TextStyle subTitleStyle) =>
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
_textController.text = _blockLimit.toString();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('最短片段时长', style: titleStyle),
|
||||
content: TextFormField(
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
controller: _textController,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(suffixText: 's'),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
_textController.text = _blockLimit.toString();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertDialog(
|
||||
title: Text('最短片段时长', style: titleStyle),
|
||||
content: TextFormField(
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
controller: _textController,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(suffixText: 's'),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[\d\.]+')),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_blockLimit = max(
|
||||
0.0, double.tryParse(_textController.text) ?? 0.0);
|
||||
setting.put(SettingBoxKey.blockLimit, _blockLimit);
|
||||
setState(() {});
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_blockLimit = max(0.0,
|
||||
double.tryParse(_textController.text) ?? 0.0);
|
||||
setting.put(SettingBoxKey.blockLimit, _blockLimit);
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
title: Text('最短片段时长', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'忽略短于此时长的片段',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: Text(
|
||||
'${_blockLimit}s',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
);
|
||||
},
|
||||
title: Text('最短片段时长', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'忽略短于此时长的片段',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: Text(
|
||||
'${_blockLimit}s',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _aboudItem(TextStyle titleStyle, TextStyle subTitleStyle) => ListTile(
|
||||
@@ -138,230 +142,250 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
|
||||
Widget _userIdItem(
|
||||
ThemeData theme, TextStyle titleStyle, TextStyle subTitleStyle) =>
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: Text('用户ID', style: titleStyle),
|
||||
subtitle: Text(_userId, style: subTitleStyle),
|
||||
onTap: () {
|
||||
final key = GlobalKey<FormState>();
|
||||
_textController.text = _userId;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('用户ID', style: titleStyle),
|
||||
content: Form(
|
||||
key: key,
|
||||
child: TextFormField(
|
||||
minLines: 1,
|
||||
maxLines: 4,
|
||||
autofocus: true,
|
||||
controller: _textController,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\d]+')),
|
||||
],
|
||||
validator: (value) {
|
||||
if ((value?.length ?? -1) < 30) {
|
||||
return '用户ID要求至少为30个字符长度的纯字符串';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_userId = const Uuid().v4().replaceAll('-', '');
|
||||
setting.put(SettingBoxKey.blockUserID, _userId);
|
||||
setState(() {});
|
||||
},
|
||||
child: const Text('随机'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text('用户ID', style: titleStyle),
|
||||
subtitle: Text(_userId, style: subTitleStyle),
|
||||
onTap: () {
|
||||
final key = GlobalKey<FormState>();
|
||||
_textController.text = _userId;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertDialog(
|
||||
title: Text('用户ID', style: titleStyle),
|
||||
content: Form(
|
||||
key: key,
|
||||
child: TextFormField(
|
||||
minLines: 1,
|
||||
maxLines: 4,
|
||||
autofocus: true,
|
||||
controller: _textController,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'[a-zA-Z\d]+')),
|
||||
],
|
||||
validator: (value) {
|
||||
if ((value?.length ?? -1) < 30) {
|
||||
return '用户ID要求至少为30个字符长度的纯字符串';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (key.currentState?.validate() == true) {
|
||||
Get.back();
|
||||
_userId = _textController.text;
|
||||
setting.put(SettingBoxKey.blockUserID, _userId);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_userId = const Uuid().v4().replaceAll('-', '');
|
||||
setting.put(SettingBoxKey.blockUserID, _userId);
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
child: const Text('随机'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (key.currentState?.validate() == true) {
|
||||
Get.back();
|
||||
_userId = _textController.text;
|
||||
setting.put(SettingBoxKey.blockUserID, _userId);
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
void _updateBlockToast() {
|
||||
_blockToast = !_blockToast;
|
||||
setting.put(SettingBoxKey.blockToast, _blockToast);
|
||||
setState(() {});
|
||||
}
|
||||
Widget _blockToastItem(TextStyle titleStyle) => Builder(
|
||||
builder: (context) {
|
||||
void update() {
|
||||
_blockToast = !_blockToast;
|
||||
setting.put(SettingBoxKey.blockToast, _blockToast);
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
|
||||
void _updateBlockTrack() {
|
||||
_blockTrack = !_blockTrack;
|
||||
setting.put(SettingBoxKey.blockTrack, _blockTrack);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Widget _blockToastItem(TextStyle titleStyle) => ListTile(
|
||||
dense: true,
|
||||
onTap: _updateBlockToast,
|
||||
title: Text(
|
||||
'显示跳过Toast',
|
||||
style: titleStyle,
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((states) {
|
||||
if (states.isNotEmpty && states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value: _blockToast,
|
||||
onChanged: (val) {
|
||||
_updateBlockToast();
|
||||
},
|
||||
),
|
||||
));
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: update,
|
||||
title: Text(
|
||||
'显示跳过Toast',
|
||||
style: titleStyle,
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value: _blockToast,
|
||||
onChanged: (val) => update(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Widget _blockTrackItem(TextStyle titleStyle, TextStyle subTitleStyle) =>
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: _updateBlockTrack,
|
||||
title: Text(
|
||||
'跳过次数统计跟踪',
|
||||
style: titleStyle,
|
||||
),
|
||||
subtitle: Text(
|
||||
// from origin extension
|
||||
'此功能追踪您跳过了哪些片段,让用户知道他们提交的片段帮助了多少人。同时点赞会作为依据,确保垃圾信息不会污染数据库。在您每次跳过片段时,我们都会向服务器发送一条消息。希望大家开启此项设置,以便得到更准确的统计数据。:)',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((states) {
|
||||
if (states.isNotEmpty && states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value: _blockTrack,
|
||||
onChanged: (val) {
|
||||
_updateBlockTrack();
|
||||
},
|
||||
),
|
||||
));
|
||||
Builder(
|
||||
builder: (context) {
|
||||
void update() {
|
||||
_blockTrack = !_blockTrack;
|
||||
setting.put(SettingBoxKey.blockTrack, _blockTrack);
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: update,
|
||||
title: Text(
|
||||
'跳过次数统计跟踪',
|
||||
style: titleStyle,
|
||||
),
|
||||
subtitle: Text(
|
||||
// from origin extension
|
||||
'此功能追踪您跳过了哪些片段,让用户知道他们提交的片段帮助了多少人。同时点赞会作为依据,确保垃圾信息不会污染数据库。在您每次跳过片段时,我们都会向服务器发送一条消息。希望大家开启此项设置,以便得到更准确的统计数据。:)',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value: _blockTrack,
|
||||
onChanged: (val) => update(),
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
|
||||
Widget _blockServerItem(
|
||||
ThemeData theme, TextStyle titleStyle, TextStyle subTitleStyle) =>
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
_textController.text = _blockServer;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('服务器地址', style: titleStyle),
|
||||
content: TextFormField(
|
||||
keyboardType: TextInputType.url,
|
||||
controller: _textController,
|
||||
autofocus: true,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_blockServer = HttpString.sponsorBlockBaseUrl;
|
||||
setting.put(SettingBoxKey.blockServer, _blockServer);
|
||||
Request.accountManager.blockServer = _blockServer;
|
||||
setState(() {});
|
||||
},
|
||||
child: const Text('重置'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
_textController.text = _blockServer;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertDialog(
|
||||
title: Text('服务器地址', style: titleStyle),
|
||||
content: TextFormField(
|
||||
keyboardType: TextInputType.url,
|
||||
controller: _textController,
|
||||
autofocus: true,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_blockServer = _textController.text;
|
||||
setting.put(SettingBoxKey.blockServer, _blockServer);
|
||||
Request.accountManager.blockServer = _blockServer;
|
||||
setState(() {});
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_blockServer = HttpString.sponsorBlockBaseUrl;
|
||||
setting.put(SettingBoxKey.blockServer, _blockServer);
|
||||
Request.accountManager.blockServer = _blockServer;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
child: const Text('重置'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
_blockServer = _textController.text;
|
||||
setting.put(SettingBoxKey.blockServer, _blockServer);
|
||||
Request.accountManager.blockServer = _blockServer;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
'服务器地址',
|
||||
style: titleStyle,
|
||||
),
|
||||
subtitle: Text(
|
||||
_blockServer,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
'服务器地址',
|
||||
style: titleStyle,
|
||||
),
|
||||
subtitle: Text(
|
||||
_blockServer,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _serverStatusItem(ThemeData theme, TextStyle titleStyle) => ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_serverStatus = null;
|
||||
});
|
||||
_checkServerStatus();
|
||||
Widget _serverStatusItem(ThemeData theme, TextStyle titleStyle) => Builder(
|
||||
builder: (context) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
_serverStatus = null;
|
||||
(context as Element).markNeedsBuild();
|
||||
_checkServerStatus();
|
||||
},
|
||||
title: Text('服务器状态', style: titleStyle),
|
||||
trailing: Text(
|
||||
_serverStatus == null
|
||||
? '——'
|
||||
: _serverStatus == true
|
||||
? '正常'
|
||||
: '错误',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: _serverStatus == null
|
||||
? null
|
||||
: _serverStatus == true
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
title: Text('服务器状态', style: titleStyle),
|
||||
trailing: Text(
|
||||
_serverStatus == null
|
||||
? '——'
|
||||
: _serverStatus == true
|
||||
? '正常'
|
||||
: '错误',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: _serverStatus == null
|
||||
? null
|
||||
: _serverStatus == true
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
void onSelectColor(int index) {
|
||||
void onSelectColor(BuildContext context, int index, Color color,
|
||||
Pair<SegmentType, SkipType> item) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (_) => AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||
title: Text.rich(
|
||||
@@ -378,28 +402,28 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _blockColor[index],
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
style: const TextStyle(fontSize: 13, height: 1),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${_blockSettings[index].first.title}',
|
||||
text: ' ${item.first.title}',
|
||||
style: const TextStyle(fontSize: 13, height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
content: SlideColorPicker(
|
||||
color: _blockColor[index],
|
||||
color: color,
|
||||
callback: (Color? color) {
|
||||
_blockColor[index] = color ?? _blockSettings[index].first.color;
|
||||
_blockColor[index] = color ?? item.first.color;
|
||||
setting.put(
|
||||
SettingBoxKey.blockColor,
|
||||
_blockColor
|
||||
.map((item) => item.value.toRadixString(16).substring(2))
|
||||
.toList());
|
||||
setState(() {});
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -447,95 +471,8 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
dividerL,
|
||||
SliverList.separated(
|
||||
itemCount: _blockSettings.length,
|
||||
itemBuilder: (context, index) => ListTile(
|
||||
dense: true,
|
||||
enabled: _blockSettings[index].second != SkipType.disable,
|
||||
onTap: () => onSelectColor(index),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _blockColor[index],
|
||||
),
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${_blockSettings[index].first.title}',
|
||||
style: const TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuButton(
|
||||
initialValue: _blockSettings[index].second,
|
||||
onSelected: (item) {
|
||||
_blockSettings[index].second = item;
|
||||
setting.put(
|
||||
SettingBoxKey.blockSettings,
|
||||
_blockSettings
|
||||
.map((item) => item.second.index)
|
||||
.toList());
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (context) => SkipType.values
|
||||
.map((item) => PopupMenuItem<SkipType>(
|
||||
value: item,
|
||||
child: Text(item.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_blockSettings[index].second.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: _blockSettings[index].second ==
|
||||
SkipType.disable
|
||||
? theme.colorScheme.outline
|
||||
.withValues(alpha: 0.7)
|
||||
: theme.colorScheme.secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(height: 1, leading: 0),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(context).scale(14),
|
||||
color:
|
||||
_blockSettings[index].second == SkipType.disable
|
||||
? theme.colorScheme.outline
|
||||
.withValues(alpha: 0.7)
|
||||
: theme.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
_blockSettings[index].first.description,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: _blockSettings[index].second == SkipType.disable
|
||||
? null
|
||||
: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index) =>
|
||||
_buildItem(theme, index, _blockSettings[index]),
|
||||
separatorBuilder: (context, index) => divider,
|
||||
),
|
||||
dividerL,
|
||||
@@ -555,4 +492,101 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(
|
||||
ThemeData theme, int index, Pair<SegmentType, SkipType> item) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
Color color = _blockColor[index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
enabled: item.second != SkipType.disable,
|
||||
onTap: () => onSelectColor(context, index, color, item),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${item.first.title}',
|
||||
style: const TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return PopupMenuButton(
|
||||
initialValue: item.second,
|
||||
onSelected: (e) {
|
||||
item.second = e;
|
||||
setting.put(SettingBoxKey.blockSettings,
|
||||
_blockSettings.map((e) => e.second.index).toList());
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
itemBuilder: (context) => SkipType.values
|
||||
.map((item) => PopupMenuItem<SkipType>(
|
||||
value: item,
|
||||
child: Text(item.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
item.second.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: item.second == SkipType.disable
|
||||
? theme.colorScheme.outline
|
||||
.withValues(alpha: 0.7)
|
||||
: theme.colorScheme.secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(height: 1, leading: 0),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(context).scale(14),
|
||||
color: item.second == SkipType.disable
|
||||
? theme.colorScheme.outline
|
||||
.withValues(alpha: 0.7)
|
||||
: theme.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
item.first.description,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: item.second == SkipType.disable
|
||||
? null
|
||||
: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ class ActionItemState extends State<ActionItem>
|
||||
|
||||
late final _isThumbsUp = widget.semanticsLabel == '点赞';
|
||||
late int _lastTime;
|
||||
late bool _hideCircle = false;
|
||||
Timer? _timer;
|
||||
|
||||
void _startLongPress() {
|
||||
@@ -100,15 +99,12 @@ class ActionItemState extends State<ActionItem>
|
||||
}
|
||||
|
||||
void listener() {
|
||||
setState(() {
|
||||
_hideCircle = controller?.value == 1;
|
||||
if (_hideCircle) {
|
||||
controller?.reset();
|
||||
if (_isThumbsUp) {
|
||||
widget.onLongPress?.call();
|
||||
}
|
||||
if (controller!.value == 1) {
|
||||
controller!.reset();
|
||||
if (_isThumbsUp) {
|
||||
widget.onLongPress?.call();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void cancelTimer() {
|
||||
@@ -157,12 +153,15 @@ class ActionItemState extends State<ActionItem>
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (widget.needAnim && !_hideCircle)
|
||||
CustomPaint(
|
||||
size: const Size(28, 28),
|
||||
painter: _ArcPainter(
|
||||
color: theme.colorScheme.primary,
|
||||
sweepAngle: _animation!.value,
|
||||
if (widget.needAnim)
|
||||
AnimatedBuilder(
|
||||
animation: _animation!,
|
||||
builder: (context, child) => CustomPaint(
|
||||
size: const Size(28, 28),
|
||||
painter: _ArcPainter(
|
||||
color: theme.colorScheme.primary,
|
||||
sweepAngle: _animation!.value,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
|
||||
@@ -267,9 +267,7 @@ class _PayCoinsPageState extends State<PayCoinsPage>
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemCount: widget.copyright == 1 ? 2 : 1,
|
||||
controller: _controller,
|
||||
onPageChanged: (index) => setState(() {
|
||||
_scale();
|
||||
}),
|
||||
onPageChanged: (index) => setState(_scale),
|
||||
itemBuilder: (context, index) {
|
||||
return ListenableBuilder(
|
||||
listenable: _controller,
|
||||
|
||||
@@ -131,23 +131,33 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
theme,
|
||||
isFirst: true,
|
||||
index: index,
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
context,
|
||||
theme,
|
||||
isFirst: true,
|
||||
index: index,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (list![index].category !=
|
||||
SegmentType.poi_highlight)
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
theme,
|
||||
isFirst: false,
|
||||
index: index,
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
context,
|
||||
theme,
|
||||
isFirst: false,
|
||||
index: index,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -161,72 +171,86 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('分类: '),
|
||||
PopupMenuButton<SegmentType>(
|
||||
initialValue: list![index].category,
|
||||
onSelected: (item) {
|
||||
list![index].category = item;
|
||||
List<ActionType> constraintList =
|
||||
item.toActionType;
|
||||
if (!constraintList.contains(
|
||||
list![index].actionType)) {
|
||||
list![index].actionType =
|
||||
constraintList.first;
|
||||
}
|
||||
switch (item) {
|
||||
case SegmentType.poi_highlight:
|
||||
updateSegment(
|
||||
isFirst: false,
|
||||
index: index,
|
||||
value: list![index]
|
||||
.segment
|
||||
.first,
|
||||
);
|
||||
break;
|
||||
case SegmentType.exclusive_access:
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
break;
|
||||
case _:
|
||||
}
|
||||
setState(() {});
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return PopupMenuButton<SegmentType>(
|
||||
initialValue:
|
||||
list![index].category,
|
||||
onSelected: (item) {
|
||||
list![index].category = item;
|
||||
List<ActionType>
|
||||
constraintList =
|
||||
item.toActionType;
|
||||
if (!constraintList.contains(
|
||||
list![index].actionType)) {
|
||||
list![index].actionType =
|
||||
constraintList.first;
|
||||
}
|
||||
switch (item) {
|
||||
case SegmentType
|
||||
.poi_highlight:
|
||||
updateSegment(
|
||||
isFirst: false,
|
||||
index: index,
|
||||
value: list![index]
|
||||
.segment
|
||||
.first,
|
||||
);
|
||||
break;
|
||||
case SegmentType
|
||||
.exclusive_access:
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
(context as Element)
|
||||
.markNeedsBuild();
|
||||
},
|
||||
itemBuilder: (context) =>
|
||||
SegmentType.values
|
||||
.map((item) =>
|
||||
PopupMenuItem<
|
||||
SegmentType>(
|
||||
value: item,
|
||||
child: Text(
|
||||
item.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index].category.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
strutStyle:
|
||||
const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons
|
||||
.unfoldMoreHorizontal,
|
||||
size:
|
||||
MediaQuery.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: theme
|
||||
.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
itemBuilder: (context) => SegmentType
|
||||
.values
|
||||
.map((item) =>
|
||||
PopupMenuItem<SegmentType>(
|
||||
value: item,
|
||||
child: Text(item.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index].category.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme
|
||||
.colorScheme.secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color:
|
||||
theme.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -234,60 +258,64 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('行为类别: '),
|
||||
PopupMenuButton<ActionType>(
|
||||
initialValue: list![index].actionType,
|
||||
onSelected: (item) {
|
||||
list![index].actionType = item;
|
||||
if (item == ActionType.full) {
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
itemBuilder: (context) => ActionType
|
||||
.values
|
||||
.map(
|
||||
(item) =>
|
||||
PopupMenuItem<ActionType>(
|
||||
enabled: list![index]
|
||||
.category
|
||||
.toActionType
|
||||
.contains(item),
|
||||
value: item,
|
||||
child: Text(item.title),
|
||||
Builder(builder: (context) {
|
||||
return PopupMenuButton<ActionType>(
|
||||
initialValue:
|
||||
list![index].actionType,
|
||||
onSelected: (item) {
|
||||
list![index].actionType = item;
|
||||
if (item == ActionType.full) {
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
}
|
||||
(context as Element)
|
||||
.markNeedsBuild();
|
||||
},
|
||||
itemBuilder: (context) => ActionType
|
||||
.values
|
||||
.map(
|
||||
(item) =>
|
||||
PopupMenuItem<ActionType>(
|
||||
enabled: list![index]
|
||||
.category
|
||||
.toActionType
|
||||
.contains(item),
|
||||
value: item,
|
||||
child: Text(item.title),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index].actionType.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme
|
||||
.colorScheme.secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index].actionType.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: theme
|
||||
.colorScheme.secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color:
|
||||
theme.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -419,6 +447,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
}
|
||||
|
||||
List<Widget> segmentWidget(
|
||||
BuildContext context,
|
||||
ThemeData theme, {
|
||||
required int index,
|
||||
required bool isFirst,
|
||||
@@ -436,13 +465,12 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
tooltip: '设为当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
value: currentPos,
|
||||
);
|
||||
});
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
value: currentPos,
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
@@ -452,13 +480,12 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
tooltip: isFirst ? '视频开头' : '视频结尾',
|
||||
icon: isFirst ? Icons.first_page : Icons.last_page,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
value: isFirst ? 0 : videoDuration,
|
||||
);
|
||||
});
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
value: isFirst ? 0 : videoDuration,
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
@@ -508,13 +535,12 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
duration += split[i] * pow(60, i);
|
||||
}
|
||||
if (duration <= videoDuration) {
|
||||
setState(() {
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
value: duration,
|
||||
);
|
||||
});
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
value: duration,
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint(e.toString());
|
||||
|
||||
@@ -22,42 +22,32 @@ class PlayOrPauseButton extends StatefulWidget {
|
||||
|
||||
class PlayOrPauseButtonState extends State<PlayOrPauseButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController animation;
|
||||
|
||||
StreamSubscription<bool>? subscription;
|
||||
late final AnimationController controller;
|
||||
late final StreamSubscription<bool> subscription;
|
||||
late Player player;
|
||||
bool isOpacity = false;
|
||||
|
||||
PlPlayerController get plPlayerController => widget.plPlayerController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
player = plPlayerController.videoPlayerController!;
|
||||
animation = AnimationController(
|
||||
player = widget.plPlayerController.videoPlayerController!;
|
||||
controller = AnimationController(
|
||||
vsync: this,
|
||||
value: player.state.playing ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
subscription ??= player.stream.playing.listen((event) {
|
||||
if (event) {
|
||||
animation.forward().whenComplete(() => {isOpacity = true});
|
||||
subscription = player.stream.playing.listen((playing) {
|
||||
if (playing) {
|
||||
controller.forward();
|
||||
} else {
|
||||
animation.reverse().whenComplete(() => {isOpacity = false});
|
||||
controller.reverse();
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
animation.dispose();
|
||||
subscription?.cancel();
|
||||
subscription.cancel();
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -67,6 +57,7 @@ class PlayOrPauseButtonState extends State<PlayOrPauseButton>
|
||||
width: 42,
|
||||
height: 34,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () async {
|
||||
if (player.state.completed) {
|
||||
await player.seek(Duration.zero);
|
||||
@@ -77,11 +68,8 @@ class PlayOrPauseButtonState extends State<PlayOrPauseButton>
|
||||
},
|
||||
child: Center(
|
||||
child: AnimatedIcon(
|
||||
semanticLabel:
|
||||
plPlayerController.videoPlayerController!.state.playing
|
||||
? '暂停'
|
||||
: '播放',
|
||||
progress: animation,
|
||||
semanticLabel: player.state.playing ? '暂停' : '播放',
|
||||
progress: controller,
|
||||
icon: AnimatedIcons.play_pause,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
|
||||
@@ -45,8 +45,8 @@ class ShutdownTimerService with WidgetsBindingObserver {
|
||||
return;
|
||||
}
|
||||
SmartDialog.showToast("设置 $scheduledExitInMinutes 分钟后定时关闭");
|
||||
_shutdownTimer = Timer(
|
||||
Duration(minutes: scheduledExitInMinutes), () => _shutdownDecider());
|
||||
_shutdownTimer =
|
||||
Timer(Duration(minutes: scheduledExitInMinutes), _shutdownDecider);
|
||||
}
|
||||
|
||||
void _showTimeUpButPauseDialog() {
|
||||
|
||||
@@ -119,8 +119,9 @@ class PageUtils {
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
int choice = int.tryParse(duration) ?? 0;
|
||||
shutdownTimerService.scheduledExitInMinutes = choice;
|
||||
shutdownTimerService.startShutdownTimer();
|
||||
shutdownTimerService
|
||||
..scheduledExitInMinutes = choice
|
||||
..startShutdownTimer();
|
||||
setState(() {});
|
||||
},
|
||||
child: const Text('确定'),
|
||||
@@ -181,59 +182,71 @@ class PageUtils {
|
||||
),
|
||||
),
|
||||
if (!isLive) ...[
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
shutdownTimerService.waitForPlayingCompleted =
|
||||
!shutdownTimerService.waitForPlayingCompleted;
|
||||
setState(() {});
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
shutdownTimerService.waitForPlayingCompleted =
|
||||
!shutdownTimerService.waitForPlayingCompleted;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
title: const Text("额外等待视频播放完毕", style: titleStyle),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon:
|
||||
WidgetStateProperty.resolveWith<Icon?>(
|
||||
(Set<WidgetState> states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value: shutdownTimerService
|
||||
.waitForPlayingCompleted,
|
||||
onChanged: (value) {
|
||||
shutdownTimerService.waitForPlayingCompleted =
|
||||
value;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
title: const Text("额外等待视频播放完毕", style: titleStyle),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment.centerRight,
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
|
||||
(Set<WidgetState> states) {
|
||||
if (states.isNotEmpty &&
|
||||
states.first == WidgetState.selected) {
|
||||
return const Icon(Icons.done);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
value: shutdownTimerService.waitForPlayingCompleted,
|
||||
onChanged: (value) => setState(() =>
|
||||
shutdownTimerService.waitForPlayingCompleted =
|
||||
value),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 18),
|
||||
const Text('倒计时结束:', style: titleStyle),
|
||||
const Spacer(),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = false;
|
||||
setState(() {});
|
||||
},
|
||||
text: " 暂停视频 ",
|
||||
selectStatus: !shutdownTimerService.exitApp,
|
||||
),
|
||||
const Spacer(),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = true;
|
||||
setState(() {});
|
||||
},
|
||||
text: " 退出APP ",
|
||||
selectStatus: shutdownTimerService.exitApp,
|
||||
),
|
||||
const SizedBox(width: 25),
|
||||
],
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
children: [
|
||||
const SizedBox(width: 18),
|
||||
const Text('倒计时结束:', style: titleStyle),
|
||||
const Spacer(),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = false;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
text: " 暂停视频 ",
|
||||
selectStatus: !shutdownTimerService.exitApp,
|
||||
),
|
||||
const Spacer(),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = true;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
text: " 退出APP ",
|
||||
selectStatus: shutdownTimerService.exitApp,
|
||||
),
|
||||
const SizedBox(width: 25),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user