diff --git a/lib/features/settings/view/debug_showcase_page.dart b/lib/features/settings/view/debug_showcase_page.dart new file mode 100644 index 00000000..fdd04205 --- /dev/null +++ b/lib/features/settings/view/debug_showcase_page.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tsdm_client/features/theme/cubit/theme_cubit.dart'; +import 'package:tsdm_client/utils/html/html_muncher.dart'; +import 'package:universal_html/parsing.dart'; + +/// Showcase page for debugging. +class DebugShowcasePage extends StatefulWidget { + /// Constructor. + const DebugShowcasePage({super.key}); + + @override + State createState() => _DebugShowcasePageState(); +} + +class _DebugShowcasePageState extends State + with SingleTickerProviderStateMixin { + late TabController tabController; + + @override + void initState() { + super.initState(); + tabController = TabController(length: 1, vsync: this); + } + + @override + void dispose() { + tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('SHOWCASE'), + bottom: TabBar( + tabAlignment: TabAlignment.start, + isScrollable: true, + controller: tabController, + tabs: const [ + Tab(text: 'HTML'), + ], + ), + actions: [ + IconButton( + icon: const Icon(Icons.dark_mode_outlined), + selectedIcon: const Icon(Icons.dark_mode), + isSelected: Theme.of(context).brightness == Brightness.dark, + onPressed: () { + switch (Theme.of(context).brightness) { + case Brightness.dark: + context.read().setThemeModeIndex(1); + case Brightness.light: + context.read().setThemeModeIndex(2); + } + }, + ), + ], + ), + body: TabBarView( + controller: tabController, + children: const [ + _HtmlFragment(), + ], + ), + ); + } +} + +class _HtmlFragment extends StatelessWidget { + const _HtmlFragment(); + + static const htmlData = ''' + +

TITLE 1

+

TITLE 2

+

TITLE 3

+ + +
+
+ +
+
+
+ detail 1 +
+ detail 2 +
+
+
+ + +
+ + SOMETHING HERE + + 已有 12347890 人购买 + 4 coins +
+ + +
此帖仅作者可见
+ + +
+ USERNAME本帖隐藏的内容需要积分高于 1234567 才可浏览,您当前积分为 1234 +
+ + +
+ USERNAME,如果您要查看本帖隐藏内容请回复 +
+ +'''; + + @override + Widget build(BuildContext context) { + return munchElement( + context, + parseHtmlDocument(htmlData).body!, + parseLockedWithPurchase: true, + ); + } +} diff --git a/lib/features/settings/view/settings_page.dart b/lib/features/settings/view/settings_page.dart index 8771d059..735b3cc6 100644 --- a/lib/features/settings/view/settings_page.dart +++ b/lib/features/settings/view/settings_page.dart @@ -8,6 +8,7 @@ import 'package:system_theme/system_theme.dart'; import 'package:tsdm_client/constants/constants.dart'; import 'package:tsdm_client/features/settings/bloc/settings_bloc.dart'; import 'package:tsdm_client/features/settings/repositories/settings_repository.dart'; +import 'package:tsdm_client/features/settings/view/debug_showcase_page.dart'; import 'package:tsdm_client/features/settings/widgets/check_in_dialog.dart'; import 'package:tsdm_client/features/settings/widgets/clear_cache_bottom_sheet.dart'; import 'package:tsdm_client/features/settings/widgets/color_picker_dialog.dart'; @@ -440,6 +441,18 @@ class _SettingsPageState extends State { final tr = context.t.settingsPage.advancedSection; return [ SectionTitleText(tr.title), + SectionListTile( + leading: const Icon(Icons.developer_mode_outlined), + title: const Text('DEBUG SHOWCASE'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DebugShowcasePage(), + ), + ); + }, + ), SectionSwitchListTile( secondary: Icon(MdiIcons.networkOutline), title: Text(tr.useProxy), diff --git a/lib/i18n/strings.i18n.json b/lib/i18n/strings.i18n.json index 2d46fec7..20afe600 100644 --- a/lib/i18n/strings.i18n.json +++ b/lib/i18n/strings.i18n.json @@ -472,11 +472,11 @@ "lockedCard": { "points": { "title": "Some content requires enough points to be visible", - "detail": "$requiredPoints points are required, but you only have $points points" + "detail(rich)": "$requiredPoints points are required, but you only have $points points" }, "purchase": { "title": "Some content requires purchase", - "purchasedInfo": "$num people have purchased this article", + "purchasedInfo(rich)": "$num people have purchased this article", "failedPurchase": "Can not purchase", "failedParsingPurchase": "Failed when parsing purchase info", "failedParsingConfirmInfo": "Failed when parsing purchase confirm info", @@ -488,10 +488,13 @@ }, "reply": { "title": "Some content requires reply", - "detail": "Reply to see contents here" + "detail(rich)": "$reply to see contents here", + "detailReply": "Reply" }, "author": { - "title": "Only visible to the thread author" + "title": "Only visible to the thread author", + "detail(rich)": "This content only visible to $author", + "detailAuthor": "author" } }, "rateCard": { diff --git a/lib/i18n/strings_zh-CN.i18n.json b/lib/i18n/strings_zh-CN.i18n.json index ddde0a30..d59efcae 100644 --- a/lib/i18n/strings_zh-CN.i18n.json +++ b/lib/i18n/strings_zh-CN.i18n.json @@ -472,11 +472,11 @@ "lockedCard": { "points": { "title": "部分内容要求积分足够才可见", - "detail": "需要 $requiredPoints 积分,但是你只有 $points 积分" + "detail(rich)": "需要 $requiredPoints 积分,但是你只有 $points 积分" }, "purchase": { "title": "部分内容需要购买才可浏览", - "purchasedInfo": "$num 人已购买此帖子", + "purchasedInfo(rich)": "$num 人已购买此帖子", "failedPurchase": "购买失败", "failedParsingPurchase": "解析购买信息时失败", "failedParsingConfirmInfo": "解析购买确认信息时失败", @@ -488,10 +488,13 @@ }, "reply": { "title": "部分需要回复才可浏览", - "detail": "回复以查看该部分内容" + "detail(rich)": "$reply以查看该部分内容", + "detailReply": "回复" }, "author": { - "title": "仅对作者可见" + "title": "仅对作者可见", + "detail(rich)": "该部分内容仅对$author可见", + "detailAuthor": "作者" } }, "rateCard": { diff --git a/lib/i18n/strings_zh-TW.i18n.json b/lib/i18n/strings_zh-TW.i18n.json index 024bdf91..e0d14647 100644 --- a/lib/i18n/strings_zh-TW.i18n.json +++ b/lib/i18n/strings_zh-TW.i18n.json @@ -472,11 +472,11 @@ "lockedCard": { "points": { "title": "部分內容要求積分足夠才可見", - "detail": "需要 $requiredPoints 積分,但你只有 $points 積分" + "detail(rich)": "需要 $requiredPoints 積分,但你只有 $points 積分" }, "purchase": { "title": "部分內容需要購買才可瀏覽", - "purchasedInfo": "$num 人已購買此帖子", + "purchasedInfo(rich)": "$num 人已購買此帖子", "failedPurchase": "購買失敗", "failedParsingPurchase": "解析購買資訊時失敗", "failedPurchaseMessage": "購買失敗: $message", @@ -488,10 +488,13 @@ }, "reply": { "title": "部分需要回覆才可瀏覽", - "detail": "回覆以查看該部分內容" + "detail(rich)": "$reply以查看該部分內容", + "detailReply": "回覆" }, "author": { - "title": "僅對作者可見" + "title": "僅對作者可見", + "detail(rich)": "該部分內容僅對$author可見", + "detailAuthor": "作者" } }, "rateCard": { diff --git a/lib/utils/html/html_muncher.dart b/lib/utils/html/html_muncher.dart index 817dfca1..9bf85b49 100644 --- a/lib/utils/html/html_muncher.dart +++ b/lib/utils/html/html_muncher.dart @@ -31,9 +31,14 @@ const emptySpan = TextSpan(text: '\n'); /// widget. /// /// Main entry of this package. -Widget munchElement(BuildContext context, uh.Element rootElement) { +Widget munchElement( + BuildContext context, + uh.Element rootElement, { + bool parseLockedWithPurchase = false, +}) { final muncher = _Muncher( context, + parseLockedWithPurchase: parseLockedWithPurchase, ); final ret = muncher._munch(rootElement); @@ -178,11 +183,14 @@ class _MunchState { /// Munch html nodes into flutter widgets. final class _Muncher with LoggerMixin { /// Constructor. - _Muncher(this.context); + _Muncher(this.context, {required this.parseLockedWithPurchase}); /// Context to build widget when munching. final BuildContext context; + //////// Configs //////// + bool parseLockedWithPurchase; + /// Munch state to use when munching. final _MunchState state = _MunchState(); @@ -565,8 +573,10 @@ final class _Muncher with LoggerMixin { } List? _buildLockedArea(uh.Element element) { - final lockedArea = - Locked.fromLockDivNode(element, allowWithPurchase: false); + final lockedArea = Locked.fromLockDivNode( + element, + allowWithPurchase: parseLockedWithPurchase, + ); if (lockedArea.isNotValid()) { return null; } diff --git a/lib/widgets/card/lock_card/locked_card.dart b/lib/widgets/card/lock_card/locked_card.dart index c5246e11..def34287 100644 --- a/lib/widgets/card/lock_card/locked_card.dart +++ b/lib/widgets/card/lock_card/locked_card.dart @@ -37,6 +37,21 @@ class _LockedCardState extends State { ); } + WidgetSpan _buildUnderlineText(BuildContext context, String text) { + return WidgetSpan( + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + ), + ), + child: Text(text), + ), + ); + } + Widget _buildPurchaseBody(BuildContext context) { final tr = context.t.lockedCard.purchase; return MultiBlocProvider( @@ -132,66 +147,86 @@ class _LockedCardState extends State { Widget build(BuildContext context) { final tr = context.t.lockedCard; final widgets = []; + + final primaryStyle = Theme.of(context).textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSecondaryContainer, + ); + final secondaryStyle = Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSecondaryContainer, + ); + if (widget.locked.lockedWithPoints) { widgets.addAll([ - Text( - tr.points.title, - style: Theme.of(context).textTheme.titleMedium, - ), - Text( + Text(tr.points.title, style: primaryStyle), + Text.rich( tr.points.detail( - requiredPoints: widget.locked.requiredPoints!, - points: widget.locked.points!, + requiredPoints: _buildUnderlineText( + context, + '${widget.locked.requiredPoints!}', + ), + points: _buildUnderlineText( + context, + '${widget.locked.points!}', + ), ), - style: Theme.of(context).textTheme.labelMedium, + style: secondaryStyle, ), ]); } else if (widget.locked.lockedWithPurchase) { widgets.addAll([ - Text( - tr.purchase.title, - style: Theme.of(context).textTheme.titleMedium, - ), - Text( - tr.purchase.purchasedInfo(num: widget.locked.purchasedCount!), - style: Theme.of(context).textTheme.labelMedium, + Text(tr.purchase.title, style: primaryStyle), + Text.rich( + tr.purchase.purchasedInfo( + num: _buildUnderlineText( + context, + '${widget.locked.purchasedCount!}', + ), + ), + style: secondaryStyle, ), + sizedBoxW4H4, _buildPurchaseBody(context), ]); } else if (widget.locked.lockedWithReply) { widgets.addAll([ - Text( - tr.reply.title, - style: Theme.of(context).textTheme.titleMedium, - ), - Text( - tr.reply.detail, - style: Theme.of(context).textTheme.labelMedium, + Text(tr.reply.title, style: primaryStyle), + Text.rich( + tr.reply.detail( + reply: _buildUnderlineText( + context, + tr.reply.detailReply, + ), + ), + style: secondaryStyle, ), ]); } else if (widget.locked.lockedWithAuthor) { - widgets.add( - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.lock_outline), - sizedBoxW4H4, - Text( - tr.author.title, - style: Theme.of(context).textTheme.titleMedium, + widgets.addAll([ + Text(tr.author.title, style: primaryStyle), + Text.rich( + tr.author.detail( + author: _buildUnderlineText( + context, + tr.author.detailAuthor, ), - ], + ), + style: secondaryStyle, ), - ); + ]); } return Card( elevation: widget.elevation, - child: Padding( - padding: edgeInsetsL16T16R16B16, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: widgets.insertBetween(sizedBoxW4H4), + clipBehavior: Clip.antiAlias, + child: ColoredBox( + color: Theme.of(context).colorScheme.secondaryContainer, + child: Padding( + padding: edgeInsetsL16T16R16B16, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widgets.insertBetween(sizedBoxW4H4), + ), ), ), );