feat: repost dynamic

This commit is contained in:
bggRGjQaUbCoE
2024-09-30 12:55:22 +08:00
parent 161d3244ba
commit 6077e51a4a
6 changed files with 336 additions and 17 deletions

View File

@@ -619,4 +619,8 @@ class Api {
static const String removeSysMsg = '/x/sys-msg/del_notify_list';
static const String setTop = '/session_svr/v1/session_svr/set_top';
static const String createDynamic = '/x/dynamic/feed/create/dyn';
static const String removeDynamic = '/dynamic_svr/v1/dynamic_svr/rm_dynamic';
}

View File

@@ -143,6 +143,49 @@ class MsgHttp {
}
}
static Future createDynamic({
dynamic mid,
dynamic dynIdStr,
dynamic rawText,
}) async {
String csrf = await Request.getCsrf();
var res = await Request().post(
Api.createDynamic,
queryParameters: {
'platform': 'web',
'csrf': csrf,
'x-bili-device-req-json[platform]': 'web',
'x-bili-device-req-json[device]': 'pc',
'x-bili-web-req-json[spm_id]': 333.999,
},
data: {
"dyn_req": {
"content": {
"contents": [
{"raw_text": rawText, "type": 1, "biz_id": ""}
]
},
"scene": 4,
"attach_card": null,
"upload_id":
"${mid}_${DateTime.now().millisecondsSinceEpoch ~/ 1000}_${Random().nextInt(9000) + 1000}",
"meta": {
"app_meta": {"from": "create.dynamic.web", "mobi_app": "web"}
}
},
"web_repost_src": {"dyn_id_str": dynIdStr}
},
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
}
static Future removeMsg(
dynamic talkerId,
) async {

View File

@@ -556,11 +556,9 @@ class DynamicOpusModel {
String? title;
DynamicOpusModel.fromJson(Map<String, dynamic> json) {
jumpUrl = json['jump_url'];
pics = json['pics'] != null
? json['pics']
.map<OpusPicsModel>((e) => OpusPicsModel.fromJson(e))
.toList()
: [];
pics = json['pics']
?.map<OpusPicsModel>((e) => OpusPicsModel.fromJson(e))
.toList();
summary =
json['summary'] != null ? SummaryModel.fromJson(json['summary']) : null;
title = json['title'];

View File

@@ -1,4 +1,8 @@
// 操作栏
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/msg.dart';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -81,7 +85,12 @@ class _ActionPanelState extends State<ActionPanel> {
flex: 1,
child: TextButton.icon(
onPressed: () {
SmartDialog.showToast('暂不支持');
showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (_) => RepostPanel(item: widget.item),
);
},
icon: const Icon(
FontAwesomeIcons.shareFromSquare,
@@ -122,7 +131,7 @@ class _ActionPanelState extends State<ActionPanel> {
: FontAwesomeIcons.thumbsUp,
size: 16,
color: stat.like!.status! ? primary : color,
semanticLabel: stat.like!.status! ? "已赞": "点赞",
semanticLabel: stat.like!.status! ? "已赞" : "点赞",
),
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
@@ -147,3 +156,272 @@ class _ActionPanelState extends State<ActionPanel> {
);
}
}
class RepostPanel extends StatefulWidget {
const RepostPanel({super.key, required this.item});
final dynamic item;
@override
State<RepostPanel> createState() => _RepostPanelState();
}
class _RepostPanelState extends State<RepostPanel> {
bool _isMax = false;
final _ctr = TextEditingController();
final _focusNode = FocusNode();
Future _onRepost() async {
dynamic result = await MsgHttp.createDynamic(
mid: GStorage.userInfo.get('userInfoCache').mid,
dynIdStr: widget.item.idStr,
rawText: _ctr.text,
);
if (result['status']) {
Get.back();
SmartDialog.showToast('转发成功');
} else {
SmartDialog.showToast(result['msg']);
}
}
@override
void dispose() {
_ctr.dispose();
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
dynamic pic = (widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
?.archive
?.cover ??
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
?.pgc
?.cover ??
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
?.opus
?.pics
?.firstOrNull
?.url;
return AnimatedSize(
alignment: Alignment.topCenter,
curve: Curves.linearToEaseOut,
duration: const Duration(milliseconds: 500),
child: Column(
mainAxisSize: _isMax ? MainAxisSize.max : MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: _isMax ? 16 : 10),
if (!_isMax)
Row(
children: [
const SizedBox(width: 16),
const Text(
'转发动态',
style: TextStyle(fontWeight: FontWeight.bold),
),
const Spacer(),
TextButton(
onPressed: _onRepost,
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
visualDensity: const VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text('立即转发'),
),
const SizedBox(width: 16),
],
),
if (_isMax)
Row(
children: [
const SizedBox(width: 16),
SizedBox(
width: 34,
height: 34,
child: IconButton(
tooltip: '返回',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor:
WidgetStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.secondaryContainer;
}),
),
onPressed: Get.back,
icon: Icon(
Icons.arrow_back_outlined,
size: 18,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
),
const Spacer(),
const Text(
'转发动态',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
const Spacer(),
FilledButton.tonal(
onPressed: _onRepost,
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
visualDensity: const VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: const Text('转发'),
),
const SizedBox(width: 16),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
width: double.infinity,
decoration: !_isMax
? BoxDecoration(
border: Border(
left: BorderSide(
width: 2,
color: Theme.of(context).colorScheme.primary,
),
),
)
: null,
child: !_isMax
? GestureDetector(
onTap: () async {
setState(() => _isMax = true);
await Future.delayed(const Duration(milliseconds: 500));
if (mounted && context.mounted) {
_focusNode.requestFocus();
}
},
child: Text(
'说点什么吧',
style: TextStyle(
height: 1.75,
fontSize: 15,
color: Theme.of(context).colorScheme.outline,
),
),
)
: TextField(
controller: _ctr,
minLines: 4,
maxLines: 8,
focusNode: _focusNode,
decoration: const InputDecoration(
hintText: '说点什么吧',
border: OutlineInputBorder(
borderSide: BorderSide.none,
gapPadding: 0,
),
contentPadding: EdgeInsets.symmetric(vertical: 10),
),
),
),
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
if (pic != null) ...[
NetworkImgLayer(
radius: 8,
width: 40,
height: 40,
src: pic,
),
const SizedBox(width: 10),
],
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'@${(widget.item as DynamicItemModel?)?.modules?.moduleAuthor?.name}',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontSize: 13,
),
),
Text(
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
?.opus
?.summary
?.text ??
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.desc
?.text ??
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
?.archive
?.title ??
(widget.item as DynamicItemModel?)
?.modules
?.moduleDynamic
?.major
?.pgc
?.title ??
'',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
const SizedBox(height: 10),
if (!_isMax)
ListTile(
dense: true,
onTap: Get.back,
title: Center(
child: Text(
'取消',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
),
SizedBox(height: 10 + MediaQuery.of(context).padding.bottom),
],
),
);
}
}

View File

@@ -117,20 +117,16 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
child: IconButton(
tooltip: '返回',
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor: MaterialStateProperty.resolveWith(
(Set<MaterialState> states) {
return Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.6);
padding: WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor: WidgetStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.secondaryContainer;
}),
),
onPressed: () => Get.back(),
onPressed: Get.back,
icon: Icon(
Icons.arrow_back_outlined,
size: 18,
color: Theme.of(context).colorScheme.onPrimaryContainer,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
),