tx history improved layout
What changed, and why it matters
This commit is a user-interface redesign of the transaction history screen in the Cake Wallet app. It changes how transaction lists are grouped by date, moves filter and export buttons into a new modal, and adds new UI components for selecting/deselecting filters. There is no indication of any security fix or vulnerability being addressed.
No security action required. Treat as a normal UI/UX refactor and review through standard QA.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is a large UI refactor of the transaction history feature. It introduces new widgets (HistoryModal, HistoryFiltersPage, HistorySwapProvidersPage, HistoryTopBar, SelectDeselectAllBar), restructures filter items from a grouped map to flat lists, adds date bucketing (today, last 7 days, last 30 days, by month), and updates navigation/presentation of transaction details. The CSV export functionality is relocated but not altered in behavior. No cryptographic, networking, storage, or permission changes are present.
Changed components
lib/new-ui/widgets/coins_page/assets_history/*lib/view_model/dashboard/dashboard_view_model.dartlib/view_model/dashboard/formatted_item_list.dartlib/view_model/dashboard/filter_item.dartlib/view_model/dashboard/date_section_item.dartlib/store/dashboard/trade_filter_store.dartlib/src/widgets/new_list_row/*Inspect captured patch +915 / −344
diff --git a/lib/entities/new_ui_entities/list_item/list_Item_checkbox.dart b/lib/entities/new_ui_entities/list_item/list_Item_checkbox.dart
index c02f076e..2f84a372 100644
--- a/lib/entities/new_ui_entities/list_item/list_Item_checkbox.dart
+++ b/lib/entities/new_ui_entities/list_item/list_Item_checkbox.dart
@@ -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;
}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart b/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart
index e57f46c7..ecc16309 100644
--- a/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart
+++ b/lib/new-ui/widgets/coins_page/assets_history/assets_history_section.dart
@@ -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,
+ ));
+ }
}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/assets_top_bar.dart b/lib/new-ui/widgets/coins_page/assets_history/assets_top_bar.dart
index 0de34d70..a99e8f13 100644
--- a/lib/new-ui/widgets/coins_page/assets_history/assets_top_bar.dart
+++ b/lib/new-ui/widgets/coins_page/assets_history/assets_top_bar.dart
@@ -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;
}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_filters_page.dart b/lib/new-ui/widgets/coins_page/assets_history/history_filters_page.dart
new file mode 100644
index 00000000..3a71a86c
--- /dev/null
+++ b/lib/new-ui/widgets/coins_page/assets_history/history_filters_page.dart
@@ -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),
+ ))
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_modal.dart b/lib/new-ui/widgets/coins_page/assets_history/history_modal.dart
new file mode 100644
index 00000000..9b93a9d0
--- /dev/null
+++ b/lib/new-ui/widgets/coins_page/assets_history/history_modal.dart
@@ -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),
+ )
+ ],
+ ),
+ ),
+ ),
+ ),
+ ))
+ ],
+ ))
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_section.dart b/lib/new-ui/widgets/coins_page/assets_history/history_section.dart
index e4ecd623..fd2e0814 100644
--- a/lib/new-ui/widgets/coins_page/assets_history/history_section.dart
+++ b/lib/new-ui/widgets/coins_page/assets_history/history_section.dart
@@ -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';
+ }
}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart b/lib/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart
new file mode 100644
index 00000000..1010e4c4
--- /dev/null
+++ b/lib/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart
@@ -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()
+ }),
+ ],
+ ),
+ ),
+ ),
+ ))
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_tile_base.dart b/lib/new-ui/widgets/coins_page/assets_history/history_tile_base.dart
index 44fe3882..df8e51cd 100644
--- a/lib/new-ui/widgets/coins_page/assets_history/history_tile_base.dart
+++ b/lib/new-ui/widgets/coins_page/assets_history/history_tile_base.dart
@@ -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)),
diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_top_bar.dart b/lib/new-ui/widgets/coins_page/assets_history/history_top_bar.dart
new file mode 100644
index 00000000..c8749b73
--- /dev/null
+++ b/lib/new-ui/widgets/coins_page/assets_history/history_top_bar.dart
@@ -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),
+ )
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
index aa9b522a..06bbfd50 100644
--- a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
+++ b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
@@ -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),
- child: Column(
- spacing: 12,
- children: [
- NewListSections(sections: {
- "": widget.transactionDetailsViewModel.items
- .map((item) {
- if (item.value.isEmpty) return null;
+ 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;
+ 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
- ]);
- })
- ]
- }),
+ 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),
)
],
),
),
- 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) {
diff --git a/lib/new-ui/widgets/select_deselect_all.dart b/lib/new-ui/widgets/select_deselect_all.dart
new file mode 100644
index 00000000..ca6711ed
--- /dev/null
+++ b/lib/new-ui/widgets/select_deselect_all.dart
@@ -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)),
+ )
+ ],
+ )
+ ]);
+ }
+}
diff --git a/lib/router.dart b/lib/router.dart
index 638329ae..bd1d5ad0 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -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));
diff --git a/lib/src/screens/dashboard/widgets/header_row.dart b/lib/src/screens/dashboard/widgets/header_row.dart
index 5b89c0e8..c8d7c86c 100644
--- a/lib/src/screens/dashboard/widgets/header_row.dart
+++ b/lib/src/screens/dashboard/widgets/header_row.dart
@@ -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',
diff --git a/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart b/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart
index 82332872..a2a75d55 100644
--- a/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart
+++ b/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart
@@ -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),
)
],
),
diff --git a/lib/src/widgets/new_list_row/new_list_section.dart b/lib/src/widgets/new_list_row/new_list_section.dart
index 8abef379..ad78013c 100644
--- a/lib/src/widgets/new_list_row/new_list_section.dart
+++ b/lib/src/widgets/new_list_row/new_list_section.dart
@@ -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,
diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart
index 345a37b6..abc4c337 100644
--- a/lib/store/dashboard/trade_filter_store.dart
+++ b/lib/store/dashboard/trade_filter_store.dart
@@ -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 &&
diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart
index 1a2e476e..02c373f0 100644
--- a/lib/view_model/dashboard/dashboard_view_model.dart
+++ b/lib/view_model/dashboard/dashboard_view_model.dart
@@ -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;
diff --git a/lib/view_model/dashboard/date_section_item.dart b/lib/view_model/dashboard/date_section_item.dart
index 75250a7e..5bc607dc 100644
--- a/lib/view_model/dashboard/date_section_item.dart
+++ b/lib/view_model/dashboard/date_section_item.dart
@@ -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;
-}
\ No newline at end of file
+}
+
+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;
+}
diff --git a/lib/view_model/dashboard/filter_item.dart b/lib/view_model/dashboard/filter_item.dart
index 0944e899..ffd0a35a 100644
--- a/lib/view_model/dashboard/filter_item.dart
+++ b/lib/view_model/dashboard/filter_item.dart
@@ -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;
}
\ No newline at end of file
diff --git a/lib/view_model/dashboard/formatted_item_list.dart b/lib/view_model/dashboard/formatted_item_list.dart
index 04464ca8..2348222c 100644
--- a/lib/view_model/dashboard/formatted_item_list.dart
+++ b/lib/view_model/dashboard/formatted_item_list.dart
@@ -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];
-
- if (lastDate == null) {
- lastDate = transaction.date;
- formattedList.add(
- DateSectionItem(
- transaction.date,
- key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'),
- ),
- );
- formattedList.add(transaction);
- continue;
- }
+ 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));
+
+ _DateBucket? lastBucket;
+ DateTime? lastMonthDate;
+
+ for (final transaction in items) {
+ final date = transaction.date;
- final isCurrentDay = lastDate.year == transaction.date.year &&
- lastDate.month == transaction.date.month &&
- lastDate.day == transaction.date.day;
+ final bucket = date.isAfter(last24hThreshold)
+ ? _DateBucket.recent
+ : date.isAfter(last7daysThreshold)
+ ? _DateBucket.last7Days
+ : date.isAfter(last30daysThreshold)
+ ? _DateBucket.last30Days
+ : _DateBucket.byMonth;
- if (isCurrentDay) {
- formattedList.add(transaction);
- continue;
+ 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;
}
- lastDate = transaction.date;
- formattedList.add(
- DateSectionItem(
- transaction.date,
- key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'),
- ),
- );
+ lastBucket = bucket;
formattedList.add(transaction);
}
return formattedList;
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index b7f1832f..19a6820e 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -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",
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.