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

@@ -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,