Merge branch 'main' into fix
This commit is contained in:
@ -506,4 +506,6 @@ class Api {
|
|||||||
/// 排行榜
|
/// 排行榜
|
||||||
static const String getRankApi = "/x/web-interface/ranking/v2";
|
static const String getRankApi = "/x/web-interface/ranking/v2";
|
||||||
|
|
||||||
|
/// 取消订阅
|
||||||
|
static const String cancelSub = '/x/v3/fav/season/unfav';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -349,4 +349,21 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 取消订阅
|
||||||
|
static Future cancelSub({required int seasonId}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.cancelSub,
|
||||||
|
queryParameters: {
|
||||||
|
'platform': 'web',
|
||||||
|
'season_id': seasonId,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ void main() async {
|
|||||||
.then((_) async {
|
.then((_) async {
|
||||||
await GStrorage.init();
|
await GStrorage.init();
|
||||||
await setupServiceLocator();
|
await setupServiceLocator();
|
||||||
|
clearLogs();
|
||||||
Request();
|
Request();
|
||||||
await Request.setCookie();
|
await Request.setCookie();
|
||||||
RecommendFilter();
|
RecommendFilter();
|
||||||
|
|||||||
@ -47,18 +47,23 @@ class Vip {
|
|||||||
this.status,
|
this.status,
|
||||||
this.dueDate,
|
this.dueDate,
|
||||||
this.label,
|
this.label,
|
||||||
|
this.nicknameColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? type;
|
int? type;
|
||||||
int? status;
|
int? status;
|
||||||
int? dueDate;
|
int? dueDate;
|
||||||
Map? label;
|
Map? label;
|
||||||
|
int? nicknameColor;
|
||||||
|
|
||||||
Vip.fromJson(Map<String, dynamic> json) {
|
Vip.fromJson(Map<String, dynamic> json) {
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
dueDate = json['due_date'];
|
dueDate = json['due_date'];
|
||||||
label = json['label'];
|
label = json['label'];
|
||||||
|
nicknameColor = json['nickname_color'] == ''
|
||||||
|
? null
|
||||||
|
: int.parse("0xFF${json['nickname_color'].replaceAll('#', '')}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -258,24 +258,27 @@ class HistoryItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
videoItem.progress != 0
|
||||||
|
? Positioned(
|
||||||
left: 3,
|
left: 3,
|
||||||
right: 3,
|
right: 3,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
bottomLeft:
|
bottomLeft: Radius.circular(
|
||||||
Radius.circular(StyleString.imgRadius.x),
|
StyleString.imgRadius.x),
|
||||||
bottomRight:
|
bottomRight: Radius.circular(
|
||||||
Radius.circular(StyleString.imgRadius.x),
|
StyleString.imgRadius.x),
|
||||||
),
|
),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: videoItem.progress == -1
|
value: videoItem.progress == -1
|
||||||
? 100
|
? 100
|
||||||
: videoItem.progress / videoItem.duration,
|
: videoItem.progress /
|
||||||
|
videoItem.duration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
: const SizedBox()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem, ctr: ctr)
|
VideoContent(videoItem: videoItem, ctr: ctr)
|
||||||
|
|||||||
@ -281,8 +281,8 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
Map data = snapshot.data!;
|
Map? data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data != null && data['status']) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => Stack(
|
() => Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
@ -302,7 +302,14 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleMedium!
|
.titleMedium!
|
||||||
.copyWith(fontWeight: FontWeight.bold),
|
.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: _memberController.memberInfo.value
|
||||||
|
.vip!.nicknameColor !=
|
||||||
|
null
|
||||||
|
? Color(_memberController.memberInfo
|
||||||
|
.value.vip!.nicknameColor!)
|
||||||
|
: null),
|
||||||
)),
|
)),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
if (_memberController.memberInfo.value.sex == '女')
|
if (_memberController.memberInfo.value.sex == '女')
|
||||||
|
|||||||
@ -18,45 +18,32 @@ class MemberSeasonsPanel extends StatelessWidget {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
MemberSeasonsList item = data!.seasonsList![index];
|
MemberSeasonsList item = data!.seasonsList![index];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12, right: 4),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
ListTile(
|
||||||
padding: const EdgeInsets.only(bottom: 12, left: 4),
|
onTap: () => Get.toNamed(
|
||||||
child: Row(
|
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
||||||
children: [
|
title: Text(
|
||||||
Text(
|
|
||||||
item.meta!.name!,
|
item.meta!.name!,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.titleSmall!,
|
style: Theme.of(context).textTheme.titleSmall!,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
dense: true,
|
||||||
PBadge(
|
leading: PBadge(
|
||||||
stack: 'relative',
|
stack: 'relative',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
text: item.meta!.total.toString(),
|
text: item.meta!.total.toString(),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
trailing: const Icon(
|
||||||
SizedBox(
|
|
||||||
width: 35,
|
|
||||||
height: 35,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: () => Get.toNamed(
|
|
||||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
),
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.arrow_forward,
|
Icons.arrow_forward,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
const SizedBox(height: 10),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
|
|||||||
@ -46,4 +46,40 @@ class SubController extends GetxController {
|
|||||||
Future onLoad() async {
|
Future onLoad() async {
|
||||||
querySubFolder(type: 'onload');
|
querySubFolder(type: 'onload');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 取消订阅
|
||||||
|
Future<void> cancelSub(SubFolderItemData subFolderItem) async {
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('确定取消订阅吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
var res = await UserHttp.cancelSub(seasonId: subFolderItem.id!);
|
||||||
|
if (res['status']) {
|
||||||
|
subFolderData.value.list!.remove(subFolderItem);
|
||||||
|
subFolderData.update((val) {});
|
||||||
|
SmartDialog.showToast('取消订阅成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,8 @@ class _SubPageState extends State<SubPage> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return SubItem(
|
return SubItem(
|
||||||
subFolderItem:
|
subFolderItem:
|
||||||
_subController.subFolderData.value.list![index]);
|
_subController.subFolderData.value.list![index],
|
||||||
|
cancelSub: _subController.cancelSub);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,7 +8,12 @@ import '../../../models/user/sub_folder.dart';
|
|||||||
|
|
||||||
class SubItem extends StatelessWidget {
|
class SubItem extends StatelessWidget {
|
||||||
final SubFolderItemData subFolderItem;
|
final SubFolderItemData subFolderItem;
|
||||||
const SubItem({super.key, required this.subFolderItem});
|
final Function(SubFolderItemData) cancelSub;
|
||||||
|
const SubItem({
|
||||||
|
super.key,
|
||||||
|
required this.subFolderItem,
|
||||||
|
required this.cancelSub,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -51,7 +56,10 @@ class SubItem extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VideoContent(subFolderItem: subFolderItem)
|
VideoContent(
|
||||||
|
subFolderItem: subFolderItem,
|
||||||
|
cancelSub: cancelSub,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -64,7 +72,8 @@ class SubItem extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final SubFolderItemData subFolderItem;
|
final SubFolderItemData subFolderItem;
|
||||||
const VideoContent({super.key, required this.subFolderItem});
|
final Function(SubFolderItemData)? cancelSub;
|
||||||
|
const VideoContent({super.key, required this.subFolderItem, this.cancelSub});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -100,6 +109,24 @@ class VideoContent extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 35,
|
||||||
|
width: 35,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () => cancelSub?.call(subFolderItem),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 18),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -525,14 +525,18 @@ InlineSpan buildContent(
|
|||||||
if (jumpUrlKeysList.isNotEmpty) {
|
if (jumpUrlKeysList.isNotEmpty) {
|
||||||
patternStr += '|${jumpUrlKeysList.join('|')}';
|
patternStr += '|${jumpUrlKeysList.join('|')}';
|
||||||
}
|
}
|
||||||
|
RegExp bv23Regex = RegExp(r'https://b23\.tv/[a-zA-Z0-9]{7}');
|
||||||
final RegExp pattern = RegExp(patternStr);
|
final RegExp pattern = RegExp(patternStr);
|
||||||
List<String> matchedStrs = [];
|
List<String> matchedStrs = [];
|
||||||
void addPlainTextSpan(str) {
|
void addPlainTextSpan(str) {
|
||||||
spanChilds.add(TextSpan(
|
spanChilds.add(
|
||||||
|
TextSpan(
|
||||||
text: str,
|
text: str,
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () =>
|
..onTap = () =>
|
||||||
replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem)));
|
replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分割文本并处理每个部分
|
// 分割文本并处理每个部分
|
||||||
@ -734,8 +738,36 @@ InlineSpan buildContent(
|
|||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
onNonMatch: (String nonMatchStr) {
|
onNonMatch: (String nonMatchStr) {
|
||||||
addPlainTextSpan(nonMatchStr);
|
return nonMatchStr.splitMapJoin(
|
||||||
return nonMatchStr;
|
bv23Regex,
|
||||||
|
onMatch: (Match match) {
|
||||||
|
String matchStr = match[0]!;
|
||||||
|
spanChilds.add(
|
||||||
|
TextSpan(
|
||||||
|
text: ' $matchStr ',
|
||||||
|
style: isVideoPage
|
||||||
|
? TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () => Get.toNamed(
|
||||||
|
'/webview',
|
||||||
|
parameters: {
|
||||||
|
'url': matchStr,
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': matchStr
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
onNonMatch: (String nonMatchOtherStr) {
|
||||||
|
addPlainTextSpan(nonMatchOtherStr);
|
||||||
|
return nonMatchOtherStr;
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -208,16 +208,18 @@ class Utils {
|
|||||||
|
|
||||||
static int findClosestNumber(int target, List<int> numbers) {
|
static int findClosestNumber(int target, List<int> numbers) {
|
||||||
int minDiff = 127;
|
int minDiff = 127;
|
||||||
late int closestNumber;
|
int closestNumber = 0; // 初始化为0,表示没有找到比目标值小的整数
|
||||||
try {
|
try {
|
||||||
for (int number in numbers) {
|
for (int number in numbers) {
|
||||||
int diff = (number - target).abs();
|
if (number < target) {
|
||||||
|
int diff = target - number; // 计算目标值与当前整数的差值
|
||||||
|
|
||||||
if (diff < minDiff) {
|
if (diff < minDiff) {
|
||||||
minDiff = diff;
|
minDiff = diff;
|
||||||
closestNumber = number;
|
closestNumber = number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
return closestNumber;
|
return closestNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user