Build exchange workspace and shared transaction services (#28)
This commit is contained in:
1 parent
52b7135eda
commit
c496da8a55
48 files changed
+4757
-120
No files matched your search
@@ -0,0 +1,245 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:sxchnge_wallet/sxchnge_wallet.dart';
|
||||
|
||||
import 'activity.dart';
|
||||
import 'copy.dart';
|
||||
|
||||
import '../generated/i18n/app_localizations.dart';
|
||||
|
||||
class AccountsPage extends StatelessWidget {
|
||||
const AccountsPage({super.key, this.accountId});
|
||||
final String? accountId;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final p = context.watch<WalletProvider>();
|
||||
final l = AppLocalizations.of(context)!;
|
||||
final t = Theme.of(context);
|
||||
final c = t.colorScheme;
|
||||
final accounts = p.data?.accounts ?? [];
|
||||
final selected = accountId == null
|
||||
? accounts.firstOrNull
|
||||
: p.account(accountId);
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 1120),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(l.wHeadline, style: t.textTheme.displayMedium),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: l.wRefresh,
|
||||
onPressed: p.loading ? null : p.refresh,
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(l.wSubheading, style: t.textTheme.bodyLarge),
|
||||
const SizedBox(height: 28),
|
||||
if (p.loading) const LinearProgressIndicator(),
|
||||
if (p.failed)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(l.wUnavailable, style: TextStyle(color: c.error)),
|
||||
),
|
||||
if (!p.loading && accounts.isEmpty) Text(l.wEmptyAccounts),
|
||||
if (accountId != null &&
|
||||
p.account(accountId) == null &&
|
||||
!p.loading)
|
||||
Text(l.wNotFound),
|
||||
if (selected != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
Text(selected.currency, style: t.textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
for (final kind in TransferKind.values)
|
||||
_action(
|
||||
context,
|
||||
kind,
|
||||
selected,
|
||||
primary:
|
||||
kind ==
|
||||
(selected.available == 0
|
||||
? TransferKind.topUp
|
||||
: TransferKind.exchange),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
LayoutBuilder(
|
||||
builder: (context, box) {
|
||||
final width = box.maxWidth >= 660
|
||||
? (box.maxWidth - 32) / 3
|
||||
: box.maxWidth;
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
for (var i = 0; i < accounts.length; i++)
|
||||
SizedBox(
|
||||
width: width,
|
||||
child: _AccountCard(
|
||||
account: accounts[i],
|
||||
selected: accounts[i].id == selected?.id,
|
||||
index: i,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l.wRecent, style: t.textTheme.headlineSmall),
|
||||
TextButton(
|
||||
onPressed: () => context.go('/activity'),
|
||||
child: Text(l.wSeeAll),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...?p.data?.operations
|
||||
.where(
|
||||
(o) =>
|
||||
accountId == null ||
|
||||
o.quote.request.source == accountId ||
|
||||
o.quote.request.destination == accountId,
|
||||
)
|
||||
.take(5)
|
||||
.map((o) => OperationTile(operation: o)),
|
||||
if (!(p.data?.operations.any(
|
||||
(o) =>
|
||||
accountId == null ||
|
||||
o.quote.request.source == accountId ||
|
||||
o.quote.request.destination == accountId,
|
||||
) ??
|
||||
false))
|
||||
Container(
|
||||
padding: const EdgeInsets.all(28),
|
||||
decoration: BoxDecoration(
|
||||
color: c.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: Text(l.wEmptyActivity),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _action(
|
||||
BuildContext context,
|
||||
TransferKind kind,
|
||||
CurrencyAccount a, {
|
||||
required bool primary,
|
||||
}) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
void go() => context.go(
|
||||
'/${kind.name == "topUp" ? "top-up" : kind.name}',
|
||||
extra: a.id,
|
||||
);
|
||||
return primary
|
||||
? FilledButton.icon(
|
||||
key: ValueKey('${kind.name}-${a.id}'),
|
||||
onPressed: go,
|
||||
icon: Icon(kindIcon(kind)),
|
||||
label: Text(kindText(l, kind)),
|
||||
)
|
||||
: FilledButton.tonalIcon(
|
||||
key: ValueKey('${kind.name}-${a.id}'),
|
||||
onPressed: go,
|
||||
icon: Icon(kindIcon(kind)),
|
||||
label: Text(kindText(l, kind)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountCard extends StatelessWidget {
|
||||
const _AccountCard({
|
||||
required this.account,
|
||||
required this.selected,
|
||||
required this.index,
|
||||
});
|
||||
final CurrencyAccount account;
|
||||
final bool selected;
|
||||
final int index;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
final t = Theme.of(context);
|
||||
final c = t.colorScheme;
|
||||
final bg = selected
|
||||
? c.primary
|
||||
: index == 1
|
||||
? c.tertiaryContainer
|
||||
: c.primaryContainer;
|
||||
final fg = selected
|
||||
? c.onPrimary
|
||||
: index == 1
|
||||
? c.onTertiaryContainer
|
||||
: c.onPrimaryContainer;
|
||||
return Semantics(
|
||||
selected: selected,
|
||||
child: Material(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: InkWell(
|
||||
key: ValueKey('account-${account.id}'),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
onTap: () => context.go('/accounts/${account.id}'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
account.currency,
|
||||
style: t.textTheme.titleLarge?.copyWith(color: fg),
|
||||
),
|
||||
const Spacer(),
|
||||
Icon(
|
||||
selected
|
||||
? Icons.check_circle_outline
|
||||
: Icons.arrow_outward_rounded,
|
||||
color: fg,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(l.wAvailable, style: TextStyle(color: fg)),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
money(context, account, account.available),
|
||||
key: ValueKey('balance-${account.id}'),
|
||||
style: t.textTheme.headlineMedium?.copyWith(color: fg),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user