mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
opt: item
opt: util Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/models/common/reply_type.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -84,7 +85,7 @@ class PiliScheme {
|
||||
String? id = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (id != null) {
|
||||
bool isEp = path.contains('/ep/');
|
||||
Utils.viewBangumi(
|
||||
PageUtils.viewBangumi(
|
||||
seasonId: isEp ? null : id,
|
||||
epId: isEp ? id : null,
|
||||
progress: uri.queryParameters['start_progress']);
|
||||
@@ -95,7 +96,7 @@ class PiliScheme {
|
||||
// bilibili://space/12345678?frommodule=XX&h5awaken=random
|
||||
String? mid = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (mid != null) {
|
||||
Utils.toDupNamed('/member?mid=$mid', off: off);
|
||||
PageUtils.toDupNamed('/member?mid=$mid', off: off);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -159,7 +160,7 @@ class PiliScheme {
|
||||
if (aid != null || bvid != null) {
|
||||
if (queryParameters['cid'] != null) {
|
||||
bvid ??= IdUtils.av2bv(int.parse(aid!));
|
||||
Utils.toViewPage(
|
||||
PageUtils.toVideoPage(
|
||||
'bvid=$bvid&cid=${queryParameters['cid']}',
|
||||
arguments: {
|
||||
'pic': null,
|
||||
@@ -185,7 +186,7 @@ class PiliScheme {
|
||||
// bilibili://live/12345678?extra_jump_from=1&from=1&is_room_feed=1&h5awaken=random
|
||||
String? roomId = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (roomId != null) {
|
||||
Utils.toDupNamed('/liveRoom?roomid=$roomId', off: off);
|
||||
PageUtils.toDupNamed('/liveRoom?roomid=$roomId', off: off);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -194,7 +195,7 @@ class PiliScheme {
|
||||
if (path.startsWith('/season')) {
|
||||
String? seasonId = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (seasonId != null) {
|
||||
Utils.viewBangumi(seasonId: seasonId, epId: null);
|
||||
PageUtils.viewBangumi(seasonId: seasonId, epId: null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -203,7 +204,7 @@ class PiliScheme {
|
||||
// bilibili://opus/detail/12345678?h5awaken=random
|
||||
// String? id = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
// if (id != null) {
|
||||
// Utils.toDupNamed(
|
||||
// PageUtils.toDupNamed(
|
||||
// '/htmlRender',
|
||||
// parameters: {
|
||||
// 'url': 'https://www.bilibili.com/opus/$id',
|
||||
@@ -219,7 +220,7 @@ class PiliScheme {
|
||||
bool hasMatch = await _onPushDynDetail(path, off);
|
||||
return hasMatch;
|
||||
case 'search':
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/searchResult',
|
||||
parameters: {'keyword': ''},
|
||||
off: off,
|
||||
@@ -229,7 +230,7 @@ class PiliScheme {
|
||||
// bilibili://article/40679479?jump_opus=1&jump_opus_type=1&opus_type=article&h5awaken=random
|
||||
String? id = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (id != null) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/htmlRender',
|
||||
parameters: {
|
||||
'url': 'www.bilibili.com/read/cv$id',
|
||||
@@ -351,7 +352,7 @@ class PiliScheme {
|
||||
.firstMatch(path)
|
||||
?.group(1);
|
||||
if (cvid != null) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/htmlRender',
|
||||
parameters: {
|
||||
'url': 'https://www.bilibili.com/read/cv$cvid',
|
||||
@@ -424,7 +425,7 @@ class PiliScheme {
|
||||
dynamic res = await DynamicsHttp.dynamicDetail(rid: rid, type: 2);
|
||||
SmartDialog.dismiss();
|
||||
if (res['status']) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/dynamicDetail',
|
||||
arguments: {
|
||||
'item': res['data'],
|
||||
@@ -442,7 +443,7 @@ class PiliScheme {
|
||||
case 'medialist':
|
||||
String? mediaId = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (mediaId != null) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/favDetail',
|
||||
parameters: {
|
||||
'mediaId': mediaId,
|
||||
@@ -546,7 +547,7 @@ class PiliScheme {
|
||||
if (host.contains('live.bilibili.com')) {
|
||||
String? roomId = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (roomId != null) {
|
||||
Utils.toDupNamed('/liveRoom?roomid=$roomId', off: off);
|
||||
PageUtils.toDupNamed('/liveRoom?roomid=$roomId', off: off);
|
||||
return true;
|
||||
}
|
||||
launchURL();
|
||||
@@ -556,7 +557,7 @@ class PiliScheme {
|
||||
if (host.contains('space.bilibili.com')) {
|
||||
String? mid = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (mid != null) {
|
||||
Utils.toDupNamed('/member?mid=$mid', off: off);
|
||||
PageUtils.toDupNamed('/member?mid=$mid', off: off);
|
||||
return true;
|
||||
}
|
||||
launchURL();
|
||||
@@ -579,7 +580,7 @@ class PiliScheme {
|
||||
.firstMatch(uri.query)
|
||||
?.group(1);
|
||||
if (id != null) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/htmlRender',
|
||||
parameters: {
|
||||
'url': 'https://www.bilibili.com/read/cv$id',
|
||||
@@ -597,7 +598,7 @@ class PiliScheme {
|
||||
// case 'opus':
|
||||
// String? id = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
// if (id != null) {
|
||||
// Utils.toDupNamed(
|
||||
// PageUtils.toDupNamed(
|
||||
// '/htmlRender',
|
||||
// parameters: {
|
||||
// 'url': 'https://www.bilibili.com/opus/$id',
|
||||
@@ -635,7 +636,7 @@ class PiliScheme {
|
||||
if (id != null) {
|
||||
bool isSeason = id.startsWith('ss');
|
||||
id = id.substring(2);
|
||||
Utils.viewBangumi(
|
||||
PageUtils.viewBangumi(
|
||||
seasonId: isSeason ? id : null,
|
||||
epId: isSeason ? null : id,
|
||||
progress: uri.queryParameters['start_progress']);
|
||||
@@ -664,7 +665,7 @@ class PiliScheme {
|
||||
String? id =
|
||||
RegExp(r'cv(\d+)', caseSensitive: false).firstMatch(path)?.group(1);
|
||||
if (id != null) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/htmlRender',
|
||||
parameters: {
|
||||
'url': 'https://www.bilibili.com/read/cv$id',
|
||||
@@ -682,7 +683,7 @@ class PiliScheme {
|
||||
debugPrint('个人空间');
|
||||
String? mid = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (mid != null) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/member?mid=$mid',
|
||||
off: off,
|
||||
);
|
||||
@@ -708,7 +709,7 @@ class PiliScheme {
|
||||
static Future<bool> _onPushDynDetail(path, off) async {
|
||||
String? id = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (id != null) {
|
||||
Utils.pushDynFromId(id, off: off);
|
||||
PageUtils.pushDynFromId(id, off: off);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -719,7 +720,7 @@ class PiliScheme {
|
||||
bool off,
|
||||
Map? parameters,
|
||||
) {
|
||||
Utils.toDupNamed(
|
||||
PageUtils.toDupNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': url,
|
||||
@@ -752,7 +753,7 @@ class PiliScheme {
|
||||
if (showDialog) {
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
Utils.toViewPage(
|
||||
PageUtils.toVideoPage(
|
||||
'bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'pic': null,
|
||||
|
||||
@@ -12,7 +12,7 @@ class Grid {
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: 2,
|
||||
maxCrossAxisExtent: Grid.smallCardWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.4,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.2,
|
||||
minHeight: MediaQuery.textScalerOf(context).scale(minHeight),
|
||||
);
|
||||
}
|
||||
|
||||
747
lib/utils/page_utils.dart
Normal file
747
lib/utils/page_utils.dart
Normal file
@@ -0,0 +1,747 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart';
|
||||
import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/http/search.dart';
|
||||
import 'package:PiliPlus/models/bangumi/info.dart';
|
||||
import 'package:PiliPlus/models/common/search_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/models/live/item.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||
import 'package:PiliPlus/services/shutdown_timer_service.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/url_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class PageUtils {
|
||||
static void scheduleExit(BuildContext context, isFullScreen,
|
||||
[bool isLive = false]) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
const List<int> scheduleTimeChoices = [0, 15, 30, 45, 60];
|
||||
const TextStyle titleStyle = TextStyle(fontSize: 14);
|
||||
if (isLive) {
|
||||
shutdownTimerService.waitForPlayingCompleted = false;
|
||||
}
|
||||
PageUtils.showVideoBottomSheet(
|
||||
context,
|
||||
isFullScreen: () => isFullScreen,
|
||||
child: StatefulBuilder(
|
||||
builder: (_, setState) {
|
||||
return Theme(
|
||||
data: Theme.of(context),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
child: ListView(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
const Center(child: Text('定时关闭', style: titleStyle)),
|
||||
const SizedBox(height: 10),
|
||||
...[
|
||||
...[
|
||||
...scheduleTimeChoices,
|
||||
if (scheduleTimeChoices
|
||||
.contains(
|
||||
shutdownTimerService.scheduledExitInMinutes)
|
||||
.not)
|
||||
shutdownTimerService.scheduledExitInMinutes,
|
||||
]..sort(),
|
||||
-1,
|
||||
].map(
|
||||
(choice) => ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
if (choice == -1) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
String duration = '';
|
||||
return AlertDialog(
|
||||
title: const Text('自定义时长'),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
onChanged: (value) => duration = value,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'\d+')),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
suffixText: 'min'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
int choice =
|
||||
int.tryParse(duration) ?? 0;
|
||||
shutdownTimerService
|
||||
.scheduledExitInMinutes = choice;
|
||||
shutdownTimerService
|
||||
.startShutdownTimer();
|
||||
setState(() {});
|
||||
},
|
||||
child: Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
Get.back();
|
||||
shutdownTimerService.scheduledExitInMinutes =
|
||||
choice;
|
||||
shutdownTimerService.startShutdownTimer();
|
||||
}
|
||||
},
|
||||
contentPadding: const EdgeInsets.only(),
|
||||
title: Text(choice == -1
|
||||
? '自定义'
|
||||
: choice == 0
|
||||
? "禁用"
|
||||
: "$choice分钟后"),
|
||||
trailing: shutdownTimerService.scheduledExitInMinutes ==
|
||||
choice
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
width: 125,
|
||||
child: Divider(height: 1),
|
||||
),
|
||||
),
|
||||
if (isLive.not) ...[
|
||||
const SizedBox(height: 10),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
shutdownTimerService.waitForPlayingCompleted =
|
||||
!shutdownTimerService.waitForPlayingCompleted;
|
||||
setState(() {});
|
||||
},
|
||||
contentPadding: const EdgeInsets.only(),
|
||||
title: const Text("额外等待视频播放完毕", style: titleStyle),
|
||||
trailing: Transform.scale(
|
||||
alignment: Alignment
|
||||
.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
|
||||
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 Text('倒计时结束:', style: titleStyle),
|
||||
const Spacer(),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = false;
|
||||
setState(() {});
|
||||
// Get.back();
|
||||
},
|
||||
text: " 暂停视频 ",
|
||||
selectStatus: !shutdownTimerService.exitApp,
|
||||
),
|
||||
const Spacer(),
|
||||
// const SizedBox(width: 10),
|
||||
ActionRowLineItem(
|
||||
onTap: () {
|
||||
shutdownTimerService.exitApp = true;
|
||||
setState(() {});
|
||||
// Get.back();
|
||||
},
|
||||
text: " 退出APP ",
|
||||
selectStatus: shutdownTimerService.exitApp,
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future pushDynFromId(id, {bool off = false}) async {
|
||||
SmartDialog.showLoading();
|
||||
dynamic res = await DynamicsHttp.dynamicDetail(id: id);
|
||||
SmartDialog.dismiss();
|
||||
if (res['status']) {
|
||||
DynamicItemModel data = res['data'];
|
||||
if (data.basic?['comment_type'] == 12) {
|
||||
toDupNamed(
|
||||
'/htmlRender',
|
||||
parameters: {
|
||||
'url': 'www.bilibili.com/opus/$id',
|
||||
'title': '',
|
||||
'id': id,
|
||||
'dynamicType': 'opus'
|
||||
},
|
||||
off: off,
|
||||
);
|
||||
} else {
|
||||
toDupNamed(
|
||||
'/dynamicDetail',
|
||||
arguments: {
|
||||
'item': res['data'],
|
||||
'floor': 1,
|
||||
'action': 'detail',
|
||||
},
|
||||
off: off,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
static void showFavBottomSheet({
|
||||
required BuildContext context,
|
||||
required dynamic ctr,
|
||||
}) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
sheetAnimationStyle: AnimationStyle(curve: Curves.ease),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: min(640, min(Get.width, Get.height)),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
minChildSize: 0,
|
||||
maxChildSize: 1,
|
||||
initialChildSize: 0.7,
|
||||
snap: true,
|
||||
expand: false,
|
||||
snapSizes: const [0.7],
|
||||
builder: (BuildContext context, ScrollController scrollController) {
|
||||
return FavPanel(
|
||||
ctr: ctr,
|
||||
scrollController: scrollController,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static void reportVideo(int aid) {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {'url': 'https://www.bilibili.com/appeal/?avid=$aid'},
|
||||
);
|
||||
}
|
||||
|
||||
static void enterPip(Floating floating, int width, int height) {
|
||||
Rational aspectRatio = Rational(width, height);
|
||||
floating.enable(
|
||||
EnableManual(
|
||||
aspectRatio: aspectRatio.fitsInAndroidRequirements
|
||||
? aspectRatio
|
||||
: height > width
|
||||
? const Rational.vertical()
|
||||
: const Rational.landscape(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void pushDynDetail(item, floor, {action = 'all'}) async {
|
||||
feedBack();
|
||||
|
||||
/// 点击评论action 直接查看评论
|
||||
if (action == 'comment') {
|
||||
toDupNamed(
|
||||
'/dynamicDetail',
|
||||
arguments: {
|
||||
'item': item,
|
||||
'floor': floor,
|
||||
'action': action,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('pushDynDetail: ${item.type}');
|
||||
|
||||
switch (item.type) {
|
||||
case 'DYNAMIC_TYPE_AV':
|
||||
if (item.modules.moduleDynamic.major.archive?.type == 2) {
|
||||
if (item.modules.moduleDynamic.major.archive.jumpUrl
|
||||
.startsWith('//')) {
|
||||
item.modules.moduleDynamic.major.archive.jumpUrl =
|
||||
'https:${item.modules.moduleDynamic.major.archive.jumpUrl}';
|
||||
}
|
||||
String? redirectUrl = await UrlUtils.parseRedirectUrl(
|
||||
item.modules.moduleDynamic.major.archive.jumpUrl, false);
|
||||
if (redirectUrl != null) {
|
||||
viewPgcFromUri(redirectUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String bvid = item.modules.moduleDynamic.major.archive.bvid;
|
||||
String cover = item.modules.moduleDynamic.major.archive.cover;
|
||||
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
toVideoPage(
|
||||
'bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'pic': cover,
|
||||
'heroTag': Utils.makeHeroTag(bvid),
|
||||
},
|
||||
preventDuplicates: false,
|
||||
);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
break;
|
||||
|
||||
/// 专栏文章查看
|
||||
case 'DYNAMIC_TYPE_ARTICLE':
|
||||
String? url = item?.modules?.moduleDynamic?.major?.opus?.jumpUrl;
|
||||
if (url != null) {
|
||||
String? title = item?.modules?.moduleDynamic?.major?.opus?.title;
|
||||
if (url.contains('opus') || url.contains('read')) {
|
||||
RegExp digitRegExp = RegExp(r'\d+');
|
||||
Iterable<Match> matches = digitRegExp.allMatches(url);
|
||||
String number = matches.first.group(0)!;
|
||||
if (url.contains('read')) {
|
||||
number = 'cv$number';
|
||||
}
|
||||
toDupNamed('/htmlRender', parameters: {
|
||||
'url': url.startsWith('//') ? url.split('//').last : url,
|
||||
'title': title ?? '',
|
||||
'id': number,
|
||||
'dynamicType': url.split('//').last.split('/')[1]
|
||||
});
|
||||
} else {
|
||||
handleWebview('https:$url');
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'DYNAMIC_TYPE_PGC':
|
||||
debugPrint('番剧');
|
||||
SmartDialog.showToast('暂未支持的类型,请联系开发者');
|
||||
break;
|
||||
|
||||
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
||||
DynamicLiveModel liveRcmd = item.modules.moduleDynamic.major.liveRcmd;
|
||||
ModuleAuthorModel author = item.modules.moduleAuthor;
|
||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||
'title': liveRcmd.title,
|
||||
'uname': author.name,
|
||||
'cover': liveRcmd.cover,
|
||||
'mid': author.mid,
|
||||
'face': author.face,
|
||||
'roomid': liveRcmd.roomId,
|
||||
'watched_show': liveRcmd.watchedShow,
|
||||
});
|
||||
toDupNamed('/liveRoom?roomid=${liveItem.roomId}');
|
||||
break;
|
||||
|
||||
/// 合集查看
|
||||
case 'DYNAMIC_TYPE_UGC_SEASON':
|
||||
DynamicArchiveModel ugcSeason =
|
||||
item.modules.moduleDynamic.major.ugcSeason;
|
||||
int aid = ugcSeason.aid!;
|
||||
String bvid = IdUtils.av2bv(aid);
|
||||
String cover = ugcSeason.cover!;
|
||||
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
toVideoPage(
|
||||
'bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'pic': cover,
|
||||
'heroTag': Utils.makeHeroTag(bvid),
|
||||
},
|
||||
preventDuplicates: false,
|
||||
);
|
||||
break;
|
||||
|
||||
/// 番剧查看
|
||||
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||
debugPrint('DYNAMIC_TYPE_PGC_UNION 番剧');
|
||||
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
|
||||
if (pgc.epid != null) {
|
||||
viewBangumi(epId: pgc.epid);
|
||||
}
|
||||
break;
|
||||
case 'DYNAMIC_TYPE_MEDIALIST':
|
||||
if (item.modules?.moduleDynamic?.major?.medialist != null) {
|
||||
final String? url =
|
||||
item.modules.moduleDynamic.major.medialist['jump_url'];
|
||||
if (url?.contains('medialist/detail/ml') == true) {
|
||||
Get.toNamed(
|
||||
'/favDetail',
|
||||
parameters: {
|
||||
'heroTag':
|
||||
'${item.modules.moduleDynamic.major.medialist['cover']}',
|
||||
'mediaId':
|
||||
'${item.modules.moduleDynamic.major.medialist['id']}',
|
||||
},
|
||||
);
|
||||
} else if (url != null) {
|
||||
handleWebview(url.http2https);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// 纯文字动态查看
|
||||
// case 'DYNAMIC_TYPE_WORD':
|
||||
// # 装扮/剧集点评/普通分享
|
||||
// case 'DYNAMIC_TYPE_COMMON_SQUARE':
|
||||
// 转发的动态
|
||||
// case 'DYNAMIC_TYPE_FORWARD':
|
||||
// 图文动态查看
|
||||
// case 'DYNAMIC_TYPE_DRAW':
|
||||
default:
|
||||
toDupNamed(
|
||||
'/dynamicDetail',
|
||||
arguments: {
|
||||
'item': item,
|
||||
'floor': floor,
|
||||
},
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void onHorizontalPreview(
|
||||
GlobalKey<ScaffoldState> key,
|
||||
transitionAnimationController,
|
||||
ctr,
|
||||
List<String> imgList,
|
||||
index,
|
||||
onClose,
|
||||
) {
|
||||
key.currentState?.showBottomSheet(
|
||||
(context) {
|
||||
return FadeTransition(
|
||||
opacity: Tween<double>(begin: 0, end: 1).animate(ctr),
|
||||
child: InteractiveviewerGallery(
|
||||
sources: imgList.map((url) => SourceModel(url: url)).toList(),
|
||||
initIndex: index,
|
||||
setStatusBar: false,
|
||||
onClose: onClose,
|
||||
),
|
||||
);
|
||||
},
|
||||
enableDrag: false,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
transitionAnimationController: transitionAnimationController,
|
||||
sheetAnimationStyle: AnimationStyle(duration: Duration.zero),
|
||||
);
|
||||
}
|
||||
|
||||
static void inAppWebview(
|
||||
String url, {
|
||||
bool off = false,
|
||||
}) {
|
||||
if (GStorage.openInBrowser) {
|
||||
launchURL(url);
|
||||
} else {
|
||||
if (off) {
|
||||
Get.offNamed(
|
||||
'/webview',
|
||||
parameters: {'url': url},
|
||||
arguments: {'inApp': true},
|
||||
);
|
||||
} else {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {'url': url},
|
||||
arguments: {'inApp': true},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static launchURL(String url) async {
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
if (!await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
SmartDialog.showToast('Could not launch $url');
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.showToast(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static void handleWebview(
|
||||
String url, {
|
||||
bool off = false,
|
||||
bool inApp = false,
|
||||
Map? parameters,
|
||||
}) async {
|
||||
if (inApp.not && GStorage.openInBrowser) {
|
||||
if ((await PiliScheme.routePushFromUrl(url, selfHandle: true)).not) {
|
||||
launchURL(url);
|
||||
}
|
||||
} else {
|
||||
if (off) {
|
||||
Get.offNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': url,
|
||||
if (parameters != null) ...parameters,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
PiliScheme.routePushFromUrl(url, parameters: parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void showVideoBottomSheet(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
required Function isFullScreen,
|
||||
double? padding,
|
||||
}) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
Get.generalDialog(
|
||||
barrierLabel: '',
|
||||
barrierDismissible: true,
|
||||
pageBuilder: (buildContext, animation, secondaryAnimation) {
|
||||
return MediaQuery.orientationOf(Get.context!) == Orientation.portrait
|
||||
? SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(flex: 3),
|
||||
Expanded(flex: 7, child: child),
|
||||
if (isFullScreen() && padding != null)
|
||||
SizedBox(height: padding),
|
||||
],
|
||||
),
|
||||
)
|
||||
: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 350),
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
Offset begin =
|
||||
MediaQuery.orientationOf(Get.context!) == Orientation.portrait
|
||||
? Offset(0.0, 1.0)
|
||||
: Offset(1.0, 0.0);
|
||||
var tween = Tween(begin: begin, end: Offset.zero)
|
||||
.chain(CurveTween(curve: Curves.easeInOut));
|
||||
return SlideTransition(
|
||||
position: animation.drive(tween),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
routeSettings: RouteSettings(arguments: Get.arguments),
|
||||
);
|
||||
}
|
||||
|
||||
static void toVideoPage(
|
||||
String page, {
|
||||
dynamic arguments,
|
||||
int? id,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
bool off = false,
|
||||
}) {
|
||||
if (off) {
|
||||
Get.offNamed(
|
||||
'/videoV?$page',
|
||||
arguments: arguments,
|
||||
id: id,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
);
|
||||
} else {
|
||||
Get.toNamed(
|
||||
'/videoV?$page',
|
||||
arguments: arguments,
|
||||
id: id,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static bool viewPgcFromUri(String uri) {
|
||||
String? id = RegExp(r'(ep|ss)\d+').firstMatch(uri)?.group(0);
|
||||
if (id != null) {
|
||||
bool isSeason = id.startsWith('ss');
|
||||
id = id.substring(2);
|
||||
viewBangumi(
|
||||
seasonId: isSeason ? id : null,
|
||||
epId: isSeason ? null : id,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void viewBangumi(
|
||||
{dynamic seasonId, dynamic epId, dynamic progress}) async {
|
||||
try {
|
||||
SmartDialog.showLoading(msg: '资源获取中');
|
||||
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
|
||||
SmartDialog.dismiss();
|
||||
if (result['status']) {
|
||||
BangumiInfoModel data = result['data'];
|
||||
|
||||
// epId episode -> progress episode -> first episode
|
||||
EpisodeItem? episode;
|
||||
|
||||
if (epId != null) {
|
||||
if (data.episodes?.isNotEmpty == true) {
|
||||
episode = data.episodes!.firstWhereOrNull(
|
||||
(item) {
|
||||
return item.epId.toString() == epId.toString();
|
||||
},
|
||||
);
|
||||
}
|
||||
if (episode == null && data.section?.isNotEmpty == true) {
|
||||
for (Section item in data.section!) {
|
||||
if (item.episodes?.isNotEmpty == true) {
|
||||
for (EpisodeItem item in item.episodes!) {
|
||||
if (item.epId.toString() == epId.toString()) {
|
||||
// view as normal video
|
||||
toVideoPage(
|
||||
'bvid=${item.bvid}&cid=${item.cid}&seasonId=${data.seasonId}&epId=${item.epId}',
|
||||
arguments: {
|
||||
'pgcApi': true,
|
||||
'pic': item.cover,
|
||||
'heroTag': Utils.makeHeroTag(item.cid),
|
||||
'videoType': SearchType.video,
|
||||
if (progress != null) 'progress': int.tryParse(progress)
|
||||
},
|
||||
preventDuplicates: false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.episodes.isNullOrEmpty) {
|
||||
SmartDialog.showToast('资源加载失败');
|
||||
return;
|
||||
}
|
||||
|
||||
episode ??= data.userStatus?.progress?.lastEpId != null
|
||||
? data.episodes!.firstWhereOrNull(
|
||||
(item) => item.epId == data.userStatus?.progress?.lastEpId,
|
||||
) ??
|
||||
data.episodes!.first
|
||||
: data.episodes!.first;
|
||||
toVideoPage(
|
||||
'bvid=${episode.bvid}&cid=${episode.cid}&seasonId=${data.seasonId}&epId=${episode.epId}&type=${data.type}',
|
||||
arguments: {
|
||||
'pic': episode.cover,
|
||||
'heroTag': Utils.makeHeroTag(episode.cid),
|
||||
'videoType': SearchType.media_bangumi,
|
||||
'bangumiItem': data,
|
||||
if (progress != null) 'progress': int.tryParse(progress)
|
||||
},
|
||||
preventDuplicates: false,
|
||||
);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast('$e');
|
||||
debugPrint('$e');
|
||||
}
|
||||
}
|
||||
|
||||
static void toDupNamed(
|
||||
String page, {
|
||||
dynamic arguments,
|
||||
Map<String, String>? parameters,
|
||||
bool off = false,
|
||||
}) {
|
||||
if (off) {
|
||||
Get.offNamed(
|
||||
page,
|
||||
arguments: arguments,
|
||||
parameters: parameters,
|
||||
preventDuplicates: false,
|
||||
);
|
||||
} else {
|
||||
Get.toNamed(
|
||||
page,
|
||||
arguments: arguments,
|
||||
parameters: parameters,
|
||||
preventDuplicates: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
386
lib/utils/request_utils.dart
Normal file
386
lib/utils/request_utils.dart
Normal file
@@ -0,0 +1,386 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/member.dart';
|
||||
import 'package:PiliPlus/http/user.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/later/controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/group_panel.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:html/dom.dart' as dom;
|
||||
import 'package:html/parser.dart' as html_parser;
|
||||
|
||||
class RequestUtils {
|
||||
static Future actionRelationMod({
|
||||
required BuildContext context,
|
||||
required dynamic mid,
|
||||
required bool isFollow,
|
||||
required ValueChanged<int>? callback,
|
||||
Map? followStatus,
|
||||
}) async {
|
||||
if (mid == null) {
|
||||
return;
|
||||
}
|
||||
feedBack();
|
||||
if (!isFollow) {
|
||||
var res = await VideoHttp.relationMod(
|
||||
mid: mid,
|
||||
act: 1,
|
||||
reSrc: 11,
|
||||
);
|
||||
SmartDialog.showToast(res['status'] ? "关注成功" : res['msg']);
|
||||
if (res['status']) {
|
||||
callback?.call(2);
|
||||
}
|
||||
} else {
|
||||
if (followStatus == null) {
|
||||
Map<String, dynamic> result = await UserHttp.hasFollow(mid);
|
||||
if (result['status']) {
|
||||
followStatus = result['data'];
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
bool isSpecialFollowed = followStatus!['special'] == 1;
|
||||
String text = isSpecialFollowed ? '移除特别关注' : '加入特别关注';
|
||||
return AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
final res = await MemberHttp.specialAction(
|
||||
fid: mid,
|
||||
isAdd: !isSpecialFollowed,
|
||||
);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('$text成功');
|
||||
callback?.call(-10);
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
},
|
||||
title: Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
var result = await showModalBottomSheet<List?>(
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
sheetAnimationStyle: AnimationStyle(curve: Curves.ease),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: min(640, min(Get.width, Get.height)),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
minChildSize: 0,
|
||||
maxChildSize: 1,
|
||||
initialChildSize: 0.7,
|
||||
snap: true,
|
||||
expand: false,
|
||||
snapSizes: const [0.7],
|
||||
builder: (BuildContext context,
|
||||
ScrollController scrollController) {
|
||||
return GroupPanel(
|
||||
mid: mid,
|
||||
tags: followStatus!['tag'],
|
||||
scrollController: scrollController,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
followStatus!['tag'] = result;
|
||||
if (result != null) {
|
||||
callback?.call(2);
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
'设置分组',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
var res = await VideoHttp.relationMod(
|
||||
mid: mid,
|
||||
act: 2,
|
||||
reSrc: 11,
|
||||
);
|
||||
SmartDialog.showToast(
|
||||
res['status'] ? "取消关注成功" : res['msg']);
|
||||
if (res['status']) {
|
||||
callback?.call(0);
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
'取消关注',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ReplyInfo replyCast(res) {
|
||||
Map? emote = res['content']['emote'];
|
||||
emote?.forEach((key, value) {
|
||||
value['size'] = value['meta']['size'];
|
||||
});
|
||||
return ReplyInfo.create()
|
||||
..mergeFromProto3Json(
|
||||
res
|
||||
..['id'] = res['rpid']
|
||||
..['member']['name'] = res['member']['uname']
|
||||
..['member']['face'] = res['member']['avatar']
|
||||
..['member']['level'] = res['member']['level_info']['current_level']
|
||||
..['member']['vipStatus'] = res['member']['vip']['vipStatus']
|
||||
..['member']['vipType'] = res['member']['vip']['vipType']
|
||||
..['member']['officialVerifyType'] =
|
||||
res['member']['official_verify']['type']
|
||||
..['content']['emote'] = emote,
|
||||
ignoreUnknownFields: true,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<dynamic> getWwebid(mid) async {
|
||||
try {
|
||||
dynamic response = await Request().get(
|
||||
'${HttpString.spaceBaseUrl}/$mid/dynamic',
|
||||
options: Options(
|
||||
extra: {'account': AnonymousAccount()},
|
||||
),
|
||||
);
|
||||
dom.Document document = html_parser.parse(response.data);
|
||||
dom.Element? scriptElement =
|
||||
document.querySelector('script#__RENDER_DATA__');
|
||||
return jsonDecode(
|
||||
Uri.decodeComponent(scriptElement?.text ?? ''))['access_id'];
|
||||
} catch (e) {
|
||||
debugPrint('failed to get wwebid: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future insertCreatedDyn(result) async {
|
||||
try {
|
||||
dynamic id = result['data']['dyn_id'];
|
||||
if (id != null) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
dynamic res = await DynamicsHttp.dynamicDetail(id: id);
|
||||
if (res['status']) {
|
||||
final ctr = Get.find<DynamicsTabController>(tag: 'all');
|
||||
if (ctr.loadingState.value is Success) {
|
||||
List<DynamicItemModel>? list =
|
||||
(ctr.loadingState.value as Success).response;
|
||||
if (list != null) {
|
||||
list.insert(0, res['data']);
|
||||
ctr.loadingState.refresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ctr.loadingState.value = LoadingState.success([res['data']]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('create dyn $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future checkCreatedDyn({id, dynText, isManual}) async {
|
||||
if (isManual == true || GStorage.enableCreateDynAntifraud) {
|
||||
try {
|
||||
if (id != null) {
|
||||
if (isManual != true) {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
}
|
||||
dynamic res =
|
||||
await DynamicsHttp.dynamicDetail(id: id, clearCookie: true);
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('动态检查结果'),
|
||||
content: SelectableText(
|
||||
'${res['status'] ? '无账号状态下找到了你的动态,动态正常!' : '你的动态被shadow ban(仅自己可见)!'}${dynText != null ? ' \n\n动态内容: $dynText' : ''}'),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('check dyn error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 动态点赞
|
||||
static Future onLikeDynamic(item, VoidCallback callback) async {
|
||||
feedBack();
|
||||
String dynamicId = item.idStr!;
|
||||
// 1 已点赞 2 不喜欢 0 未操作
|
||||
item.modules?.moduleStat ??= ModuleStatModel();
|
||||
item.modules?.moduleStat.like ??= Like();
|
||||
Like like = item.modules.moduleStat.like;
|
||||
int count = like.count == '点赞' ? 0 : int.parse(like.count ?? '0');
|
||||
bool status = like.status ?? false;
|
||||
int up = status ? 2 : 1;
|
||||
var res = await DynamicsHttp.likeDynamic(dynamicId: dynamicId, up: up);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast(!status ? '点赞成功' : '取消赞');
|
||||
if (up == 1) {
|
||||
item.modules.moduleStat.like.count = (count + 1).toString();
|
||||
item.modules.moduleStat.like.status = true;
|
||||
} else {
|
||||
if (count == 1) {
|
||||
item.modules.moduleStat.like.count = '点赞';
|
||||
} else {
|
||||
item.modules.moduleStat.like.count = (count - 1).toString();
|
||||
}
|
||||
item.modules.moduleStat.like.status = false;
|
||||
}
|
||||
callback();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
static void onCopyOrMove<R, T extends MultiSelectData>({
|
||||
required BuildContext context,
|
||||
required bool isCopy,
|
||||
required MultiSelectController<R, T> ctr,
|
||||
required dynamic mediaId,
|
||||
required dynamic mid,
|
||||
}) {
|
||||
VideoHttp.allFavFolders(mid).then((res) {
|
||||
if (context.mounted &&
|
||||
res['status'] &&
|
||||
(res['data'].list as List?)?.isNotEmpty == true) {
|
||||
List<FavFolderItemData> list = res['data'].list;
|
||||
dynamic checkedId;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('${isCopy ? '复制' : '移动'}到'),
|
||||
contentPadding: const EdgeInsets.only(top: 5),
|
||||
content: SingleChildScrollView(
|
||||
child: Builder(
|
||||
builder: (context) => Column(
|
||||
children: List.generate(list.length, (index) {
|
||||
return RadioWidget(
|
||||
padding: const EdgeInsets.only(left: 14),
|
||||
title: list[index].title ?? '',
|
||||
groupValue: checkedId,
|
||||
value: list[index].id,
|
||||
onChanged: (value) {
|
||||
checkedId = value;
|
||||
if (context.mounted) {
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (checkedId != null) {
|
||||
List resources = ((ctr.loadingState.value as Success)
|
||||
.response as List<T>)
|
||||
.where((e) => e.checked == true)
|
||||
.toList();
|
||||
SmartDialog.showLoading();
|
||||
VideoHttp.copyOrMoveFav(
|
||||
isCopy: isCopy,
|
||||
isFav: ctr is! LaterController,
|
||||
srcMediaId: mediaId,
|
||||
tarMediaId: checkedId,
|
||||
resources: resources
|
||||
.map((item) => ctr is LaterController
|
||||
? item.aid
|
||||
: '${item.id}:${item.type}')
|
||||
.toList(),
|
||||
mid: isCopy ? mid : null,
|
||||
).then((res) {
|
||||
if (res['status']) {
|
||||
ctr.handleSelect(false);
|
||||
if (isCopy.not) {
|
||||
List<T> dataList =
|
||||
(ctr.loadingState.value as Success).response;
|
||||
List<T> remainList = dataList
|
||||
.toSet()
|
||||
.difference(resources.toSet())
|
||||
.toList();
|
||||
ctr.loadingState.value =
|
||||
LoadingState.success(remainList);
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast('${isCopy ? '复制' : '移动'}成功');
|
||||
Get.back();
|
||||
} else {
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast('${res['msg']}');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Text('确认'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
SmartDialog.showToast('${res['msg']}');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account_adapter.dart';
|
||||
import 'package:PiliPlus/utils/accounts/cookie_jar_adapter.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account_type_adapter.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:PiliPlus/utils/login_utils.dart';
|
||||
import 'package:PiliPlus/utils/set_int_adapter.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
155
lib/utils/theme_utils.dart
Normal file
155
lib/utils/theme_utils.dart
Normal file
@@ -0,0 +1,155 @@
|
||||
import 'package:PiliPlus/main.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ThemeUtils {
|
||||
static ThemeData getThemeData({
|
||||
required ColorScheme colorScheme,
|
||||
required bool isDynamic,
|
||||
bool isDark = false,
|
||||
required FlexSchemeVariant variant,
|
||||
}) {
|
||||
final appFontWeight =
|
||||
GStorage.appFontWeight.clamp(-1, FontWeight.values.length - 1);
|
||||
final fontWeight =
|
||||
appFontWeight == -1 ? null : FontWeight.values[appFontWeight];
|
||||
late final textStyle = TextStyle(fontWeight: fontWeight);
|
||||
ThemeData themeData = ThemeData(
|
||||
colorScheme: colorScheme,
|
||||
useMaterial3: true,
|
||||
textTheme: fontWeight == null
|
||||
? null
|
||||
: TextTheme(
|
||||
displayLarge: textStyle,
|
||||
displayMedium: textStyle,
|
||||
displaySmall: textStyle,
|
||||
headlineLarge: textStyle,
|
||||
headlineMedium: textStyle,
|
||||
headlineSmall: textStyle,
|
||||
titleLarge: textStyle,
|
||||
titleMedium: textStyle,
|
||||
titleSmall: textStyle,
|
||||
bodyLarge: textStyle,
|
||||
bodyMedium: textStyle,
|
||||
bodySmall: textStyle,
|
||||
labelLarge: textStyle,
|
||||
labelMedium: textStyle,
|
||||
labelSmall: textStyle,
|
||||
),
|
||||
tabBarTheme:
|
||||
fontWeight == null ? null : TabBarTheme(labelStyle: textStyle),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor: isDynamic ? null : colorScheme.surface,
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
color: colorScheme.onSurface,
|
||||
fontWeight: fontWeight,
|
||||
),
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
actionTextColor: colorScheme.primary,
|
||||
backgroundColor: colorScheme.secondaryContainer,
|
||||
closeIconColor: colorScheme.secondary,
|
||||
contentTextStyle: TextStyle(color: colorScheme.secondary),
|
||||
elevation: 20,
|
||||
),
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||
TargetPlatform.android: ZoomPageTransitionsBuilder(
|
||||
allowEnterRouteSnapshotting: false,
|
||||
),
|
||||
},
|
||||
),
|
||||
popupMenuTheme: PopupMenuThemeData(
|
||||
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
elevation: 1,
|
||||
surfaceTintColor: isDynamic
|
||||
? colorScheme.onSurfaceVariant
|
||||
: isDark
|
||||
? colorScheme.onSurfaceVariant
|
||||
: null,
|
||||
shadowColor: Colors.transparent,
|
||||
),
|
||||
// dialogTheme: DialogTheme(
|
||||
// surfaceTintColor: isDark ? colorScheme.onSurfaceVariant : null,
|
||||
// ),
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
refreshBackgroundColor: colorScheme.onSecondary,
|
||||
),
|
||||
dialogTheme: DialogTheme(
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
color: colorScheme.onSurface,
|
||||
fontWeight: fontWeight,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (isDark && GStorage.isPureBlackTheme) {
|
||||
themeData = darkenTheme(themeData);
|
||||
}
|
||||
if (isDark && GStorage.darkVideoPage) {
|
||||
MyApp.darkThemeData = themeData;
|
||||
}
|
||||
return themeData;
|
||||
}
|
||||
|
||||
static darkenTheme(ThemeData themeData) {
|
||||
Color color = themeData.colorScheme.surfaceContainerHighest.darken(0.7);
|
||||
return themeData.copyWith(
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
appBarTheme: themeData.appBarTheme.copyWith(
|
||||
backgroundColor: Colors.black,
|
||||
),
|
||||
cardTheme: themeData.cardTheme.copyWith(
|
||||
color: Colors.black,
|
||||
),
|
||||
dialogTheme: themeData.dialogTheme.copyWith(
|
||||
backgroundColor: color,
|
||||
),
|
||||
bottomSheetTheme:
|
||||
themeData.bottomSheetTheme.copyWith(backgroundColor: color),
|
||||
bottomNavigationBarTheme:
|
||||
themeData.bottomNavigationBarTheme.copyWith(backgroundColor: color),
|
||||
navigationBarTheme:
|
||||
themeData.navigationBarTheme.copyWith(backgroundColor: color),
|
||||
navigationRailTheme:
|
||||
themeData.navigationRailTheme.copyWith(backgroundColor: Colors.black),
|
||||
colorScheme: themeData.colorScheme.copyWith(
|
||||
primary: themeData.colorScheme.primary.darken(0.1),
|
||||
onPrimary: themeData.colorScheme.onPrimary.darken(0.1),
|
||||
primaryContainer: themeData.colorScheme.primaryContainer.darken(0.1),
|
||||
onPrimaryContainer:
|
||||
themeData.colorScheme.onPrimaryContainer.darken(0.1),
|
||||
inversePrimary: themeData.colorScheme.inversePrimary.darken(0.1),
|
||||
secondary: themeData.colorScheme.secondary.darken(0.1),
|
||||
onSecondary: themeData.colorScheme.onSecondary.darken(0.1),
|
||||
secondaryContainer:
|
||||
themeData.colorScheme.secondaryContainer.darken(0.1),
|
||||
onSecondaryContainer:
|
||||
themeData.colorScheme.onSecondaryContainer.darken(0.1),
|
||||
error: themeData.colorScheme.error.darken(0.1),
|
||||
surface: Colors.black,
|
||||
onSurface: themeData.colorScheme.onSurface.darken(0.15),
|
||||
surfaceTint: themeData.colorScheme.surfaceTint.darken(),
|
||||
inverseSurface: themeData.colorScheme.inverseSurface.darken(),
|
||||
onInverseSurface: themeData.colorScheme.onInverseSurface.darken(),
|
||||
surfaceContainer: themeData.colorScheme.surfaceContainer.darken(),
|
||||
surfaceContainerHigh:
|
||||
themeData.colorScheme.surfaceContainerHigh.darken(),
|
||||
surfaceContainerHighest:
|
||||
themeData.colorScheme.surfaceContainerHighest.darken(0.4),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -65,7 +66,7 @@ class UrlUtils {
|
||||
String? bvid = matchRes['BV'];
|
||||
bvid ??= IdUtils.av2bv(aid!);
|
||||
final int cid = await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
Utils.toViewPage(
|
||||
PageUtils.toVideoPage(
|
||||
'bvid=$bvid&cid=$cid',
|
||||
arguments: <String, String?>{
|
||||
'pic': '',
|
||||
@@ -75,7 +76,7 @@ class UrlUtils {
|
||||
);
|
||||
} else {
|
||||
if (redirectUrl.isNotEmpty) {
|
||||
Utils.handleWebview(redirectUrl);
|
||||
PageUtils.handleWebview(redirectUrl);
|
||||
} else {
|
||||
SmartDialog.showToast('matchUrlPush: $pathSegment');
|
||||
}
|
||||
|
||||
1276
lib/utils/utils.dart
1276
lib/utils/utils.dart
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user