feat: pm: share video

Closes #693

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-25 11:52:12 +08:00
parent 738cd61825
commit afe812e2be
33 changed files with 7972 additions and 111 deletions

View File

@@ -265,6 +265,7 @@ class BangumiIntroController
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
dense: true,
title: const Text(
'复制链接',
style: TextStyle(fontSize: 14),
@@ -275,6 +276,7 @@ class BangumiIntroController
},
),
ListTile(
dense: true,
title: const Text(
'其它app打开',
style: TextStyle(fontSize: 14),
@@ -285,6 +287,7 @@ class BangumiIntroController
},
),
ListTile(
dense: true,
title: const Text(
'分享视频',
style: TextStyle(fontSize: 14),
@@ -295,6 +298,7 @@ class BangumiIntroController
},
),
ListTile(
dense: true,
title: const Text(
'分享至动态',
style: TextStyle(fontSize: 14),
@@ -334,6 +338,29 @@ class BangumiIntroController
);
},
),
ListTile(
dense: true,
title: const Text(
'分享至消息',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
try {
EpisodeItem item = bangumiItem!.episodes!
.firstWhere((item) => item.epId == epId);
PageUtils.pmShareVideo(
id: epId!,
source: 16,
cover: item.cover!,
title: '${bangumiItem!.title!} ${item.showTitle}',
url: item.shareUrl,
);
} catch (e) {
SmartDialog.showToast(e.toString());
}
},
),
],
),
);

View File

@@ -4,28 +4,17 @@ import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/fans/result.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/utils/storage.dart';
class FansController
extends CommonListController<FansDataModel, FansItemModel> {
FansController(this.mid);
int ps = 20;
int total = 0;
late int? mid;
late String? name;
dynamic userInfo;
late bool isOwner = false;
int mid;
@override
void onInit() {
super.onInit();
userInfo = GStorage.userInfo.get('userInfoCache');
mid = Get.parameters['mid'] != null
? int.parse(Get.parameters['mid']!)
: userInfo?.mid;
isOwner = mid == userInfo?.mid;
name = Get.parameters['name'] ?? userInfo?.uname;
queryData();
}

View File

@@ -4,6 +4,8 @@ import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/fans/result.dart';
import 'package:PiliPlus/pages/video/detail/share/view.dart' show UserModel;
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -13,31 +15,44 @@ import '../../utils/grid.dart';
import 'controller.dart';
class FansPage extends StatefulWidget {
const FansPage({super.key});
const FansPage({
super.key,
this.mid,
this.onSelect,
});
final int? mid;
final ValueChanged<UserModel>? onSelect;
@override
State<FansPage> createState() => _FansPageState();
}
class _FansPageState extends State<FansPage> {
late String mid;
late int mid;
String? name;
late bool isOwner;
late FansController _fansController;
@override
void initState() {
super.initState();
mid = Get.parameters['mid']!;
_fansController = Get.put(FansController(), tag: Utils.makeHeroTag(mid));
final userInfo = GStorage.userInfo.get('userInfoCache');
mid = widget.mid ??
(Get.parameters['mid'] != null
? int.parse(Get.parameters['mid']!)
: userInfo?.mid);
isOwner = mid == userInfo?.mid;
name = Get.parameters['name'] ?? userInfo?.uname;
_fansController = Get.put(FansController(mid), tag: Utils.makeHeroTag(mid));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
_fansController.isOwner ? '我的粉丝' : '${_fansController.name}的粉丝',
),
),
appBar: widget.onSelect != null
? null
: AppBar(title: Text(isOwner ? '我的粉丝' : '$name的粉丝')),
body: SafeArea(
bottom: false,
child: refreshIndicator(
@@ -86,22 +101,33 @@ class _FansPageState extends State<FansPage> {
String heroTag = Utils.makeHeroTag(item.mid);
return ListTile(
onTap: () {
if (widget.onSelect != null) {
widget.onSelect!(UserModel(
mid: item.mid!,
name: item.uname!,
avatar: item.face!,
));
return;
}
Get.toNamed(
'/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag},
);
},
onLongPress: _fansController.isOwner
? () {
showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () {
_fansController.onRemoveFan(index, item.mid!);
},
);
}
: null,
onLongPress: widget.onSelect != null
? null
: isOwner
? () {
showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () {
_fansController.onRemoveFan(
index, item.mid!);
},
);
}
: null,
leading: Hero(
tag: heroTag,
child: NetworkImgLayer(

View File

@@ -16,7 +16,7 @@ extension OrderTypeExt on OrderType {
class FollowChildController
extends CommonListController<FollowDataModel, FollowItemModel> {
FollowChildController(this.controller, this.mid, this.tagid);
final FollowController controller;
final FollowController? controller;
final int? tagid;
final int mid;
@@ -35,15 +35,17 @@ class FollowChildController
@override
bool customHandleResponse(bool isRefresh, Success<FollowDataModel> response) {
try {
if (controller.isOwner &&
tagid == null &&
isRefresh &&
controller.followState.value is Success) {
controller.tabs[0].count = response.response.total;
controller.tabs.refresh();
}
} catch (_) {}
if (controller != null) {
try {
if (controller!.isOwner &&
tagid == null &&
isRefresh &&
controller!.followState.value is Success) {
controller!.tabs[0].count = response.response.total;
controller!.tabs.refresh();
}
} catch (_) {}
}
return false;
}

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/models/follow/result.dart';
import 'package:PiliPlus/pages/follow/child_controller.dart';
import 'package:PiliPlus/pages/follow/controller.dart';
import 'package:PiliPlus/pages/follow/widgets/follow_item.dart';
import 'package:PiliPlus/pages/video/detail/share/view.dart' show UserModel;
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -13,14 +14,16 @@ import 'package:get/get.dart';
class FollowChildPage extends StatefulWidget {
const FollowChildPage({
super.key,
required this.controller,
this.controller,
required this.mid,
this.tagid,
this.onSelect,
});
final FollowController controller;
final FollowController? controller;
final int mid;
final int? tagid;
final ValueChanged<UserModel>? onSelect;
@override
State<FollowChildPage> createState() => _FollowChildPageState();
@@ -35,7 +38,8 @@ class _FollowChildPageState extends State<FollowChildPage>
@override
Widget build(BuildContext context) {
super.build(context);
if (widget.controller.isOwner && widget.tagid == null) {
if (widget.onSelect != null ||
(widget.controller?.isOwner == true && widget.tagid == null)) {
return Scaffold(
backgroundColor: Colors.transparent,
body: _child,
@@ -90,7 +94,8 @@ class _FollowChildPageState extends State<FollowChildPage>
}
return FollowItem(
item: loadingState.response![index],
isOwner: widget.controller.isOwner,
isOwner: widget.controller?.isOwner,
onSelect: widget.onSelect,
callback: (attr) {
List<FollowItemModel> list =
(_followController.loadingState.value as Success)
@@ -113,5 +118,6 @@ class _FollowChildPageState extends State<FollowChildPage>
}
@override
bool get wantKeepAlive => widget.controller.tabController != null;
bool get wantKeepAlive =>
widget.onSelect != null || widget.controller?.tabController != null;
}

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/pages/video/detail/share/view.dart' show UserModel;
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -10,12 +11,14 @@ class FollowItem extends StatelessWidget {
final FollowItemModel item;
final bool? isOwner;
final ValueChanged? callback;
final ValueChanged<UserModel>? onSelect;
const FollowItem({
super.key,
required this.item,
this.callback,
this.isOwner,
this.onSelect,
});
@override
@@ -23,9 +26,19 @@ class FollowItem extends StatelessWidget {
String heroTag = Utils.makeHeroTag(item.mid);
return ListTile(
onTap: () {
feedBack();
Get.toNamed('/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag});
if (onSelect != null) {
onSelect!.call(
UserModel(
mid: item.mid!,
name: item.uname!,
avatar: item.face!,
),
);
} else {
feedBack();
Get.toNamed('/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag});
}
},
leading: Stack(
clipBehavior: Clip.none,

View File

@@ -2,11 +2,11 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/follow/result.dart';
import 'package:PiliPlus/pages/common/common_search_controller.dart';
import 'package:get/get.dart';
class FollowSearchController
extends CommonSearchController<FollowDataModel, FollowItemModel> {
dynamic mid = Get.arguments['mid'];
FollowSearchController(this.mid);
final int mid;
@override
Future<LoadingState<FollowDataModel>> customGetData() =>

View File

@@ -8,7 +8,9 @@ import 'package:get/get.dart';
import 'controller.dart';
class FollowSearchPage extends CommonSearchPage {
const FollowSearchPage({super.key});
const FollowSearchPage({super.key, this.mid});
final int? mid;
@override
State<FollowSearchPage> createState() => _FollowSearchPageState();
@@ -17,8 +19,8 @@ class FollowSearchPage extends CommonSearchPage {
class _FollowSearchPageState extends CommonSearchPageState<FollowSearchPage,
FollowDataModel, FollowItemModel> {
@override
final FollowSearchController controller = Get.put(
FollowSearchController(),
late final FollowSearchController controller = Get.put(
FollowSearchController(widget.mid ?? Get.arguments['mid']),
tag: Utils.generateRandomString(8),
);
@@ -34,7 +36,14 @@ class _FollowSearchPageState extends CommonSearchPageState<FollowSearchPage,
if (index == list.length - 1) {
controller.onLoadMore();
}
return FollowItem(item: list[index]);
return FollowItem(
item: list[index],
onSelect: widget.mid != null
? (userModel) {
Get.back(result: userModel);
}
: null,
);
}),
),
);

View File

@@ -68,7 +68,6 @@ class SearchArticleController
useSafeArea: true,
isScrollControlled: true,
clipBehavior: Clip.hardEdge,
backgroundColor: Theme.of(context).colorScheme.surface,
constraints: BoxConstraints(
maxWidth: min(640, min(Get.width, Get.height)),
),

View File

@@ -38,7 +38,6 @@ class SearchUserController
useSafeArea: true,
isScrollControlled: true,
clipBehavior: Clip.hardEdge,
backgroundColor: Theme.of(context).colorScheme.surface,
constraints: BoxConstraints(
maxWidth: min(640, min(Get.width, Get.height)),
),

View File

@@ -146,7 +146,6 @@ class SearchVideoController
useSafeArea: true,
isScrollControlled: true,
clipBehavior: Clip.hardEdge,
backgroundColor: Theme.of(context).colorScheme.surface,
constraints: BoxConstraints(
maxWidth: min(640, min(Get.width, Get.height)),
),

View File

@@ -135,7 +135,6 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
useSafeArea: true,
isScrollControlled: true,
clipBehavior: Clip.hardEdge,
backgroundColor: Theme.of(context).colorScheme.surface,
constraints: BoxConstraints(
maxWidth: min(640, min(Get.width, Get.height)),
),

View File

@@ -0,0 +1,70 @@
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/pages/fan/index.dart';
import 'package:PiliPlus/pages/follow/child_view.dart';
import 'package:PiliPlus/pages/follow_search/view.dart';
import 'package:PiliPlus/pages/video/detail/share/view.dart' show UserModel;
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class ContactPage extends StatefulWidget {
const ContactPage({super.key});
@override
State<ContactPage> createState() => _ContactPageState();
}
class _ContactPageState extends State<ContactPage>
with SingleTickerProviderStateMixin {
final mid = Accounts.main.mid;
late final _controller = TabController(length: 2, vsync: this);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void onSelect(UserModel userModel) {
Get.back(result: userModel);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('通讯录'),
bottom: TabBar(
controller: _controller,
tabs: const [
Tab(text: '我的关注'),
Tab(text: '我的粉丝'),
],
),
actions: [
IconButton(
onPressed: () async {
UserModel? userModel = await Get.dialog(
FollowSearchPage(mid: mid),
useSafeArea: false,
transitionDuration: const Duration(milliseconds: 120),
);
if (userModel != null) {
Get.back(result: userModel);
}
},
icon: const Icon(Icons.search),
),
const SizedBox(width: 16),
],
),
body: tabBarView(
controller: _controller,
children: [
FollowChildPage(mid: mid, onSelect: onSelect),
FansPage(mid: mid, onSelect: onSelect),
],
),
);
}
}

View File

@@ -444,6 +444,7 @@ class VideoIntroController extends GetxController {
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
dense: true,
title: const Text(
'复制链接',
style: TextStyle(fontSize: 14),
@@ -454,6 +455,7 @@ class VideoIntroController extends GetxController {
},
),
ListTile(
dense: true,
title: const Text(
'其它app打开',
style: TextStyle(fontSize: 14),
@@ -464,6 +466,7 @@ class VideoIntroController extends GetxController {
},
),
ListTile(
dense: true,
title: const Text(
'分享视频',
style: TextStyle(fontSize: 14),
@@ -476,6 +479,7 @@ class VideoIntroController extends GetxController {
},
),
ListTile(
dense: true,
title: const Text(
'分享至动态',
style: TextStyle(fontSize: 14),
@@ -496,6 +500,28 @@ class VideoIntroController extends GetxController {
);
},
),
ListTile(
dense: true,
title: const Text(
'分享至消息',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
try {
PageUtils.pmShareVideo(
author: videoDetail.value.owner!.name!,
id: videoDetail.value.aid!,
source: 5,
cover: videoDetail.value.pic!,
title: videoDetail.value.title!,
bvid: videoDetail.value.bvid!,
);
} catch (e) {
SmartDialog.showToast(e.toString());
}
},
),
],
),
);

View File

@@ -0,0 +1,275 @@
import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPlus/pages/video/detail/contact/view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class UserModel {
const UserModel({
required this.mid,
required this.name,
required this.avatar,
});
final int mid;
final String name;
final String avatar;
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other is UserModel) {
return mid == other.mid;
}
return false;
}
@override
int get hashCode => mid.hashCode;
}
class ShareVideoPanel extends StatefulWidget {
const ShareVideoPanel({
super.key,
this.author,
required this.id,
required this.source,
required this.cover,
required this.title,
this.bvid,
this.url,
this.authorId,
this.sourceDesc,
this.userList,
});
final String? author;
final int id;
final int source;
final String cover;
final String title;
final String? bvid;
final String? url;
final int? authorId;
final String? sourceDesc;
final List<UserModel>? userList;
@override
State<ShareVideoPanel> createState() => _ShareVideoPanelState();
}
class _ShareVideoPanelState extends State<ShareVideoPanel> {
int _selectedIndex = -1;
final List<UserModel> _userList = <UserModel>[];
final ScrollController _scrollController = ScrollController();
final FocusNode _focusNode = FocusNode();
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_focusNode.dispose();
_controller.dispose();
_scrollController.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
if (widget.userList?.isNotEmpty == true) {
_userList.addAll(widget.userList!);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12) +
MediaQuery.paddingOf(context) +
MediaQuery.of(context).viewInsets,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('分享给'),
iconButton(
size: 32,
iconSize: 18,
tooltip: '关闭',
context: context,
icon: Icons.clear,
onPressed: Get.back,
),
],
),
const SizedBox(height: 5),
Row(
children: [
Expanded(
child: SelfSizedHorizontalList(
gapSize: 10,
itemCount: _userList.length,
controller: _scrollController,
childBuilder: (index) {
return GestureDetector(
onTap: () {
_selectedIndex = index;
setState(() {});
},
behavior: HitTestBehavior.opaque,
child: SizedBox(
width: 65,
child: Column(
children: [
Container(
decoration: index == _selectedIndex
? BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 1.5,
color: Theme.of(context)
.colorScheme
.primary,
),
)
: null,
width: 50,
height: 50,
alignment: Alignment.center,
child: NetworkImgLayer(
width: 40,
height: 40,
src: _userList[index].avatar,
type: 'avatar',
),
),
const SizedBox(height: 2),
Text(
_userList[index].name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 12),
),
],
),
),
);
},
),
),
GestureDetector(
onTap: () async {
_focusNode.unfocus();
UserModel? userModel = await Get.dialog(
const ContactPage(),
useSafeArea: false,
transitionDuration: const Duration(milliseconds: 120),
);
if (userModel != null) {
_userList.remove(userModel);
_userList.insert(0, userModel);
_selectedIndex = 0;
_scrollController.jumpToTop();
setState(() {});
}
},
behavior: HitTestBehavior.opaque,
child: SizedBox(
width: 65,
child: Column(
children: [
Container(
width: 50,
height: 50,
alignment: Alignment.center,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context)
.colorScheme
.secondaryContainer,
),
child: Icon(
Icons.person_add_alt,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
),
const SizedBox(height: 2),
const Text('更多', style: TextStyle(fontSize: 12)),
],
),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: TextField(
controller: _controller,
focusNode: _focusNode,
decoration: InputDecoration(
hintText: '说说你的想法吧...',
hintStyle: const TextStyle(fontSize: 14),
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(25),
),
filled: true,
isDense: true,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
fillColor: Theme.of(context).colorScheme.onInverseSurface,
),
),
),
const SizedBox(width: 12),
FilledButton.tonal(
onPressed: () {
if (_selectedIndex == -1) {
SmartDialog.showToast('请选择分享的用户');
return;
}
RequestUtils.pmShareVideo(
receiverId: _userList[_selectedIndex].mid,
author: widget.author,
id: widget.id,
source: widget.source,
cover: widget.cover,
title: widget.title,
bvid: widget.bvid,
url: widget.url,
authorId: widget.authorId,
sourceDesc: widget.sourceDesc,
message: _controller.text,
);
},
style: FilledButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity:
const VisualDensity(horizontal: -2, vertical: -1),
),
child: const Text('发送'),
),
],
),
],
),
);
}
}

View File

@@ -2288,7 +2288,6 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
void onShowMemberPage(mid) {
videoDetailController.childKey.currentState?.showBottomSheet(
shape: const RoundedRectangleBorder(),
backgroundColor: themeData.colorScheme.surface,
(context) {
return HorizontalMemberPage(
mid: mid,

View File

@@ -71,7 +71,7 @@ class WhisperDetailController
Future sendMsg({
required String message,
dynamic picMsg,
Map? picMsg,
required VoidCallback onClearText,
int? msgType,
int? index,
@@ -95,8 +95,11 @@ class WhisperDetailController
var result = await MsgHttp.sendMsg(
senderUid: ownerMid,
receiverId: int.parse(mid!),
content:
msgType == 5 ? message : jsonEncode(picMsg ?? {"content": message}),
content: msgType == 5
? message
: jsonEncode(
picMsg ?? {"content": message},
),
msgType: msgType ?? (picMsg != null ? 2 : 1),
);
SmartDialog.dismiss();

View File

@@ -273,7 +273,7 @@ class _WhisperDetailPageState
);
if (pickedFile != null) {
SmartDialog.showLoading(msg: '正在上传图片');
dynamic result = await MsgHttp.uploadBfs(
final result = await MsgHttp.uploadBfs(
path: pickedFile.path,
biz: 'im',
);
@@ -281,8 +281,8 @@ class _WhisperDetailPageState
String mimeType = lookupMimeType(pickedFile.path)
?.split('/')
.getOrNull(1) ??
'png';
dynamic picMsg = {
'jpg';
Map picMsg = {
'url': result['data']['image_url'],
'height': result['data']['image_height'],
'width': result['data']['image_width'],

View File

@@ -181,27 +181,38 @@ class ChatItem extends StatelessWidget {
children: [
GestureDetector(
onTap: () async {
dynamic aid = content['id'];
if (aid is String) {
aid = int.tryParse(aid);
}
dynamic bvid = content["bvid"];
if (aid == null && bvid == null) {
SmartDialog.showToast('null');
if (content['source'] == 16) {
PageUtils.viewBangumi(epId: content['id']);
return;
}
bvid ??= IdUtils.av2bv(aid);
SmartDialog.showLoading();
final int cid = await SearchHttp.ab2c(bvid: bvid);
SmartDialog.dismiss<dynamic>().then(
(e) => PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: <String, String?>{
'pic': content['thumb'],
'heroTag': Utils.makeHeroTag(bvid),
},
),
);
if (content['source'] == 5) {
dynamic aid = content['id'];
if (aid is String) {
aid = int.tryParse(aid);
}
dynamic bvid = content["bvid"];
if (aid == null && bvid == null) {
SmartDialog.showToast('null');
return;
}
bvid ??= IdUtils.av2bv(aid);
SmartDialog.showLoading();
final int cid = await SearchHttp.ab2c(bvid: bvid);
SmartDialog.dismiss<dynamic>().then(
(e) => PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: <String, String?>{
'pic': content['thumb'],
'heroTag': Utils.makeHeroTag(bvid),
},
),
);
return;
}
SmartDialog.showToast(
'unsupported source type: ${content['source']}');
},
child: NetworkImgLayer(
width: 220,