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