diff --git a/lib/http/api.dart b/lib/http/api.dart index b6975c4b..e2e973f2 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -511,4 +511,10 @@ class Api { /// 取消订阅 static const String cancelSub = '/x/v3/fav/season/unfav'; + + /// 动态转发 + static const String dynamicForwardUrl = '/x/dynamic/feed/create/submit_check'; + + /// 创建动态 + static const String dynamicCreate = '/x/dynamic/feed/create/dyn'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index d62de12f..f0740d36 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -1,3 +1,4 @@ +import 'dart:math'; import '../models/dynamics/result.dart'; import '../models/dynamics/up.dart'; import 'index.dart'; @@ -117,4 +118,82 @@ class DynamicsHttp { }; } } + + static Future dynamicForward() async { + var res = await Request().post( + Api.dynamicForwardUrl, + queryParameters: { + 'csrf': await Request.getCsrf(), + 'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'}, + 'x-bili-web-req-json': {'spm_id': '333.999'}, + }, + data: { + 'attach_card': null, + 'scene': 4, + 'content': { + 'conetents': [ + {'raw_text': "2", 'type': 1, 'biz_id': ""} + ] + } + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } + + static Future dynamicCreate({ + required String dynIdStr, + required int mid, + String? rawText, + }) async { + DateTime now = DateTime.now(); + int timestamp = now.millisecondsSinceEpoch ~/ 1000; + Random random = Random(); + int randomNumber = random.nextInt(9000) + 1000; + String uploadId = + mid.toString() + timestamp.toString() + randomNumber.toString(); + var res = await Request().post(Api.dynamicCreate, queryParameters: { + 'platform': 'web', + 'csrf': await Request.getCsrf(), + 'x-bili-device-req-json': {'platform': 'web', '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': uploadId, + '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, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index 0ca09b8c..0e67b0a7 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -3,10 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/http/dynamics.dart'; import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:status_bar_control/status_bar_control.dart'; +import 'rich_node_panel.dart'; class ActionPanel extends StatefulWidget { const ActionPanel({ @@ -20,21 +23,37 @@ class ActionPanel extends StatefulWidget { State createState() => _ActionPanelState(); } -class _ActionPanelState extends State { +class _ActionPanelState extends State + with TickerProviderStateMixin { final DynamicsController _dynamicsController = Get.put(DynamicsController()); late ModuleStatModel stat; bool isProcessing = false; + RxDouble height = (310.0).obs; + RxBool isExpand = false.obs; + late double statusHeight; + TextEditingController _inputController = TextEditingController(); + FocusNode myFocusNode = FocusNode(); + String _inputText = ''; + void Function()? handleState(Future Function() action) { - return isProcessing ? null : () async { - setState(() => isProcessing = true); - await action(); - setState(() => isProcessing = false); - }; + return isProcessing + ? null + : () async { + isProcessing = true; + await action(); + isProcessing = false; + }; } + @override void initState() { super.initState(); stat = widget.item!.modules.moduleStat; + onInit(); + } + + onInit() async { + statusHeight = await StatusBarControl.getHeight; } // 动态点赞 @@ -67,6 +86,214 @@ class _ActionPanelState extends State { } } + // 转发动态预览 + Widget dynamicPreview() { + return Padding( + padding: const EdgeInsets.fromLTRB(12, 0, 14, 12), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.4), + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '@${widget.item.modules.moduleAuthor.name}', + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + const SizedBox(height: 6), + Row( + children: [ + NetworkImgLayer( + src: widget.item.modules.moduleAuthor.face, + width: 34, + height: 34, + type: 'emote', + ), + const SizedBox(width: 10), + Expanded( + child: Text.rich( + style: const TextStyle(height: 0), + richNode(widget.item, context), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + // Text(data) + ], + ) + ], + )), + ), + ); + } + + // 动态转发 + void forwardHandler() async { + showModalBottomSheet( + context: context, + enableDrag: false, + useRootNavigator: true, + isScrollControlled: true, + builder: (context) { + return Obx( + () => AnimatedContainer( + duration: Durations.medium1, + onEnd: () async { + if (isExpand.value) { + await Future.delayed(const Duration(milliseconds: 120)); + myFocusNode.requestFocus(); + } + }, + height: height.value, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedContainer( + duration: Durations.medium1, + height: isExpand.value ? statusHeight : 0, + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 10, 12, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AnimatedSwitcher( + duration: Durations.medium1, + transitionBuilder: + (Widget child, Animation animation) { + return ScaleTransition( + scale: animation, child: child); + }, + child: isExpand.value + ? IconButton( + onPressed: () => togglePanelState(false), + icon: const Icon(Icons.close)) + : const Text( + '转发动态', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + AnimatedSwitcher( + duration: Durations.medium1, + transitionBuilder: + (Widget child, Animation animation) { + return ScaleTransition( + scale: animation, child: child); + }, + child: isExpand.value + ? FilledButton( + onPressed: () => dynamicForward('forward'), + child: const Text('转发'), + ) + : TextButton( + onPressed: () {}, + child: const Text('立即转发'), + ), + ), + ], + ), + ), + if (!isExpand.value) ...[ + GestureDetector( + onTap: () => togglePanelState(true), + behavior: HitTestBehavior.translucent, + child: Container( + width: double.infinity, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.fromLTRB(16, 0, 10, 15), + child: Text( + '说点什么吧', + textAlign: TextAlign.start, + style: TextStyle( + color: Theme.of(context).colorScheme.outline), + ), + ), + ), + ] else ...[ + Padding( + padding: const EdgeInsets.fromLTRB(16, 10, 16, 0), + child: TextField( + maxLines: 5, + focusNode: myFocusNode, + controller: _inputController, + onChanged: (value) { + setState(() { + _inputText = value; + }); + }, + decoration: const InputDecoration( + border: InputBorder.none, + hintText: '说点什么吧', + ), + ), + ), + ], + dynamicPreview(), + if (!isExpand.value) ...[ + const Divider(thickness: 0.1, height: 1), + ListTile( + onTap: () => Get.back(), + minLeadingWidth: 0, + dense: true, + title: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline), + textAlign: TextAlign.center, + ), + ), + SizedBox( + height: MediaQuery.of(context).padding.bottom + 20, + ) + ] + ], + ), + ), + ); + }, + ); + } + + togglePanelState(status) { + if (!status) { + Get.back(); + height.value = 310; + _inputText = ''; + _inputController.clear(); + } else { + height.value = Get.size.height; + } + isExpand.value = !(isExpand.value); + } + + dynamicForward(String type) async { + String dynamicId = widget.item.idStr!; + var res = await DynamicsHttp.dynamicCreate( + dynIdStr: dynamicId, + mid: _dynamicsController.userInfo.mid, + rawText: _inputText, + ); + if (res['status']) { + SmartDialog.showToast(type == 'forward' ? '转发成功' : '发布成功'); + togglePanelState(false); + } + } + + @override + void dispose() { + myFocusNode.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { var color = Theme.of(context).colorScheme.outline; @@ -77,7 +304,7 @@ class _ActionPanelState extends State { Expanded( flex: 1, child: TextButton.icon( - onPressed: () {}, + onPressed: forwardHandler, icon: const Icon( FontAwesomeIcons.shareFromSquare, size: 16,