diff --git a/CHANGELOG.md b/CHANGELOG.md index 0245cf81..620a597a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - 帖子:我的帖子页面中显示更多帖子信息。 - 帖子:我的帖子页面中修改帖子卡片布局。 - 帖子:支持解析新人报道分区的帖子中的新人报道表格。 +- 帖子:现在已被领完的红包会直接显示领完了。 - 消息:现在来源于帖子中的消息会直接跳转到帖子的相应楼层,而不经过消息详情页。 - app:支持设置和使用代理。 - 默认关闭,可在设置 -> 高级 -> 启用代理中打开。 diff --git a/lib/features/packet/cubit/packet_cubit.dart b/lib/features/packet/cubit/packet_cubit.dart index 76856422..e470ebfc 100644 --- a/lib/features/packet/cubit/packet_cubit.dart +++ b/lib/features/packet/cubit/packet_cubit.dart @@ -9,9 +9,15 @@ part 'packet_state.dart'; /// Cubit of read packets. class PacketCubit extends Cubit with LoggerMixin { /// Constructor. - PacketCubit({required PacketRepository packetRepository}) - : _packetRepository = packetRepository, - super(const PacketState()); + PacketCubit({ + required PacketRepository packetRepository, + required bool allTaken, + }) : _packetRepository = packetRepository, + super( + allTaken + ? const PacketState(status: PacketStatus.takenAway) + : const PacketState(), + ); final PacketRepository _packetRepository; diff --git a/lib/features/settings/view/debug_showcase_page.dart b/lib/features/settings/view/debug_showcase_page.dart index dd295522..6cb0dd17 100644 --- a/lib/features/settings/view/debug_showcase_page.dart +++ b/lib/features/settings/view/debug_showcase_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tsdm_client/features/theme/cubit/theme_cubit.dart'; import 'package:tsdm_client/shared/models/models.dart'; import 'package:tsdm_client/utils/html/html_muncher.dart'; +import 'package:tsdm_client/widgets/card/packet_card.dart'; import 'package:tsdm_client/widgets/card/rate_card.dart'; import 'package:universal_html/parsing.dart'; @@ -188,6 +189,9 @@ class _HtmlFragment extends StatelessWidget { + + + '''; @@ -203,6 +207,8 @@ class _HtmlFragment extends StatelessWidget { parseLockedWithPurchase: true, ), RateCard(Rate.fromRateLogNode(parseHtmlDocument(rateBlockData).body)!), + const PacketCard('', allTaken: false), + const PacketCard('', allTaken: true), ], ); } diff --git a/lib/i18n/strings.i18n.json b/lib/i18n/strings.i18n.json index daae1e95..c33cc3d3 100644 --- a/lib/i18n/strings.i18n.json +++ b/lib/i18n/strings.i18n.json @@ -555,7 +555,8 @@ "share": "Copy share link" }, "packetCard": { - "title": "You found a packet!", + "title": "Packet", + "detail": "You found a packet!", "open": "Open It", "failedToOpen": "Failed to open", "allTakenAway": "All packets were taken away" @@ -627,5 +628,9 @@ "newcomerReportCard": { "title": "Detail Info" } + }, + "musicCard": { + "title": "Music", + "openLink": "Open link" } } diff --git a/lib/i18n/strings_zh-CN.i18n.json b/lib/i18n/strings_zh-CN.i18n.json index c0918252..02a2f8a2 100644 --- a/lib/i18n/strings_zh-CN.i18n.json +++ b/lib/i18n/strings_zh-CN.i18n.json @@ -555,7 +555,8 @@ "share": "复制分享链接" }, "packetCard": { - "title": "你找到了一个红包!", + "title": "红包", + "detail": "你找到了一个红包!", "open": "打开", "failedToOpen": "打开失败", "allTakenAway": "红包都领完了" @@ -627,5 +628,9 @@ "newcomerReportCard": { "title": "详细信息" } + }, + "musicCard": { + "title": "音乐", + "openLink": "打开链接" } } diff --git a/lib/i18n/strings_zh-TW.i18n.json b/lib/i18n/strings_zh-TW.i18n.json index 88930ed3..8e7d7738 100644 --- a/lib/i18n/strings_zh-TW.i18n.json +++ b/lib/i18n/strings_zh-TW.i18n.json @@ -555,7 +555,8 @@ "share": "複製分享連結" }, "packetCard": { - "title": "你找到一個紅包了!", + "title": "紅包", + "detail": "你找到一個紅包了!", "open": "開啟", "failedToOpen": "打開失敗", "allTakenAway": "红包都領完了" @@ -627,5 +628,9 @@ "newcomerReportCard": { "title": "詳細資料" } + }, + "musicCard": { + "title": "音樂", + "openLink": "開啟連結" } } diff --git a/lib/shared/models/post.dart b/lib/shared/models/post.dart index 743b3752..79bf0910 100644 --- a/lib/shared/models/post.dart +++ b/lib/shared/models/post.dart @@ -1,5 +1,8 @@ part of 'models.dart'; +/// Image url of empty packet. +const _emptyPacketImageUrl = 'static/image/post/rp-2.svg'; + extension _ParseExtension on uh.Element { static final _rateActionRe = RegExp("'rate', '(?forum.php[^']*)',"); @@ -29,6 +32,7 @@ class Post with PostMappable { required this.shareLink, required this.page, required this.isDraft, + required this.packetAllTaken, this.locked = const [], this.rate, this.packetUrl, @@ -74,6 +78,9 @@ class Post with PostMappable { /// Optional. final String? packetUrl; + /// Is all packet taken away. + final bool packetAllTaken; + /// Url to edit this post. /// /// Generally this field is not null only when current user is the author @@ -232,6 +239,10 @@ class Post with PostMappable { ?.querySelector('div.pct > div#ts_packet > a') ?.attributes['href'] ?.prependHost(); + final packetAllTaken = postDataNode + ?.querySelector('div.pct > div#ts_packet > a > img') + ?.attributes['src'] == + _emptyPacketImageUrl; // Parse rate action: // * If current post is the first floor in thread, rate action node is in
...
. @@ -301,6 +312,7 @@ class Post with PostMappable { rate: rate, rateAction: rateAction, packetUrl: packetUrl, + packetAllTaken: packetAllTaken, editUrl: editUrl, lastEditUsername: lastEditUsername, lastEditTime: lastEditTime, diff --git a/lib/utils/html/html_muncher.dart b/lib/utils/html/html_muncher.dart index 09975611..af66f4a2 100644 --- a/lib/utils/html/html_muncher.dart +++ b/lib/utils/html/html_muncher.dart @@ -202,7 +202,7 @@ final class _Muncher with LoggerMixin { /// Regex to match netease player iframe. final _neteasePlayerRe = - RegExp(r'//music\.163\.com/outchain/player\?.+id=(?\d+).*'); + RegExp(r'//music\.163\.com/outchain/player\?.*id=(?\d+).*'); List? _munch(uh.Element rootElement) { final spanList = []; diff --git a/lib/utils/html/netease_card.dart b/lib/utils/html/netease_card.dart index 85fe81ff..b3bfc03d 100644 --- a/lib/utils/html/netease_card.dart +++ b/lib/utils/html/netease_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fpdart/fpdart.dart' as fp; import 'package:tsdm_client/constants/layout.dart'; +import 'package:tsdm_client/i18n/strings.g.dart'; import 'package:tsdm_client/instance.dart'; import 'package:tsdm_client/shared/providers/net_client_provider/net_client_provider.dart'; import 'package:tsdm_client/shared/providers/providers.dart'; @@ -47,49 +48,56 @@ class _NeteaseCardState extends State with LoggerMixin { @override Widget build(BuildContext context) { + final tr = context.t.musicCard; + final primaryColor = Theme.of(context).colorScheme.primary; + final primaryStyle = Theme.of(context).textTheme.titleMedium?.copyWith( + color: primaryColor, + ); final infoStyle = Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.secondary, ); - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () async => launchUrlString('$urlPrefix${widget.id}'), - child: Card( - child: Padding( - padding: edgeInsetsL12T12R12B12, - child: Row( + + return Card( + child: Padding( + padding: edgeInsetsL16T16R16B16, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - Icon( - Icons.music_note_outlined, - color: Theme.of(context).colorScheme.secondary, - ), + Icon(Icons.music_note_outlined, color: primaryColor), sizedBoxW8H8, - Expanded( - child: FutureBuilder( - future: getIt - .get( - instanceName: ServiceKeys.noCookie, - ) - .get('$urlPrefix${widget.id}') - .run(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return switch (snapshot.data!) { - fp.Left() => Text(fallbackInfo, style: infoStyle), - fp.Right(:final value) => Text( - _parseMusicInfo(value.data as String), - style: infoStyle, - ), - }; - } - - return sizedCircularProgressIndicator; - }, - ), - ), + Text(tr.title, style: primaryStyle), ], ), - ), + sizedBoxW12H12, + FutureBuilder( + future: getIt + .get( + instanceName: ServiceKeys.noCookie, + ) + .get('$urlPrefix${widget.id}') + .run(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return switch (snapshot.data!) { + fp.Left() => Text(fallbackInfo, style: infoStyle), + fp.Right(:final value) => Text( + _parseMusicInfo(value.data as String), + style: infoStyle, + ), + }; + } + return sizedCircularProgressIndicator; + }, + ), + sizedBoxW12H12, + OutlinedButton.icon( + icon: const Icon(Icons.launch_outlined), + label: Text(tr.openLink), + onPressed: () async => launchUrlString('$urlPrefix${widget.id}'), + ), + ], ), ), ); diff --git a/lib/widgets/card/packet_card.dart b/lib/widgets/card/packet_card.dart index 6769cc2b..62ca02a5 100644 --- a/lib/widgets/card/packet_card.dart +++ b/lib/widgets/card/packet_card.dart @@ -9,21 +9,33 @@ import 'package:tsdm_client/i18n/strings.g.dart'; /// 红包 class PacketCard extends StatelessWidget { /// Constructor. - const PacketCard(this.packetUrl, {super.key}); + const PacketCard( + this.packetUrl, { + required this.allTaken, + super.key, + }); /// Url to fetch/receive the packet. final String packetUrl; + /// Are all packet taken away. + final bool allTaken; + @override Widget build(BuildContext context) { + final primaryColor = Theme.of(context).colorScheme.primary; + final tr = context.t.packetCard; + return MultiBlocProvider( providers: [ RepositoryProvider( create: (_) => PacketRepository(), ), BlocProvider( - create: (context) => - PacketCubit(packetRepository: RepositoryProvider.of(context)), + create: (context) => PacketCubit( + packetRepository: RepositoryProvider.of(context), + allTaken: allTaken, + ), ), ], child: BlocListener( @@ -34,7 +46,7 @@ class PacketCard extends StatelessWidget { .showSnackBar(SnackBar(content: Text(state.reason!))); } else { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.t.packetCard.failedToOpen)), + SnackBar(content: Text(tr.failedToOpen)), ); } } else if (state.status == PacketStatus.success) { @@ -42,7 +54,7 @@ class PacketCard extends StatelessWidget { .showSnackBar(SnackBar(content: Text(state.reason!))); } else if (state.status == PacketStatus.takenAway) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.t.packetCard.allTakenAway)), + SnackBar(content: Text(tr.allTakenAway)), ); } }, @@ -62,9 +74,9 @@ class PacketCard extends StatelessWidget { PacketStatus.loading => const Text(''), PacketStatus.initial || PacketStatus.success || - PacketStatus.failed || - PacketStatus.takenAway => - Text(context.t.packetCard.open), + PacketStatus.failed => + Text(tr.open), + PacketStatus.takenAway => Text(tr.allTakenAway), }; final callback = switch (state.status) { @@ -80,15 +92,28 @@ class PacketCard extends StatelessWidget { child: Padding( padding: edgeInsetsL16T16R16B16, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - context.t.packetCard.title, - style: Theme.of(context).textTheme.titleMedium, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(FontAwesomeIcons.coins, color: primaryColor), + sizedBoxW8H8, + Text( + tr.title, + style: + Theme.of(context).textTheme.titleMedium?.copyWith( + color: primaryColor, + ), + ), + ], ), sizedBoxW12H12, + Text(tr.detail), + sizedBoxW12H12, SizedBox( width: sizeButtonInCardMinWidth, - child: FilledButton.icon( + child: OutlinedButton.icon( icon: body, label: label, onPressed: callback, diff --git a/lib/widgets/card/post_card/post_card.dart b/lib/widgets/card/post_card/post_card.dart index 42ba4e1b..f6853d00 100644 --- a/lib/widgets/card/post_card/post_card.dart +++ b/lib/widgets/card/post_card/post_card.dart @@ -373,7 +373,10 @@ class _PostCardState extends State ...widget.post.locked.where((e) => e.isValid()).map(LockedCard.new), if (widget.post.packetUrl != null) ...[ sizedBoxW12H12, - PacketCard(widget.post.packetUrl!), + PacketCard( + widget.post.packetUrl!, + allTaken: widget.post.packetAllTaken, + ), ], // Rate status if any. if (widget.post.rate != null) ...[