diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index e899977e..ac008c28 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -243,10 +243,10 @@ class DynamicsHttp { } static Future> doVote({ - required int voteId, + required voteId, required List votes, bool anonymity = false, - int? dynamicId, + dynamicId, }) async { final csrf = Accounts.main.csrf; final data = { diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index e110cb27..b3ec1808 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -396,8 +396,10 @@ class Vote { int? type; int? uid; int? voteId; + String? desc; Vote.fromJson(Map json) { + desc = json['desc']; choiceCnt = json['choice_cnt']; share = json['share']; defaultShare = json['default_share']; @@ -805,6 +807,7 @@ class RichTextNodeItem { String? type; String? rid; List? pics; + String? jumpUrl; RichTextNodeItem.fromJson(Map json) { emoji = json['emoji'] != null ? Emoji.fromJson(json['emoji']) : null; @@ -817,6 +820,7 @@ class RichTextNodeItem { : (json['pics'] as List?) ?.map((e) => OpusPicsModel.fromJson(e)) .toList(); + jumpUrl = json['jump_url']; } } diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index 325e8764..df31c7ca 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -208,7 +208,7 @@ class OpusContent extends StatelessWidget { showVoteDialog( context, element.linkCard!.card!.vote?.voteId ?? - int.parse(element.linkCard!.card!.oid!), + element.linkCard!.card!.oid, ); return; } diff --git a/lib/pages/dynamics/widgets/additional_panel.dart b/lib/pages/dynamics/widgets/additional_panel.dart index d2f625ba..ba712d95 100644 --- a/lib/pages/dynamics/widgets/additional_panel.dart +++ b/lib/pages/dynamics/widgets/additional_panel.dart @@ -1,197 +1,400 @@ +import 'package:PiliPlus/build_config.dart'; +import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; +import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -/// TODO 点击跳转 Widget addWidget( ThemeData theme, DynamicItemModel item, BuildContext context, type, {floor = 1}) { - Color bgColor = floor == 1 + late final Color bgColor = floor == 1 ? theme.dividerColor.withOpacity(0.08) : theme.colorScheme.surface; - switch (type) { - case 'ADDITIONAL_TYPE_UGC': - final content = item.modules.moduleDynamic!.additional!.ugc!; + try { + switch (type) { // 转发的投稿 - return InkWell( - onTap: content.jumpUrl == null - ? null - : () { - PiliScheme.routePushFromUrl(content.jumpUrl!); - }, - child: Container( - padding: - const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8), - color: bgColor, - child: Row( - children: [ - NetworkImgLayer( - width: 120, - height: 75, - src: content.cover, - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, + case 'ADDITIONAL_TYPE_UGC': + final ugc = item.modules.moduleDynamic!.additional!.ugc!; + final borderRadius = floor == 1 ? null : StyleString.mdRadius; + return Padding( + padding: const EdgeInsets.only(top: 6), + child: Material( + borderRadius: borderRadius, + color: bgColor, + child: InkWell( + borderRadius: borderRadius, + onTap: ugc.jumpUrl == null + ? null + : () { + PiliScheme.routePushFromUrl(ugc.jumpUrl!); + }, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( children: [ - Text( - content.title!, - maxLines: 2, - overflow: TextOverflow.ellipsis, + NetworkImgLayer( + width: 120, + height: 75, + src: ugc.cover, ), - const SizedBox(height: 4), - Text( - content.descSecond!, - style: TextStyle( - color: theme.colorScheme.outline, - fontSize: theme.textTheme.labelMedium!.fontSize, + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + ugc.title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + ugc.descSecond!, + style: TextStyle( + color: theme.colorScheme.outline, + fontSize: theme.textTheme.labelMedium!.fontSize, + ), + ) + ], ), - ) + ), ], ), ), - ], + ), ), - ), - ); - case 'ADDITIONAL_TYPE_RESERVE': - final content = item.modules.moduleDynamic!.additional!.reserve!; - return content.state != -1 - ? content.title != null - ? Padding( - padding: const EdgeInsets.only(top: 8), - child: InkWell( - onTap: () {}, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - horizontal: 12, vertical: 10), + ); + + case 'ADDITIONAL_TYPE_RESERVE': + final reserve = item.modules.moduleDynamic!.additional!.reserve!; + late final borderRadius = floor == 1 ? null : StyleString.mdRadius; + return reserve.state != -1 + ? reserve.title != null + ? Padding( + padding: const EdgeInsets.only(top: 6), + child: Material( color: bgColor, + borderRadius: borderRadius, + child: InkWell( + onTap: () {}, + borderRadius: borderRadius, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + reserve.title!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 1), + Text.rich( + TextSpan( + style: TextStyle( + color: theme.colorScheme.outline, + fontSize: theme + .textTheme.labelMedium!.fontSize, + ), + children: [ + if (reserve.desc1 != null) + TextSpan(text: reserve.desc1!.text), + const TextSpan(text: ' '), + if (reserve.desc2 != null) + TextSpan(text: reserve.desc2!.text), + ], + ), + ) + ], + ), + ), + if (reserve.button != null) + Builder( + builder: (context) { + final btn = reserve.button!; + final isReserved = btn.status == btn.type; + final bool canJump = btn.jumpUrl != null; + return FilledButton.tonal( + style: FilledButton.styleFrom( + foregroundColor: canJump + ? null + : isReserved + ? theme.colorScheme.onSurface + .withOpacity(0.38) + : null, + backgroundColor: canJump + ? null + : isReserved + ? theme.colorScheme.onSurface + .withOpacity(0.12) + : null, + visualDensity: VisualDensity.compact, + padding: const EdgeInsets.symmetric( + horizontal: 16), + tapTargetSize: + MaterialTapTargetSize.shrinkWrap, + ), + onPressed: canJump + ? () { + PiliScheme.routePushFromUrl( + btn.jumpUrl!); + } + : btn.disable == 1 + ? null + : () async { + var res = await DynamicsHttp + .dynReserve( + reserveId: reserve.rid, + curBtnStatus: btn.status, + dynamicIdStr: item.idStr, + reserveTotal: + reserve.reserveTotal, + ); + if (res['status']) { + reserve + ..desc2?.text = + res['data'] + ['desc_update'] + ..reserveTotal = + res['data'] + ['reserve_update'] + ..button!.status = res[ + 'data'] + ['final_btn_status']; + if (context.mounted) { + (context as Element?) + ?.markNeedsBuild(); + } + } else { + SmartDialog.showToast( + res['msg']); + } + }, + child: Text( + btn.jumpText != null + ? btn.jumpText! + : isReserved + ? btn.checkText! + : btn.uncheckText!, + ), + ); + }, + ), + ], + ), + ), + ), + ), + ) + : const SizedBox.shrink() + : const SizedBox.shrink(); + // 商品 + case 'ADDITIONAL_TYPE_GOODS': + final content = item.modules.moduleDynamic!.additional!.goods!; + if (content.items?.isNotEmpty == true) { + final borderRadius = floor == 1 ? null : StyleString.mdRadius; + return Padding( + padding: const EdgeInsets.only(top: 6), + child: Material( + color: bgColor, + borderRadius: borderRadius, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: content.items!.map((e) { + return InkWell( + borderRadius: borderRadius, + onTap: () { + PiliScheme.routePushFromUrl(e.jumpUrl!); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), child: Row( children: [ + if (e.cover?.isNotEmpty == true) ...[ + NetworkImgLayer( + width: 45, + height: 45, + src: e.cover, + radius: 6, + ), + const SizedBox(width: 10), + ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - content.title!, + e.name!, maxLines: 1, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 1), - Text.rich( - TextSpan( - style: TextStyle( - color: theme.colorScheme.outline, - fontSize: - theme.textTheme.labelMedium!.fontSize, + if (e.price?.isNotEmpty == true) + Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${e.price}', + style: TextStyle( + color: theme.colorScheme.primary, + ), + ), + const TextSpan( + text: ' 起', + style: TextStyle(fontSize: 12), + ), + ], ), - children: [ - if (content.desc1 != null) - TextSpan(text: content.desc1!.text), - const TextSpan(text: ' '), - if (content.desc2 != null) - TextSpan(text: content.desc2!.text), - ], ), - ) ], ), ), - if (content.button != null) - Builder( - builder: (context) { - final btn = content.button!; - final isReserved = btn.status == btn.type; - final bool canJump = btn.jumpUrl != null; - return FilledButton.tonal( - style: FilledButton.styleFrom( - foregroundColor: canJump - ? null - : isReserved - ? theme.colorScheme.onSurface - .withOpacity(0.38) - : null, - backgroundColor: canJump - ? null - : isReserved - ? theme.colorScheme.onSurface - .withOpacity(0.12) - : null, - visualDensity: VisualDensity.compact, - padding: const EdgeInsets.symmetric( - horizontal: 16), - tapTargetSize: - MaterialTapTargetSize.shrinkWrap, - ), - onPressed: canJump - ? () { - PiliScheme.routePushFromUrl( - btn.jumpUrl!); - } - : btn.disable == 1 - ? null - : () async { - var res = - await DynamicsHttp.dynReserve( - reserveId: content.rid, - curBtnStatus: btn.status, - dynamicIdStr: item.idStr, - reserveTotal: - content.reserveTotal, - ); - if (res['status']) { - content - ..desc2?.text = - res['data']['desc_update'] - ..reserveTotal = res['data'] - ['reserve_update'] - ..button!.status = res['data'] - ['final_btn_status']; - if (context.mounted) { - (context as Element?) - ?.markNeedsBuild(); - } - } else { - SmartDialog.showToast( - res['msg']); - } - }, - child: Text( - btn.jumpText != null - ? btn.jumpText! - : isReserved - ? btn.checkText! - : btn.uncheckText!, - ), - ); + if (e.jumpDesc?.isNotEmpty == true) ...[ + const SizedBox(width: 10), + FilledButton.tonal( + onPressed: () { + PiliScheme.routePushFromUrl(e.jumpUrl!); }, + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(6)), + ), + padding: + const EdgeInsets.symmetric(horizontal: 10), + visualDensity: const VisualDensity( + horizontal: -2, vertical: -3), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Text(e.jumpDesc!), ), + ], ], ), ), - ), - ) - : const SizedBox.shrink() - : const SizedBox.shrink(); - case 'ADDITIONAL_TYPE_GOODS': - // final content = item.modules.moduleDynamic!.additional!.goods; - // 商品 - return const SizedBox.shrink(); - case 'ADDITIONAL_TYPE_MATCH': + ); + }).toList(), + ), + ), + ); + } + return const SizedBox.shrink(); + // case 'ADDITIONAL_TYPE_MATCH': // final content = item.modules.moduleDynamic!.additional!.match; - return const SizedBox.shrink(); - case 'ADDITIONAL_TYPE_COMMON': + // return const SizedBox.shrink(); + // case 'ADDITIONAL_TYPE_COMMON': // final content = item.modules.moduleDynamic!.additional!.common; - return const SizedBox.shrink(); - case 'ADDITIONAL_TYPE_VOTE': - return const SizedBox.shrink(); - default: - return const SizedBox.shrink(); + // return const SizedBox.shrink(); + case 'ADDITIONAL_TYPE_VOTE': + final vote = item.modules.moduleDynamic!.additional!.vote!; + final borderRadius = floor == 1 ? null : StyleString.mdRadius; + return Padding( + padding: const EdgeInsets.only(top: 6), + child: Material( + color: bgColor, + borderRadius: borderRadius, + child: InkWell( + borderRadius: borderRadius, + onTap: () { + showVoteDialog( + context, + vote.voteId, + item.idStr, + ); + }, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: floor == 1 + ? theme.colorScheme.surface + : theme.dividerColor.withOpacity(0.08), + borderRadius: + const BorderRadius.all(Radius.circular(8)), + ), + width: 70, + height: 56, + child: const Icon(Icons.bar_chart_rounded), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + vote.desc!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + '${Utils.numFormat(vote.joinNum)}人参与', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13, color: theme.colorScheme.outline), + ), + ], + ), + ), + const SizedBox(width: 10), + FilledButton.tonal( + onPressed: () { + showVoteDialog( + context, + vote.voteId, + item.idStr, + ); + }, + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(6)), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + visualDensity: + const VisualDensity(horizontal: -2, vertical: -3), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: const Text('参与'), + ), + ], + ), + ), + ), + ), + ); + default: + if (BuildConfig.isDebug) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text('additional panel\ntype: $type'), + ); + } + return const SizedBox.shrink(); + } + } catch (e) { + return Padding( + padding: const EdgeInsets.all(12), + child: SelectableText( + ''' +additional panel error +id: ${item.idStr} +type: $type +err: $e''', + ), + ); } } diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index b7b9244e..4297203d 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -123,10 +123,7 @@ TextSpan? richNode( ), recognizer: TapGestureRecognizer() ..onTap = () { - final dynIdStr = item.basic?.commentIdStr; - final dynId = - dynIdStr != null ? int.tryParse(dynIdStr) : null; - showVoteDialog(context, int.parse(i.rid!), dynId); + showVoteDialog(context, i.rid, item.basic?.commentIdStr); }, ), ); @@ -175,7 +172,6 @@ TextSpan? richNode( ); break; - /// TODO 商品 case 'RICH_TEXT_NODE_TYPE_GOODS': spanChildren ..add( @@ -192,6 +188,12 @@ TextSpan? richNode( TextSpan( text: '${i.text} ', style: authorStyle, + recognizer: i.jumpUrl == null + ? null + : (TapGestureRecognizer() + ..onTap = () { + PiliScheme.routePushFromUrl(i.jumpUrl!); + }), ), ); break; diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index 3f20d45d..a6918115 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -340,8 +340,7 @@ class PercentageChip extends StatelessWidget { // } // } -Future showVoteDialog(BuildContext context, int voteId, - [int? dynamicId]) async { +Future showVoteDialog(BuildContext context, voteId, [dynamicId]) async { final voteInfo = await DynamicsHttp.voteInfo(voteId); if (context.mounted) { if (voteInfo.isSuccess) {