tx history improved layout

This commit is contained in:
Robert Malikowski
2026-06-26 01:40:34 +02:00
parent 7c7ddf8205
commit ac60a6ecce
21 changed files with 916 additions and 345 deletions
@@ -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
View File
@@ -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 &&
+115 -106
View File
@@ -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;
}
+29 -1
View File
@@ -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;
}
}