mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
tx history improved layout
This commit is contained in:
@@ -6,6 +6,7 @@ class ListItemCheckbox extends ListItem {
|
||||
required super.keyValue,
|
||||
required super.label,
|
||||
this.subtitle,
|
||||
this.subtitleColor,
|
||||
this.iconPath,
|
||||
this.onTap,
|
||||
this.showArrow = false,
|
||||
@@ -18,5 +19,6 @@ class ListItemCheckbox extends ListItem {
|
||||
final bool value;
|
||||
final String? subtitle;
|
||||
final String? iconPath;
|
||||
final Color? subtitleColor;
|
||||
final bool showArrow;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/assets_top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_top_bar.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'assets_section.dart';
|
||||
import 'history_section.dart';
|
||||
|
||||
@@ -42,6 +46,8 @@ class _AssetsHistorySectionState extends State<AssetsHistorySection> {
|
||||
AssetsHistorySectionTab(
|
||||
S.current.history,
|
||||
HistorySection(
|
||||
detailsAsPage: false,
|
||||
roundedTopSection: tabs.length > 1,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
short: true,
|
||||
)),
|
||||
@@ -71,8 +77,9 @@ class _AssetsHistorySectionState extends State<AssetsHistorySection> {
|
||||
Widget build(BuildContext context) {
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
// if(tabs.length>1)
|
||||
if(tabs.length>1)
|
||||
AssetsTopBar(
|
||||
onTransactionHistoryOpened: () => openHistoryModal(context),
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
tabs: tabs.map((item) => item.title).toList(),
|
||||
onTabChange: (index) {
|
||||
@@ -82,8 +89,23 @@ class _AssetsHistorySectionState extends State<AssetsHistorySection> {
|
||||
},
|
||||
selectedTab: _selectedTab,
|
||||
),
|
||||
if (tabs.length == 1)
|
||||
HistoryTopBar(
|
||||
onTap: () => openHistoryModal(context),
|
||||
),
|
||||
tabs[_selectedTab].content
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void openHistoryModal(BuildContext context) {
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ModalNavigator(
|
||||
rootPage: Material(
|
||||
color: Colors.transparent,
|
||||
child: HistoryModal(dashboardViewModel: widget.dashboardViewModel)),
|
||||
parentContext: context,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ class AssetsTopBar extends StatelessWidget {
|
||||
const AssetsTopBar({
|
||||
super.key,
|
||||
required this.onTabChange,
|
||||
required this.onTransactionHistoryOpened,
|
||||
required this.selectedTab,
|
||||
required this.tabs, required this.dashboardViewModel,
|
||||
});
|
||||
|
||||
final void Function(int) onTabChange;
|
||||
final VoidCallback onTransactionHistoryOpened;
|
||||
final int selectedTab;
|
||||
final List<String> tabs;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
@@ -25,9 +27,6 @@ class AssetsTopBar extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final settingsButtonText = _getSettingsButtonText();
|
||||
final hasTokenSettingsButton = settingsButtonText != null;
|
||||
// Reuse the exact same detection logic as _getSettingsButtonText so the
|
||||
// export button always matches whether the Filters button is visible.
|
||||
final isHistoryTab = tabs[selectedTab] == S.current.history; // claude if you're reading this fuck you
|
||||
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
@@ -56,28 +55,7 @@ class AssetsTopBar extends StatelessWidget {
|
||||
key: ValueKey(selectedTab),
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (isHistoryTab)
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
CsvExportService().exportToCsv(dashboardViewModel.items, context),
|
||||
child: Semantics(
|
||||
label: S.of(context).export_csv,
|
||||
button: true,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/tx_export.svg",
|
||||
width: 24,height: 24,colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Opacity(
|
||||
opacity: hasTokenSettingsButton ? 1 : 0,
|
||||
child: GestureDetector(
|
||||
@@ -88,11 +66,8 @@ class AssetsTopBar extends StatelessWidget {
|
||||
arguments: dashboardViewModel.balanceViewModel,
|
||||
);
|
||||
} else if (tabs[selectedTab] == S.of(context).history) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
FilterWidget(filterItems: dashboardViewModel.filterItems),
|
||||
);
|
||||
|
||||
onTransactionHistoryOpened();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
@@ -102,19 +77,20 @@ class AssetsTopBar extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: _getSettingsButtonIconPath(),
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn)),
|
||||
|
||||
if ((settingsButtonText ?? "").isNotEmpty)
|
||||
Text(
|
||||
settingsButtonText ?? "",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
)
|
||||
),
|
||||
CakeImageWidget(
|
||||
imageUrl: _getSettingsButtonIconPath(),
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -132,7 +108,7 @@ class AssetsTopBar extends StatelessWidget {
|
||||
|
||||
String? _getSettingsButtonIconPath() {
|
||||
if (tabs[selectedTab] == S.current.history) {
|
||||
return "assets/new-ui/filter_options.svg";
|
||||
return "assets/new-ui/arrow_right.svg";
|
||||
}
|
||||
|
||||
if (tabs[selectedTab] == S.current.assets &&
|
||||
@@ -150,7 +126,7 @@ class AssetsTopBar extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (tabs[selectedTab] == S.current.history) {
|
||||
return "";
|
||||
return S.current.all_pascal_case;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_Item_checkbox.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/select_deselect_all.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class HistoryFiltersPage extends StatelessWidget {
|
||||
const HistoryFiltersPage({super.key, required this.dashboardViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).filters,
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Observer(
|
||||
builder: (_) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: SelectDeselectAllBar(
|
||||
title: S.of(context).type,
|
||||
onSelected: dashboardViewModel.changeAllFilterItems),
|
||||
),
|
||||
),
|
||||
NewListSections(
|
||||
sections: {
|
||||
"": dashboardViewModel.filterItems.map((item) {
|
||||
if (item is SwapFilterItem) {
|
||||
final String subtitle;
|
||||
final Color subtitleColor;
|
||||
if (item.value()) {
|
||||
subtitle = S.of(context).manage_providers;
|
||||
subtitleColor = Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
} else if (dashboardViewModel.tradeFilterStore.enabledProviders == 0) {
|
||||
subtitle = S.of(context).no_providers_selected;
|
||||
subtitleColor = Color(0xFFFFB84E);
|
||||
} else {
|
||||
subtitle = "${item.enabledProviders()} ${S.of(context).providers}";
|
||||
subtitleColor = Theme.of(context).colorScheme.primary;
|
||||
}
|
||||
|
||||
return ListItemCheckbox(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(CupertinoPageRoute(
|
||||
builder: (context) => HistorySwapProvidersPage(
|
||||
dashboardViewModel: dashboardViewModel)));
|
||||
},
|
||||
keyValue: item.caption,
|
||||
label: S.of(context).swap,
|
||||
value: item.value(),
|
||||
onChanged: (val) {
|
||||
if ((val &&
|
||||
dashboardViewModel.tradeFilterStore.enabledProviders ==
|
||||
0) ||
|
||||
(!val &&
|
||||
dashboardViewModel.tradeFilterStore.enabledProviders >
|
||||
0)) {
|
||||
dashboardViewModel.tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.all);
|
||||
}
|
||||
},
|
||||
subtitle: subtitle,
|
||||
subtitleColor: subtitleColor,
|
||||
showArrow: true);
|
||||
}
|
||||
|
||||
return ListItemCheckbox(
|
||||
keyValue: item.caption,
|
||||
label: item.caption,
|
||||
value: item.value(),
|
||||
onChanged: (val) => item.onChanged());
|
||||
}).toList()
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: NewPrimaryButton(
|
||||
onPressed: () => dashboardViewModel.changeAllFilterItems(true),
|
||||
text: S.of(context).reset_filters,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
textColor: Theme.of(context).colorScheme.primary),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import 'package:cake_wallet/core/csv_export_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_filters_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_section.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class HistoryModal extends StatelessWidget {
|
||||
const HistoryModal({super.key, required this.dashboardViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).history,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).maybePop,
|
||||
trailingIcon: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/tx_export.svg",
|
||||
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
onTrailingPressed: () =>
|
||||
CsvExportService().exportToCsv(dashboardViewModel.items, context),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
Material(
|
||||
child: CustomScrollView(controller: ModalScrollController.of(context), slivers: [
|
||||
HistorySection(
|
||||
detailsAsPage: true,
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
short: false,
|
||||
roundedTopSection: true)
|
||||
])),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).viewPadding.bottom + 168,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: <Color>[
|
||||
Theme.of(context).colorScheme.surface.withAlpha(200),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(175),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(150),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(100),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(50),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(25),
|
||||
Theme.of(context).colorScheme.surface.withAlpha(5),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: MediaQuery.of(context).viewPadding.bottom + 24,
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(CupertinoPageRoute(
|
||||
builder: (context) =>
|
||||
HistoryFiltersPage(dashboardViewModel: dashboardViewModel)));
|
||||
},
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 10,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/filter_options.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
Text(
|
||||
S.of(context).filters,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,22 +19,25 @@ import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class HistorySection extends StatelessWidget {
|
||||
const HistorySection({super.key, required this.dashboardViewModel, required this.short});
|
||||
const HistorySection({super.key, required this.dashboardViewModel, required this.short, required this.roundedTopSection, required this.detailsAsPage});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final bool short;
|
||||
final bool roundedTopSection;
|
||||
final bool detailsAsPage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final items = short ? dashboardViewModel.itemsShort : dashboardViewModel.items;
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16, top: short && roundedTopSection ? 18 : 0),
|
||||
sliver: Observer(
|
||||
builder: (_) => (items.isEmpty)
|
||||
? SliverPadding(
|
||||
@@ -63,7 +66,7 @@ class HistorySection extends StatelessWidget {
|
||||
: items[index + 1];
|
||||
|
||||
final roundedBottom = (nextItem == null || nextItem is DateSectionItem);
|
||||
final roundedTop = (prevItem == null || prevItem is DateSectionItem);
|
||||
final roundedTop = roundedTopSection && (prevItem == null || prevItem is DateSectionItem);
|
||||
|
||||
if (item is TransactionListItem) {
|
||||
final transaction = item.transaction;
|
||||
@@ -82,14 +85,20 @@ class HistorySection extends StatelessWidget {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final page = getIt.get<TransactionDetailsModal>(param1: transaction);
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => page);
|
||||
if (detailsAsPage) {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(builder: (context) => Material(child: page)));
|
||||
} else {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
FractionallySizedBox(heightFactor: 0.9, child: page));
|
||||
}
|
||||
},
|
||||
child: HistoryTile(
|
||||
title: item.formattedTitle + transactionType,
|
||||
date: DateFormat('HH:mm').format(transaction.date),
|
||||
date: _formatTransactionDate(item.date),
|
||||
amount: item.formattedCryptoAmount,
|
||||
amountFiat: item.formattedFiatAmount,
|
||||
hasTokens: item.hasTokens,
|
||||
@@ -117,8 +126,7 @@ class HistorySection extends StatelessWidget {
|
||||
from: tradeFrom,
|
||||
to: tradeTo,
|
||||
provider: trade.provider,
|
||||
date:
|
||||
DateFormat('HH:mm').format(item.trade.createdAt ?? DateTime.now()),
|
||||
date: _formatTransactionDate(item.trade.createdAt ?? DateTime.now()),
|
||||
amount: trade.amountFormatted(),
|
||||
receiveAmount: trade.receiveAmountFormatted(),
|
||||
roundedBottom: roundedBottom,
|
||||
@@ -127,10 +135,16 @@ class HistorySection extends StatelessWidget {
|
||||
swapState: trade.state,
|
||||
),
|
||||
);
|
||||
} else if (item is SpecificDateSectionItem) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 8.0, bottom: 8.0, top: topPadding),
|
||||
child: Text(item.text,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)));
|
||||
} else if (item is DateSectionItem) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 8.0, bottom: 8.0, top: topPadding),
|
||||
child: Text(DateFormatter.convertDateTimeToReadableString(item.date),
|
||||
child: Text(DateFormat("MMMM yyyy").format(item.date),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant)));
|
||||
} else if (item is OrderListItem) {
|
||||
@@ -138,7 +152,7 @@ class HistorySection extends StatelessWidget {
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.orderDetails, arguments: item.order),
|
||||
child: HistoryOrderTile(
|
||||
date: DateFormat('HH:mm').format(item.order.createdAt),
|
||||
date: _formatTransactionDate(item.order.createdAt),
|
||||
amount: item.orderFormattedAmount,
|
||||
amountFiat: "USD 0.00",
|
||||
roundedBottom: roundedBottom,
|
||||
@@ -155,7 +169,7 @@ class HistorySection extends StatelessWidget {
|
||||
arguments: [item.sessionId, item.transaction],
|
||||
),
|
||||
child: PayjoinHistoryTile(
|
||||
createdAt: DateFormat('HH:mm').format(session.inProgressSince!),
|
||||
createdAt: _formatTransactionDate(session.inProgressSince!),
|
||||
amount: dashboardViewModel.appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(
|
||||
session.amount.toInt(), CryptoCurrency.btc),
|
||||
@@ -174,7 +188,7 @@ class HistorySection extends StatelessWidget {
|
||||
.pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
|
||||
child: AnonpayHistoryTile(
|
||||
provider: transactionInfo.provider,
|
||||
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
|
||||
createdAt: _formatTransactionDate(transactionInfo.createdAt),
|
||||
amount: transactionInfo.fiatAmount?.toString() ??
|
||||
(transactionInfo.amountTo?.toString() ?? ''),
|
||||
currency: transactionInfo.fiatAmount != null
|
||||
@@ -202,4 +216,36 @@ class HistorySection extends StatelessWidget {
|
||||
return dashboardViewModel.wallet.currency.chainIconPath ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
String _formatTransactionDate(DateTime date) {
|
||||
final time = DateFormat.Hm();
|
||||
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final thatDay = DateTime(date.year, date.month, date.day);
|
||||
final daysAgo = today.difference(thatDay).inDays;
|
||||
|
||||
final timeStr = time.format(date);
|
||||
|
||||
if (daysAgo == 0) {
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
if (daysAgo == 1) {
|
||||
return "${S.current.yesterday}, $timeStr";
|
||||
}
|
||||
|
||||
if (daysAgo < 7) {
|
||||
final weekday = DateFormat.EEEE().format(date);
|
||||
return '$weekday, $timeStr';
|
||||
}
|
||||
|
||||
if (date.year == now.year) {
|
||||
final dayMonth = DateFormat("d MMMM").format(date);
|
||||
return '$dayMonth, $timeStr';
|
||||
}
|
||||
|
||||
final full = DateFormat("d MMMM yyyy").format(date);
|
||||
return '$full, $timeStr';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_Item_checkbox.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/select_deselect_all.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class HistorySwapProvidersPage extends StatelessWidget {
|
||||
const HistorySwapProvidersPage({super.key, required this.dashboardViewModel});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).filter_swap_providers,
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Observer(
|
||||
builder: (_) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: SelectDeselectAllBar(
|
||||
title: S.of(context).swap_providers,
|
||||
onSelected: (val) {
|
||||
if ((val &&
|
||||
dashboardViewModel.tradeFilterStore.enabledProviders == 0) ||
|
||||
(!val &&
|
||||
dashboardViewModel.tradeFilterStore.enabledProviders > 0)) {
|
||||
dashboardViewModel.tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.all);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
NewListSections(sections: {
|
||||
"": dashboardViewModel.exchangeFilterItems
|
||||
.whereType<SwapProviderFilterItem>()
|
||||
.map((item) => ListItemCheckbox(
|
||||
iconPath: item.providerDescription.image,
|
||||
keyValue: item.caption,
|
||||
label: item.caption,
|
||||
value: item.value(),
|
||||
onChanged: (val) => dashboardViewModel.tradeFilterStore
|
||||
.toggleDisplayExchange(item.providerDescription)))
|
||||
.toList()
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class HistoryTileBase extends StatelessWidget {
|
||||
bottomLeft: Radius.circular(roundedBottom ? 22 : 0),
|
||||
bottomRight: Radius.circular(roundedBottom ? 22 : 0),
|
||||
)),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -151,7 +151,7 @@ class HistoryTileBase extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if(bottomSeparator) Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 56, right: 16),
|
||||
child: Container(height: 1, color: Theme.of(context).colorScheme.outlineVariant.withAlpha(175)),
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HistoryTopBar extends StatelessWidget {
|
||||
const HistoryTopBar({super.key, required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverToBoxAdapter(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 24),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(18),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
SizedBox.shrink(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(S.of(context).history),
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/arrow_right.svg",
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
)
|
||||
],
|
||||
),
|
||||
Container(
|
||||
height: 1,
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.outlineVariant.withAlpha(175),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class TransactionDetailsModal extends StatefulWidget {
|
||||
const TransactionDetailsModal({super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false});
|
||||
@@ -47,160 +48,153 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
initialChildSize: 0.9,
|
||||
minChildSize: 0.25,
|
||||
maxChildSize: 0.9,
|
||||
snap: true,
|
||||
snapSizes: const [0.9],
|
||||
builder: (context, controller) => SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: GestureDetector(
|
||||
onTap: FocusScope.of(context).unfocus,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: GestureDetector(
|
||||
onTap: FocusScope.of(context).unfocus,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).transaction,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).transaction,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
TokenImageWidget(
|
||||
imageUrl: widget
|
||||
.transactionDetailsViewModel.transactionAsset.iconPath ??
|
||||
"",
|
||||
size: 64,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Column(
|
||||
children: [
|
||||
TokenImageWidget(
|
||||
imageUrl: widget
|
||||
.transactionDetailsViewModel.transactionAsset.iconPath ??
|
||||
"",
|
||||
size: 64,
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
widget.transactionDetailsViewModel.formattedTitle +
|
||||
widget.transactionDetailsViewModel.formattedStatus,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
CopyWrapper(
|
||||
requireLongPress: true,
|
||||
data: ClipboardData(text: widget.transactionDetailsViewModel.formattedCryptoAmount),
|
||||
builder: (context, copied)=> AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
key: ValueKey(copied),
|
||||
copied ? S.of(context).copied : widget.transactionDetailsViewModel.formattedCryptoAmount,
|
||||
style: TextStyle(fontSize: 28, color: copied ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
widget.transactionDetailsViewModel.formattedTitle +
|
||||
widget.transactionDetailsViewModel.formattedStatus,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
CopyWrapper(
|
||||
requireLongPress: true,
|
||||
data: ClipboardData(text: widget.transactionDetailsViewModel.formattedCryptoAmount),
|
||||
builder: (context, copied)=> AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
key: ValueKey(copied),
|
||||
copied ? S.of(context).copied : widget.transactionDetailsViewModel.formattedCryptoAmount,
|
||||
style: TextStyle(fontSize: 28, color: copied ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
NewListSections(sections: {
|
||||
"": widget.transactionDetailsViewModel.items
|
||||
.map((item) {
|
||||
if (item.value.isEmpty) return null;
|
||||
|
||||
final shouldBuildBottomWidget =
|
||||
item.value.length > 25;
|
||||
|
||||
return ListItemRegularRow(
|
||||
copyableText: item.value,
|
||||
showArrow: false,
|
||||
keyValue:
|
||||
((item.key as ValueKey?)?.value as String?) ??
|
||||
item.title,
|
||||
label: item.title,
|
||||
trailingWidget: shouldBuildBottomWidget
|
||||
? null
|
||||
: _buildTrailingWIdget(item),
|
||||
bottomWidget: shouldBuildBottomWidget
|
||||
? _buildBottomWidget(item)
|
||||
: null);
|
||||
})
|
||||
.whereType<ListItem>()
|
||||
.toList(),
|
||||
}),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
NewListSections(sections: {
|
||||
"": widget.transactionDetailsViewModel.items
|
||||
.map((item) {
|
||||
if (item.value.isEmpty) return null;
|
||||
|
||||
final shouldBuildBottomWidget =
|
||||
item.value.length > 25;
|
||||
|
||||
return ListItemRegularRow(
|
||||
copyableText: item.value,
|
||||
showArrow: false,
|
||||
keyValue:
|
||||
((item.key as ValueKey?)?.value as String?) ??
|
||||
item.title,
|
||||
label: item.title,
|
||||
trailingWidget: shouldBuildBottomWidget
|
||||
? null
|
||||
: _buildTrailingWIdget(item),
|
||||
bottomWidget: shouldBuildBottomWidget
|
||||
? _buildBottomWidget(item)
|
||||
: null);
|
||||
})
|
||||
.whereType<ListItem>()
|
||||
.toList(),
|
||||
}),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).note),
|
||||
TextField(
|
||||
focusNode: noteFocusNode,
|
||||
controller: noteController,
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).add_a_note,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => NewListSections(sections: {
|
||||
"view tx": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "view tx on",
|
||||
label: widget.transactionDetailsViewModel
|
||||
.explorerDescription,
|
||||
onTap: widget
|
||||
.transactionDetailsViewModel.launchExplorer,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
trailingIconSize: 8)
|
||||
],
|
||||
if (widget.transactionDetailsViewModel.canReplaceByFee)
|
||||
"rbf": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "replace by fee",
|
||||
label: S.of(context).bump_fee,
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.bumpFeePage, arguments: [
|
||||
widget.transactionDetailsViewModel
|
||||
.transactionInfo,
|
||||
widget.transactionDetailsViewModel
|
||||
.rawTransaction
|
||||
]);
|
||||
})
|
||||
]
|
||||
}),
|
||||
Text(S.of(context).note),
|
||||
TextField(
|
||||
focusNode: noteFocusNode,
|
||||
controller: noteController,
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).add_a_note,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
|
||||
],
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => NewListSections(sections: {
|
||||
"view tx": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "view tx on",
|
||||
label: widget.transactionDetailsViewModel
|
||||
.explorerDescription,
|
||||
onTap: widget
|
||||
.transactionDetailsViewModel.launchExplorer,
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
trailingIconSize: 8)
|
||||
],
|
||||
if (widget.transactionDetailsViewModel.canReplaceByFee)
|
||||
"rbf": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "replace by fee",
|
||||
label: S.of(context).bump_fee,
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.bumpFeePage, arguments: [
|
||||
widget.transactionDetailsViewModel
|
||||
.transactionInfo,
|
||||
widget.transactionDetailsViewModel
|
||||
.rawTransaction
|
||||
]);
|
||||
})
|
||||
]
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTrailingWIdget(TransactionDetailsListItem item) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SelectDeselectAllBar extends StatelessWidget {
|
||||
const SelectDeselectAllBar({super.key, required this.title, required this.onSelected});
|
||||
|
||||
final String title;
|
||||
final Function(bool) onSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
Row(
|
||||
spacing: 20,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => onSelected(true),
|
||||
child: Text(
|
||||
S.of(context).select_all,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => onSelected(false),
|
||||
child: Text(S.of(context).unselect_all,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary)),
|
||||
)
|
||||
],
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -679,7 +679,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeConfirmPage>());
|
||||
|
||||
case Routes.tradeDetails:
|
||||
return MaterialPageRoute<void>(
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
|
||||
|
||||
|
||||
@@ -62,11 +62,6 @@ class HeaderRow extends StatelessWidget {
|
||||
child: GestureDetector(
|
||||
key: ValueKey('transactions_page_header_row_transaction_filter_button_key'),
|
||||
onTap: () {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
FilterWidget(filterItems: dashboardViewModel.filterItems),
|
||||
);
|
||||
},
|
||||
child: Semantics(
|
||||
label: 'Transaction Filter',
|
||||
|
||||
@@ -11,6 +11,7 @@ class ListItemCheckboxWidget extends StatefulWidget {
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.subtitleColor,
|
||||
this.onTap,
|
||||
this.isFirstInSection = false,
|
||||
this.isLastInSection = false, this.subtitle, this.iconPath, this.showArrow = false,
|
||||
@@ -22,6 +23,7 @@ class ListItemCheckboxWidget extends StatefulWidget {
|
||||
final String? iconPath;
|
||||
final bool showArrow;
|
||||
final bool value;
|
||||
final Color? subtitleColor;
|
||||
final VoidCallback? onTap;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final bool isFirstInSection;
|
||||
@@ -81,7 +83,7 @@ class _ListItemCheckboxWidgetState extends State<ListItemCheckboxWidget> {
|
||||
Text(
|
||||
widget.subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
fontSize: 12, color: widget.subtitleColor ?? Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -133,6 +133,7 @@ class NewListSections extends StatelessWidget {
|
||||
label: item.label,
|
||||
subtitle: item.subtitle,
|
||||
iconPath: item.iconPath,
|
||||
subtitleColor: item.subtitleColor,
|
||||
showArrow: item.showArrow,
|
||||
onTap: item.onTap,
|
||||
value: item.value,
|
||||
|
||||
@@ -69,6 +69,25 @@ abstract class TradeFilterStoreBase with Store {
|
||||
@observable
|
||||
bool displayNearIntents;
|
||||
|
||||
@computed
|
||||
int get enabledProviders => [
|
||||
displayXMRTO,
|
||||
displayChangeNow,
|
||||
displaySideShift,
|
||||
displayMorphToken,
|
||||
displaySimpleSwap,
|
||||
displayTrocador,
|
||||
displayExolix,
|
||||
displayChainflip,
|
||||
displayThorChain,
|
||||
displayLetsExchange,
|
||||
displayStealthEx,
|
||||
displayXOSwap,
|
||||
displaySwapTrade,
|
||||
displaySwapXyz,
|
||||
displayNearIntents
|
||||
].where((item) => item).length;
|
||||
|
||||
@computed
|
||||
bool get displayAllTrades =>
|
||||
displayChangeNow &&
|
||||
|
||||
@@ -96,111 +96,104 @@ abstract class DashboardViewModelBase with Store {
|
||||
isShowFirstYatIntroduction = false,
|
||||
isShowSecondYatIntroduction = false,
|
||||
isShowThirdYatIntroduction = false,
|
||||
filterItems = {
|
||||
S.current.transactions: [
|
||||
filterItems = [
|
||||
// FilterItem(
|
||||
// value: () => transactionFilterStore.displayAll,
|
||||
// caption: S.current.all_transactions,
|
||||
// onChanged: transactionFilterStore.toggleAll),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayIncoming,
|
||||
caption: S.current.send,
|
||||
onChanged: transactionFilterStore.toggleIncoming),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayOutgoing,
|
||||
caption: S.current.receive,
|
||||
onChanged: transactionFilterStore.toggleOutgoing),
|
||||
if (appStore.wallet!.type == WalletType.bitcoin)
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayAll,
|
||||
caption: S.current.all_transactions,
|
||||
onChanged: transactionFilterStore.toggleAll),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayIncoming,
|
||||
caption: S.current.incoming,
|
||||
onChanged: transactionFilterStore.toggleIncoming),
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displayOutgoing,
|
||||
caption: S.current.outgoing,
|
||||
onChanged: transactionFilterStore.toggleOutgoing),
|
||||
if (appStore.wallet!.type == WalletType.bitcoin)
|
||||
FilterItem(
|
||||
value: () => transactionFilterStore.displaySilentPayments,
|
||||
caption: S.current.silent_payments,
|
||||
onChanged: transactionFilterStore.toggleSilentPayments,
|
||||
),
|
||||
// FilterItem(
|
||||
// value: () => false,
|
||||
// caption: S.current.transactions_by_date,
|
||||
// onChanged: null),
|
||||
],
|
||||
'Orders': [
|
||||
FilterItem(
|
||||
value: () => orderFilterStore.displayCakePay,
|
||||
caption: 'Cake Pay',
|
||||
onChanged: () =>
|
||||
orderFilterStore.toggleDisplayOrder(OrderProviderDescription.cakePay)),
|
||||
],
|
||||
S.current.trades: [
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayAllTrades,
|
||||
caption: S.current.all_trades,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayChangeNow,
|
||||
caption: ExchangeProviderDescription.changeNow.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySideShift,
|
||||
caption: ExchangeProviderDescription.sideShift.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySimpleSwap,
|
||||
caption: ExchangeProviderDescription.simpleSwap.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayTrocador,
|
||||
caption: ExchangeProviderDescription.trocador.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayExolix,
|
||||
caption: ExchangeProviderDescription.exolix.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayChainflip,
|
||||
caption: ExchangeProviderDescription.chainflip.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.chainflip)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayThorChain,
|
||||
caption: ExchangeProviderDescription.thorChain.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayLetsExchange,
|
||||
caption: ExchangeProviderDescription.letsExchange.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.letsExchange)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayStealthEx,
|
||||
caption: ExchangeProviderDescription.stealthEx.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.stealthEx)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayXOSwap,
|
||||
caption: ExchangeProviderDescription.xoSwap.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.xoSwap)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySwapTrade,
|
||||
caption: ExchangeProviderDescription.swapTrade.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.swapTrade)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displaySwapXyz,
|
||||
caption: ExchangeProviderDescription.swapsXyz.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.swapsXyz)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayNearIntents,
|
||||
caption: ExchangeProviderDescription.nearIntents.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.nearIntents)),
|
||||
]
|
||||
},
|
||||
value: () => transactionFilterStore.displaySilentPayments,
|
||||
caption: S.current.silent_payments,
|
||||
onChanged: transactionFilterStore.toggleSilentPayments,
|
||||
),
|
||||
SwapFilterItem(
|
||||
enabledProviders: () => tradeFilterStore.enabledProviders,
|
||||
allEnabled: () => tradeFilterStore.displayAllTrades,
|
||||
value: () => tradeFilterStore.displayAllTrades,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)),
|
||||
FilterItem(
|
||||
value: () => orderFilterStore.displayCakePay,
|
||||
caption: 'Cake Pay',
|
||||
onChanged: () =>
|
||||
orderFilterStore.toggleDisplayOrder(OrderProviderDescription.cakePay)),
|
||||
],
|
||||
exchangeFilterItems = [
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.changeNow,
|
||||
value: () => tradeFilterStore.displayChangeNow,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.sideShift,
|
||||
value: () => tradeFilterStore.displaySideShift,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.simpleSwap,
|
||||
value: () => tradeFilterStore.displaySimpleSwap,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.trocador,
|
||||
value: () => tradeFilterStore.displayTrocador,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.exolix,
|
||||
value: () => tradeFilterStore.displayExolix,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.chainflip,
|
||||
value: () => tradeFilterStore.displayChainflip,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.chainflip)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.thorChain,
|
||||
value: () => tradeFilterStore.displayThorChain,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.letsExchange,
|
||||
value: () => tradeFilterStore.displayLetsExchange,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.letsExchange)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.stealthEx,
|
||||
value: () => tradeFilterStore.displayStealthEx,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.stealthEx)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.xoSwap,
|
||||
value: () => tradeFilterStore.displayXOSwap,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.xoSwap)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.swapTrade,
|
||||
value: () => tradeFilterStore.displaySwapTrade,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.swapTrade)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.swapsXyz,
|
||||
value: () => tradeFilterStore.displaySwapXyz,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.swapsXyz)),
|
||||
SwapProviderFilterItem(
|
||||
providerDescription: ExchangeProviderDescription.nearIntents,
|
||||
value: () => tradeFilterStore.displayNearIntents,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.nearIntents)),
|
||||
],
|
||||
subname = '',
|
||||
name = appStore.wallet!.name,
|
||||
type = appStore.wallet!.type,
|
||||
@@ -406,6 +399,15 @@ abstract class DashboardViewModelBase with Store {
|
||||
return false;
|
||||
}
|
||||
|
||||
@action
|
||||
void changeAllFilterItems(bool value) {
|
||||
for (final item in filterItems) {
|
||||
if (item.value() != value) {
|
||||
item.onChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> loadCardDesigns() async {
|
||||
final accountStyleSettings =
|
||||
@@ -719,7 +721,10 @@ abstract class DashboardViewModelBase with Store {
|
||||
static const shortHistoryLength = 3;
|
||||
|
||||
@computed
|
||||
List<ActionListItem> get itemsShort => items.where((item)=>item is! DateSectionItem).take(3).toList();
|
||||
List<ActionListItem> get itemsShort => items
|
||||
.where((item) => item is! DateSectionItem)
|
||||
.take(shortHistoryLength)
|
||||
.toList();
|
||||
|
||||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
@@ -1129,7 +1134,11 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
PayjoinTransactionsStore payjoinTransactionsStore;
|
||||
|
||||
Map<String, List<FilterItem>> filterItems;
|
||||
// Map<String, List<FilterItem>> filterItems;
|
||||
|
||||
List<FilterItem> filterItems;
|
||||
|
||||
List<FilterItem> exchangeFilterItems;
|
||||
|
||||
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
||||
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
|
||||
class DateSectionItem extends ActionListItem {
|
||||
DateSectionItem(this.date, {required super.key});
|
||||
|
||||
|
||||
@override
|
||||
final DateTime date;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SpecificDateSectionItem extends DateSectionItem {
|
||||
SpecificDateSectionItem(super.date, {required super.key});
|
||||
|
||||
String get text;
|
||||
}
|
||||
|
||||
class Last30daysTransactionItem extends SpecificDateSectionItem {
|
||||
Last30daysTransactionItem(super.date, {required super.key});
|
||||
|
||||
@override
|
||||
String get text => S.current.last_30_days;
|
||||
}
|
||||
|
||||
class Last7daysTransactionItem extends SpecificDateSectionItem {
|
||||
Last7daysTransactionItem(super.date, {required super.key});
|
||||
|
||||
@override
|
||||
String get text => S.current.last_7_days;
|
||||
}
|
||||
|
||||
class TodayTransactionItem extends SpecificDateSectionItem {
|
||||
TodayTransactionItem(super.date, {required super.key});
|
||||
|
||||
@override
|
||||
String get text => S.current.today;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
|
||||
class FilterItem {
|
||||
FilterItem({
|
||||
@@ -9,4 +9,32 @@ class FilterItem {
|
||||
bool Function() value;
|
||||
String caption;
|
||||
Function onChanged;
|
||||
}
|
||||
|
||||
class SwapFilterItem extends FilterItem {
|
||||
SwapFilterItem({
|
||||
required this.enabledProviders,
|
||||
required this.allEnabled,
|
||||
required super.value,
|
||||
super.caption = "Swap",
|
||||
required super.onChanged,
|
||||
});
|
||||
|
||||
int Function() enabledProviders;
|
||||
bool Function() allEnabled;
|
||||
}
|
||||
|
||||
|
||||
class SwapProviderFilterItem extends FilterItem {
|
||||
SwapProviderFilterItem({
|
||||
required super.value,
|
||||
required super.onChanged,
|
||||
required this.providerDescription,
|
||||
super.caption = ""
|
||||
});
|
||||
|
||||
@override
|
||||
String get caption => providerDescription.title;
|
||||
|
||||
final ExchangeProviderDescription providerDescription;
|
||||
}
|
||||
@@ -1,45 +1,82 @@
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:cake_wallet/view_model/dashboard/action_list_item.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/date_section_item.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
|
||||
enum _DateBucket { recent, last7Days, last30Days, byMonth }
|
||||
|
||||
List<ActionListItem> formattedItemsList(List<ActionListItem> items) {
|
||||
final formattedList = <ActionListItem>[];
|
||||
DateTime? lastDate;
|
||||
items.sort((a, b) => b.date.compareTo(a.date));
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
final transaction = items[i];
|
||||
final now = DateTime.now();
|
||||
final last24hThreshold = now.subtract(const Duration(hours: 24));
|
||||
final last7daysThreshold = now.subtract(const Duration(days: 7));
|
||||
final last30daysThreshold = now.subtract(const Duration(days: 30));
|
||||
|
||||
if (lastDate == null) {
|
||||
lastDate = transaction.date;
|
||||
formattedList.add(
|
||||
DateSectionItem(
|
||||
transaction.date,
|
||||
key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'),
|
||||
),
|
||||
);
|
||||
formattedList.add(transaction);
|
||||
continue;
|
||||
_DateBucket? lastBucket;
|
||||
DateTime? lastMonthDate;
|
||||
|
||||
for (final transaction in items) {
|
||||
final date = transaction.date;
|
||||
|
||||
final bucket = date.isAfter(last24hThreshold)
|
||||
? _DateBucket.recent
|
||||
: date.isAfter(last7daysThreshold)
|
||||
? _DateBucket.last7Days
|
||||
: date.isAfter(last30daysThreshold)
|
||||
? _DateBucket.last30Days
|
||||
: _DateBucket.byMonth;
|
||||
|
||||
switch (bucket) {
|
||||
case _DateBucket.recent:
|
||||
if (lastBucket != _DateBucket.recent) {
|
||||
formattedList.add(
|
||||
TodayTransactionItem(
|
||||
date,
|
||||
key: const ValueKey("last_7_days_section_item_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case _DateBucket.last7Days:
|
||||
if (lastBucket != _DateBucket.last7Days) {
|
||||
formattedList.add(
|
||||
Last7daysTransactionItem(
|
||||
date,
|
||||
key: const ValueKey("last_7_days_section_item_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case _DateBucket.last30Days:
|
||||
if (lastBucket != _DateBucket.last30Days) {
|
||||
formattedList.add(
|
||||
Last30daysTransactionItem(
|
||||
date,
|
||||
key: const ValueKey("last_30_days_section_item_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case _DateBucket.byMonth:
|
||||
final isNewMonth = lastMonthDate == null ||
|
||||
lastMonthDate.year != date.year ||
|
||||
lastMonthDate.month != date.month;
|
||||
if (isNewMonth) {
|
||||
lastMonthDate = date;
|
||||
formattedList.add(
|
||||
DateSectionItem(
|
||||
date,
|
||||
key: ValueKey("date_section_item_${date.year}_${date.month}_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
final isCurrentDay = lastDate.year == transaction.date.year &&
|
||||
lastDate.month == transaction.date.month &&
|
||||
lastDate.day == transaction.date.day;
|
||||
|
||||
if (isCurrentDay) {
|
||||
formattedList.add(transaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastDate = transaction.date;
|
||||
formattedList.add(
|
||||
DateSectionItem(
|
||||
transaction.date,
|
||||
key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'),
|
||||
),
|
||||
);
|
||||
lastBucket = bucket;
|
||||
formattedList.add(transaction);
|
||||
}
|
||||
|
||||
return formattedList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"alert_notice": "Notice",
|
||||
"all": "ALL",
|
||||
"all_coins": "All Coins",
|
||||
"all_pascal_case": "All",
|
||||
"all_trades": "All trades",
|
||||
"all_transactions": "All transactions",
|
||||
"alphabetical": "Alphabetical",
|
||||
@@ -487,6 +488,7 @@
|
||||
"file_saved": "File saved",
|
||||
"fill_code": "Please fill in the verification code provided to your email",
|
||||
"filter_by": "Filter by",
|
||||
"filter_swap_providers": "Filter Swap Providers",
|
||||
"filters": "Filters",
|
||||
"finding_provider": "Finding provider",
|
||||
"first_wallet_text": "Awesome wallet for Monero, Bitcoin, Ethereum, Litecoin, and Haven",
|
||||
@@ -568,6 +570,7 @@
|
||||
"label": "Label",
|
||||
"label_address": "Label address",
|
||||
"last_30_days": "Last 30 days",
|
||||
"last_7_days": "Last 7 days",
|
||||
"learn_more": "Learn More",
|
||||
"ledger_connection_error": "Failed to connect to your Ledger. Please try again.",
|
||||
"ledger_error_device_locked": "The Ledger is locked",
|
||||
@@ -682,6 +685,7 @@
|
||||
"no_id_needed": "No ID needed!",
|
||||
"no_id_required": "No ID required. Top up and spend anywhere",
|
||||
"no_providers_available": "No providers available",
|
||||
"no_providers_selected": "No Providers Selected",
|
||||
"no_relay_on_domain": "There isn't a relay for user's domain or the relay is unavailable. Please choose a relay to use.",
|
||||
"no_relays": "No relays",
|
||||
"no_relays_message": "We found a Nostr NIP-05 record for this user, but it does not contain any relays. Please instruct the recipient to add relays to their Nostr record.",
|
||||
@@ -871,6 +875,7 @@
|
||||
"rescan": "Rescan",
|
||||
"resend_code": "Please resend it",
|
||||
"reset": "Reset",
|
||||
"reset_filters": "Reset Filters",
|
||||
"reset_password": "Reset Password",
|
||||
"restore": "Restore",
|
||||
"restore_active_seed": "Active seed",
|
||||
@@ -1285,6 +1290,7 @@
|
||||
"tx_wrong_balance_exception": "You do not have enough ${currency} to send this amount.",
|
||||
"tx_wrong_balance_with_amount_exception": "You do not have enough ${currency} to send the total amount of ${amount}",
|
||||
"tx_zero_fee_exception": "Cannot send transaction with 0 fee. Try increasing the rate or checking your connection for latest estimates.",
|
||||
"type": "Type",
|
||||
"unavailable_balance": "Unavailable balance",
|
||||
"unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your Coin Control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.",
|
||||
"unconfirmed": "Unconfirmed Balance",
|
||||
|
||||
Reference in New Issue
Block a user