diff --git a/lib/http/api.dart b/lib/http/api.dart index bbafefe76..da32ce0a8 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -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'; } diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 9599c81c8..0dac45bc2 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -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 { diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index d532881e4..8cc6e284c 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -556,11 +556,9 @@ class DynamicOpusModel { String? title; DynamicOpusModel.fromJson(Map json) { jumpUrl = json['jump_url']; - pics = json['pics'] != null - ? json['pics'] - .map((e) => OpusPicsModel.fromJson(e)) - .toList() - : []; + pics = json['pics'] + ?.map((e) => OpusPicsModel.fromJson(e)) + .toList(); summary = json['summary'] != null ? SummaryModel.fromJson(json['summary']) : null; title = json['title']; diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index bb937bf48..a89338e75 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -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 { 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 { : 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 { ); } } + +class RepostPanel extends StatefulWidget { + const RepostPanel({super.key, required this.item}); + + final dynamic item; + @override + State createState() => _RepostPanelState(); +} + +class _RepostPanelState extends State { + 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), + ], + ), + ); + } +} diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index 2ad325898..0db7a713f 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -117,20 +117,16 @@ class _WhisperDetailPageState extends State child: IconButton( tooltip: '返回', style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - backgroundColor: MaterialStateProperty.resolveWith( - (Set 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, ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 3bf6aa121..a90ef5ecf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.22+114514 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. # To automatically upgrade your package dependencies to the latest versions