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 removeSysMsg = '/x/sys-msg/del_notify_list';
static const String setTop = '/session_svr/v1/session_svr/set_top'; 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( static Future removeMsg(
dynamic talkerId, dynamic talkerId,
) async { ) async {

View File

@@ -556,11 +556,9 @@ class DynamicOpusModel {
String? title; String? title;
DynamicOpusModel.fromJson(Map<String, dynamic> json) { DynamicOpusModel.fromJson(Map<String, dynamic> json) {
jumpUrl = json['jump_url']; jumpUrl = json['jump_url'];
pics = json['pics'] != null pics = json['pics']
? json['pics'] ?.map<OpusPicsModel>((e) => OpusPicsModel.fromJson(e))
.map<OpusPicsModel>((e) => OpusPicsModel.fromJson(e)) .toList();
.toList()
: [];
summary = summary =
json['summary'] != null ? SummaryModel.fromJson(json['summary']) : null; json['summary'] != null ? SummaryModel.fromJson(json['summary']) : null;
title = json['title']; 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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -81,7 +85,12 @@ class _ActionPanelState extends State<ActionPanel> {
flex: 1, flex: 1,
child: TextButton.icon( child: TextButton.icon(
onPressed: () { onPressed: () {
SmartDialog.showToast('暂不支持'); showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (_) => RepostPanel(item: widget.item),
);
}, },
icon: const Icon( icon: const Icon(
FontAwesomeIcons.shareFromSquare, FontAwesomeIcons.shareFromSquare,
@@ -122,7 +131,7 @@ class _ActionPanelState extends State<ActionPanel> {
: FontAwesomeIcons.thumbsUp, : FontAwesomeIcons.thumbsUp,
size: 16, size: 16,
color: stat.like!.status! ? primary : color, color: stat.like!.status! ? primary : color,
semanticLabel: stat.like!.status! ? "已赞": "点赞", semanticLabel: stat.like!.status! ? "已赞" : "点赞",
), ),
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), 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( child: IconButton(
tooltip: '返回', tooltip: '返回',
style: ButtonStyle( style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero), padding: WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor: MaterialStateProperty.resolveWith( backgroundColor: WidgetStateProperty.resolveWith((states) {
(Set<MaterialState> states) { return Theme.of(context).colorScheme.secondaryContainer;
return Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.6);
}), }),
), ),
onPressed: () => Get.back(), onPressed: Get.back,
icon: Icon( icon: Icon(
Icons.arrow_back_outlined, Icons.arrow_back_outlined,
size: 18, size: 18,
color: Theme.of(context).colorScheme.onPrimaryContainer, color: Theme.of(context).colorScheme.onSecondaryContainer,
), ),
), ),
), ),

View File

@@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.22+114514 version: 1.0.22+114514
environment: environment:
sdk: ">=2.19.6 <3.0.0" sdk: ">=3.0.0 <4.0.0"
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions