Merge branch 'main' into design
This commit is contained in:
BIN
assets/images/coin.png
Normal file
BIN
assets/images/coin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
@ -23,7 +23,7 @@ PODS:
|
|||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 2.7.5)
|
||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
- gt3_flutter_plugin (0.0.8):
|
- gt3_flutter_plugin (0.0.9):
|
||||||
- Flutter
|
- Flutter
|
||||||
- GT3Captcha-iOS
|
- GT3Captcha-iOS
|
||||||
- GT3Captcha-iOS (0.15.8.3)
|
- GT3Captcha-iOS (0.15.8.3)
|
||||||
@ -171,13 +171,13 @@ SPEC CHECKSUMS:
|
|||||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
|
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
|
||||||
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
|
@ -189,7 +189,7 @@ class Api {
|
|||||||
'https://s.search.bilibili.com/main/suggest';
|
'https://s.search.bilibili.com/main/suggest';
|
||||||
|
|
||||||
// 分类搜索
|
// 分类搜索
|
||||||
static const String searchByType = '/x/web-interface/search/type';
|
static const String searchByType = '/x/web-interface/wbi/search/type';
|
||||||
|
|
||||||
// 记录视频播放进度
|
// 记录视频播放进度
|
||||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md
|
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md
|
||||||
|
@ -75,6 +75,7 @@ class SearchHttp {
|
|||||||
required page,
|
required page,
|
||||||
String? order,
|
String? order,
|
||||||
int? duration,
|
int? duration,
|
||||||
|
int? tids,
|
||||||
}) async {
|
}) async {
|
||||||
var reqData = {
|
var reqData = {
|
||||||
'search_type': searchType.type,
|
'search_type': searchType.type,
|
||||||
@ -84,6 +85,7 @@ class SearchHttp {
|
|||||||
'page': page,
|
'page': page,
|
||||||
if (order != null) 'order': order,
|
if (order != null) 'order': order,
|
||||||
if (duration != null) 'duration': duration,
|
if (duration != null) 'duration': duration,
|
||||||
|
if (tids != null && tids != -1) 'tids': tids,
|
||||||
};
|
};
|
||||||
var res = await Request().get(Api.searchByType, data: reqData);
|
var res = await Request().get(Api.searchByType, data: reqData);
|
||||||
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
||||||
|
93
lib/models/common/action_type.dart
Normal file
93
lib/models/common/action_type.dart
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// 操作类型的枚举值:点赞 不喜欢 收藏 投币 稍后再看 下载封面 后台播放 听视频 分享 下载视频
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
enum ActionType {
|
||||||
|
like,
|
||||||
|
coin,
|
||||||
|
collect,
|
||||||
|
watchLater,
|
||||||
|
share,
|
||||||
|
dislike,
|
||||||
|
downloadCover,
|
||||||
|
copyLink,
|
||||||
|
// backgroundPlay,
|
||||||
|
// listenVideo,
|
||||||
|
// downloadVideo,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ActionTypeExtension on ActionType {
|
||||||
|
String get value => [
|
||||||
|
'like',
|
||||||
|
'coin',
|
||||||
|
'collect',
|
||||||
|
'watchLater',
|
||||||
|
'share',
|
||||||
|
'dislike',
|
||||||
|
'downloadCover',
|
||||||
|
'copyLink',
|
||||||
|
// 'backgroundPlay',
|
||||||
|
// 'listenVideo',
|
||||||
|
// 'downloadVideo',
|
||||||
|
][index];
|
||||||
|
String get label => [
|
||||||
|
'点赞视频',
|
||||||
|
'投币',
|
||||||
|
'收藏视频',
|
||||||
|
'稍后再看',
|
||||||
|
'视频分享',
|
||||||
|
'不喜欢',
|
||||||
|
'下载封面',
|
||||||
|
'复制链接',
|
||||||
|
// '后台播放',
|
||||||
|
// '听视频',
|
||||||
|
// '下载视频',
|
||||||
|
][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map> actionMenuConfig = [
|
||||||
|
{
|
||||||
|
'icon': const Icon(Icons.thumb_up_alt_outlined),
|
||||||
|
'label': '点赞视频',
|
||||||
|
'value': ActionType.like,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Image.asset(
|
||||||
|
'assets/images/coin.png',
|
||||||
|
width: 26,
|
||||||
|
color: IconTheme.of(Get.context!).color!.withOpacity(0.65),
|
||||||
|
),
|
||||||
|
'label': '投币',
|
||||||
|
'value': ActionType.coin,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(Icons.star_border),
|
||||||
|
'label': '收藏视频',
|
||||||
|
'value': ActionType.collect,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(Icons.watch_later_outlined),
|
||||||
|
'label': '稍后再看',
|
||||||
|
'value': ActionType.watchLater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(Icons.share),
|
||||||
|
'label': '视频分享',
|
||||||
|
'value': ActionType.share,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(Icons.thumb_down_alt_outlined),
|
||||||
|
'label': '不喜欢',
|
||||||
|
'value': ActionType.dislike,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(Icons.image_outlined),
|
||||||
|
'label': '下载封面',
|
||||||
|
'value': ActionType.downloadCover,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(Icons.link_outlined),
|
||||||
|
'label': '复制链接',
|
||||||
|
'value': ActionType.copyLink,
|
||||||
|
},
|
||||||
|
];
|
@ -31,7 +31,7 @@ class LoginPageController extends GetxController {
|
|||||||
|
|
||||||
// 倒计时60s
|
// 倒计时60s
|
||||||
RxInt seconds = 60.obs;
|
RxInt seconds = 60.obs;
|
||||||
late Timer timer;
|
Timer? timer;
|
||||||
RxBool smsCodeSendStatus = false.obs;
|
RxBool smsCodeSendStatus = false.obs;
|
||||||
|
|
||||||
// 默认密码登录
|
// 默认密码登录
|
||||||
@ -43,7 +43,7 @@ class LoginPageController extends GetxController {
|
|||||||
late int webSmsCode;
|
late int webSmsCode;
|
||||||
|
|
||||||
RxInt validSeconds = 180.obs;
|
RxInt validSeconds = 180.obs;
|
||||||
late Timer validTimer;
|
Timer? validTimer;
|
||||||
late String qrcodeKey;
|
late String qrcodeKey;
|
||||||
|
|
||||||
// 监听pageView切换
|
// 监听pageView切换
|
||||||
@ -329,7 +329,7 @@ class LoginPageController extends GetxController {
|
|||||||
var res = await LoginHttp.queryWebQrcodeStatus(qrcodeKey);
|
var res = await LoginHttp.queryWebQrcodeStatus(qrcodeKey);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
await LoginUtils.confirmLogin('', null);
|
await LoginUtils.confirmLogin('', null);
|
||||||
validTimer.cancel();
|
validTimer?.cancel();
|
||||||
Get.back();
|
Get.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
final LoginPageController _loginPageCtr = Get.put(LoginPageController());
|
final LoginPageController _loginPageCtr = Get.put(LoginPageController());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void dispose() {
|
||||||
super.initState();
|
_loginPageCtr.validTimer?.cancel();
|
||||||
|
_loginPageCtr.timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -51,7 +53,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.language),
|
icon: const Icon(Icons.language, size: 20),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: '二维码登录',
|
tooltip: '二维码登录',
|
||||||
@ -90,7 +92,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
Map data = snapshot.data as Map;
|
Map data = snapshot.data as Map;
|
||||||
return QrImageView(
|
return QrImageView(
|
||||||
data: data['data']['url'],
|
data: data['data']['url'],
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.white,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const Center(
|
return const Center(
|
||||||
@ -131,9 +133,11 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
).then((value) {
|
||||||
|
_loginPageCtr.validTimer!.cancel();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.qr_code),
|
icon: const Icon(Icons.qr_code, size: 20),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 22),
|
const SizedBox(width: 22),
|
||||||
],
|
],
|
||||||
@ -164,17 +168,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
fontSize: 34,
|
fontSize: 34,
|
||||||
fontWeight: FontWeight.w500),
|
fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
Row(
|
Text(
|
||||||
children: [
|
'请使用您的 BiliBili 账号登录。',
|
||||||
Text(
|
style: Theme.of(context).textTheme.titleSmall!,
|
||||||
'请使用您的 BiliBili 账号登录。',
|
|
||||||
style: Theme.of(context).textTheme.titleSmall!,
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {},
|
|
||||||
child: const Icon(Icons.info_outline, size: 16),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.only(top: 38, bottom: 15),
|
margin: const EdgeInsets.only(top: 38, bottom: 15),
|
||||||
|
@ -33,8 +33,7 @@ class MineController extends GetxController {
|
|||||||
|
|
||||||
onLogin() async {
|
onLogin() async {
|
||||||
if (!userLogin.value) {
|
if (!userLogin.value) {
|
||||||
// RoutePush.loginPush();
|
Get.toNamed('/loginPage', preventDuplicates: false);
|
||||||
Get.toNamed('/loginPage');
|
|
||||||
} else {
|
} else {
|
||||||
int mid = userInfo.value.mid!;
|
int mid = userInfo.value.mid!;
|
||||||
String face = userInfo.value.face!;
|
String face = userInfo.value.face!;
|
||||||
|
@ -5,21 +5,25 @@ class SearchText extends StatelessWidget {
|
|||||||
final Function? onSelect;
|
final Function? onSelect;
|
||||||
final int? searchTextIdx;
|
final int? searchTextIdx;
|
||||||
final Function? onLongSelect;
|
final Function? onLongSelect;
|
||||||
|
final bool isSelect;
|
||||||
const SearchText({
|
const SearchText({
|
||||||
super.key,
|
super.key,
|
||||||
this.searchText,
|
this.searchText,
|
||||||
this.onSelect,
|
this.onSelect,
|
||||||
this.searchTextIdx,
|
this.searchTextIdx,
|
||||||
this.onLongSelect,
|
this.onLongSelect,
|
||||||
|
this.isSelect = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Theme.of(context)
|
color: isSelect
|
||||||
.colorScheme
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
.surfaceContainerHighest
|
: Theme.of(context)
|
||||||
.withOpacity(0.5),
|
.colorScheme
|
||||||
|
.surfaceContainerHighest
|
||||||
|
.withOpacity(0.5),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
@ -37,7 +41,10 @@ class SearchText extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
searchText!,
|
searchText!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
color: isSelect
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -16,14 +16,18 @@ class SearchPanelController extends GetxController {
|
|||||||
RxString order = ''.obs;
|
RxString order = ''.obs;
|
||||||
// 视频时长筛选 仅用于搜索视频
|
// 视频时长筛选 仅用于搜索视频
|
||||||
RxInt duration = 0.obs;
|
RxInt duration = 0.obs;
|
||||||
|
// 视频分区筛选 仅用于搜索视频 -1时不传
|
||||||
|
RxInt tids = (-1).obs;
|
||||||
|
|
||||||
Future onSearch({type = 'init'}) async {
|
Future onSearch({type = 'init'}) async {
|
||||||
var result = await SearchHttp.searchByType(
|
var result = await SearchHttp.searchByType(
|
||||||
searchType: searchType!,
|
searchType: searchType!,
|
||||||
keyword: keyword!,
|
keyword: keyword!,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
order: searchType!.type != 'video' ? null : order.value,
|
order: searchType!.type != 'video' ? null : order.value,
|
||||||
duration: searchType!.type != 'video' ? null : duration.value);
|
duration: searchType!.type != 'video' ? null : duration.value,
|
||||||
|
tids: searchType!.type != 'video' ? null : tids.value,
|
||||||
|
);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (type == 'onRefresh') {
|
if (type == 'onRefresh') {
|
||||||
resultList.value = result['data'].list;
|
resultList.value = result['data'].list;
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
|
import 'package:pilipala/pages/search/widgets/search_text.dart';
|
||||||
import 'package:pilipala/pages/search_panel/index.dart';
|
import 'package:pilipala/pages/search_panel/index.dart';
|
||||||
|
|
||||||
class SearchVideoPanel extends StatelessWidget {
|
class SearchVideoPanel extends StatelessWidget {
|
||||||
@ -94,7 +95,7 @@ class SearchVideoPanel extends StatelessWidget {
|
|||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||||
),
|
),
|
||||||
onPressed: () => controller.onShowFilterDialog(ctr),
|
onPressed: () => controller.onShowFilterSheet(ctr),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.filter_list_outlined,
|
Icons.filter_list_outlined,
|
||||||
size: 18,
|
size: 18,
|
||||||
@ -165,7 +166,33 @@ class VideoPanelController extends GetxController {
|
|||||||
{'label': '30-60分钟', 'value': 3},
|
{'label': '30-60分钟', 'value': 3},
|
||||||
{'label': '60分钟+', 'value': 4},
|
{'label': '60分钟+', 'value': 4},
|
||||||
];
|
];
|
||||||
|
List<Map<String, dynamic>> partFiltersList = [
|
||||||
|
{'label': '全部', 'value': -1},
|
||||||
|
{'label': '动画', 'value': 1},
|
||||||
|
{'label': '番剧', 'value': 13},
|
||||||
|
{'label': '国创', 'value': 167},
|
||||||
|
{'label': '音乐', 'value': 3},
|
||||||
|
{'label': '舞蹈', 'value': 129},
|
||||||
|
{'label': '游戏', 'value': 4},
|
||||||
|
{'label': '知识', 'value': 36},
|
||||||
|
{'label': '科技', 'value': 188},
|
||||||
|
{'label': '运动', 'value': 234},
|
||||||
|
{'label': '汽车', 'value': 223},
|
||||||
|
{'label': '生活', 'value': 160},
|
||||||
|
{'label': '美食', 'value': 211},
|
||||||
|
{'label': '动物', 'value': 217},
|
||||||
|
{'label': '鬼畜', 'value': 119},
|
||||||
|
{'label': '时尚', 'value': 155},
|
||||||
|
{'label': '资讯', 'value': 202},
|
||||||
|
{'label': '娱乐', 'value': 5},
|
||||||
|
{'label': '影视', 'value': 181},
|
||||||
|
{'label': '记录', 'value': 177},
|
||||||
|
{'label': '电影', 'value': 23},
|
||||||
|
{'label': '电视', 'value': 11},
|
||||||
|
];
|
||||||
|
|
||||||
RxInt currentTimeFilterval = 0.obs;
|
RxInt currentTimeFilterval = 0.obs;
|
||||||
|
RxInt currentPartFilterval = (-1).obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -219,4 +246,100 @@ class VideoPanelController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onShowFilterSheet(searchPanelCtr) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, StateSetter setState) {
|
||||||
|
return Container(
|
||||||
|
color: Theme.of(Get.context!).colorScheme.surface,
|
||||||
|
padding: const EdgeInsets.only(top: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const ListTile(
|
||||||
|
title: Text('内容时长'),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 14,
|
||||||
|
right: 14,
|
||||||
|
bottom: 14,
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
children: [
|
||||||
|
for (var i in timeFiltersList)
|
||||||
|
Obx(
|
||||||
|
() => SearchText(
|
||||||
|
searchText: i['label'],
|
||||||
|
searchTextIdx: i['value'],
|
||||||
|
isSelect:
|
||||||
|
currentTimeFilterval.value == i['value'],
|
||||||
|
onSelect: (value) async {
|
||||||
|
currentTimeFilterval.value = i['value'];
|
||||||
|
setState(() {});
|
||||||
|
SmartDialog.showToast("「${i['label']}」的筛选结果");
|
||||||
|
SearchPanelController ctr =
|
||||||
|
Get.find<SearchPanelController>(
|
||||||
|
tag: 'video${searchPanelCtr.keyword!}');
|
||||||
|
ctr.duration.value = i['value'];
|
||||||
|
Get.back();
|
||||||
|
SmartDialog.showLoading(msg: '获取中');
|
||||||
|
await ctr.onRefresh();
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
},
|
||||||
|
onLongSelect: (value) => {},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const ListTile(
|
||||||
|
title: Text('内容分区'),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
children: [
|
||||||
|
for (var i in partFiltersList)
|
||||||
|
SearchText(
|
||||||
|
searchText: i['label'],
|
||||||
|
searchTextIdx: i['value'],
|
||||||
|
isSelect: currentPartFilterval.value == i['value'],
|
||||||
|
onSelect: (value) async {
|
||||||
|
currentPartFilterval.value = i['value'];
|
||||||
|
setState(() {});
|
||||||
|
SmartDialog.showToast("「${i['label']}」的筛选结果");
|
||||||
|
SearchPanelController ctr =
|
||||||
|
Get.find<SearchPanelController>(
|
||||||
|
tag: 'video${searchPanelCtr.keyword!}');
|
||||||
|
ctr.tids.value = i['value'];
|
||||||
|
Get.back();
|
||||||
|
SmartDialog.showLoading(msg: '获取中');
|
||||||
|
await ctr.onRefresh();
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
},
|
||||||
|
onLongSelect: (value) => {},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
102
lib/pages/setting/pages/action_menu_set.dart
Normal file
102
lib/pages/setting/pages/action_menu_set.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/models/common/action_type.dart';
|
||||||
|
import 'package:pilipala/utils/global_data.dart';
|
||||||
|
import '../../../utils/storage.dart';
|
||||||
|
|
||||||
|
class ActionMenuSetPage extends StatefulWidget {
|
||||||
|
const ActionMenuSetPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ActionMenuSetPage> createState() => _ActionMenuSetPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ActionMenuSetPageState extends State<ActionMenuSetPage> {
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
late List<String> actionTypeSort;
|
||||||
|
late List<Map> allLabels;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
actionTypeSort = setting.get(SettingBoxKey.actionTypeSort,
|
||||||
|
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
|
||||||
|
allLabels = actionMenuConfig;
|
||||||
|
allLabels.sort((a, b) {
|
||||||
|
int indexA = actionTypeSort.indexOf((a['value'] as ActionType).value);
|
||||||
|
int indexB = actionTypeSort.indexOf((b['value'] as ActionType).value);
|
||||||
|
if (indexA == -1) indexA = actionTypeSort.length;
|
||||||
|
if (indexB == -1) indexB = actionTypeSort.length;
|
||||||
|
return indexA.compareTo(indexB);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveEdit() {
|
||||||
|
List<String> sortedTabbar = allLabels
|
||||||
|
.where((i) => actionTypeSort.contains((i['value'] as ActionType).value))
|
||||||
|
.map<String>((i) => (i['value'] as ActionType).value)
|
||||||
|
.toList();
|
||||||
|
setting.put(SettingBoxKey.actionTypeSort, sortedTabbar);
|
||||||
|
GlobalData().actionTypeSort = sortedTabbar;
|
||||||
|
SmartDialog.showToast('操作成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReorder(int oldIndex, int newIndex) {
|
||||||
|
setState(() {
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
final tabsItem = allLabels.removeAt(oldIndex);
|
||||||
|
allLabels.insert(newIndex, tabsItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final listTiles = [
|
||||||
|
for (int i = 0; i < allLabels.length; i++) ...[
|
||||||
|
CheckboxListTile(
|
||||||
|
key: Key((allLabels[i]['value'] as ActionType).value),
|
||||||
|
value: actionTypeSort
|
||||||
|
.contains((allLabels[i]['value'] as ActionType).value),
|
||||||
|
onChanged: (bool? newValue) {
|
||||||
|
String actionTypeId = (allLabels[i]['value'] as ActionType).value;
|
||||||
|
if (!newValue!) {
|
||||||
|
actionTypeSort.remove(actionTypeId);
|
||||||
|
} else {
|
||||||
|
actionTypeSort.add(actionTypeId);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
allLabels[i]['icon'],
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(allLabels[i]['label']),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
secondary: const Icon(Icons.drag_indicator_rounded),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('视频操作菜单'),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
|
||||||
|
const SizedBox(width: 12)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: ReorderableListView(
|
||||||
|
onReorder: onReorder,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
footer: SizedBox(
|
||||||
|
height: MediaQuery.of(context).padding.bottom + 30,
|
||||||
|
),
|
||||||
|
children: listTiles,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -289,6 +289,11 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
onTap: () => Get.toNamed('/navbarSetting'),
|
onTap: () => Get.toNamed('/navbarSetting'),
|
||||||
title: Text('底部导航栏设置', style: titleStyle),
|
title: Text('底部导航栏设置', style: titleStyle),
|
||||||
),
|
),
|
||||||
|
// ListTile(
|
||||||
|
// dense: false,
|
||||||
|
// onTap: () => Get.toNamed('/actionMenuSet'),
|
||||||
|
// title: Text('操作菜单设置', style: titleStyle),
|
||||||
|
// ),
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
|
@ -38,6 +38,8 @@ class VideoIntroController extends GetxController {
|
|||||||
RxBool hasCoin = false.obs;
|
RxBool hasCoin = false.obs;
|
||||||
// 是否收藏
|
// 是否收藏
|
||||||
RxBool hasFav = false.obs;
|
RxBool hasFav = false.obs;
|
||||||
|
// 是否不喜欢
|
||||||
|
RxBool hasDisLike = false.obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
bool userLogin = false;
|
bool userLogin = false;
|
||||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||||
@ -153,36 +155,16 @@ class VideoIntroController extends GetxController {
|
|||||||
SmartDialog.showToast('🙏 UP已经收到了~');
|
SmartDialog.showToast('🙏 UP已经收到了~');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SmartDialog.show(
|
var result = await VideoHttp.oneThree(bvid: bvid);
|
||||||
useSystem: true,
|
print('🤣🦴:${result["data"]}');
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
if (result['status']) {
|
||||||
builder: (BuildContext context) {
|
hasLike.value = result["data"]["like"];
|
||||||
return AlertDialog(
|
hasCoin.value = result["data"]["coin"];
|
||||||
title: const Text('提示'),
|
hasFav.value = result["data"]["fav"];
|
||||||
content: const Text('一键三连 给UP送温暖'),
|
SmartDialog.showToast('三连成功');
|
||||||
actions: [
|
} else {
|
||||||
TextButton(
|
SmartDialog.showToast(result['msg']);
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
}
|
||||||
child: const Text('点错了')),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
var result = await VideoHttp.oneThree(bvid: bvid);
|
|
||||||
if (result['status']) {
|
|
||||||
hasLike.value = result["data"]["like"];
|
|
||||||
hasCoin.value = result["data"]["coin"];
|
|
||||||
hasFav.value = result["data"]["fav"];
|
|
||||||
SmartDialog.showToast('三连成功 🎉');
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast(result['msg']);
|
|
||||||
}
|
|
||||||
SmartDialog.dismiss();
|
|
||||||
},
|
|
||||||
child: const Text('确认'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// (取消)点赞
|
// (取消)点赞
|
||||||
@ -193,9 +175,8 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
// hasLike.value = result["data"] == 1 ? true : false;
|
|
||||||
if (!hasLike.value) {
|
if (!hasLike.value) {
|
||||||
SmartDialog.showToast('点赞成功 👍');
|
SmartDialog.showToast('点赞成功');
|
||||||
hasLike.value = true;
|
hasLike.value = true;
|
||||||
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
|
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
|
||||||
} else if (hasLike.value) {
|
} else if (hasLike.value) {
|
||||||
@ -215,6 +196,10 @@ class VideoIntroController extends GetxController {
|
|||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (hasCoin.value) {
|
||||||
|
SmartDialog.showToast('已投过币了');
|
||||||
|
return;
|
||||||
|
}
|
||||||
showDialog(
|
showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@ -236,7 +221,7 @@ class VideoIntroController extends GetxController {
|
|||||||
var res = await VideoHttp.coinVideo(
|
var res = await VideoHttp.coinVideo(
|
||||||
bvid: bvid, multiply: _tempThemeValue);
|
bvid: bvid, multiply: _tempThemeValue);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
SmartDialog.showToast('投币成功 👏');
|
SmartDialog.showToast('投币成功');
|
||||||
hasCoin.value = true;
|
hasCoin.value = true;
|
||||||
videoDetail.value.stat!.coin =
|
videoDetail.value.stat!.coin =
|
||||||
videoDetail.value.stat!.coin! + _tempThemeValue;
|
videoDetail.value.stat!.coin! + _tempThemeValue;
|
||||||
@ -269,7 +254,7 @@ class VideoIntroController extends GetxController {
|
|||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
// 重新获取收藏状态
|
// 重新获取收藏状态
|
||||||
await queryHasFavVideo();
|
await queryHasFavVideo();
|
||||||
SmartDialog.showToast('✅ 操作成功');
|
SmartDialog.showToast('操作成功');
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
@ -299,7 +284,7 @@ class VideoIntroController extends GetxController {
|
|||||||
Get.back();
|
Get.back();
|
||||||
// 重新获取收藏状态
|
// 重新获取收藏状态
|
||||||
await queryHasFavVideo();
|
await queryHasFavVideo();
|
||||||
SmartDialog.showToast('✅ 操作成功');
|
SmartDialog.showToast('操作成功');
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -16,6 +16,7 @@ import 'package:pilipala/models/video_detail_res.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
|
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
|
||||||
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/global_data.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import '../../../../http/user.dart';
|
import '../../../../http/user.dart';
|
||||||
@ -147,13 +148,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
RxBool isExpand = false.obs;
|
RxBool isExpand = false.obs;
|
||||||
late ExpandableController _expandableCtr;
|
late ExpandableController _expandableCtr;
|
||||||
|
|
||||||
void Function()? handleState(Future Function() action) {
|
// 一键三连动画
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _scaleTransition;
|
||||||
|
final RxDouble _progress = 0.0.obs;
|
||||||
|
|
||||||
|
void Function()? handleState(Future<dynamic> Function() action) {
|
||||||
return isProcessing
|
return isProcessing
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
setState(() => isProcessing = true);
|
isProcessing = true;
|
||||||
await action();
|
await action.call();
|
||||||
setState(() => isProcessing = false);
|
isProcessing = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +176,26 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
follower = Utils.numFormat(videoIntroController.userStat['follower']);
|
follower = Utils.numFormat(videoIntroController.userStat['follower']);
|
||||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||||
_expandableCtr = ExpandableController(initialExpanded: false);
|
_expandableCtr = ExpandableController(initialExpanded: false);
|
||||||
|
|
||||||
|
/// 一键三连动画
|
||||||
|
_controller = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 1500),
|
||||||
|
reverseDuration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_scaleTransition = Tween<double>(begin: 0.5, end: 1.5).animate(_controller)
|
||||||
|
..addListener(() async {
|
||||||
|
_progress.value =
|
||||||
|
double.parse((_scaleTransition.value - 0.5).toStringAsFixed(3));
|
||||||
|
if (_progress.value == 1) {
|
||||||
|
if (_controller.status == AnimationStatus.completed) {
|
||||||
|
await videoIntroController.actionOneThree();
|
||||||
|
}
|
||||||
|
_progress.value = 0;
|
||||||
|
_scaleTransition.removeListener(() {});
|
||||||
|
_controller.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -250,6 +276,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_expandableCtr.dispose();
|
_expandableCtr.dispose();
|
||||||
|
_controller.dispose();
|
||||||
|
_scaleTransition.removeListener(() {});
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,62 +567,166 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget actionGrid(BuildContext context, videoIntroController) {
|
Widget actionGrid(BuildContext context, videoIntroController) {
|
||||||
|
final actionTypeSort = GlobalData().actionTypeSort;
|
||||||
|
|
||||||
|
Widget progressWidget(progress) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 33,
|
||||||
|
height: 33,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: progress.value,
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Widget> menuListWidgets = {
|
||||||
|
'like': Obx(
|
||||||
|
() {
|
||||||
|
bool likeStatus = videoIntroController.hasLike.value;
|
||||||
|
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(child: progressWidget(_progress), top: 15, left: 24),
|
||||||
|
InkWell(
|
||||||
|
onTapDown: (details) {
|
||||||
|
feedBack();
|
||||||
|
// if (videoIntroController.userInfo == null) {
|
||||||
|
// SmartDialog.showToast('账号未登录');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
_controller.forward();
|
||||||
|
},
|
||||||
|
onTapUp: (TapUpDetails details) {
|
||||||
|
if (_progress.value == 0) {
|
||||||
|
feedBack();
|
||||||
|
EasyThrottle.throttle(
|
||||||
|
'my-throttler', const Duration(milliseconds: 200), () {
|
||||||
|
videoIntroController.actionLikeVideo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_controller.reverse();
|
||||||
|
},
|
||||||
|
borderRadius: StyleString.mdRadius,
|
||||||
|
child: SizedBox(
|
||||||
|
width: (Get.size.width - 24) / 5,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
transitionBuilder:
|
||||||
|
(Widget child, Animation<double> animation) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: animation, child: child);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
key: ValueKey<bool>(likeStatus),
|
||||||
|
likeStatus
|
||||||
|
? Icons.thumb_up
|
||||||
|
: Icons.thumb_up_alt_outlined,
|
||||||
|
color: likeStatus
|
||||||
|
? colorScheme.primary
|
||||||
|
: colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
widget.videoDetail!.stat!.like!.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: likeStatus ? colorScheme.primary : null,
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
'coin': Obx(
|
||||||
|
() => Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(child: progressWidget(_progress), top: 15, left: 24),
|
||||||
|
ActionItem(
|
||||||
|
icon: Image.asset('assets/images/coin.png', width: 30),
|
||||||
|
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||||
|
selectStatus: videoIntroController.hasCoin.value,
|
||||||
|
text: widget.videoDetail!.stat!.coin!.toString(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'collect': Obx(
|
||||||
|
() => Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(child: progressWidget(_progress), top: 15, left: 24),
|
||||||
|
ActionItem(
|
||||||
|
icon: const Icon(Icons.star_border),
|
||||||
|
selectIcon: const Icon(Icons.star),
|
||||||
|
onTap: () => showFavBottomSheet(),
|
||||||
|
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||||
|
selectStatus: videoIntroController.hasFav.value,
|
||||||
|
text: widget.videoDetail!.stat!.favorite!.toString(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'watchLater': ActionItem(
|
||||||
|
icon: const Icon(Icons.watch_later_outlined),
|
||||||
|
onTap: () async {
|
||||||
|
final res =
|
||||||
|
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
},
|
||||||
|
selectStatus: false,
|
||||||
|
text: '稍后看',
|
||||||
|
),
|
||||||
|
'share': ActionItem(
|
||||||
|
icon: const Icon(Icons.share),
|
||||||
|
onTap: () => videoIntroController.actionShareVideo(),
|
||||||
|
selectStatus: false,
|
||||||
|
text: '分享',
|
||||||
|
),
|
||||||
|
'dislike': Obx(
|
||||||
|
() => ActionItem(
|
||||||
|
icon: const Icon(Icons.thumb_down_alt_outlined),
|
||||||
|
selectIcon: const Icon(Icons.thumb_down),
|
||||||
|
onTap: () {},
|
||||||
|
selectStatus: videoIntroController.hasDisLike.value,
|
||||||
|
text: '不喜欢',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'downloadCover': ActionItem(
|
||||||
|
icon: const Icon(Icons.image_outlined),
|
||||||
|
onTap: () {},
|
||||||
|
selectStatus: false,
|
||||||
|
text: '下载封面',
|
||||||
|
),
|
||||||
|
'copyLink': ActionItem(
|
||||||
|
icon: const Icon(Icons.link_outlined),
|
||||||
|
onTap: () {},
|
||||||
|
selectStatus: false,
|
||||||
|
text: '复制链接',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
final List<Widget> list = [];
|
||||||
|
for (var i = 0; i < actionTypeSort.length; i++) {
|
||||||
|
list.add(menuListWidgets[actionTypeSort[i]]!);
|
||||||
|
}
|
||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(top: 6, bottom: 4),
|
margin: const EdgeInsets.only(top: 6, bottom: 4),
|
||||||
height: constraints.maxWidth / 5 * 0.8,
|
height: constraints.maxWidth / 5,
|
||||||
child: GridView.count(
|
child: ListView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
scrollDirection: Axis.horizontal,
|
||||||
primary: false,
|
children: list,
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
crossAxisCount: 5,
|
|
||||||
childAspectRatio: 1.25,
|
|
||||||
children: <Widget>[
|
|
||||||
Obx(
|
|
||||||
() => ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
|
||||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
|
||||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
|
||||||
selectStatus: videoIntroController.hasLike.value,
|
|
||||||
text: widget.videoDetail!.stat!.like!.toString()),
|
|
||||||
),
|
|
||||||
Obx(
|
|
||||||
() => ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.b),
|
|
||||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
|
||||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
|
||||||
selectStatus: videoIntroController.hasCoin.value,
|
|
||||||
text: widget.videoDetail!.stat!.coin!.toString(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Obx(
|
|
||||||
() => ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.star),
|
|
||||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
|
||||||
onTap: () => showFavBottomSheet(),
|
|
||||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
|
||||||
selectStatus: videoIntroController.hasFav.value,
|
|
||||||
text: widget.videoDetail!.stat!.favorite!.toString(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.clock),
|
|
||||||
onTap: () async {
|
|
||||||
final res =
|
|
||||||
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
},
|
|
||||||
selectStatus: false,
|
|
||||||
text: '稍后看',
|
|
||||||
),
|
|
||||||
ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
|
||||||
onTap: () => videoIntroController.actionShareVideo(),
|
|
||||||
selectStatus: false,
|
|
||||||
text: '分享',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
|
||||||
class ActionItem extends StatelessWidget {
|
class ActionItem extends StatelessWidget {
|
||||||
final Icon? icon;
|
final dynamic icon;
|
||||||
final Icon? selectIcon;
|
final Icon? selectIcon;
|
||||||
final Function? onTap;
|
final Function? onTap;
|
||||||
final Function? onLongPress;
|
final Function? onLongPress;
|
||||||
@ -31,26 +32,46 @@ class ActionItem extends StatelessWidget {
|
|||||||
if (onLongPress != null) {onLongPress!()}
|
if (onLongPress != null) {onLongPress!()}
|
||||||
},
|
},
|
||||||
borderRadius: StyleString.mdRadius,
|
borderRadius: StyleString.mdRadius,
|
||||||
child: Column(
|
child: SizedBox(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
width: (Get.size.width - 24) / 5,
|
||||||
children: [
|
child: Column(
|
||||||
const SizedBox(height: 4),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
selectStatus
|
children: [
|
||||||
? Icon(selectIcon!.icon!,
|
const SizedBox(height: 4),
|
||||||
size: 18, color: Theme.of(context).colorScheme.primary)
|
AnimatedSwitcher(
|
||||||
: Icon(icon!.icon!,
|
duration: const Duration(milliseconds: 300),
|
||||||
size: 18, color: Theme.of(context).colorScheme.outline),
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
const SizedBox(height: 6),
|
return ScaleTransition(scale: animation, child: child);
|
||||||
Text(
|
},
|
||||||
text ?? '',
|
child: icon is Icon
|
||||||
style: TextStyle(
|
? Icon(
|
||||||
color: selectStatus
|
selectStatus
|
||||||
? Theme.of(context).colorScheme.primary
|
? selectIcon!.icon ?? icon!.icon
|
||||||
: Theme.of(context).colorScheme.outline,
|
: icon!.icon,
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
color: selectStatus
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.outline,
|
||||||
|
)
|
||||||
|
: Image.asset(
|
||||||
|
key: ValueKey<bool>(selectStatus),
|
||||||
|
'assets/images/coin.png',
|
||||||
|
width: 25,
|
||||||
|
color: selectStatus
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 6),
|
||||||
],
|
Text(
|
||||||
|
text ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
selectStatus ? Theme.of(context).colorScheme.primary : null,
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -259,115 +259,114 @@ class ChatItem extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
case MsgType.auto_reply_push:
|
case MsgType.auto_reply_push:
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
maxWidth: 300.0, // 设置最大宽度为200.0
|
maxWidth: 300.0, // 设置最大宽度为200.0
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer
|
||||||
|
.withOpacity(0.4),
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(16),
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color: Theme.of(context)
|
margin: const EdgeInsets.all(12),
|
||||||
.colorScheme
|
padding: const EdgeInsets.all(12),
|
||||||
.secondaryContainer
|
child: Column(
|
||||||
.withOpacity(0.4),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
borderRadius: const BorderRadius.only(
|
children: [
|
||||||
topLeft: Radius.circular(16),
|
Text(
|
||||||
topRight: Radius.circular(16),
|
content['main_title'],
|
||||||
bottomLeft: Radius.circular(6),
|
style: TextStyle(
|
||||||
bottomRight: Radius.circular(16),
|
letterSpacing: 0.6,
|
||||||
),
|
height: 1.5,
|
||||||
),
|
color: textColor(context),
|
||||||
margin: const EdgeInsets.all(12),
|
fontWeight: FontWeight.bold,
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
content['main_title'],
|
|
||||||
style: TextStyle(
|
|
||||||
letterSpacing: 0.6,
|
|
||||||
height: 1.5,
|
|
||||||
color: textColor(context),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
for (var i in content['sub_cards']) ...<Widget>[
|
),
|
||||||
const SizedBox(height: 6),
|
for (var i in content['sub_cards']) ...<Widget>[
|
||||||
GestureDetector(
|
const SizedBox(height: 6),
|
||||||
onTap: () async {
|
GestureDetector(
|
||||||
RegExp bvRegex = RegExp(r'BV[0-9A-Za-z]{10}',
|
onTap: () async {
|
||||||
caseSensitive: false);
|
RegExp bvRegex =
|
||||||
Iterable<Match> matches =
|
RegExp(r'BV[0-9A-Za-z]{10}', caseSensitive: false);
|
||||||
bvRegex.allMatches(i['jump_url']);
|
Iterable<Match> matches =
|
||||||
if (matches.isNotEmpty) {
|
bvRegex.allMatches(i['jump_url']);
|
||||||
Match match = matches.first;
|
if (matches.isNotEmpty) {
|
||||||
String bvid = match.group(0)!;
|
Match match = matches.first;
|
||||||
try {
|
String bvid = match.group(0)!;
|
||||||
SmartDialog.showLoading();
|
try {
|
||||||
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
SmartDialog.showLoading();
|
||||||
final String heroTag = Utils.makeHeroTag(bvid);
|
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
SmartDialog.dismiss<dynamic>().then(
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
(e) => Get.toNamed<dynamic>(
|
SmartDialog.dismiss<dynamic>().then(
|
||||||
'/video?bvid=$bvid&cid=$cid',
|
(e) => Get.toNamed<dynamic>(
|
||||||
arguments: <String, String?>{
|
'/video?bvid=$bvid&cid=$cid',
|
||||||
'pic': i['cover_url'],
|
arguments: <String, String?>{
|
||||||
'heroTag': heroTag,
|
'pic': i['cover_url'],
|
||||||
}),
|
'heroTag': heroTag,
|
||||||
);
|
}),
|
||||||
} catch (err) {
|
);
|
||||||
SmartDialog.dismiss();
|
} catch (err) {
|
||||||
SmartDialog.showToast(err.toString());
|
SmartDialog.dismiss();
|
||||||
}
|
SmartDialog.showToast(err.toString());
|
||||||
} else {
|
}
|
||||||
SmartDialog.showToast('未匹配到 BV 号');
|
} else {
|
||||||
Get.toNamed('/webview',
|
SmartDialog.showToast('未匹配到 BV 号');
|
||||||
arguments: {'url': i['jump_url']});
|
Get.toNamed('/webview',
|
||||||
}
|
arguments: {'url': i['jump_url']});
|
||||||
},
|
}
|
||||||
child: Row(
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: 130,
|
||||||
|
height: 130 * 9 / 16,
|
||||||
|
src: i['cover_url'],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
Text(
|
||||||
width: 130,
|
i['field1'],
|
||||||
height: 130 * 9 / 16,
|
maxLines: 2,
|
||||||
src: i['cover_url'],
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
i['field2'],
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context).withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
i['field3'],
|
||||||
|
style: TextStyle(
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
height: 1.5,
|
||||||
|
color: textColor(context).withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
i['field1'],
|
|
||||||
maxLines: 2,
|
|
||||||
style: TextStyle(
|
|
||||||
letterSpacing: 0.6,
|
|
||||||
height: 1.5,
|
|
||||||
color: textColor(context),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
i['field2'],
|
|
||||||
style: TextStyle(
|
|
||||||
letterSpacing: 0.6,
|
|
||||||
height: 1.5,
|
|
||||||
color: textColor(context).withOpacity(0.6),
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
Utils.timeFormat(int.parse(i['field3'])),
|
|
||||||
style: TextStyle(
|
|
||||||
letterSpacing: 0.6,
|
|
||||||
height: 1.5,
|
|
||||||
color: textColor(context).withOpacity(0.6),
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
));
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return Text(
|
return Text(
|
||||||
content != null && content != ''
|
content != null && content != ''
|
||||||
|
@ -35,6 +35,7 @@ import '../pages/search/index.dart';
|
|||||||
import '../pages/search_result/index.dart';
|
import '../pages/search_result/index.dart';
|
||||||
import '../pages/setting/extra_setting.dart';
|
import '../pages/setting/extra_setting.dart';
|
||||||
import '../pages/setting/index.dart';
|
import '../pages/setting/index.dart';
|
||||||
|
import '../pages/setting/pages/action_menu_set.dart';
|
||||||
import '../pages/setting/pages/color_select.dart';
|
import '../pages/setting/pages/color_select.dart';
|
||||||
import '../pages/setting/pages/display_mode.dart';
|
import '../pages/setting/pages/display_mode.dart';
|
||||||
import '../pages/setting/pages/font_size_select.dart';
|
import '../pages/setting/pages/font_size_select.dart';
|
||||||
@ -174,6 +175,9 @@ class Routes {
|
|||||||
// navigation bar
|
// navigation bar
|
||||||
CustomGetPage(
|
CustomGetPage(
|
||||||
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
|
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
|
||||||
|
// 操作菜单
|
||||||
|
CustomGetPage(
|
||||||
|
name: '/actionMenuSet', page: () => const ActionMenuSetPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,8 @@ class GlobalData {
|
|||||||
bool enablePlayerControlAnimation = true;
|
bool enablePlayerControlAnimation = true;
|
||||||
final bool enableMYBar =
|
final bool enableMYBar =
|
||||||
setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
||||||
|
List<String> actionTypeSort = setting.get(SettingBoxKey.actionTypeSort,
|
||||||
|
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
|
||||||
// 私有构造函数
|
// 私有构造函数
|
||||||
GlobalData._();
|
GlobalData._();
|
||||||
|
|
||||||
|
@ -149,7 +149,8 @@ class SettingBoxKey {
|
|||||||
tabbarSort = 'tabbarSort', // 首页tabbar
|
tabbarSort = 'tabbarSort', // 首页tabbar
|
||||||
dynamicBadgeMode = 'dynamicBadgeMode',
|
dynamicBadgeMode = 'dynamicBadgeMode',
|
||||||
enableGradientBg = 'enableGradientBg',
|
enableGradientBg = 'enableGradientBg',
|
||||||
navBarSort = 'navBarSort';
|
navBarSort = 'navBarSort',
|
||||||
|
actionTypeSort = 'actionTypeSort';
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalCacheKey {
|
class LocalCacheKey {
|
||||||
|
Reference in New Issue
Block a user