Skip to content

Commit f0aab7d

Browse files
style(core): structure disclosure content for readability
1 parent 3fe5ec8 commit f0aab7d

3 files changed

Lines changed: 227 additions & 7 deletions

File tree

lib/core/widgets/bottom_sheet/disclosure_bottom_sheet.dart

Lines changed: 169 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,179 @@ class DisclosureBottomSheet extends StatelessWidget {
109109
Flexible(
110110
child: SingleChildScrollView(
111111
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
112-
child: BBText(
113-
body,
114-
style: context.font.bodyMedium,
115-
color: context.appColors.text,
116-
),
112+
child: _DisclosureBody(body),
117113
),
118114
),
119115
],
120116
),
121117
);
122118
}
123119
}
120+
121+
class _DisclosureBody extends StatelessWidget {
122+
const _DisclosureBody(this.body);
123+
124+
final String body;
125+
126+
@override
127+
Widget build(BuildContext context) {
128+
final blocks = body.trim().split(RegExp(r'\n\s*\n'));
129+
final children = <Widget>[];
130+
131+
for (final block in blocks) {
132+
if (block.trim().isEmpty) continue;
133+
if (children.isNotEmpty) {
134+
children.add(Gap(_spacingBefore(block)));
135+
}
136+
children.add(_buildBlock(context, block.trim()));
137+
}
138+
139+
return Column(
140+
crossAxisAlignment: CrossAxisAlignment.stretch,
141+
children: children,
142+
);
143+
}
144+
145+
double _spacingBefore(String block) {
146+
if (block.startsWith('## ')) return 24;
147+
if (block.startsWith('### ') || block.startsWith('> ')) return 16;
148+
return 12;
149+
}
150+
151+
Widget _buildBlock(BuildContext context, String block) {
152+
if (block.startsWith('## ')) {
153+
return Semantics(
154+
header: true,
155+
child: BBText(
156+
block.substring(3),
157+
style: context.font.titleMedium?.copyWith(fontWeight: .w600),
158+
color: context.appColors.text,
159+
),
160+
);
161+
}
162+
163+
if (block.startsWith('### ')) {
164+
return Semantics(
165+
header: true,
166+
child: BBText(
167+
block.substring(4),
168+
style: context.font.bodyLarge?.copyWith(fontWeight: .w600),
169+
color: context.appColors.text,
170+
),
171+
);
172+
}
173+
174+
final lines = block.split('\n');
175+
if (lines.every((line) => line.startsWith('> '))) {
176+
return _buildCallout(
177+
context,
178+
title: lines.first.substring(2),
179+
body: lines.skip(1).map((line) => line.substring(2)).join('\n'),
180+
);
181+
}
182+
183+
if (lines.every((line) => line.startsWith('- '))) {
184+
return _buildBulletList(
185+
context,
186+
lines.map((line) => line.substring(2)).toList(),
187+
);
188+
}
189+
190+
return BBText(
191+
block,
192+
style: context.font.bodyMedium,
193+
color: context.appColors.text,
194+
);
195+
}
196+
197+
Widget _buildCallout(
198+
BuildContext context, {
199+
required String title,
200+
required String body,
201+
}) {
202+
return Container(
203+
padding: const EdgeInsets.all(16),
204+
decoration: BoxDecoration(
205+
color: context.appColors.warningContainer,
206+
border: Border.all(color: context.appColors.warning),
207+
borderRadius: BorderRadius.circular(2),
208+
),
209+
child: Row(
210+
crossAxisAlignment: CrossAxisAlignment.start,
211+
children: [
212+
Icon(
213+
Icons.warning_amber_rounded,
214+
size: 22,
215+
color: context.appColors.warning,
216+
),
217+
const Gap(12),
218+
Expanded(
219+
child: Column(
220+
crossAxisAlignment: CrossAxisAlignment.start,
221+
children: [
222+
BBText(
223+
title,
224+
style: context.font.bodyLarge?.copyWith(fontWeight: .w600),
225+
color: context.appColors.text,
226+
),
227+
const Gap(6),
228+
BBText(
229+
body,
230+
style: context.font.bodyMedium,
231+
color: context.appColors.text,
232+
),
233+
],
234+
),
235+
),
236+
],
237+
),
238+
);
239+
}
240+
241+
Widget _buildBulletList(BuildContext context, List<String> items) {
242+
return Column(
243+
crossAxisAlignment: CrossAxisAlignment.start,
244+
children: [
245+
for (var index = 0; index < items.length; index++) ...[
246+
if (index > 0) const Gap(8),
247+
Row(
248+
crossAxisAlignment: CrossAxisAlignment.start,
249+
children: [
250+
SizedBox(
251+
width: 18,
252+
child: BBText(
253+
'•',
254+
style: context.font.bodyMedium,
255+
color: context.appColors.text,
256+
),
257+
),
258+
Expanded(child: _buildBulletText(context, items[index])),
259+
],
260+
),
261+
],
262+
],
263+
);
264+
}
265+
266+
Widget _buildBulletText(BuildContext context, String item) {
267+
final style = context.font.bodyMedium?.copyWith(
268+
color: context.appColors.text,
269+
);
270+
final match = RegExp(r'^\*\*(.+?)\*\*(.*)$').firstMatch(item);
271+
272+
if (match == null) return Text(item, style: style);
273+
274+
return Text.rich(
275+
TextSpan(
276+
style: style,
277+
children: [
278+
TextSpan(
279+
text: match.group(1),
280+
style: style?.copyWith(fontWeight: .w600),
281+
),
282+
TextSpan(text: match.group(2)),
283+
],
284+
),
285+
);
286+
}
287+
}

localization/app_en.arb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3465,9 +3465,9 @@
34653465
"@liquidRiskDisclosureTitle": {
34663466
"description": "Title for the Liquid Bitcoin risk disclosure bottom sheet"
34673467
},
3468-
"liquidRiskDisclosureBody": "The funds in the Instant Payment Wallet are not \"real\" Bitcoin. They are Liquid Network Bitcoin (L-BTC), a Bitcoin-backed token which exists on a separate ledger called a \"sidechain\". Each Liquid Bitcoin is backed 1:1 by real Bitcoin using a transparent peg mechanism maintained by a federation of 15 companies. The Bitcoin reserves held by the Liquid Federation are fully auditable, and anybody can verify that every Liquid Bitcoin is backed by a real Bitcoin.\n\nKeeping money on the Liquid Network is much safer than using a traditional custodial wallet or an exchange. However, holding your money as Liquid Bitcoin in the Instant Bitcoin Payments wallet is far less secure than holding it as real Bitcoin in the Secure Bitcoin Wallet.\n\nLiquid Network payments are faster, cheaper and more private than regular Bitcoin payments. You have full self-custody of the Liquid Bitcoin assets in the Instant Payments Wallet and you don't need any permission from anyone to send and receive payments. You are fully and exclusively responsible for securing access to your wallet backup, without which nobody can restore access to your Liquid Bitcoin assets.\n\nThe real-world value of the L-BTC depends on your ability to redeem them for real Bitcoin, which in turn depends on swap providers that are members of the Liquid Network, which in turn depends on the Liquid Network federation never being compromised or shut down. In other words, in the event that Liquid Network federation members become unable or unwilling to redeem your Liquid Bitcoin for real Bitcoin, the value of L-BTC tokens would become worthless.\n\nFor this reason, we recommend that you only keep small amounts in the Instant Payments Wallet and that you keep your savings in the Secure Bitcoin Wallet, or in an imported hardware wallet.\n\nWhat is the Instant Payments Wallet?\n\nEvery time you receive a Lightning Network payment with the Instant Payment Wallet, the sender's funds are actually received by a 3rd party swap provider, converted to Liquid Bitcoin and sent to your wallet.\n\nEvery time you send a Lightning Network payment with this wallet, you are actually sending a Liquid Bitcoin payment to a 3rd party swap provider which converts them to real Bitcoin on the Lightning Network and sends a Lightning Network payment to the recipient.\n\nThese swaps are fully non-custodial and trustless, meaning that there is no way for the swap provider to steal your funds. Bull Bitcoin is not involved in making those swap transactions and does not require or store any personal information.\n\nWhat should I do with this wallet?\n\nThe Instant Payments Wallet is designed for you to be able to receive Bitcoin payments (or buying Bitcoin) and send Bitcoin payments (or sell Bitcoin) on a day-to-day basis without paying Bitcoin network fees.\n\nIf you are a merchant or you are accumulating Bitcoin, use this wallet to receive payments below 0.01 BTC and, once you have accumulated over 0.01 BTC, transfer those Bitcoins to the Secure Bitcoin Wallet or any other self-custodial Bitcoin wallet.\n\nIf you want to spend your Bitcoin, transfer a small amount in the Instant Payment Wallet and use it to pay for your expenses. When you run out of funds in the Instant Payments Wallet, simply refill it.\n\nWhat is auto-swap and how does it work?\n\nAuto-swap, also called Auto-Transfer, is a feature invented by Bull Bitcoin which automatically converts Liquid Network Bitcoin (L-BTC) to Bitcoin (BTC) once the L-BTC amount in your wallet reaches a defined threshold.\n\nThis feature is turned on by default. The default threshold is 0.005 BTC and the default minimum transfer amount is 0.005 BTC. This means that as soon as your balance reaches 0.01 BTC, a transfer will be initiated as soon as you open the app and any funds in excess of 0.005 BTC will be converted from L-BTC to BTC.\n\nFor example, if you have 0.012 L-BTC and auto-swap is enabled, a transfer of 0.007 L-BTC will be initiated and your remaining L-BTC will be 0.005 L-BTC, and your BTC balance will be increased by 0.007 BTC.\n\nHow do I fund or withdraw L-BTC from the Instant Bitcoin Wallet?\n\nTo fund the wallet with \"real\" Bitcoin, click \"Receive\" and select \"Lightning Network\". To withdraw L-BTC, click \"Send\" and paste a Lightning invoice.\n\nHow does the backup work?\n\nThe Instant Bitcoin Wallet uses the same backup seed words as the Secure Bitcoin Wallet. You only need one backup for both wallets.\n\nShould I use the Instant Payments wallet instead of a non-custodial Lightning Network Wallet?\n\nUsing a non-custodial Lightning Network wallet such as Phoenix Wallet will give you a higher degree of security. Because you will have to deal with Lightning Network channel management, it may be less convenient and harder to use. Because Lightning channels require on-chain transactions to be opened and closed, and because you will likely need to pay a fee to Lightning service providers, it may in some cases be more expensive to use a fully non-custodial Lightning wallet. If you are able and willing to use a fully non-custodial pure Lightning wallet, we highly recommend you try Phoenix Wallet.\n\nWhat are the transaction fees?\n\nEach payment in and out of the Instant Payments Wallet implies two Liquid Network transactions. These are typically very small and should be around 0.00000050 BTC (50 sats). If you are receiving or sending Lightning Network payments with the Secure Bitcoin Wallet, you will have to pay for two on-chain Bitcoin transactions every payment, which can be quite expensive.\n\nIn addition, the swap provider may also charge swap fees. At the moment, the swap provider (Boltz) charges the following fees:\n\n- Receiving Lightning Network payments in the Instant Payment Wallet: 0.25%\n- Sending Lightning Network payments from the Instant Payment Wallet: 0.1%\n- Receiving Lightning Network payments in the Secure Bitcoin Wallet: 0.5%\n- Sending Lightning Network payments from the Secure Bitcoin Wallet: 0.1%",
3468+
"liquidRiskDisclosureBody": "The funds in the Instant Payment Wallet are not \"real\" Bitcoin. They are Liquid Network Bitcoin (L-BTC), a Bitcoin-backed token which exists on a separate ledger called a \"sidechain\".\n\nEach Liquid Bitcoin is backed 1:1 by real Bitcoin using a transparent peg mechanism maintained by a federation of 15 companies. The Bitcoin reserves held by the Liquid Federation are fully auditable, and anybody can verify that every Liquid Bitcoin is backed by a real Bitcoin.\n\nKeeping money on the Liquid Network is much safer than using a traditional custodial wallet or an exchange. However, holding your money as Liquid Bitcoin in the Instant Bitcoin Payments wallet is far less secure than holding it as real Bitcoin in the Secure Bitcoin Wallet.\n\nLiquid Network payments are faster, cheaper and more private than regular Bitcoin payments. You have full self-custody of the Liquid Bitcoin assets in the Instant Payments Wallet and you don't need any permission from anyone to send and receive payments.\n\nYou are fully and exclusively responsible for securing access to your wallet backup, without which nobody can restore access to your Liquid Bitcoin assets.\n\nThe real-world value of the L-BTC depends on your ability to redeem them for real Bitcoin, which in turn depends on swap providers that are members of the Liquid Network, which in turn depends on the Liquid Network federation never being compromised or shut down.\n\nIn other words, in the event that Liquid Network federation members become unable or unwilling to redeem your Liquid Bitcoin for real Bitcoin, the value of L-BTC tokens would become worthless.\n\n> Recommendation\n> Only keep small amounts in the Instant Payments Wallet. Keep your savings in the Secure Bitcoin Wallet or in an imported hardware wallet.\n\n## What is the Instant Payments Wallet?\n\nEvery time you receive a Lightning Network payment with the Instant Payment Wallet, the sender's funds are actually received by a 3rd party swap provider, converted to Liquid Bitcoin and sent to your wallet.\n\nEvery time you send a Lightning Network payment with this wallet, you are actually sending a Liquid Bitcoin payment to a 3rd party swap provider which converts them to real Bitcoin on the Lightning Network and sends a Lightning Network payment to the recipient.\n\nThese swaps are fully non-custodial and trustless, meaning that there is no way for the swap provider to steal your funds. Bull Bitcoin is not involved in making those swap transactions and does not require or store any personal information.\n\n## What should I do with this wallet?\n\nThe Instant Payments Wallet is designed for you to be able to receive Bitcoin payments (or buying Bitcoin) and send Bitcoin payments (or sell Bitcoin) on a day-to-day basis without paying Bitcoin network fees.\n\nIf you are a merchant or you are accumulating Bitcoin, use this wallet to receive payments below 0.01 BTC and, once you have accumulated over 0.01 BTC, transfer those Bitcoins to the Secure Bitcoin Wallet or any other self-custodial Bitcoin wallet.\n\nIf you want to spend your Bitcoin, transfer a small amount in the Instant Payment Wallet and use it to pay for your expenses. When you run out of funds in the Instant Payments Wallet, simply refill it.\n\n## What is auto-swap and how does it work?\n\nAuto-swap, also called Auto-Transfer, is a feature invented by Bull Bitcoin which automatically converts Liquid Network Bitcoin (L-BTC) to Bitcoin (BTC) once the L-BTC amount in your wallet reaches a defined threshold.\n\nAuto-swap is enabled by default:\n\n- **Maximum Instant Wallet balance:** 0.01 BTC\n- **Target balance after auto-swap:** 0.005 BTC\n- **Minimum transfer amount:** 0.005 BTC\n\nWhen your balance reaches 0.01 BTC, a transfer will be initiated as soon as you open the app and any funds in excess of 0.005 BTC will be converted from L-BTC to BTC.\n\n### Example\n\nIf you have 0.012 L-BTC and auto-swap is enabled, a transfer of 0.007 L-BTC will be initiated and your remaining L-BTC will be 0.005 L-BTC, and your BTC balance will be increased by 0.007 BTC.\n\n## How do I fund or withdraw L-BTC from the Instant Bitcoin Wallet?\n\nTo fund the wallet with \"real\" Bitcoin, click \"Receive\" and select \"Lightning Network\". To withdraw L-BTC, click \"Send\" and paste a Lightning invoice.\n\n## How does the backup work?\n\nThe Instant Bitcoin Wallet uses the same backup seed words as the Secure Bitcoin Wallet. You only need one backup for both wallets.\n\n## Should I use the Instant Payments wallet instead of a non-custodial Lightning Network Wallet?\n\nUsing a non-custodial Lightning Network wallet such as Phoenix Wallet will give you a higher degree of security. Because you will have to deal with Lightning Network channel management, it may be less convenient and harder to use.\n\nBecause Lightning channels require on-chain transactions to be opened and closed, and because you will likely need to pay a fee to Lightning service providers, it may in some cases be more expensive to use a fully non-custodial Lightning wallet.\n\nIf you are able and willing to use a fully non-custodial pure Lightning wallet, we highly recommend you try Phoenix Wallet.\n\n## What are the transaction fees?\n\nEach payment in and out of the Instant Payments Wallet implies two Liquid Network transactions. These are typically very small and should be around 0.00000050 BTC (50 sats).\n\nIf you are receiving or sending Lightning Network payments with the Secure Bitcoin Wallet, you will have to pay for two on-chain Bitcoin transactions every payment, which can be quite expensive.\n\nIn addition, the swap provider may also charge swap fees. At the moment, the swap provider (Boltz) charges the following fees:\n\n- **Receiving Lightning Network payments in the Instant Payment Wallet:** 0.25%\n- **Sending Lightning Network payments from the Instant Payment Wallet:** 0.1%\n- **Receiving Lightning Network payments in the Secure Bitcoin Wallet:** 0.5%\n- **Sending Lightning Network payments from the Secure Bitcoin Wallet:** 0.1%",
34693469
"@liquidRiskDisclosureBody": {
3470-
"description": "Long-form disclosure explaining Liquid Bitcoin and Instant Payments wallet risks"
3470+
"description": "Long-form disclosure explaining Liquid Bitcoin and Instant Payments wallet risks. Preserve lightweight formatting markers: ## section headings, ### subsection headings, > callouts, - bullets, and **bold bullet labels**."
34713471
},
34723472
"walletTypeWatchOnly": "Watch-Only",
34733473
"@walletTypeWatchOnly": {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'package:bb_mobile/core/themes/app_theme.dart';
2+
import 'package:bb_mobile/core/widgets/bottom_sheet/disclosure_bottom_sheet.dart';
3+
import 'package:bb_mobile/generated/l10n/localization.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_test/flutter_test.dart';
6+
7+
void main() {
8+
testWidgets('renders headings, callouts, and bullet lists', (tester) async {
9+
const body = '''
10+
Introductory paragraph.
11+
12+
> Recommendation
13+
> Keep only a small balance here.
14+
15+
## What is the Instant Payments Wallet?
16+
17+
Section paragraph.
18+
19+
### Example
20+
21+
- **Maximum balance:** 0.01 BTC
22+
- Plain bullet
23+
''';
24+
25+
await tester.pumpWidget(
26+
MaterialApp(
27+
theme: AppTheme.themeData(AppThemeType.light),
28+
localizationsDelegates: AppLocalizations.localizationsDelegates,
29+
supportedLocales: AppLocalizations.supportedLocales,
30+
home: const Scaffold(
31+
body: DisclosureBottomSheet(title: 'Disclosure', body: body),
32+
),
33+
),
34+
);
35+
await tester.pumpAndSettle();
36+
37+
expect(find.text('Introductory paragraph.'), findsOneWidget);
38+
expect(find.text('Recommendation'), findsOneWidget);
39+
expect(find.byIcon(Icons.warning_amber_rounded), findsOneWidget);
40+
41+
final sectionHeading = tester.widget<Text>(
42+
find.text('What is the Instant Payments Wallet?'),
43+
);
44+
expect(sectionHeading.style?.fontWeight, FontWeight.w600);
45+
46+
final exampleHeading = tester.widget<Text>(find.text('Example'));
47+
expect(exampleHeading.style?.fontWeight, FontWeight.w600);
48+
49+
expect(
50+
find.text('Maximum balance: 0.01 BTC', findRichText: true),
51+
findsOneWidget,
52+
);
53+
expect(find.text('Plain bullet'), findsOneWidget);
54+
expect(find.textContaining('**'), findsNothing);
55+
});
56+
}

0 commit comments

Comments
 (0)