From 12aab7861b96b14582b2dcb26e459ffd33d2b991 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 27 Mar 2026 21:54:46 +0100 Subject: [PATCH 01/40] charts (wip) --- lib/di.dart | 6 ++++++ lib/entities/new_main_actions.dart | 14 +++++++------- lib/new-ui/new_dashboard.dart | 3 ++- lib/new-ui/pages/charts_page.dart | 13 +++++++++++++ lib/new-ui/viewmodels/charts_bloc.dart | 13 +++++++++++++ lib/new-ui/viewmodels/charts_event.dart | 4 ++++ lib/new-ui/viewmodels/charts_state.dart | 6 ++++++ 7 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 lib/new-ui/pages/charts_page.dart create mode 100644 lib/new-ui/viewmodels/charts_bloc.dart create mode 100644 lib/new-ui/viewmodels/charts_event.dart create mode 100644 lib/new-ui/viewmodels/charts_state.dart diff --git a/lib/di.dart b/lib/di.dart index 715c88660a..0b80d2d969 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -52,12 +52,14 @@ import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/new-ui/new_dashboard.dart'; import 'package:cake_wallet/new-ui/pages/about_page.dart'; import 'package:cake_wallet/new-ui/pages/account_customizer.dart'; +import 'package:cake_wallet/new-ui/pages/charts_page.dart'; import 'package:cake_wallet/new-ui/pages/coin_control_page.dart'; import 'package:cake_wallet/new-ui/pages/addresses_page.dart'; import 'package:cake_wallet/new-ui/pages/home_page.dart'; import 'package:cake_wallet/new-ui/pages/send_page.dart'; import 'package:cake_wallet/new-ui/pages/lightning_username_page.dart'; import 'package:cake_wallet/new-ui/pages/receive_page.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts_bloc.dart'; import 'package:cake_wallet/new-ui/viewmodels/lightning_username/lightning_username_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart'; @@ -603,6 +605,10 @@ Future setup({ (displayMode == BitcoinAmountDisplayMode.satoshiForLightning && lightningMode))); }); + getIt.registerFactory(()=>ChartsBloc()); + + getIt.registerFactory(()=>ChartsPage(chartsBloc: getIt.get(),)); + getIt.registerFactory(() => AccountCreationModal( accountEditOrCreateViewModel: getIt.get())); diff --git a/lib/entities/new_main_actions.dart b/lib/entities/new_main_actions.dart index 519b914ff9..8616c8bae3 100644 --- a/lib/entities/new_main_actions.dart +++ b/lib/entities/new_main_actions.dart @@ -24,7 +24,7 @@ class NewMainActions { walletsAction, contactsAction, appsAction, - //chartsAction, + chartsAction, ]; static NewMainActions homeAction = NewMainActions._( @@ -56,10 +56,10 @@ class NewMainActions { onTap: () {}, ); - // static NewMainActions chartsAction = NewMainActions._( - // name: (context) => 'Charts', //TODO S.of(context).charts, - // image: 'assets/new-ui/navbar/charts.svg', - // key: ValueKey('dashboard_page_charts_action_button_key'), - // onTap: () {}, - // ); + static NewMainActions chartsAction = NewMainActions._( + name: (context) => 'Charts', //TODO S.of(context).charts, + image: 'assets/new-ui/navbar/charts.svg', + key: ValueKey('dashboard_page_charts_action_button_key'), + onTap: () {}, + ); } diff --git a/lib/new-ui/new_dashboard.dart b/lib/new-ui/new_dashboard.dart index a285e9cac4..b236d9d250 100644 --- a/lib/new-ui/new_dashboard.dart +++ b/lib/new-ui/new_dashboard.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/new-ui/pages/charts_page.dart'; import 'package:cake_wallet/new-ui/pages/home_page.dart'; import 'package:cake_wallet/new-ui/widgets/changelog_modal.dart'; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; @@ -34,7 +35,7 @@ class NewDashboard extends StatefulWidget { getIt.get(), getIt.get(), getIt.get(), - Placeholder(), + getIt.get() ]; @override diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart new file mode 100644 index 0000000000..a54604c817 --- /dev/null +++ b/lib/new-ui/pages/charts_page.dart @@ -0,0 +1,13 @@ +import 'package:cake_wallet/new-ui/viewmodels/charts_bloc.dart'; +import 'package:flutter/material.dart'; + +class ChartsPage extends StatelessWidget { + const ChartsPage({super.key, required this.chartsBloc}); + + final ChartsBloc chartsBloc; + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/new-ui/viewmodels/charts_bloc.dart b/lib/new-ui/viewmodels/charts_bloc.dart new file mode 100644 index 0000000000..2f9ad1f9a5 --- /dev/null +++ b/lib/new-ui/viewmodels/charts_bloc.dart @@ -0,0 +1,13 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'charts_event.dart'; +part 'charts_state.dart'; + +class ChartsBloc extends Bloc { + ChartsBloc() : super(ChartsInitial()) { + on((event, emit) { + // TODO: implement event handler + }); + } +} diff --git a/lib/new-ui/viewmodels/charts_event.dart b/lib/new-ui/viewmodels/charts_event.dart new file mode 100644 index 0000000000..dbf5a55bb0 --- /dev/null +++ b/lib/new-ui/viewmodels/charts_event.dart @@ -0,0 +1,4 @@ +part of 'charts_bloc.dart'; + +@immutable +sealed class ChartsEvent {} diff --git a/lib/new-ui/viewmodels/charts_state.dart b/lib/new-ui/viewmodels/charts_state.dart new file mode 100644 index 0000000000..719b1af2c1 --- /dev/null +++ b/lib/new-ui/viewmodels/charts_state.dart @@ -0,0 +1,6 @@ +part of 'charts_bloc.dart'; + +@immutable +sealed class ChartsState {} + +final class ChartsInitial extends ChartsState {} From da10776411f7c49d46a4e0a43e44fdbe266dae21 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 2 Apr 2026 01:41:08 +0200 Subject: [PATCH 02/40] charts (wip) --- lib/new-ui/pages/charts_page.dart | 24 +- .../widgets/charts_page/chart_view.dart | 398 ++++++++++++++++++ .../widgets/new_main_navbar_widget.dart | 2 +- pubspec_base.yaml | 3 +- res/pictures/favorite.svg | 3 + res/pictures/price_change_arrow.svg | 3 + 6 files changed, 430 insertions(+), 3 deletions(-) create mode 100644 lib/new-ui/widgets/charts_page/chart_view.dart create mode 100644 res/pictures/favorite.svg create mode 100644 res/pictures/price_change_arrow.svg diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart index a54604c817..9739d85dda 100644 --- a/lib/new-ui/pages/charts_page.dart +++ b/lib/new-ui/pages/charts_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/new-ui/viewmodels/charts_bloc.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; import 'package:flutter/material.dart'; class ChartsPage extends StatelessWidget { @@ -8,6 +9,27 @@ class ChartsPage extends StatelessWidget { @override Widget build(BuildContext context) { - return const Placeholder(); + return Container( + height: MediaQuery.of(context).size.height, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surfaceDim, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0), + child: Column(children: [ + ChartHeader() + ],), + ), + ), + ); } } diff --git a/lib/new-ui/widgets/charts_page/chart_view.dart b/lib/new-ui/widgets/charts_page/chart_view.dart new file mode 100644 index 0000000000..4f5ac103d5 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/chart_view.dart @@ -0,0 +1,398 @@ +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class PriceChangeDirection { + final Color color; + final String symbol; + + const PriceChangeDirection._(this.color, this.symbol); + + static const up = PriceChangeDirection._(Color(0xFF6FC84E), "+"); + static const down = PriceChangeDirection._(Color(0xFFEA696F), "-"); +} + +class ChartRange { + final Duration? duration; + final String displayText; + + const ChartRange._(this.duration, this.displayText); + + static const oneHour = ChartRange._(Duration(hours: 1), "1H"); + static const oneDay = ChartRange._(Duration(days: 1), "1D"); + static const sevenDays = ChartRange._(Duration(days: 7), "7D"); + static const thirtyDays = ChartRange._(Duration(days: 30), "30D"); + static const oneYear = ChartRange._(Duration(days: 365), "1Y"); + static const all = ChartRange._(null, "ALL"); + + static const ranges = [oneHour, oneDay, sevenDays, thirtyDays, oneYear, all]; +} + +class ChartRangeSelector extends StatelessWidget { + const ChartRangeSelector({super.key, required this.selectedRange, required this.onRangeSelected}); + + final ChartRange selectedRange; + final Function(ChartRange) onRangeSelected; + + static const double optionSize = 36; + static const double optionPadding = 24; + static const Duration switchDuration = Duration(milliseconds: 250); + + double pillPosition(int selectedIndex) => selectedIndex * (optionSize + optionPadding); + + @override + Widget build(BuildContext context) { + final selectedIndex = ChartRange.ranges.indexOf(selectedRange); + + return Stack( + children: [ + AnimatedPositioned( + curve: Curves.easeOutCubic, + left: pillPosition(selectedIndex), + child: Container( + height: optionSize, + width: optionSize, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onSurface.withAlpha(25), + borderRadius: BorderRadius.circular(999999)), + ), + duration: switchDuration), + Row( + spacing: optionPadding, + children: ChartRange.ranges.map((item) { + final selected = selectedRange == item; + return GestureDetector( + onTap: ()=>onRangeSelected(item), + child: Container( + width: optionSize, + height: optionSize, + child: AnimatedDefaultTextStyle( + duration: switchDuration, + style: TextStyle( + fontWeight: selected ? FontWeight.w400 : FontWeight.w500, + color: selected + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).colorScheme.onSurfaceVariant), + child: Center(child: Text(item.displayText))), + ), + ); + }).toList(), + ) + ], + ); + } +} + +Map get chartMockData { + final DateTime now = DateTime.now(); + + return { + now.subtract(const Duration(hours: 24, minutes: 0)): "120.50", + now.subtract(const Duration(hours: 23, minutes: 15)): "135.20", + now.subtract(const Duration(hours: 22, minutes: 30)): "105.00", + now.subtract(const Duration(hours: 21, minutes: 45)): "160.75", + now.subtract(const Duration(hours: 21, minutes: 0)): "140.00", + now.subtract(const Duration(hours: 20, minutes: 15)): "210.25", + now.subtract(const Duration(hours: 19, minutes: 30)): "185.50", + now.subtract(const Duration(hours: 18, minutes: 45)): "300.00", + now.subtract(const Duration(hours: 18, minutes: 0)): "250.50", + now.subtract(const Duration(hours: 17, minutes: 15)): "310.00", + now.subtract(const Duration(hours: 16, minutes: 30)): "310.00", + now.subtract(const Duration(hours: 15, minutes: 45)): "220.25", + now.subtract(const Duration(hours: 15, minutes: 0)): "300.50", + now.subtract(const Duration(hours: 14, minutes: 15)): "260.00", + now.subtract(const Duration(hours: 13, minutes: 30)): "280.75", + now.subtract(const Duration(hours: 12, minutes: 45)): "265.00", + now.subtract(const Duration(hours: 12, minutes: 0)): "245.50", + now.subtract(const Duration(hours: 11, minutes: 15)): "245.50", + now.subtract(const Duration(hours: 10, minutes: 30)): "300.00", + now.subtract(const Duration(hours: 9, minutes: 45)): "270.25", + now.subtract(const Duration(hours: 9, minutes: 0)): "300.00", + now.subtract(const Duration(hours: 8, minutes: 15)): "250.50", + now.subtract(const Duration(hours: 7, minutes: 30)): "270.00", + now.subtract(const Duration(hours: 6, minutes: 45)): "235.75", + now.subtract(const Duration(hours: 6, minutes: 0)): "280.00", + now.subtract(const Duration(hours: 5, minutes: 15)): "320.50", + now.subtract(const Duration(hours: 4, minutes: 30)): "295.00", + now.subtract(const Duration(hours: 3, minutes: 45)): "340.25", + now.subtract(const Duration(hours: 3, minutes: 0)): "250.00", + now.subtract(const Duration(hours: 2, minutes: 15)): "280.50", + now.subtract(const Duration(hours: 1, minutes: 30)): "255.00", + now.subtract(const Duration(hours: 0, minutes: 45)): "270.25", + now: "285.50", + }; +} + +class PriceChart extends StatelessWidget { + const PriceChart( + {super.key, + required this.height, + required this.prices, + required this.direction, + required this.touchCallback}); + + final Map prices; + final double height; + final PriceChangeDirection direction; + final Function(FlTouchEvent, LineTouchResponse?) touchCallback; + + @override + Widget build(BuildContext context) { + final chartPoints = chartMockData.entries.map((entry) { + final x = entry.key.millisecondsSinceEpoch.toDouble(); + final y = double.parse(entry.value); + return FlSpot(x, y); + }).toList(); + + return SizedBox( + height: height, + child: LineChart( + LineChartData( + gridData: FlGridData(show: false), + titlesData: FlTitlesData(show: false), + borderData: FlBorderData(show: false), + lineBarsData: [ + LineChartBarData( + spots: chartPoints, + gradient: + LinearGradient(colors: [direction.color.withAlpha(25), direction.color]), + barWidth: 1.5, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData(show: false), + ), + ], + lineTouchData: LineTouchData( + enabled: true, + getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { + return spotIndexes.map((index) { + return TouchedSpotIndicatorData( + FlLine( + color: Colors.transparent, + ), + FlDotData(), + ); + }).toList(); + }, + touchTooltipData: LineTouchTooltipData( + getTooltipItems: (touchedSpots) { + return touchedSpots.map((spot) => null).toList(); + }, + ), + touchCallback: touchCallback), + ), + ), + ); + } +} + +class ChartHeader extends StatefulWidget { + const ChartHeader({super.key}); + + @override + State createState() => _ChartHeaderState(); +} + +class _ChartHeaderState extends State { + String? _viewedPrice; + ChartRange _range = ChartRange.oneDay; + + @override + Widget build(BuildContext context) { + return Column( + spacing: 20, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 10, + children: [ + ChartViewCoinHeader(currency: CryptoCurrency.btc, isFavorite: true), + ChartViewPriceHeader( + price: _viewedPrice ?? "109437.05", + ticker: "USD", + highlight: _viewedPrice != null, + ), + Column( + children: [ + ChangeDisplay( + changeAmount: "85.6", + changePercentage: "2.31", + direction: PriceChangeDirection.up, + ticker: "USD"), + Padding( + padding: const EdgeInsets.symmetric(vertical: 22.0), + child: PriceChart( + height: 100, + prices: chartMockData, + direction: PriceChangeDirection.up, + touchCallback: (event, response) { + if (!event.isInterestedForInteractions) { + setState(() { + _viewedPrice = null; + }); + return; + } + setState(() { + _viewedPrice = response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); + }); + }, + ), + ), + Container( + width: double.infinity, + height: 1, + color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(128), + ), + ChartRangeSelector(selectedRange: _range, onRangeSelected: (range)=>setState(() { + _range = range; + })) + ], + ) + ], + ) + ], + ); + } +} + +class ChartViewCoinHeader extends StatelessWidget { + const ChartViewCoinHeader({super.key, required this.currency, required this.isFavorite}); + + final CryptoCurrency currency; + final bool isFavorite; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + spacing: 10, + children: [ + CakeImageWidget( + imageUrl: currency.iconSvgPath ?? currency.iconPath ?? "", + width: 30, + height: 30, + ), + Row( + spacing: 5, + children: [ + Text( + currency.fullName ?? currency.title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface), + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(9999999), + color: Theme.of(context).colorScheme.surfaceContainer, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), + child: Text( + currency.title, + style: TextStyle( + fontSize: 14, color: Theme.of(context).colorScheme.onSurfaceVariant), + ), + ), + ) + ], + ), + ], + ), + if (isFavorite) + CakeImageWidget( + imageUrl: "assets/new-ui/favorite.svg", + width: 16, + height: 16, + colorFilter: + ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn), + ) + else + SizedBox.shrink() + ], + ); + } +} + +class ChartViewPriceHeader extends StatelessWidget { + const ChartViewPriceHeader({ + super.key, + required this.price, + required this.ticker, + required this.highlight, + }); + + final String price; + final String ticker; + final bool highlight; + + @override + Widget build(BuildContext context) { + return FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Row( + spacing: 6, + children: [ + Text( + price, + style: TextStyle( + fontSize: 36, + color: highlight + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface), + ), + Text( + ticker, + style: TextStyle( + fontSize: 36, + color: highlight + ? Theme.of(context).colorScheme.primary.withAlpha(128) + : Theme.of(context).colorScheme.onSurfaceVariant), + ) + ], + ), + ); + } +} + +class ChangeDisplay extends StatelessWidget { + const ChangeDisplay( + {super.key, + required this.changeAmount, + required this.changePercentage, + required this.direction, + required this.ticker}); + + final String changeAmount; + final String changePercentage; + final String ticker; + final PriceChangeDirection direction; + + @override + Widget build(BuildContext context) { + return Row( + spacing: 10, + children: [ + Text( + "${direction.symbol}${ticker} ${changeAmount}", + style: TextStyle(fontSize: 16, color: direction.color), + ), + Container( + decoration: BoxDecoration( + color: direction.color.withAlpha(52), borderRadius: BorderRadius.circular(999999)), + child: Padding( + padding: EdgeInsets.only(top: 2.5, bottom: 2.5, left: 4, right: 8), + child: Text( + "${direction.symbol} $changePercentage%", + style: TextStyle(color: direction.color), + ), + )) + ], + ); + } +} diff --git a/lib/src/screens/dashboard/widgets/new_main_navbar_widget.dart b/lib/src/screens/dashboard/widgets/new_main_navbar_widget.dart index f16cb2f6e0..9d766a8403 100644 --- a/lib/src/screens/dashboard/widgets/new_main_navbar_widget.dart +++ b/lib/src/screens/dashboard/widgets/new_main_navbar_widget.dart @@ -53,7 +53,7 @@ class _NEWNewMainNavBarState extends State { static const iconColorChangeDuration = Duration(milliseconds: 200); static const pillTextStyle = TextStyle( - fontSize: 14, + fontSize: 16, fontWeight: FontWeight.w500, ); diff --git a/pubspec_base.yaml b/pubspec_base.yaml index ff54ecaaf8..518f9bf086 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -163,7 +163,7 @@ dependencies: torch_dart: path: ./scripts/torch_dart quick_actions: ^1.1.0 - fl_chart: ^0.70.2 + fl_chart: ^1.2.0 dev_dependencies: flutter_test: @@ -226,6 +226,7 @@ dependency_overrides: ref: c414574bc5ac349450f601e7f72c7b9f31b4d087 decimal: ^2.3.3 flutter_rust_bridge: 2.11.1 + vector_math: 2.3.0 flutter_icons: image_path: "assets/images/app_logo.png" diff --git a/res/pictures/favorite.svg b/res/pictures/favorite.svg new file mode 100644 index 0000000000..ea3697b592 --- /dev/null +++ b/res/pictures/favorite.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/price_change_arrow.svg b/res/pictures/price_change_arrow.svg new file mode 100644 index 0000000000..0ef09fe676 --- /dev/null +++ b/res/pictures/price_change_arrow.svg @@ -0,0 +1,3 @@ + + + From afad271a8a533fea3659d03c0d32d8ea2e0f7478 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 2 Apr 2026 12:38:19 +0200 Subject: [PATCH 03/40] wip --- lib/new-ui/pages/charts_page.dart | 29 +++++++++++++++++-- .../widgets/charts_page/asset_grid.dart | 15 ++++++++++ res/pictures/add.svg | 3 ++ res/pictures/sort.svg | 3 ++ res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fa.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_gn.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_hy.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_vi.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 34 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 lib/new-ui/widgets/charts_page/asset_grid.dart create mode 100644 res/pictures/add.svg create mode 100644 res/pictures/sort.svg diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart index 9739d85dda..b9c3a21f82 100644 --- a/lib/new-ui/pages/charts_page.dart +++ b/lib/new-ui/pages/charts_page.dart @@ -1,5 +1,8 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/viewmodels/charts_bloc.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/asset_grid.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; +import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; import 'package:flutter/material.dart'; class ChartsPage extends StatelessWidget { @@ -25,11 +28,33 @@ class ChartsPage extends StatelessWidget { child: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0), - child: Column(children: [ - ChartHeader() + child: Column(spacing:24, children: [ + ChartHeader(), + ChartsAssetGridHeader(onAddButtonPressed: (){},onSortButtonPressed: (){},), + ChartsAssetGrid() ],), ), ), ); } } + +class ChartsAssetGridHeader extends StatelessWidget { + const ChartsAssetGridHeader({super.key, required this.onAddButtonPressed, required this.onSortButtonPressed}); + + final VoidCallback onAddButtonPressed; + final VoidCallback onSortButtonPressed; + + @override + Widget build(BuildContext context) { + return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [ + Text(S.of(context).followed_assets, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12)), + Row(spacing:8,children: [ + ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/add.svg",onPressed: onAddButtonPressed,), + ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/sort.svg",onPressed: onSortButtonPressed,) + + ],) + ],); + } +} + diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart new file mode 100644 index 0000000000..78615977db --- /dev/null +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class ChartsAssetGrid extends StatelessWidget { + const ChartsAssetGrid({super.key}); + + @override + Widget build(BuildContext context) { + return GridView.builder(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.4, + ), itemBuilder: (context, index){}); + } +} diff --git a/res/pictures/add.svg b/res/pictures/add.svg new file mode 100644 index 0000000000..d52d63a16d --- /dev/null +++ b/res/pictures/add.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/pictures/sort.svg b/res/pictures/sort.svg new file mode 100644 index 0000000000..f197cf01de --- /dev/null +++ b/res/pictures/sort.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 0f5c1845be..237b789f44 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "هذا الزوج الثابت غير مدعوم مع خدمات المبادلة المحددة", "fixed_rate": "سعر ثابت", "fixed_rate_alert": "ستتمكن من إدخال مبلغ الاستلام عند تفعيل وضع السعر الثابت. هل تريد التبديل إلى وضع السعر الثابت؟", + "followed_assets": "الأصول المتبعه", "forgot_password": "نسيت كلمة المرور", "freeze": "تجميد", "frequently_asked_questions": "الأسئلة الشائعة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 733dca13fb..fcf4f40e9b 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Тази фиксирана двойка не се поддържа от избраните услуги за обмен", "fixed_rate": "Фиксиран курс", "fixed_rate_alert": "Ще можете да въведете сума за получаване, когато е отметнат режимът с фиксиран курс. Искате ли да превключите към режим с фиксиран курс?", + "followed_assets": "Следени активи", "forgot_password": "Забравена парола", "freeze": "Замразяване", "frequently_asked_questions": "Често задавани въпроси", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 18dc2865d5..3104736042 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Tento pevný pár není podporován u vybraných swapovacích služeb", "fixed_rate": "Fixní kurz", "fixed_rate_alert": "Když je zaškrtnutý režim pevného kurzu, budete moci zadat částku k přijetí. Chcete přepnout do režimu pevného kurzu?", + "followed_assets": "Sledovaná aktiva", "forgot_password": "Zapomenuté heslo", "freeze": "Zmrazit", "frequently_asked_questions": "Často kladené otázky", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 2e778fb5e5..4b32c9d8a2 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Dieses feste Paar wird von den ausgewählten Swap-Diensten nicht unterstützt", "fixed_rate": "Fester Kurs", "fixed_rate_alert": "Sie können den Empfangsbetrag eingeben, wenn der Festkursmodus aktiviert ist. Möchten Sie zum Festkursmodus wechseln?", + "followed_assets": "Gefolgte Vermögenswerte", "forgot_password": "Passwort vergessen", "freeze": "Sperren", "frequently_asked_questions": "Häufig gestellte Fragen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 781e097452..92f2dbaa44 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -485,6 +485,7 @@ "fixed_pair_not_supported": "This fixed pair is not supported with the selected swap services", "fixed_rate": "Fixed rate", "fixed_rate_alert": "You will be able to enter receive amount when fixed rate mode is checked. Do you want to switch to fixed rate mode?", + "followed_assets": "Followed assets", "forgot_password": "Forgot Password", "freeze": "Freeze", "frequently_asked_questions": "Frequently asked questions", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 3ede77c0d4..2c90b9ec38 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Este par fijo no es compatible con los servicios de swap seleccionados", "fixed_rate": "Tipo de cambio fijo", "fixed_rate_alert": "Podrás ingresar la cantidad a recibir cuando el modo de tipo de cambio fijo esté activado. ¿Quieres cambiar al modo de tipo de cambio fijo?", + "followed_assets": "Activos seguidos", "forgot_password": "Olvidé mi contraseña", "freeze": "Congelar", "frequently_asked_questions": "Preguntas frecuentes", diff --git a/res/values/strings_fa.arb b/res/values/strings_fa.arb index 3b7ed6da65..6dfd4ebecd 100644 --- a/res/values/strings_fa.arb +++ b/res/values/strings_fa.arb @@ -481,6 +481,7 @@ "fixed_pair_not_supported": "این جفت‌ارز ثابت توسط سرویس‌های سواپ انتخاب‌شده پشتیبانی نمی‌شود", "fixed_rate": "نرخ ثابت", "fixed_rate_alert": "وقتی حالت نرخ ثابت فعال باشد، می‌توانید مقدار دریافتی را وارد کنید.\nآیا می‌خواهید به حالت نرخ ثابت تغییر دهید؟", + "followed_assets": "دارایی های دنبال شده", "forgot_password": "رمز عبور را فراموش کرده‌اید", "freeze": "فریز", "frequently_asked_questions": "سوالات متداول", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 6be3e14a83..b4bcbb5cc3 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge par les services d'échange sélectionnés", "fixed_rate": "Taux fixe", "fixed_rate_alert": "Vous pourrez saisir le montant à recevoir lorsque le mode taux fixe est activé. Souhaitez-vous passer en mode taux fixe ?", + "followed_assets": "Actifs suivis", "forgot_password": "Mot de passe oublié", "freeze": "Geler", "frequently_asked_questions": "Foire aux questions", diff --git a/res/values/strings_gn.arb b/res/values/strings_gn.arb index 5ef2a75474..cdf03ff2aa 100644 --- a/res/values/strings_gn.arb +++ b/res/values/strings_gn.arb @@ -379,6 +379,7 @@ "fixed_pair_not_supported": "Ko mokõi par fijo ndojehepyme’ẽi umi swap rembiapo ojeiporavo va’ekuépe.", "fixed_rate": "Tasa fija", "fixed_rate_alert": "Ikatu hag̃aite emoinge hag̃ua pe monto rehechaukáva rehecha hag̃ua reiporavóramo pe modo tasa fija. ¿Reipota piko remoambue pe modo tasa fija-pe?", + "followed_assets": "Umi activo rapykuéri oúva", "forgot_password": "Nderesarái ñe’ẽñemi", "freeze": "Mbo’y", "frequently_asked_questions": "Porandu ha mbohovái ojeporuvéva", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 0d5cab3455..3c080f821e 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Ba a tallafa wa wannan tsayayyen nau'i ba tare da sabis ɗin musayar da aka zaɓa ba", "fixed_rate": "Kafaffen farashi", "fixed_rate_alert": "Za ka iya shigar da adadin da za a karɓa idan an zaɓi yanayin ƙayyadadden ƙima. Kana so ka canza zuwa yanayin ƙayyadadden ƙima?", + "followed_assets": "Kadarori masu biyo baya", "forgot_password": "An manta kalmar wucewa", "freeze": "Daskare", "frequently_asked_questions": "Tambayoyin da ake yawan yi", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index f8a3ffac41..0727937511 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "यह निश्चित जोड़ी चयनित स्वैप सेवाओं के साथ समर्थित नहीं है", "fixed_rate": "स्थिर दर", "fixed_rate_alert": "फिक्स्ड रेट मोड चुनने पर आप प्राप्त राशि दर्ज कर पाएंगे। क्या आप फिक्स्ड रेट मोड पर स्विच करना चाहते हैं?", + "followed_assets": "परिसंपत्तियों का अनुसरण किया गया", "forgot_password": "पासवर्ड भूल गए?", "freeze": "फ्रीज़", "frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 1cab48efac..3fab7ec458 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Ovaj fiksni par nije podržan s odabranim uslugama zamjene", "fixed_rate": "Fiksni tečaj", "fixed_rate_alert": "Moći ćete unijeti iznos za primanje kada je uključen način rada fiksne stope. Želite li se prebaciti na način rada fiksne stope?", + "followed_assets": "Praćena sredstva", "forgot_password": "Zaboravili ste lozinku", "freeze": "Zamrzni", "frequently_asked_questions": "Često postavljana pitanja", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index ca633e8cb7..dba18f07dc 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Այս ֆիքսված զույգը չի աջակցվում ընտրված փոխանակման ծառայությունների կողմից", "fixed_rate": "Ֆիքսված փոխարժեք", "fixed_rate_alert": "Դուք կկարողանաք մուտքագրել ստացվող գումարը, երբ ֆիքսված փոխարժեքի ռեժիմը միացված է։ Ցանկանո՞ւմ եք անցնել ֆիքսված փոխարժեքի ռեժիմին։", + "followed_assets": "Հետևվող ակտիվներ", "forgot_password": "Մոռացել եք գաղտնաբառը", "freeze": "Սառեցնել", "frequently_asked_questions": "Հաճախ տրվող հարցեր", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 00ac411326..44d6a1bacc 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Pasangan tetap ini tidak didukung oleh layanan swap yang dipilih", "fixed_rate": "Kurs tetap", "fixed_rate_alert": "Anda akan dapat memasukkan jumlah yang akan diterima saat mode kurs tetap dicentang. Apakah Anda ingin beralih ke mode kurs tetap?", + "followed_assets": "Aset yang diikuti", "forgot_password": "Lupa Kata Sandi", "freeze": "Bekukan", "frequently_asked_questions": "Pertanyaan yang Sering Diajukan", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 27f94584a6..4cb16843e9 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Questa coppia fissa non è supportata dai servizi di swap selezionati", "fixed_rate": "Tasso fisso", "fixed_rate_alert": "Potrai inserire l'importo da ricevere quando è selezionata la modalità a tasso fisso. Vuoi passare alla modalità a tasso fisso?", + "followed_assets": "Risorse seguite", "forgot_password": "Password dimenticata", "freeze": "Blocca", "frequently_asked_questions": "Domande frequenti", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 1ffa6601b7..de9547b247 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "この固定ペアは、選択したスワップサービスではサポートされていません", "fixed_rate": "固定レート", "fixed_rate_alert": "固定レートモードにチェックを入れると、受取額を入力できるようになります。固定レートモードに切り替えますか?", + "followed_assets": "フォローされているアセット", "forgot_password": "パスワードをお忘れですか?", "freeze": "凍結", "frequently_asked_questions": "よくある質問", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index ad5c6bca39..a553e2f4ca 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "이 고정 페어는 선택한 스왑 서비스에서 지원되지 않습니다", "fixed_rate": "고정 환율", "fixed_rate_alert": "고정 환율 모드를 선택하면 수령 금액을 입력할 수 있습니다. 고정 환율 모드로 전환하시겠습니까?", + "followed_assets": "팔로우하는 자산", "forgot_password": "비밀번호를 잊으셨나요?", "freeze": "동결", "frequently_asked_questions": "자주 묻는 질문", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 6aef17ede1..40712d73f3 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "ဤပုံသေစုံတွဲကို ရွေးချယ်ထားသော swap ဝန်ဆောင်မှုများဖြင့် မထောက်ပံ့ပါ", "fixed_rate": "ပုံသေ နှုန်းထား", "fixed_rate_alert": "ပုံသေနှုန်းထားမုဒ်ကို ရွေးထားသောအခါ လက်ခံရရှိမည့် ပမာဏကို ထည့်သွင်းနိုင်မည်ဖြစ်သည်။ ပုံသေနှုန်းထားမုဒ်သို့ ပြောင်းလိုပါသလား။", + "followed_assets": "လိုက်နာအပ်ပါတယ်။", "forgot_password": "စကားဝှက်မေ့သွားပါသလား", "freeze": "ရပ်ဆိုင်း", "frequently_asked_questions": "မေးလေ့ရှိသော မေးခွန်းများ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index d9bf9f9434..25d57c64a8 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -481,6 +481,7 @@ "fixed_pair_not_supported": "Dit vaste paar wordt niet ondersteund met de geselecteerde swap-services", "fixed_rate": "Vast tarief", "fixed_rate_alert": "Je kunt het ontvangen bedrag invoeren wanneer de modus voor vaste tarieven is aangevinkt. Wil je overschakelen naar de vaste-tariefmodus?", + "followed_assets": "Gevolgde activa", "forgot_password": "Wachtwoord vergeten", "freeze": "Bevriezen", "frequently_asked_questions": "Veelgestelde vragen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 07c6b75a32..b9302d31ab 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -482,6 +482,7 @@ "fixed_pair_not_supported": "Ta stała para nie jest obsługiwana przez wybrane usługi swap.", "fixed_rate": "Stały kurs", "fixed_rate_alert": "Będziesz mógł wprowadzić kwotę do otrzymania, gdy tryb stałego kursu jest zaznaczony. Czy chcesz przełączyć się na tryb stałego kursu?", + "followed_assets": "Obserwowane aktywa", "forgot_password": "Nie pamiętasz hasła", "freeze": "Zamroź", "frequently_asked_questions": "Najczęściej zadawane pytania", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 6e2934a3bd..e15dc82407 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Este par fixo não é compatível com os serviços de swap selecionados", "fixed_rate": "Taxa fixa", "fixed_rate_alert": "Você poderá inserir o valor a receber quando o modo de taxa fixa estiver selecionado. Deseja mudar para o modo de taxa fixa?", + "followed_assets": "Ativos seguidos", "forgot_password": "Esqueci minha senha", "freeze": "Congelar", "frequently_asked_questions": "Perguntas frequentes", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index a692d0a78f..8f55125ce0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Эта фиксированная пара не поддерживается выбранными сервисами обмена", "fixed_rate": "Фиксированный курс", "fixed_rate_alert": "Вы сможете ввести сумму получения, когда будет включён режим фиксированного курса. Хотите переключиться в режим фиксированного курса?", + "followed_assets": "Отслеживаемые активы", "forgot_password": "Забыли пароль?", "freeze": "Заморозить", "frequently_asked_questions": "Часто задаваемые вопросы", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 2cbcc644ff..9fafe2016e 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "คู่แบบคงที่นี้ไม่รองรับกับบริการสวอปที่เลือก", "fixed_rate": "อัตราคงที่", "fixed_rate_alert": "คุณจะสามารถป้อนจำนวนเงินที่ต้องการรับได้เมื่อเปิดใช้งานโหมดอัตราคงที่ คุณต้องการสลับไปที่โหมดอัตราคงที่หรือไม่?", + "followed_assets": "ทรัพย์สินที่ตามมา", "forgot_password": "ลืมรหัสผ่าน", "freeze": "ระงับ", "frequently_asked_questions": "คำถามที่พบบ่อย", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 9afb5ec545..b78d1a6ff1 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Hindi sinusuportahan ang nakapirming pares na ito sa mga napiling serbisyo ng swap", "fixed_rate": "Nakapirming rate", "fixed_rate_alert": "Makakapaglagay ka ng halagang matatanggap kapag naka-check ang fixed rate mode. Gusto mo bang lumipat sa fixed rate mode?", + "followed_assets": "Sinunod ang mga asset", "forgot_password": "Nakalimutan ang Password", "freeze": "I-freeze", "frequently_asked_questions": "Mga madalas itanong", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index f367bffa44..bc046be11e 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Bu sabit parite, seçilen takas hizmetleriyle desteklenmiyor", "fixed_rate": "Sabit kur", "fixed_rate_alert": "Sabit oran modu seçiliyken alınacak tutarı girebileceksin. Sabit oran moduna geçmek ister misin?", + "followed_assets": "Takip edilen varlıklar", "forgot_password": "Parolamı Unuttum", "freeze": "Dondur", "frequently_asked_questions": "Sıkça sorulan sorular", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 750b66bab0..9c29f00213 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "Ця фіксована пара не підтримується вибраними сервісами обміну", "fixed_rate": "Фіксований курс", "fixed_rate_alert": "Ви зможете ввести суму отримання, коли буде увімкнено режим фіксованого курсу. Хочете перейти в режим фіксованого курсу?", + "followed_assets": "Активи, за якими ви стежите", "forgot_password": "Забули пароль", "freeze": "Заморозити", "frequently_asked_questions": "Поширені запитання", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 40f18e750b..eef3db7cfb 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "منتخب کردہ سواپ سروسز کے ساتھ یہ فکسڈ جوڑی سپورٹ نہیں کی جاتی", "fixed_rate": "مقررہ شرح", "fixed_rate_alert": "فکسڈ ریٹ موڈ منتخب ہونے پر آپ وصولی کی رقم درج کر سکیں گے۔ کیا آپ فکسڈ ریٹ موڈ پر سوئچ کرنا چاہتے ہیں؟", + "followed_assets": "اثاثوں کی پیروی کی۔", "forgot_password": "پاس ورڈ بھول گئے", "freeze": "منجمد کریں", "frequently_asked_questions": "اکثر پوچھے جانے والے سوالات", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 87466bd8b2..90dbee83c3 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -482,6 +482,7 @@ "fixed_pair_not_supported": "Cặp tỷ giá cố định này không được hỗ trợ với các dịch vụ hoán đổi đã chọn", "fixed_rate": "Tỷ giá cố định", "fixed_rate_alert": "Bạn sẽ có thể nhập số lượng nhận khi chế độ tỷ giá cố định được bật. Bạn có muốn chuyển sang chế độ tỷ giá cố định không?", + "followed_assets": "Nội dung được theo dõi", "forgot_password": "Quên mật khẩu", "freeze": "Đóng băng", "frequently_asked_questions": "Các câu hỏi thường gặp", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 70ae45c630..12984820f9 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -484,6 +484,7 @@ "fixed_pair_not_supported": "Akọsopọ paṣipaarọ ti o wa titi yii ko ni atilẹyin pẹlu awọn iṣẹ swap ti o yan", "fixed_rate": "Oṣuwọn tí ó dúró ṣinṣin", "fixed_rate_alert": "Ẹ ó lè tẹ iye tí ẹ fẹ́ gba wọlé nígbà tí a bá ti yan ipo oṣuwọn títọ́. Ṣé ẹ fẹ́ yí padà sí ipo oṣuwọn títọ́?", + "followed_assets": "Awọn ohun-ini atẹle", "forgot_password": "Gbagbe Ọ̀rọ̀ aṣínà", "freeze": "Dì", "frequently_asked_questions": "Àwọn ìbéèrè tí a máa ń béèrè lọ́pọ̀ ìgbà", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index e08bb5c3ac..5a51b51dd2 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -483,6 +483,7 @@ "fixed_pair_not_supported": "所选的兑换服务不支持该固定交易对", "fixed_rate": "固定汇率", "fixed_rate_alert": "勾选固定汇率模式后,您将可以输入接收金额。您要切换到固定汇率模式吗?", + "followed_assets": "关注资产", "forgot_password": "忘记密码", "freeze": "冻结", "frequently_asked_questions": "常见问题", From 8b3cac86d043823d9b1806966d15ac8eab77180c Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 2 Apr 2026 19:27:56 +0200 Subject: [PATCH 04/40] add asset grid --- lib/new-ui/pages/charts_page.dart | 2 +- .../widgets/charts_page/asset_grid.dart | 132 +++++++++++++++++- .../widgets/charts_page/chart_view.dart | 32 +++-- 3 files changed, 149 insertions(+), 17 deletions(-) diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart index b9c3a21f82..c54da94a33 100644 --- a/lib/new-ui/pages/charts_page.dart +++ b/lib/new-ui/pages/charts_page.dart @@ -31,7 +31,7 @@ class ChartsPage extends StatelessWidget { child: Column(spacing:24, children: [ ChartHeader(), ChartsAssetGridHeader(onAddButtonPressed: (){},onSortButtonPressed: (){},), - ChartsAssetGrid() + Expanded(child: ChartsAssetGrid()) ],), ), ), diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index 78615977db..84e49bdbcc 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -1,15 +1,135 @@ +import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; +import 'package:cake_wallet/themes/core/theme_extension.dart'; class ChartsAssetGrid extends StatelessWidget { const ChartsAssetGrid({super.key}); @override Widget build(BuildContext context) { - return GridView.builder(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10, - mainAxisSpacing: 10, - childAspectRatio: 1.4, - ), itemBuilder: (context, index){}); + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, mainAxisExtent: 100), + itemBuilder: (context, index) => ChartsAssetCard( + currency: CryptoCurrency.btc, + price: "355.87", + ticker: "USD", + changePercentage: "4.56", + direction: PriceChangeDirection.up)); + } +} + +class ChartsAssetCard extends StatelessWidget { + const ChartsAssetCard( + {super.key, + required this.currency, + required this.price, + required this.ticker, + required this.changePercentage, + required this.direction}); + + final CryptoCurrency currency; + final String price; + final String ticker; + final String changePercentage; + final PriceChangeDirection direction; + + String get displayPrice { + final priceDouble = double.parse(price); + if (priceDouble > 10000) + return priceDouble.toStringAsFixed(0); + else + return priceDouble.toStringAsFixed(2); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: + Border.all(width: 1, color: Theme.of(context).colorScheme.surfaceContainerHighest), + gradient: LinearGradient(colors: [ + context.customColors.cardGradientColorPrimary, + context.customColors.cardGradientColorSecondary + ], begin: Alignment.topCenter, end: Alignment.bottomCenter)), + child: Padding( + padding: EdgeInsets.all(12), + child: Column( + spacing: 12, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 5, + children: [ + RotatedBox( + quarterTurns: direction == PriceChangeDirection.up ? 0 : 2, + child: CakeImageWidget( + imageUrl: "assets/new-ui/price_change_arrow.svg", + width: 8, + height: 8, + colorFilter: ColorFilter.mode(direction.color, BlendMode.srcIn), + ), + ), + Text( + currency.title.toUpperCase(), + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ) + ], + ), + Text( + currency.fullName ?? "", + style: TextStyle( + fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant), + ) + ], + ), + ChangePill(changePercentage: changePercentage, direction: direction) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 8, + children: [ + CakeImageWidget( + imageUrl: currency.iconSvgPath ?? currency.iconPath, + width: 24, + height: 24, + ), + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Row( + spacing: 4, + children: [ + Text( + displayPrice, + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), + ), + Text( + ticker, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w500, + fontSize: 20), + ) + ], + ), + ), + ) + ], + ) + ], + ), + ), + ); } } diff --git a/lib/new-ui/widgets/charts_page/chart_view.dart b/lib/new-ui/widgets/charts_page/chart_view.dart index 4f5ac103d5..01a8a53e9d 100644 --- a/lib/new-ui/widgets/charts_page/chart_view.dart +++ b/lib/new-ui/widgets/charts_page/chart_view.dart @@ -382,17 +382,29 @@ class ChangeDisplay extends StatelessWidget { "${direction.symbol}${ticker} ${changeAmount}", style: TextStyle(fontSize: 16, color: direction.color), ), - Container( - decoration: BoxDecoration( - color: direction.color.withAlpha(52), borderRadius: BorderRadius.circular(999999)), - child: Padding( - padding: EdgeInsets.only(top: 2.5, bottom: 2.5, left: 4, right: 8), - child: Text( - "${direction.symbol} $changePercentage%", - style: TextStyle(color: direction.color), - ), - )) + ChangePill(changePercentage: changePercentage, direction: direction) ], ); } } + +class ChangePill extends StatelessWidget { + const ChangePill({super.key, required this.changePercentage, required this.direction}); + + final String changePercentage; + final PriceChangeDirection direction; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: direction.color.withAlpha(52), borderRadius: BorderRadius.circular(999999)), + child: Padding( + padding: EdgeInsets.only(top: 2.5, bottom: 2.5, left: 4, right: 8), + child: Text( + "${direction.symbol} $changePercentage%", + style: TextStyle(color: direction.color), + ), + )); + } +} From 474271bf2977a96f4db1e87aee33d6df329e3820 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 2 Apr 2026 19:39:25 +0200 Subject: [PATCH 05/40] split files --- lib/new-ui/pages/charts_page.dart | 66 ++-- .../viewmodels/charts/util/chart_range.dart | 16 + .../charts/util/price_change_direction.dart | 11 + .../widgets/charts_page/asset_grid.dart | 3 +- .../charts_page/asset_grid_header.dart | 22 ++ .../widgets/charts_page/change_display.dart | 31 ++ .../widgets/charts_page/change_pill.dart | 23 ++ .../widgets/charts_page/chart_header.dart | 78 +++++ .../widgets/charts_page/chart_view.dart | 303 +----------------- .../widgets/charts_page/coin_header.dart | 65 ++++ .../widgets/charts_page/price_header.dart | 43 +++ .../widgets/charts_page/range_selector.dart | 57 ++++ 12 files changed, 374 insertions(+), 344 deletions(-) create mode 100644 lib/new-ui/viewmodels/charts/util/chart_range.dart create mode 100644 lib/new-ui/viewmodels/charts/util/price_change_direction.dart create mode 100644 lib/new-ui/widgets/charts_page/asset_grid_header.dart create mode 100644 lib/new-ui/widgets/charts_page/change_display.dart create mode 100644 lib/new-ui/widgets/charts_page/change_pill.dart create mode 100644 lib/new-ui/widgets/charts_page/chart_header.dart create mode 100644 lib/new-ui/widgets/charts_page/coin_header.dart create mode 100644 lib/new-ui/widgets/charts_page/price_header.dart create mode 100644 lib/new-ui/widgets/charts_page/range_selector.dart diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart index c54da94a33..58bf685bb6 100644 --- a/lib/new-ui/pages/charts_page.dart +++ b/lib/new-ui/pages/charts_page.dart @@ -1,8 +1,7 @@ -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/viewmodels/charts_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/asset_grid.dart'; -import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; -import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/asset_grid_header.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/chart_header.dart'; import 'package:flutter/material.dart'; class ChartsPage extends StatelessWidget { @@ -13,48 +12,33 @@ class ChartsPage extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context).colorScheme.surface, - Theme.of(context).colorScheme.surfaceDim, + height: MediaQuery.of(context).size.height, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surfaceDim, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0), + child: Column( + spacing: 24, + children: [ + ChartHeader(), + ChartsAssetGridHeader( + onAddButtonPressed: () {}, + onSortButtonPressed: () {}, + ), + Expanded(child: ChartsAssetGrid()) ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, ), ), - - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 18.0), - child: Column(spacing:24, children: [ - ChartHeader(), - ChartsAssetGridHeader(onAddButtonPressed: (){},onSortButtonPressed: (){},), - Expanded(child: ChartsAssetGrid()) - ],), ), - ), ); } } - -class ChartsAssetGridHeader extends StatelessWidget { - const ChartsAssetGridHeader({super.key, required this.onAddButtonPressed, required this.onSortButtonPressed}); - - final VoidCallback onAddButtonPressed; - final VoidCallback onSortButtonPressed; - - @override - Widget build(BuildContext context) { - return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [ - Text(S.of(context).followed_assets, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12)), - Row(spacing:8,children: [ - ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/add.svg",onPressed: onAddButtonPressed,), - ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/sort.svg",onPressed: onSortButtonPressed,) - - ],) - ],); - } -} - diff --git a/lib/new-ui/viewmodels/charts/util/chart_range.dart b/lib/new-ui/viewmodels/charts/util/chart_range.dart new file mode 100644 index 0000000000..3b2af19b42 --- /dev/null +++ b/lib/new-ui/viewmodels/charts/util/chart_range.dart @@ -0,0 +1,16 @@ + +class ChartRange { + final Duration? duration; + final String displayText; + + const ChartRange._(this.duration, this.displayText); + + static const oneHour = ChartRange._(Duration(hours: 1), "1H"); + static const oneDay = ChartRange._(Duration(days: 1), "1D"); + static const sevenDays = ChartRange._(Duration(days: 7), "7D"); + static const thirtyDays = ChartRange._(Duration(days: 30), "30D"); + static const oneYear = ChartRange._(Duration(days: 365), "1Y"); + static const all = ChartRange._(null, "ALL"); + + static const ranges = [oneHour, oneDay, sevenDays, thirtyDays, oneYear, all]; +} diff --git a/lib/new-ui/viewmodels/charts/util/price_change_direction.dart b/lib/new-ui/viewmodels/charts/util/price_change_direction.dart new file mode 100644 index 0000000000..9c70ce8497 --- /dev/null +++ b/lib/new-ui/viewmodels/charts/util/price_change_direction.dart @@ -0,0 +1,11 @@ +import 'dart:ui'; + +class PriceChangeDirection { + final Color color; + final String symbol; + + const PriceChangeDirection._(this.color, this.symbol); + + static const up = PriceChangeDirection._(Color(0xFF6FC84E), "+"); + static const down = PriceChangeDirection._(Color(0xFFEA696F), "-"); +} \ No newline at end of file diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index 84e49bdbcc..a85e6a2065 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -1,4 +1,5 @@ -import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; diff --git a/lib/new-ui/widgets/charts_page/asset_grid_header.dart b/lib/new-ui/widgets/charts_page/asset_grid_header.dart new file mode 100644 index 0000000000..d0fd6cafd7 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/asset_grid_header.dart @@ -0,0 +1,22 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; +import 'package:flutter/material.dart'; + +class ChartsAssetGridHeader extends StatelessWidget { + const ChartsAssetGridHeader({super.key, required this.onAddButtonPressed, required this.onSortButtonPressed}); + + final VoidCallback onAddButtonPressed; + final VoidCallback onSortButtonPressed; + + @override + Widget build(BuildContext context) { + return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [ + Text(S.of(context).followed_assets, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12)), + Row(spacing:8,children: [ + ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/add.svg",onPressed: onAddButtonPressed,), + ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/sort.svg",onPressed: onSortButtonPressed,) + + ],) + ],); + } +} diff --git a/lib/new-ui/widgets/charts_page/change_display.dart b/lib/new-ui/widgets/charts_page/change_display.dart new file mode 100644 index 0000000000..5cbc0e91cc --- /dev/null +++ b/lib/new-ui/widgets/charts_page/change_display.dart @@ -0,0 +1,31 @@ +import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart'; +import 'package:flutter/material.dart'; + +class ChangeDisplay extends StatelessWidget { + const ChangeDisplay( + {super.key, + required this.changeAmount, + required this.changePercentage, + required this.direction, + required this.ticker}); + + final String changeAmount; + final String changePercentage; + final String ticker; + final PriceChangeDirection direction; + + @override + Widget build(BuildContext context) { + return Row( + spacing: 10, + children: [ + Text( + "${direction.symbol}${ticker} ${changeAmount}", + style: TextStyle(fontSize: 16, color: direction.color), + ), + ChangePill(changePercentage: changePercentage, direction: direction) + ], + ); + } +} \ No newline at end of file diff --git a/lib/new-ui/widgets/charts_page/change_pill.dart b/lib/new-ui/widgets/charts_page/change_pill.dart new file mode 100644 index 0000000000..8474aaf7c6 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/change_pill.dart @@ -0,0 +1,23 @@ +import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:flutter/material.dart'; + +class ChangePill extends StatelessWidget { + const ChangePill({super.key, required this.changePercentage, required this.direction}); + + final String changePercentage; + final PriceChangeDirection direction; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: direction.color.withAlpha(52), borderRadius: BorderRadius.circular(999999)), + child: Padding( + padding: EdgeInsets.only(top: 2.5, bottom: 2.5, left: 4, right: 8), + child: Text( + "${direction.symbol} $changePercentage%", + style: TextStyle(color: direction.color), + ), + )); + } +} \ No newline at end of file diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart new file mode 100644 index 0000000000..6ef9d5ee57 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -0,0 +1,78 @@ +import 'package:cake_wallet/new-ui/viewmodels/charts/util/chart_range.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/change_display.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/coin_header.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/price_header.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/range_selector.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; + +class ChartHeader extends StatefulWidget { + const ChartHeader({super.key}); + + @override + State createState() => _ChartHeaderState(); +} + +class _ChartHeaderState extends State { + String? _viewedPrice; + ChartRange _range = ChartRange.oneDay; + + @override + Widget build(BuildContext context) { + return Column( + spacing: 20, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 10, + children: [ + ChartViewCoinHeader(currency: CryptoCurrency.btc, isFavorite: true), + ChartViewPriceHeader( + price: _viewedPrice ?? "109437.05", + ticker: "USD", + highlight: _viewedPrice != null, + ), + Column( + children: [ + ChangeDisplay( + changeAmount: "85.6", + changePercentage: "2.31", + direction: PriceChangeDirection.up, + ticker: "USD"), + Padding( + padding: const EdgeInsets.symmetric(vertical: 22.0), + child: PriceChart( + height: 100, + prices: chartMockData, + direction: PriceChangeDirection.up, + touchCallback: (event, response) { + if (!event.isInterestedForInteractions) { + setState(() { + _viewedPrice = null; + }); + return; + } + setState(() { + _viewedPrice = response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); + }); + }, + ), + ), + Container( + width: double.infinity, + height: 1, + color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(128), + ), + ChartRangeSelector(selectedRange: _range, onRangeSelected: (range)=>setState(() { + _range = range; + })) + ], + ) + ], + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/new-ui/widgets/charts_page/chart_view.dart b/lib/new-ui/widgets/charts_page/chart_view.dart index 01a8a53e9d..057b970044 100644 --- a/lib/new-ui/widgets/charts_page/chart_view.dart +++ b/lib/new-ui/widgets/charts_page/chart_view.dart @@ -1,88 +1,9 @@ -import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; -import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -class PriceChangeDirection { - final Color color; - final String symbol; - const PriceChangeDirection._(this.color, this.symbol); - static const up = PriceChangeDirection._(Color(0xFF6FC84E), "+"); - static const down = PriceChangeDirection._(Color(0xFFEA696F), "-"); -} - -class ChartRange { - final Duration? duration; - final String displayText; - - const ChartRange._(this.duration, this.displayText); - - static const oneHour = ChartRange._(Duration(hours: 1), "1H"); - static const oneDay = ChartRange._(Duration(days: 1), "1D"); - static const sevenDays = ChartRange._(Duration(days: 7), "7D"); - static const thirtyDays = ChartRange._(Duration(days: 30), "30D"); - static const oneYear = ChartRange._(Duration(days: 365), "1Y"); - static const all = ChartRange._(null, "ALL"); - - static const ranges = [oneHour, oneDay, sevenDays, thirtyDays, oneYear, all]; -} - -class ChartRangeSelector extends StatelessWidget { - const ChartRangeSelector({super.key, required this.selectedRange, required this.onRangeSelected}); - - final ChartRange selectedRange; - final Function(ChartRange) onRangeSelected; - - static const double optionSize = 36; - static const double optionPadding = 24; - static const Duration switchDuration = Duration(milliseconds: 250); - - double pillPosition(int selectedIndex) => selectedIndex * (optionSize + optionPadding); - - @override - Widget build(BuildContext context) { - final selectedIndex = ChartRange.ranges.indexOf(selectedRange); - - return Stack( - children: [ - AnimatedPositioned( - curve: Curves.easeOutCubic, - left: pillPosition(selectedIndex), - child: Container( - height: optionSize, - width: optionSize, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onSurface.withAlpha(25), - borderRadius: BorderRadius.circular(999999)), - ), - duration: switchDuration), - Row( - spacing: optionPadding, - children: ChartRange.ranges.map((item) { - final selected = selectedRange == item; - return GestureDetector( - onTap: ()=>onRangeSelected(item), - child: Container( - width: optionSize, - height: optionSize, - child: AnimatedDefaultTextStyle( - duration: switchDuration, - style: TextStyle( - fontWeight: selected ? FontWeight.w400 : FontWeight.w500, - color: selected - ? Theme.of(context).colorScheme.onSurface - : Theme.of(context).colorScheme.onSurfaceVariant), - child: Center(child: Text(item.displayText))), - ), - ); - }).toList(), - ) - ], - ); - } -} Map get chartMockData { final DateTime now = DateTime.now(); @@ -186,225 +107,3 @@ class PriceChart extends StatelessWidget { ); } } - -class ChartHeader extends StatefulWidget { - const ChartHeader({super.key}); - - @override - State createState() => _ChartHeaderState(); -} - -class _ChartHeaderState extends State { - String? _viewedPrice; - ChartRange _range = ChartRange.oneDay; - - @override - Widget build(BuildContext context) { - return Column( - spacing: 20, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 10, - children: [ - ChartViewCoinHeader(currency: CryptoCurrency.btc, isFavorite: true), - ChartViewPriceHeader( - price: _viewedPrice ?? "109437.05", - ticker: "USD", - highlight: _viewedPrice != null, - ), - Column( - children: [ - ChangeDisplay( - changeAmount: "85.6", - changePercentage: "2.31", - direction: PriceChangeDirection.up, - ticker: "USD"), - Padding( - padding: const EdgeInsets.symmetric(vertical: 22.0), - child: PriceChart( - height: 100, - prices: chartMockData, - direction: PriceChangeDirection.up, - touchCallback: (event, response) { - if (!event.isInterestedForInteractions) { - setState(() { - _viewedPrice = null; - }); - return; - } - setState(() { - _viewedPrice = response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); - }); - }, - ), - ), - Container( - width: double.infinity, - height: 1, - color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(128), - ), - ChartRangeSelector(selectedRange: _range, onRangeSelected: (range)=>setState(() { - _range = range; - })) - ], - ) - ], - ) - ], - ); - } -} - -class ChartViewCoinHeader extends StatelessWidget { - const ChartViewCoinHeader({super.key, required this.currency, required this.isFavorite}); - - final CryptoCurrency currency; - final bool isFavorite; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - spacing: 10, - children: [ - CakeImageWidget( - imageUrl: currency.iconSvgPath ?? currency.iconPath ?? "", - width: 30, - height: 30, - ), - Row( - spacing: 5, - children: [ - Text( - currency.fullName ?? currency.title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface), - ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(9999999), - color: Theme.of(context).colorScheme.surfaceContainer, - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), - child: Text( - currency.title, - style: TextStyle( - fontSize: 14, color: Theme.of(context).colorScheme.onSurfaceVariant), - ), - ), - ) - ], - ), - ], - ), - if (isFavorite) - CakeImageWidget( - imageUrl: "assets/new-ui/favorite.svg", - width: 16, - height: 16, - colorFilter: - ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn), - ) - else - SizedBox.shrink() - ], - ); - } -} - -class ChartViewPriceHeader extends StatelessWidget { - const ChartViewPriceHeader({ - super.key, - required this.price, - required this.ticker, - required this.highlight, - }); - - final String price; - final String ticker; - final bool highlight; - - @override - Widget build(BuildContext context) { - return FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Row( - spacing: 6, - children: [ - Text( - price, - style: TextStyle( - fontSize: 36, - color: highlight - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface), - ), - Text( - ticker, - style: TextStyle( - fontSize: 36, - color: highlight - ? Theme.of(context).colorScheme.primary.withAlpha(128) - : Theme.of(context).colorScheme.onSurfaceVariant), - ) - ], - ), - ); - } -} - -class ChangeDisplay extends StatelessWidget { - const ChangeDisplay( - {super.key, - required this.changeAmount, - required this.changePercentage, - required this.direction, - required this.ticker}); - - final String changeAmount; - final String changePercentage; - final String ticker; - final PriceChangeDirection direction; - - @override - Widget build(BuildContext context) { - return Row( - spacing: 10, - children: [ - Text( - "${direction.symbol}${ticker} ${changeAmount}", - style: TextStyle(fontSize: 16, color: direction.color), - ), - ChangePill(changePercentage: changePercentage, direction: direction) - ], - ); - } -} - -class ChangePill extends StatelessWidget { - const ChangePill({super.key, required this.changePercentage, required this.direction}); - - final String changePercentage; - final PriceChangeDirection direction; - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: direction.color.withAlpha(52), borderRadius: BorderRadius.circular(999999)), - child: Padding( - padding: EdgeInsets.only(top: 2.5, bottom: 2.5, left: 4, right: 8), - child: Text( - "${direction.symbol} $changePercentage%", - style: TextStyle(color: direction.color), - ), - )); - } -} diff --git a/lib/new-ui/widgets/charts_page/coin_header.dart b/lib/new-ui/widgets/charts_page/coin_header.dart new file mode 100644 index 0000000000..d063743898 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/coin_header.dart @@ -0,0 +1,65 @@ +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; + +class ChartViewCoinHeader extends StatelessWidget { + const ChartViewCoinHeader({super.key, required this.currency, required this.isFavorite}); + + final CryptoCurrency currency; + final bool isFavorite; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + spacing: 10, + children: [ + CakeImageWidget( + imageUrl: currency.iconSvgPath ?? currency.iconPath ?? "", + width: 30, + height: 30, + ), + Row( + spacing: 5, + children: [ + Text( + currency.fullName ?? currency.title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface), + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(9999999), + color: Theme.of(context).colorScheme.surfaceContainer, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), + child: Text( + currency.title, + style: TextStyle( + fontSize: 14, color: Theme.of(context).colorScheme.onSurfaceVariant), + ), + ), + ) + ], + ), + ], + ), + if (isFavorite) + CakeImageWidget( + imageUrl: "assets/new-ui/favorite.svg", + width: 16, + height: 16, + colorFilter: + ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn), + ) + else + SizedBox.shrink() + ], + ); + } +} \ No newline at end of file diff --git a/lib/new-ui/widgets/charts_page/price_header.dart b/lib/new-ui/widgets/charts_page/price_header.dart new file mode 100644 index 0000000000..07f93c3255 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/price_header.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class ChartViewPriceHeader extends StatelessWidget { + const ChartViewPriceHeader({ + super.key, + required this.price, + required this.ticker, + required this.highlight, + }); + + final String price; + final String ticker; + final bool highlight; + + @override + Widget build(BuildContext context) { + return FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Row( + spacing: 6, + children: [ + Text( + price, + style: TextStyle( + fontSize: 36, + color: highlight + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface), + ), + Text( + ticker, + style: TextStyle( + fontSize: 36, + color: highlight + ? Theme.of(context).colorScheme.primary.withAlpha(128) + : Theme.of(context).colorScheme.onSurfaceVariant), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/new-ui/widgets/charts_page/range_selector.dart b/lib/new-ui/widgets/charts_page/range_selector.dart new file mode 100644 index 0000000000..c84cababd9 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/range_selector.dart @@ -0,0 +1,57 @@ +import 'package:cake_wallet/new-ui/viewmodels/charts/util/chart_range.dart'; +import 'package:flutter/material.dart'; + +class ChartRangeSelector extends StatelessWidget { + const ChartRangeSelector({super.key, required this.selectedRange, required this.onRangeSelected}); + + final ChartRange selectedRange; + final Function(ChartRange) onRangeSelected; + + static const double optionSize = 36; + static const double optionPadding = 24; + static const Duration switchDuration = Duration(milliseconds: 250); + + double pillPosition(int selectedIndex) => selectedIndex * (optionSize + optionPadding); + + @override + Widget build(BuildContext context) { + final selectedIndex = ChartRange.ranges.indexOf(selectedRange); + + return Stack( + children: [ + AnimatedPositioned( + curve: Curves.easeOutCubic, + left: pillPosition(selectedIndex), + child: Container( + height: optionSize, + width: optionSize, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onSurface.withAlpha(25), + borderRadius: BorderRadius.circular(999999)), + ), + duration: switchDuration), + Row( + spacing: optionPadding, + children: ChartRange.ranges.map((item) { + final selected = selectedRange == item; + return GestureDetector( + onTap: ()=>onRangeSelected(item), + child: Container( + width: optionSize, + height: optionSize, + child: AnimatedDefaultTextStyle( + duration: switchDuration, + style: TextStyle( + fontWeight: selected ? FontWeight.w400 : FontWeight.w500, + color: selected + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).colorScheme.onSurfaceVariant), + child: Center(child: Text(item.displayText))), + ), + ); + }).toList(), + ) + ], + ); + } +} \ No newline at end of file From 65aa1c62ad98221a4204afab0e943ff1dcca0f42 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 3 Apr 2026 10:16:33 +0200 Subject: [PATCH 06/40] logic wip --- cw_core/lib/crypto_currency.dart | 2 + cw_core/lib/currency.dart | 1 + cw_core/lib/db/sqlite.dart | 22 ++++- cw_core/lib/erc20_token.dart | 3 + cw_core/lib/spl_token.dart | 3 + cw_core/lib/tron_token.dart | 3 + lib/entities/fiat_currency.dart | 89 ++++++++++--------- .../model/charts/datetime_extension.dart | 6 ++ lib/new-ui/model/charts/price_api_client.dart | 55 ++++++++++++ lib/new-ui/model/charts/price_data.dart | 66 ++++++++++++++ lib/new-ui/model/charts/price_store.dart | 37 ++++++++ .../viewmodels/charts/util/chart_range.dart | 20 +++-- .../widgets/charts_page/asset_grid.dart | 1 + 13 files changed, 257 insertions(+), 51 deletions(-) create mode 100644 lib/new-ui/model/charts/datetime_extension.dart create mode 100644 lib/new-ui/model/charts/price_api_client.dart create mode 100644 lib/new-ui/model/charts/price_data.dart create mode 100644 lib/new-ui/model/charts/price_store.dart diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index f69748b4bc..8ca5b12557 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -31,6 +31,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen final int decimals; final bool enabled; final bool isPotentialScam; + @override + String get apiString => "crypto.$title"; set enabled(bool value) => this.enabled = value; diff --git a/cw_core/lib/currency.dart b/cw_core/lib/currency.dart index f43455a3e9..3a9699614b 100644 --- a/cw_core/lib/currency.dart +++ b/cw_core/lib/currency.dart @@ -4,4 +4,5 @@ abstract class Currency { String? get fullName; String? get iconPath; int get decimals; + String get apiString; } diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart index b32c1e82b2..61affbf205 100644 --- a/cw_core/lib/db/sqlite.dart +++ b/cw_core/lib/db/sqlite.dart @@ -39,7 +39,7 @@ Future initDb({String? pathOverride}) async { } } await db?.close(); - db = await openDatabase(dbFile.path, version: 4, + db = await openDatabase(dbFile.path, version: 5, onUpgrade: (Database db, int oldVersion, int newVersion) async { printV("migrating: $oldVersion, $newVersion"); if (oldVersion <= 1) { @@ -88,6 +88,17 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings ( // if address doesn't correspond to a valid token, fallback to primary token await _addColumnIfNotExists(db, table: "WalletInfo", column: "favoriteTokenAddress", definition: "TEXT DEFAULT NULL"); } + if(oldVersion <= 4) { + await db.execute(''' +CREATE TABLE PriceData ( + from_currency TEXT NOT NULL, + to_currency TEXT NOT NULL, + timestamp INTEGER NOT NULL, + price TEXT NOT NULL, + PRIMARY KEY (from_currency, to_currency, timestamp) +); +'''); + } }, onCreate: (Database db, int version) async { await db.execute( @@ -185,6 +196,15 @@ CREATE TABLE BalanceCardStyleSettings ( FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) ); '''); + await db.execute(''' +CREATE TABLE PriceData ( + from_currency TEXT NOT NULL, + to_currency TEXT NOT NULL, + timestamp INTEGER NOT NULL, + price TEXT NOT NULL, + PRIMARY KEY (from_currency, to_currency, timestamp) +); +'''); } ); } diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index e19cbc63f2..90eea293c5 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -79,4 +79,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { @override int get hashCode => contractAddress.hashCode; + + @override + String get apiString => "crypto.$contractAddress"; } diff --git a/cw_core/lib/spl_token.dart b/cw_core/lib/spl_token.dart index 145d7bd037..28ee279a20 100644 --- a/cw_core/lib/spl_token.dart +++ b/cw_core/lib/spl_token.dart @@ -113,4 +113,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { @override int get hashCode => mintAddress.hashCode; + + @override + String get apiString => "crypto.$mintAddress"; } diff --git a/cw_core/lib/tron_token.dart b/cw_core/lib/tron_token.dart index 3d891bea71..37b2eb9676 100644 --- a/cw_core/lib/tron_token.dart +++ b/cw_core/lib/tron_token.dart @@ -85,4 +85,7 @@ class TronToken extends CryptoCurrency with HiveObjectMixin { @override int get hashCode => contractAddress.hashCode; + + @override + String get apiString => "crypto.$contractAddress"; } diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index 55eef1afe6..33398b1e33 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -13,52 +13,55 @@ class FiatCurrency extends EnumerableItem with Serializable impl final String fullName; final int decimals; + @override + String get apiString => "fiat.$title"; + static List get all => _all.values.toList(); static List get currenciesAvailableToBuyWith => [ - amd, - aud, - bgn, - brl, - cad, - chf, - clp, - cop, - czk, - dkk, - egp, - eur, - gbp, - gtq, - hkd, - hrk, - huf, - idr, - ils, - inr, - isk, - jpy, - krw, - mad, - mxn, - myr, - ngn, - nok, - nzd, - php, - pkr, - pln, - ron, - sek, - sgd, - thb, - twd, - usd, - vnd, - zar, - tur, - kes, - ]; + amd, + aud, + bgn, + brl, + cad, + chf, + clp, + cop, + czk, + dkk, + egp, + eur, + gbp, + gtq, + hkd, + hrk, + huf, + idr, + ils, + inr, + isk, + jpy, + krw, + mad, + mxn, + myr, + ngn, + nok, + nzd, + php, + pkr, + pln, + ron, + sek, + sgd, + thb, + twd, + usd, + vnd, + zar, + tur, + kes, + ]; static const amd = FiatCurrency(symbol: 'AMD', countryCode: "arm", fullName: "Armenian Dram"); static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso"); diff --git a/lib/new-ui/model/charts/datetime_extension.dart b/lib/new-ui/model/charts/datetime_extension.dart new file mode 100644 index 0000000000..74b343da5d --- /dev/null +++ b/lib/new-ui/model/charts/datetime_extension.dart @@ -0,0 +1,6 @@ + +extension DateTimeX on DateTime { + int get secondsSinceEpoch => millisecondsSinceEpoch~/1000; + + static DateTime fromSecondsSinceEpoch(int seconds) => DateTime.fromMillisecondsSinceEpoch(seconds*1000); +} \ No newline at end of file diff --git a/lib/new-ui/model/charts/price_api_client.dart b/lib/new-ui/model/charts/price_api_client.dart new file mode 100644 index 0000000000..de84d2e1d5 --- /dev/null +++ b/lib/new-ui/model/charts/price_api_client.dart @@ -0,0 +1,55 @@ +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/new-ui/model/charts/datetime_extension.dart'; +import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; +import 'package:cw_core/currency.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_zano/zano_wallet_api.dart'; + +const priceApiHost = "prices.cakewallet.com"; + +class PriceRequest { + final DateTime? beginTime; + final Duration interval; + final int? count; + final Currency from; + final Currency to; + + const PriceRequest( + {this.beginTime, required this.interval, this.count, required this.from, required this.to}); + + Uri get uri => Uri.https(priceApiHost, "/v3/rates", { + "time": (beginTime?.secondsSinceEpoch ?? 0).toString(), + "interval": "${interval.inSeconds}s", + if (count != null) "count": count.toString(), + "base": from.apiString, + "quote": to.apiString + }); +} + +class PriceApiClient { + static Future?> _getJson(Uri uri) async { + final resp = + await ProxyWrapper().get(headers: {"x-api-key": secrets.fiatApiKey}, clearnetUri: uri); + try { + return jsonDecode(resp.body); + } catch (e) { + printV("failed to decode response for ${uri.host}/${uri.path}: $e"); + return null; + } + } + + static Future> getPrices(PriceRequest request) async { + final List ret = []; + final data = await _getJson(request.uri); + if (data == null) return []; + for (final time in data.keys) { + ret.add(PriceData( + time: DateTimeX.fromSecondsSinceEpoch(int.parse(time)), + from: request.from, + to: request.to, + price: (data[time] as int).toString())); + } + return ret; + } +} diff --git a/lib/new-ui/model/charts/price_data.dart b/lib/new-ui/model/charts/price_data.dart new file mode 100644 index 0000000000..1164a1b6c3 --- /dev/null +++ b/lib/new-ui/model/charts/price_data.dart @@ -0,0 +1,66 @@ +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/new-ui/model/charts/datetime_extension.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; +import 'package:cw_core/db/sqlite.dart'; +import 'package:sqflite/sqflite.dart'; + + +Currency currencyFromApiString(String key) { + final parts = key.split('.'); + final type = parts[0]; + final id = parts[1]; + + switch (type) { + case "fiat": + return FiatCurrency.deserialize(raw: id); + case "crypto": + return CryptoCurrency.fromString(id); + case "evm": + throw UnimplementedError("i promise i'll take care of this, i really want a working build"); + case "sol": + throw UnimplementedError(); + } + throw Exception("unknown api string"); +} + +class PriceData { + final DateTime time; + final Currency from; + final Currency to; + final String price; + + static const tableName = "PriceData"; + + Map toJson() => { + "timestamp": time.secondsSinceEpoch.toString(), + "price": price, + "from_currency": from.apiString, + "to_currency": to.apiString, + }; + + static PriceData fromJson(Map json) => PriceData( + time: DateTimeX.fromSecondsSinceEpoch(json["timestamp"] as int), + from: currencyFromApiString(json["from_currency"] as String), + to: currencyFromApiString(json["to_currency"] as String), + price: json["price"] as String); + + static Future> get(Currency from, Currency to, DateTime? start, DateTime? end) async { + final json = await db!.query(tableName, + where: "from_currency = ? AND to_currency = ? AND timestamp >= ? AND timestamp <= ?", + whereArgs: [ + from.apiString, + to.apiString, + start?.secondsSinceEpoch ?? 0, + // dart doesn't have INT_MAX, apparently. + end?.secondsSinceEpoch ?? 0x7FFFFFFFFFFFFFFF + ]); + return List.generate(json.length, (index) => PriceData.fromJson(json[index])); + } + + Future insert() async { + db!.insert(tableName, toJson(), conflictAlgorithm: ConflictAlgorithm.replace); + } + + const PriceData({required this.time, required this.from, required this.to, required this.price}); +} \ No newline at end of file diff --git a/lib/new-ui/model/charts/price_store.dart b/lib/new-ui/model/charts/price_store.dart new file mode 100644 index 0000000000..419978e234 --- /dev/null +++ b/lib/new-ui/model/charts/price_store.dart @@ -0,0 +1,37 @@ + + +import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/util/chart_range.dart'; +import 'package:cw_core/currency.dart'; + +class PriceStore { + static Future> getPrices(Currency from, Currency to, ChartRange range) async { + final List ret = []; + + final end = DateTime.now(); + final DateTime start; + if(range.duration == null) { + start = DateTime.fromMillisecondsSinceEpoch(0); + } else { + start = end.subtract(range.duration!); + } + + final pricesFromDb = await PriceData.get(from, to, start, end); + } + + static DateTime? firstUnavailablePrice(List prices, Duration precision, DateTime start, DateTime end) { + final alignedStartMs = (start.millisecondsSinceEpoch / precision.inMilliseconds).floor() * precision.inMilliseconds; + final alignedStart = DateTime.fromMillisecondsSinceEpoch(alignedStartMs); + + final priceTimestamps = prices.map((p) => p.time.millisecondsSinceEpoch).toSet(); + + for (DateTime i = alignedStart; i.isBefore(end); i = i.add(precision)) { + if (i.isBefore(start)) continue; + + if (!priceTimestamps.contains(i.millisecondsSinceEpoch)) { + return i; + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/new-ui/viewmodels/charts/util/chart_range.dart b/lib/new-ui/viewmodels/charts/util/chart_range.dart index 3b2af19b42..b7ba7749de 100644 --- a/lib/new-ui/viewmodels/charts/util/chart_range.dart +++ b/lib/new-ui/viewmodels/charts/util/chart_range.dart @@ -3,14 +3,20 @@ class ChartRange { final Duration? duration; final String displayText; - const ChartRange._(this.duration, this.displayText); + // api can handle any precision up to 5min, regardless of data range. + // we set a "preferred" precision for 2 reasons: + // - less precision means less data sent, less bandwidth used, and thus less load on the backend + // - less data points means the chart is easier to look through on a small screen + final Duration dataPrecision; - static const oneHour = ChartRange._(Duration(hours: 1), "1H"); - static const oneDay = ChartRange._(Duration(days: 1), "1D"); - static const sevenDays = ChartRange._(Duration(days: 7), "7D"); - static const thirtyDays = ChartRange._(Duration(days: 30), "30D"); - static const oneYear = ChartRange._(Duration(days: 365), "1Y"); - static const all = ChartRange._(null, "ALL"); + const ChartRange._(this.duration, this.displayText, this.dataPrecision); + + static const oneHour = ChartRange._(Duration(hours: 1), "1H", Duration(minutes: 5)); + static const oneDay = ChartRange._(Duration(days: 1), "1D", Duration(minutes: 15)); + static const sevenDays = ChartRange._(Duration(days: 7), "7D", Duration(hours: 1)); + static const thirtyDays = ChartRange._(Duration(days: 30), "30D", Duration(hours: 4)); + static const oneYear = ChartRange._(Duration(days: 365), "1Y", Duration(days: 1)); + static const all = ChartRange._(null, "ALL", Duration(days: 5)); static const ranges = [oneHour, oneDay, sevenDays, thirtyDays, oneYear, all]; } diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index a85e6a2065..7b3529277c 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -11,6 +11,7 @@ class ChartsAssetGrid extends StatelessWidget { @override Widget build(BuildContext context) { return GridView.builder( + physics: BouncingScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, mainAxisExtent: 100), itemBuilder: (context, index) => ChartsAssetCard( From ce036a2820f358a6455e05f91aa57902cabe1f74 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 3 Apr 2026 14:16:37 +0200 Subject: [PATCH 07/40] proper getPrices logic --- cw_core/lib/erc20_token.dart | 2 +- cw_core/lib/spl_token.dart | 2 +- cw_core/lib/tron_token.dart | 2 +- lib/new-ui/model/charts/price_data.dart | 39 ++++++-- lib/new-ui/model/charts/price_store.dart | 99 ++++++++++++++++--- .../charts/util/chart_range.dart | 0 .../charts/util/price_change_direction.dart | 0 .../widgets/charts_page/asset_grid.dart | 2 +- .../widgets/charts_page/change_display.dart | 2 +- .../widgets/charts_page/change_pill.dart | 2 +- .../widgets/charts_page/chart_header.dart | 4 +- .../widgets/charts_page/chart_view.dart | 2 +- .../widgets/charts_page/range_selector.dart | 2 +- 13 files changed, 124 insertions(+), 34 deletions(-) rename lib/new-ui/{viewmodels => model}/charts/util/chart_range.dart (100%) rename lib/new-ui/{viewmodels => model}/charts/util/price_change_direction.dart (100%) diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index 90eea293c5..c36dcf1ef0 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -81,5 +81,5 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { int get hashCode => contractAddress.hashCode; @override - String get apiString => "crypto.$contractAddress"; + String get apiString => "evm.$contractAddress"; } diff --git a/cw_core/lib/spl_token.dart b/cw_core/lib/spl_token.dart index 28ee279a20..f6bbda1302 100644 --- a/cw_core/lib/spl_token.dart +++ b/cw_core/lib/spl_token.dart @@ -115,5 +115,5 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { int get hashCode => mintAddress.hashCode; @override - String get apiString => "crypto.$mintAddress"; + String get apiString => "sol.$mintAddress"; } diff --git a/cw_core/lib/tron_token.dart b/cw_core/lib/tron_token.dart index 37b2eb9676..9a4f134902 100644 --- a/cw_core/lib/tron_token.dart +++ b/cw_core/lib/tron_token.dart @@ -87,5 +87,5 @@ class TronToken extends CryptoCurrency with HiveObjectMixin { int get hashCode => contractAddress.hashCode; @override - String get apiString => "crypto.$contractAddress"; + String get apiString => "trx.$contractAddress"; } diff --git a/lib/new-ui/model/charts/price_data.dart b/lib/new-ui/model/charts/price_data.dart index 1164a1b6c3..7b22aedd58 100644 --- a/lib/new-ui/model/charts/price_data.dart +++ b/lib/new-ui/model/charts/price_data.dart @@ -5,7 +5,6 @@ import 'package:cw_core/currency.dart'; import 'package:cw_core/db/sqlite.dart'; import 'package:sqflite/sqflite.dart'; - Currency currencyFromApiString(String key) { final parts = key.split('.'); final type = parts[0]; @@ -33,11 +32,11 @@ class PriceData { static const tableName = "PriceData"; Map toJson() => { - "timestamp": time.secondsSinceEpoch.toString(), - "price": price, - "from_currency": from.apiString, - "to_currency": to.apiString, - }; + "timestamp": time.secondsSinceEpoch.toString(), + "price": price, + "from_currency": from.apiString, + "to_currency": to.apiString, + }; static PriceData fromJson(Map json) => PriceData( time: DateTimeX.fromSecondsSinceEpoch(json["timestamp"] as int), @@ -45,7 +44,8 @@ class PriceData { to: currencyFromApiString(json["to_currency"] as String), price: json["price"] as String); - static Future> get(Currency from, Currency to, DateTime? start, DateTime? end) async { + static Future> get( + Currency from, Currency to, DateTime? start, DateTime? end) async { final json = await db!.query(tableName, where: "from_currency = ? AND to_currency = ? AND timestamp >= ? AND timestamp <= ?", whereArgs: [ @@ -59,8 +59,31 @@ class PriceData { } Future insert() async { - db!.insert(tableName, toJson(), conflictAlgorithm: ConflictAlgorithm.replace); + db!.insert(tableName, toJson(), conflictAlgorithm: ConflictAlgorithm.ignore); + } + + static Future insertMany(Iterable data) async { + if (data.isEmpty) return; + + final batch = db!.batch(); + + for (final datum in data) { + batch.insert( + tableName, + datum.toJson(), + conflictAlgorithm: ConflictAlgorithm.ignore, + ); + } + + await batch.commit(noResult: true); } + @override + bool operator ==(Object other) => + other is PriceData && time == other.time && from == other.from && to == other.to; + + @override + int get hashCode => Object.hash(time, from, to); + const PriceData({required this.time, required this.from, required this.to, required this.price}); } \ No newline at end of file diff --git a/lib/new-ui/model/charts/price_store.dart b/lib/new-ui/model/charts/price_store.dart index 419978e234..ccc5316da7 100644 --- a/lib/new-ui/model/charts/price_store.dart +++ b/lib/new-ui/model/charts/price_store.dart @@ -1,35 +1,102 @@ - - +import 'package:cake_wallet/new-ui/model/charts/price_api_client.dart'; import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; -import 'package:cake_wallet/new-ui/viewmodels/charts/util/chart_range.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/chart_range.dart'; import 'package:cw_core/currency.dart'; +abstract class PriceSource { + Future> get( + DateTime start, DateTime end, Currency from, Currency to, Duration interval); + + const PriceSource(); +} + +mixin UpdatablePriceSource { + Future update(Iterable newData); +} + +class DatabasePriceSource extends PriceSource with UpdatablePriceSource { + Future> get( + DateTime start, DateTime end, Currency from, Currency to, Duration interval) async => + await PriceData.get(from, to, start, end); + + Future update(Iterable newData) async { + PriceData.insertMany(newData); + } + + const DatabasePriceSource(); +} + +class ApiPriceSource extends PriceSource { + Future> get( + DateTime start, DateTime end, Currency from, Currency to, Duration interval) async => + await PriceApiClient.getPrices( + PriceRequest(beginTime: start, interval: interval, from: from, to: to)); + + const ApiPriceSource(); +} + class PriceStore { + static const priceSources = [ + // TODO InMemoryPriceSource() - easily implementable w this pattern but idk if we need to optimize this that much + const DatabasePriceSource(), + const ApiPriceSource() + ]; + static Future> getPrices(Currency from, Currency to, ChartRange range) async { - final List ret = []; - + final Set data = {}; + final end = DateTime.now(); - final DateTime start; - if(range.duration == null) { + DateTime? start; + if (range.duration == null) { start = DateTime.fromMillisecondsSinceEpoch(0); } else { start = end.subtract(range.duration!); } + start = _alignedStart(start, range.dataPrecision); + final alignedStart = start; + + for (final source in priceSources) { + final sourceData = await source.get(start!, end, from, to, range.dataPrecision); + data.addAll(sourceData); + start = _firstUnavailablePrice(data.toList(), range.dataPrecision, start, end); + if (start == null) { + break; + } + } - final pricesFromDb = await PriceData.get(from, to, start, end); + for (final source in priceSources) { + if (source case UpdatablePriceSource s) { + await s.update(data); + } + } + + return _alignedData(alignedStart, end, data, range.dataPrecision); } - static DateTime? firstUnavailablePrice(List prices, Duration precision, DateTime start, DateTime end) { - final alignedStartMs = (start.millisecondsSinceEpoch / precision.inMilliseconds).floor() * precision.inMilliseconds; - final alignedStart = DateTime.fromMillisecondsSinceEpoch(alignedStartMs); + static List _alignedData( + DateTime start, DateTime end, Iterable data, Duration precision) { + final ret = data + .where((datum) => datum.time.millisecondsSinceEpoch % precision.inMilliseconds == 0) + .toList(); + ret.sort((a, b) => a.time.compareTo(b.time)); + return ret; + } - final priceTimestamps = prices.map((p) => p.time.millisecondsSinceEpoch).toSet(); + static DateTime _alignedStart(DateTime start, Duration precision) { + final alignedStartMs = + (start.millisecondsSinceEpoch ~/ precision.inMilliseconds) * precision.inMilliseconds; + return DateTime.fromMillisecondsSinceEpoch(alignedStartMs); + } - for (DateTime i = alignedStart; i.isBefore(end); i = i.add(precision)) { - if (i.isBefore(start)) continue; + static DateTime? _firstUnavailablePrice( + List prices, Duration precision, DateTime start, DateTime end) { + final priceTimestamps = prices.map((p) => p.time.millisecondsSinceEpoch).toSet(); - if (!priceTimestamps.contains(i.millisecondsSinceEpoch)) { - return i; + for (int i = start.millisecondsSinceEpoch; + i < end.millisecondsSinceEpoch; + i += precision.inMilliseconds) { + if (!priceTimestamps.contains(i)) { + return DateTime.fromMillisecondsSinceEpoch(i); } } return null; diff --git a/lib/new-ui/viewmodels/charts/util/chart_range.dart b/lib/new-ui/model/charts/util/chart_range.dart similarity index 100% rename from lib/new-ui/viewmodels/charts/util/chart_range.dart rename to lib/new-ui/model/charts/util/chart_range.dart diff --git a/lib/new-ui/viewmodels/charts/util/price_change_direction.dart b/lib/new-ui/model/charts/util/price_change_direction.dart similarity index 100% rename from lib/new-ui/viewmodels/charts/util/price_change_direction.dart rename to lib/new-ui/model/charts/util/price_change_direction.dart diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index 7b3529277c..a30b2631a9 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; diff --git a/lib/new-ui/widgets/charts_page/change_display.dart b/lib/new-ui/widgets/charts_page/change_display.dart index 5cbc0e91cc..b024b52aca 100644 --- a/lib/new-ui/widgets/charts_page/change_display.dart +++ b/lib/new-ui/widgets/charts_page/change_display.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart'; import 'package:flutter/material.dart'; diff --git a/lib/new-ui/widgets/charts_page/change_pill.dart b/lib/new-ui/widgets/charts_page/change_pill.dart index 8474aaf7c6..fa03e4304f 100644 --- a/lib/new-ui/widgets/charts_page/change_pill.dart +++ b/lib/new-ui/widgets/charts_page/change_pill.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:flutter/material.dart'; class ChangePill extends StatelessWidget { diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index 6ef9d5ee57..59836b69db 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -1,5 +1,5 @@ -import 'package:cake_wallet/new-ui/viewmodels/charts/util/chart_range.dart'; -import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/chart_range.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/change_display.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/coin_header.dart'; diff --git a/lib/new-ui/widgets/charts_page/chart_view.dart b/lib/new-ui/widgets/charts_page/chart_view.dart index 057b970044..591ca6aa2d 100644 --- a/lib/new-ui/widgets/charts_page/chart_view.dart +++ b/lib/new-ui/widgets/charts_page/chart_view.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/new-ui/viewmodels/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; diff --git a/lib/new-ui/widgets/charts_page/range_selector.dart b/lib/new-ui/widgets/charts_page/range_selector.dart index c84cababd9..e99cb0bb8d 100644 --- a/lib/new-ui/widgets/charts_page/range_selector.dart +++ b/lib/new-ui/widgets/charts_page/range_selector.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/new-ui/viewmodels/charts/util/chart_range.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/chart_range.dart'; import 'package:flutter/material.dart'; class ChartRangeSelector extends StatelessWidget { From 86094b384a7e1b767a42c0a0e117fcbb9efcaf84 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 3 Apr 2026 21:48:20 +0200 Subject: [PATCH 08/40] viewmodel (wip) --- lib/di.dart | 7 +- lib/new-ui/model/charts/price_api_client.dart | 19 +++- lib/new-ui/model/charts/price_store.dart | 2 +- lib/new-ui/pages/charts_page.dart | 10 +- lib/new-ui/viewmodels/charts/charts_bloc.dart | 107 ++++++++++++++++++ .../viewmodels/charts/charts_event.dart | 39 +++++++ .../viewmodels/charts/charts_state.dart | 40 +++++++ lib/new-ui/viewmodels/charts_bloc.dart | 13 --- lib/new-ui/viewmodels/charts_event.dart | 4 - lib/new-ui/viewmodels/charts_state.dart | 6 - .../widgets/charts_page/asset_grid.dart | 51 ++++++--- 11 files changed, 251 insertions(+), 47 deletions(-) create mode 100644 lib/new-ui/viewmodels/charts/charts_bloc.dart create mode 100644 lib/new-ui/viewmodels/charts/charts_event.dart create mode 100644 lib/new-ui/viewmodels/charts/charts_state.dart delete mode 100644 lib/new-ui/viewmodels/charts_bloc.dart delete mode 100644 lib/new-ui/viewmodels/charts_event.dart delete mode 100644 lib/new-ui/viewmodels/charts_state.dart diff --git a/lib/di.dart b/lib/di.dart index 0b80d2d969..c88615d7af 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -49,6 +49,7 @@ import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/haven/cw_haven.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/new-ui/model/charts/price_store.dart'; import 'package:cake_wallet/new-ui/new_dashboard.dart'; import 'package:cake_wallet/new-ui/pages/about_page.dart'; import 'package:cake_wallet/new-ui/pages/account_customizer.dart'; @@ -59,7 +60,7 @@ import 'package:cake_wallet/new-ui/pages/home_page.dart'; import 'package:cake_wallet/new-ui/pages/send_page.dart'; import 'package:cake_wallet/new-ui/pages/lightning_username_page.dart'; import 'package:cake_wallet/new-ui/pages/receive_page.dart'; -import 'package:cake_wallet/new-ui/viewmodels/charts_bloc.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; import 'package:cake_wallet/new-ui/viewmodels/lightning_username/lightning_username_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart'; @@ -605,7 +606,9 @@ Future setup({ (displayMode == BitcoinAmountDisplayMode.satoshiForLightning && lightningMode))); }); - getIt.registerFactory(()=>ChartsBloc()); + getIt.registerLazySingleton(()=>PriceStore()); + + getIt.registerFactory(()=>ChartsBloc(appStore: getIt.get(), priceStore: getIt.get())); getIt.registerFactory(()=>ChartsPage(chartsBloc: getIt.get(),)); diff --git a/lib/new-ui/model/charts/price_api_client.dart b/lib/new-ui/model/charts/price_api_client.dart index de84d2e1d5..f3acdb3eeb 100644 --- a/lib/new-ui/model/charts/price_api_client.dart +++ b/lib/new-ui/model/charts/price_api_client.dart @@ -22,8 +22,8 @@ class PriceRequest { "time": (beginTime?.secondsSinceEpoch ?? 0).toString(), "interval": "${interval.inSeconds}s", if (count != null) "count": count.toString(), - "base": from.apiString, - "quote": to.apiString + "quote": from.apiString, + "base": to.apiString }); } @@ -31,6 +31,10 @@ class PriceApiClient { static Future?> _getJson(Uri uri) async { final resp = await ProxyWrapper().get(headers: {"x-api-key": secrets.fiatApiKey}, clearnetUri: uri); + if(!(resp.statusCode >= 200 && resp.statusCode < 300)) { + printV("server returned code: ${resp.statusCode}\nuri: ${uri}\nresp body: ${resp.body}"); + return null; + } try { return jsonDecode(resp.body); } catch (e) { @@ -41,14 +45,19 @@ class PriceApiClient { static Future> getPrices(PriceRequest request) async { final List ret = []; - final data = await _getJson(request.uri); + final data = (await _getJson(request.uri)); if (data == null) return []; - for (final time in data.keys) { + final results = data["results"] as Map?; + if (results == null) { + printV(data.toString()); + return []; + } + for (final time in results.keys) { ret.add(PriceData( time: DateTimeX.fromSecondsSinceEpoch(int.parse(time)), from: request.from, to: request.to, - price: (data[time] as int).toString())); + price: (results[time] as num).toStringAsFixed(2))); } return ret; } diff --git a/lib/new-ui/model/charts/price_store.dart b/lib/new-ui/model/charts/price_store.dart index ccc5316da7..ad5945b79f 100644 --- a/lib/new-ui/model/charts/price_store.dart +++ b/lib/new-ui/model/charts/price_store.dart @@ -42,7 +42,7 @@ class PriceStore { const ApiPriceSource() ]; - static Future> getPrices(Currency from, Currency to, ChartRange range) async { + Future> getPrices(Currency from, Currency to, ChartRange range) async { final Set data = {}; final end = DateTime.now(); diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart index 58bf685bb6..0d8d889989 100644 --- a/lib/new-ui/pages/charts_page.dart +++ b/lib/new-ui/pages/charts_page.dart @@ -1,8 +1,9 @@ -import 'package:cake_wallet/new-ui/viewmodels/charts_bloc.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/asset_grid.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/asset_grid_header.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/chart_header.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class ChartsPage extends StatelessWidget { const ChartsPage({super.key, required this.chartsBloc}); @@ -11,6 +12,10 @@ class ChartsPage extends StatelessWidget { @override Widget build(BuildContext context) { + return BlocProvider( + create: (context) => chartsBloc, + child: BlocBuilder( + builder: (context, state) { return Container( height: MediaQuery.of(context).size.height, decoration: BoxDecoration( @@ -40,5 +45,8 @@ class ChartsPage extends StatelessWidget { ), ), ); + }, +), +); } } diff --git a/lib/new-ui/viewmodels/charts/charts_bloc.dart b/lib/new-ui/viewmodels/charts/charts_bloc.dart new file mode 100644 index 0000000000..7aab5f250b --- /dev/null +++ b/lib/new-ui/viewmodels/charts/charts_bloc.dart @@ -0,0 +1,107 @@ +import 'package:bloc/bloc.dart'; +import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; +import 'package:cake_wallet/new-ui/model/charts/price_store.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/chart_range.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:meta/meta.dart'; + +part 'charts_event.dart'; + +part 'charts_state.dart'; + +class ChartsBloc extends Bloc { + final PriceStore priceStore; + final AppStore appStore; + + ChartsBloc({required this.priceStore, required this.appStore}) : super(ChartsInitial()) { + on(_onRangeChanged); + on(_onSortingCriteriumChanged); + on(_onCurrencyAdded); + on(_onCurrencyRemoved); + on(_onCurrencyPinned); + on(_onPageRefreshed); + on(_onPageLoadStarted); + on(_init); + add(Init()); +} + + Future _init(Init event, Emitter emit) async { + // TODO store the config data, load it here. + emit(ChartsLoading(pinnedCurrency: CryptoCurrency.btc, currencies: [CryptoCurrency.btc, CryptoCurrency.xmr, CryptoCurrency.eth], range: ChartRange.all)); + add(PageLoadStarted()); + } + + + + Future _onPageLoadStarted(PageLoadStarted event, Emitter emit) async { + if (state case ChartsStateWithData s) { + final Map> data = {}; + for (final curr in s.currencies) { + data[curr] = await priceStore.getPrices(appStore.settingsStore.fiatCurrency, curr, s.range); + } + emit(ChartsLoaded(pinnedCurrency: s.pinnedCurrency, prices: data, range: s.range)); + } else { + throw Exception("attempted price load without currency data"); + } + } + + Future _onRangeChanged( + RangeChanged event, + Emitter emit, + ) async { + if(state case ChartsStateWithData s) { + emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: s.currencies, range: event.newRange)); + add(PageLoadStarted()); + } + } + + Future _onSortingCriteriumChanged( + SortingCriteriumChanged event, + Emitter emit, + ) async { + //TODO sorting criteria ig? + } + + Future _onCurrencyAdded( + CurrencyAdded event, + Emitter emit, + ) async { + if(state case ChartsStateWithData s) { + final newCurrencies = s.currencies..add(event.currency); + emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: newCurrencies, range: s.range)); + add(PageLoadStarted()); + } + } + + Future _onCurrencyRemoved( + CurrencyRemoved event, + Emitter emit, + ) async { + if(state case ChartsStateWithData s) { + final newCurrencies = s.currencies..remove(event.currency); + emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: newCurrencies, range: s.range)); + add(PageLoadStarted()); + } + } + + Future _onCurrencyPinned( + CurrencyPinned event, + Emitter emit, + ) async { + if(state case ChartsStateWithData s) { + emit(ChartsLoading(pinnedCurrency: event.currency, currencies: s.currencies, range: s.range)); + add(PageLoadStarted()); + } + } + + Future _onPageRefreshed( + PageRefreshed event, + Emitter emit, + ) async { + if(state case ChartsStateWithData s) { + emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: s.currencies, range: s.range)); + add(PageLoadStarted()); + } + } +} diff --git a/lib/new-ui/viewmodels/charts/charts_event.dart b/lib/new-ui/viewmodels/charts/charts_event.dart new file mode 100644 index 0000000000..ca5dec7819 --- /dev/null +++ b/lib/new-ui/viewmodels/charts/charts_event.dart @@ -0,0 +1,39 @@ +part of 'charts_bloc.dart'; + +@immutable +sealed class ChartsEvent { + const ChartsEvent(); +} + + +class RangeChanged extends ChartsEvent { + final ChartRange newRange; + + const RangeChanged({required this.newRange}); +} + +class SortingCriteriumChanged extends ChartsEvent {} + +class CurrencyAdded extends ChartsEvent { + final CryptoCurrency currency; + + const CurrencyAdded({required this.currency}); +} + +class CurrencyRemoved extends ChartsEvent { + final CryptoCurrency currency; + + const CurrencyRemoved({required this.currency}); +} + +class CurrencyPinned extends ChartsEvent { + final CryptoCurrency currency; + + const CurrencyPinned({required this.currency}); +} + +class PageRefreshed extends ChartsEvent {} + +class PageLoadStarted extends ChartsEvent {} + +class Init extends ChartsEvent {} \ No newline at end of file diff --git a/lib/new-ui/viewmodels/charts/charts_state.dart b/lib/new-ui/viewmodels/charts/charts_state.dart new file mode 100644 index 0000000000..85b65ba9f0 --- /dev/null +++ b/lib/new-ui/viewmodels/charts/charts_state.dart @@ -0,0 +1,40 @@ +part of 'charts_bloc.dart'; + +@immutable +sealed class ChartsState { + const ChartsState(); +} + +final class ChartsInitial extends ChartsState { + const ChartsInitial(); +} + +abstract final class ChartsStateWithData extends ChartsState { + final CryptoCurrency pinnedCurrency; + List get currencies; + final ChartRange range; + String priceDisplayStringFor(CryptoCurrency curr); + + const ChartsStateWithData({required this.pinnedCurrency, required this.range}); +} + +final class ChartsLoading extends ChartsStateWithData { + @override + final List currencies; + + @override + String priceDisplayStringFor(CryptoCurrency curr) => "..."; + + const ChartsLoading({required super.pinnedCurrency, required this.currencies, required super.range}); +} + +final class ChartsLoaded extends ChartsStateWithData { + final Map> prices; + + List get currencies => prices.keys.toList(); + + @override + String priceDisplayStringFor(CryptoCurrency curr) => prices[curr]?.lastOrNull?.price ?? "..."; + + const ChartsLoaded({required super.pinnedCurrency, required this.prices, required super.range}); +} diff --git a/lib/new-ui/viewmodels/charts_bloc.dart b/lib/new-ui/viewmodels/charts_bloc.dart deleted file mode 100644 index 2f9ad1f9a5..0000000000 --- a/lib/new-ui/viewmodels/charts_bloc.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:meta/meta.dart'; - -part 'charts_event.dart'; -part 'charts_state.dart'; - -class ChartsBloc extends Bloc { - ChartsBloc() : super(ChartsInitial()) { - on((event, emit) { - // TODO: implement event handler - }); - } -} diff --git a/lib/new-ui/viewmodels/charts_event.dart b/lib/new-ui/viewmodels/charts_event.dart deleted file mode 100644 index dbf5a55bb0..0000000000 --- a/lib/new-ui/viewmodels/charts_event.dart +++ /dev/null @@ -1,4 +0,0 @@ -part of 'charts_bloc.dart'; - -@immutable -sealed class ChartsEvent {} diff --git a/lib/new-ui/viewmodels/charts_state.dart b/lib/new-ui/viewmodels/charts_state.dart deleted file mode 100644 index 719b1af2c1..0000000000 --- a/lib/new-ui/viewmodels/charts_state.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of 'charts_bloc.dart'; - -@immutable -sealed class ChartsState {} - -final class ChartsInitial extends ChartsState {} diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index a30b2631a9..f6c667ad13 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -1,25 +1,41 @@ import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/themes/core/theme_extension.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class ChartsAssetGrid extends StatelessWidget { const ChartsAssetGrid({super.key}); @override Widget build(BuildContext context) { - return GridView.builder( - physics: BouncingScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, mainAxisExtent: 100), - itemBuilder: (context, index) => ChartsAssetCard( - currency: CryptoCurrency.btc, - price: "355.87", - ticker: "USD", - changePercentage: "4.56", - direction: PriceChangeDirection.up)); + return BlocBuilder( + builder: (context, state) { + if(state is ChartsStateWithData) { + final currencies = state.currencies; + return GridView.builder( + physics: BouncingScrollPhysics(), + itemCount: currencies.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, mainAxisExtent: 105), + itemBuilder: (context, index) { + final curr = currencies[index]; + return ChartsAssetCard( + currency: curr, + price: state.priceDisplayStringFor(curr), + ticker: "USD", + changePercentage: "4.56", + direction: PriceChangeDirection.up); + }); + } else { + return SizedBox.shrink(); + } + }, +); } } @@ -39,11 +55,16 @@ class ChartsAssetCard extends StatelessWidget { final PriceChangeDirection direction; String get displayPrice { - final priceDouble = double.parse(price); - if (priceDouble > 10000) - return priceDouble.toStringAsFixed(0); - else - return priceDouble.toStringAsFixed(2); + try { + final priceDouble = double.parse(price); + if (priceDouble > 10000) + return priceDouble.toStringAsFixed(0); + else + return priceDouble.toStringAsFixed(2); + } catch(_) { + return price; + } + } @override From 7c3bd24ef9a1cb524af6fc10a1e02bdd0509b219 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 03:46:44 +0200 Subject: [PATCH 09/40] charts --- cw_core/lib/db/sqlite.dart | 32 ++- cw_core/lib/output_info.dart | 4 +- .../lib/src/pending_zcash_transaction.dart | 1 + lib/di.dart | 2 +- lib/entities/default_settings_migration.dart | 15 +- lib/entities/main_actions.dart | 2 +- lib/main.dart | 2 +- lib/new-ui/model/charts/charts_asset.dart | 32 +++ .../model/charts/datetime_extension.dart | 8 +- lib/new-ui/model/charts/price_api_client.dart | 11 +- lib/new-ui/model/charts/price_data.dart | 17 +- lib/new-ui/model/charts/price_store.dart | 20 +- lib/new-ui/model/charts/util/chart_range.dart | 3 +- .../model/charts/util/price_change_data.dart | 18 ++ .../charts/util/price_change_direction.dart | 2 +- .../charts/util/price_data_sort_criteria.dart | 117 ++++++++ lib/new-ui/pages/addresses_page.dart | 6 +- lib/new-ui/pages/charts_page.dart | 106 +++++-- lib/new-ui/viewmodels/charts/charts_bloc.dart | 52 +++- .../viewmodels/charts/charts_event.dart | 6 +- .../viewmodels/charts/charts_state.dart | 94 ++++++- .../widgets/charts_page/asset_grid.dart | 265 +++++++++++------- .../charts_page/asset_grid_header.dart | 81 +++++- .../widgets/charts_page/change_display.dart | 22 +- .../widgets/charts_page/chart_header.dart | 162 +++++++---- .../widgets/charts_page/chart_modal.dart | 132 +++++++++ .../widgets/charts_page/chart_view.dart | 51 +--- .../widgets/charts_page/coin_header.dart | 7 +- .../widgets/charts_page/range_selector.dart | 4 +- .../action_row/coin_action_button.dart | 5 +- .../long_press_menu/long_press_footer.dart | 20 ++ .../long_press_menu}/long_press_popup.dart | 39 ++- lib/router.dart | 3 +- lib/src/screens/buy/buy_sell_page.dart | 18 +- pubspec_base.yaml | 1 + res/pictures/buy.svg | 10 + res/pictures/charts_sort_criteria/alpha.svg | 1 + res/pictures/charts_sort_criteria/gains.svg | 1 + res/pictures/charts_sort_criteria/losses.svg | 1 + .../charts_sort_criteria/marketcap.svg | 1 + res/pictures/info.svg | 8 +- res/pictures/sell.svg | 4 + res/values/strings_en.arb | 5 + 43 files changed, 1030 insertions(+), 361 deletions(-) create mode 100644 lib/new-ui/model/charts/charts_asset.dart create mode 100644 lib/new-ui/model/charts/util/price_change_data.dart create mode 100644 lib/new-ui/model/charts/util/price_data_sort_criteria.dart create mode 100644 lib/new-ui/widgets/charts_page/chart_modal.dart create mode 100644 lib/new-ui/widgets/long_press_menu/long_press_footer.dart rename lib/new-ui/{ => widgets/long_press_menu}/long_press_popup.dart (51%) create mode 100644 res/pictures/buy.svg create mode 100644 res/pictures/charts_sort_criteria/alpha.svg create mode 100644 res/pictures/charts_sort_criteria/gains.svg create mode 100644 res/pictures/charts_sort_criteria/losses.svg create mode 100644 res/pictures/charts_sort_criteria/marketcap.svg create mode 100644 res/pictures/sell.svg diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart index 61affbf205..b68207bd6b 100644 --- a/cw_core/lib/db/sqlite.dart +++ b/cw_core/lib/db/sqlite.dart @@ -89,15 +89,7 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings ( await _addColumnIfNotExists(db, table: "WalletInfo", column: "favoriteTokenAddress", definition: "TEXT DEFAULT NULL"); } if(oldVersion <= 4) { - await db.execute(''' -CREATE TABLE PriceData ( - from_currency TEXT NOT NULL, - to_currency TEXT NOT NULL, - timestamp INTEGER NOT NULL, - price TEXT NOT NULL, - PRIMARY KEY (from_currency, to_currency, timestamp) -); -'''); + _createChartsTables(db); } }, onCreate: (Database db, int version) async { @@ -196,17 +188,27 @@ CREATE TABLE BalanceCardStyleSettings ( FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) ); '''); - await db.execute(''' +_createChartsTables(db); + } + ); +} + +void _createChartsTables(Database db) async { + await db.execute(''' CREATE TABLE PriceData ( - from_currency TEXT NOT NULL, - to_currency TEXT NOT NULL, + fromCurrency TEXT NOT NULL, + toCurrency TEXT NOT NULL, timestamp INTEGER NOT NULL, price TEXT NOT NULL, - PRIMARY KEY (from_currency, to_currency, timestamp) + PRIMARY KEY (fromCurrency, toCurrency, timestamp) ); '''); - } - ); + await db.execute(''' +CREATE TABLE ChartsAssets ( + asset TEXT PRIMARY KEY, + isFavorite BOOLEAN DEFAULT FALSE +); + '''); } Future> dumpDb() async { diff --git a/cw_core/lib/output_info.dart b/cw_core/lib/output_info.dart index 1db1c2ed6b..5e529475c9 100644 --- a/cw_core/lib/output_info.dart +++ b/cw_core/lib/output_info.dart @@ -3,7 +3,7 @@ class OutputInfo { {required this.address, required this.sendAll, required this.isParsedAddress, - this.cryptoAmount, + required this.cryptoAmount, this.formattedCryptoAmount, this.fiatAmount, this.note, @@ -12,7 +12,7 @@ class OutputInfo { this.extra = const {}}); final String? fiatAmount; - final String? cryptoAmount; + final String cryptoAmount; final String address; final String? note; final String? extractedAddress; diff --git a/cw_zcash/lib/src/pending_zcash_transaction.dart b/cw_zcash/lib/src/pending_zcash_transaction.dart index 28be246527..6d6e42a8f8 100644 --- a/cw_zcash/lib/src/pending_zcash_transaction.dart +++ b/cw_zcash/lib/src/pending_zcash_transaction.dart @@ -85,6 +85,7 @@ class PendingZcashTransaction with PendingTransaction { return OutputInfo( address: o1.address + "," + o2.address, sendAll: false, + cryptoAmount: "", isParsedAddress: false, ); }).address, diff --git a/lib/di.dart b/lib/di.dart index c88615d7af..8cee81ac76 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1507,7 +1507,7 @@ Future setup({ getIt.registerFactory(() => BuySellViewModel(getIt.get())); - getIt.registerFactory(() => BuySellPage(getIt.get())); + getIt.registerFactoryParam((params, _) => BuySellPage(getIt.get(), params: params,)); getIt.registerFactoryParam, void>((List args, _) { final items = args.first as List; diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 3c5fed7897..7f8942d52a 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -15,23 +15,21 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/new-ui/model/charts/charts_asset.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:collection/collection.dart'; -import 'package:cw_core/db/sqlite.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/node.dart'; import 'package:cake_wallet/entities/sync_status_display_mode.dart'; -import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/root_dir.dart'; import 'package:cw_core/spl_token.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/exchange/trade.dart'; import 'package:encrypt/encrypt.dart' as encrypt; import 'package:hive/hive.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:collection/collection.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/erc20_token.dart'; @@ -615,6 +613,9 @@ Future defaultSettingsMigration( case 63: await _addXaut0TokenToExistingSolanaWallets(); break; + case 64: + await createDefaultChartsData(); + break; default: break; } @@ -1529,3 +1530,9 @@ Future _addXaut0TokenToExistingSolanaWallets() async { printV('Error in XAUT0 migration: $e'); } } + +Future createDefaultChartsData() async { + await ChartsAsset(asset: CryptoCurrency.btc, isFavorite: true).insert(); + await ChartsAsset(asset: CryptoCurrency.xmr, isFavorite: false).insert(); + await ChartsAsset(asset: CryptoCurrency.eth, isFavorite: false).insert(); +} diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 743dbfaeaf..59246da9b3 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -82,7 +82,7 @@ class MainActions { canShow: (viewModel) => viewModel.hasTradeAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { if (!viewModel.isEnabledTradeAction) return; - await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false); + await Navigator.of(context).pushNamed(Routes.buySellPage); }, ); } diff --git a/lib/main.dart b/lib/main.dart index 3d4493a36a..3d3615e4dd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -324,7 +324,7 @@ Future initializeAppConfigs({bool loadWallet = true}) async { payjoinSessionSource: payjoinSessionSource, anonpayInvoiceInfo: anonpayInvoiceInfo, havenSeedStore: havenSeedStore, - initialMigrationVersion: 63, + initialMigrationVersion: 64, ); } diff --git a/lib/new-ui/model/charts/charts_asset.dart b/lib/new-ui/model/charts/charts_asset.dart new file mode 100644 index 0000000000..48252ed11e --- /dev/null +++ b/lib/new-ui/model/charts/charts_asset.dart @@ -0,0 +1,32 @@ +import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/db/sqlite.dart'; +import 'package:sqflite/sqflite.dart'; + +class ChartsAsset { + final CryptoCurrency asset; + final bool isFavorite; + + static const tableName = "ChartsAssets"; + + Map toJson() => {"asset": asset.apiString, "isFavorite": isFavorite ? 1 : 0}; + + static ChartsAsset fromJson(Map json) => ChartsAsset( + asset: currencyFromApiString(json["asset"] as String) as CryptoCurrency, + isFavorite: json["isFavorite"] != 0); + + static Future> get() async { + final json = await db!.query(tableName); + return List.generate(json.length, (index) => ChartsAsset.fromJson(json[index])); + } + + Future insert() async { + await db!.insert(tableName, toJson(), conflictAlgorithm: ConflictAlgorithm.replace); + } + + Future remove() async { + await db!.delete(tableName, where: "asset = ?", whereArgs: [asset.apiString]); + } + + const ChartsAsset({required this.asset, required this.isFavorite}); +} diff --git a/lib/new-ui/model/charts/datetime_extension.dart b/lib/new-ui/model/charts/datetime_extension.dart index 74b343da5d..f781a91544 100644 --- a/lib/new-ui/model/charts/datetime_extension.dart +++ b/lib/new-ui/model/charts/datetime_extension.dart @@ -1,6 +1,6 @@ - extension DateTimeX on DateTime { - int get secondsSinceEpoch => millisecondsSinceEpoch~/1000; + int get secondsSinceEpoch => millisecondsSinceEpoch ~/ 1000; - static DateTime fromSecondsSinceEpoch(int seconds) => DateTime.fromMillisecondsSinceEpoch(seconds*1000); -} \ No newline at end of file + static DateTime fromSecondsSinceEpoch(int seconds) => + DateTime.fromMillisecondsSinceEpoch(seconds * 1000); +} diff --git a/lib/new-ui/model/charts/price_api_client.dart b/lib/new-ui/model/charts/price_api_client.dart index f3acdb3eeb..e25b7f9b75 100644 --- a/lib/new-ui/model/charts/price_api_client.dart +++ b/lib/new-ui/model/charts/price_api_client.dart @@ -19,8 +19,9 @@ class PriceRequest { {this.beginTime, required this.interval, this.count, required this.from, required this.to}); Uri get uri => Uri.https(priceApiHost, "/v3/rates", { - "time": (beginTime?.secondsSinceEpoch ?? 0).toString(), - "interval": "${interval.inSeconds}s", + "start": (beginTime?.secondsSinceEpoch ?? 0).toString(), + "end": DateTime.now().secondsSinceEpoch.toString(), + "interval": "${interval.inSeconds}", if (count != null) "count": count.toString(), "quote": from.apiString, "base": to.apiString @@ -31,7 +32,7 @@ class PriceApiClient { static Future?> _getJson(Uri uri) async { final resp = await ProxyWrapper().get(headers: {"x-api-key": secrets.fiatApiKey}, clearnetUri: uri); - if(!(resp.statusCode >= 200 && resp.statusCode < 300)) { + if (!(resp.statusCode >= 200 && resp.statusCode < 300)) { printV("server returned code: ${resp.statusCode}\nuri: ${uri}\nresp body: ${resp.body}"); return null; } @@ -49,8 +50,8 @@ class PriceApiClient { if (data == null) return []; final results = data["results"] as Map?; if (results == null) { - printV(data.toString()); - return []; + printV(data.toString()); + return []; } for (final time in results.keys) { ret.add(PriceData( diff --git a/lib/new-ui/model/charts/price_data.dart b/lib/new-ui/model/charts/price_data.dart index 7b22aedd58..0f4e776115 100644 --- a/lib/new-ui/model/charts/price_data.dart +++ b/lib/new-ui/model/charts/price_data.dart @@ -23,7 +23,7 @@ Currency currencyFromApiString(String key) { throw Exception("unknown api string"); } -class PriceData { +class PriceData implements Comparable { final DateTime time; final Currency from; final Currency to; @@ -34,20 +34,20 @@ class PriceData { Map toJson() => { "timestamp": time.secondsSinceEpoch.toString(), "price": price, - "from_currency": from.apiString, - "to_currency": to.apiString, + "fromCurrency": from.apiString, + "toCurrency": to.apiString, }; static PriceData fromJson(Map json) => PriceData( time: DateTimeX.fromSecondsSinceEpoch(json["timestamp"] as int), - from: currencyFromApiString(json["from_currency"] as String), - to: currencyFromApiString(json["to_currency"] as String), + from: currencyFromApiString(json["fromCurrency"] as String), + to: currencyFromApiString(json["toCurrency"] as String), price: json["price"] as String); static Future> get( Currency from, Currency to, DateTime? start, DateTime? end) async { final json = await db!.query(tableName, - where: "from_currency = ? AND to_currency = ? AND timestamp >= ? AND timestamp <= ?", + where: "fromCurrency = ? AND toCurrency = ? AND timestamp >= ? AND timestamp <= ?", whereArgs: [ from.apiString, to.apiString, @@ -85,5 +85,8 @@ class PriceData { @override int get hashCode => Object.hash(time, from, to); + @override + int compareTo(PriceData other) => time.compareTo(other.time); + const PriceData({required this.time, required this.from, required this.to, required this.price}); -} \ No newline at end of file +} diff --git a/lib/new-ui/model/charts/price_store.dart b/lib/new-ui/model/charts/price_store.dart index ad5945b79f..b314b9d157 100644 --- a/lib/new-ui/model/charts/price_store.dart +++ b/lib/new-ui/model/charts/price_store.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/new-ui/model/charts/price_api_client.dart'; import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; import 'package:cake_wallet/new-ui/model/charts/util/chart_range.dart'; import 'package:cw_core/currency.dart'; +import 'package:cw_core/utils/print_verbose.dart'; abstract class PriceSource { Future> get( @@ -90,15 +91,12 @@ class PriceStore { static DateTime? _firstUnavailablePrice( List prices, Duration precision, DateTime start, DateTime end) { - final priceTimestamps = prices.map((p) => p.time.millisecondsSinceEpoch).toSet(); - - for (int i = start.millisecondsSinceEpoch; - i < end.millisecondsSinceEpoch; - i += precision.inMilliseconds) { - if (!priceTimestamps.contains(i)) { - return DateTime.fromMillisecondsSinceEpoch(i); - } - } - return null; + if (prices.isEmpty) return start; + prices.sort(); + final last = prices.last; + printV("last.time: ${last.time.toIso8601String()} end: ${end.toIso8601String()}"); + if (last.time.isAfter(end.subtract(precision)) || + last.time.isAtSameMomentAs(end.subtract(precision))) return null; + return last.time.add(precision); } -} \ No newline at end of file +} diff --git a/lib/new-ui/model/charts/util/chart_range.dart b/lib/new-ui/model/charts/util/chart_range.dart index b7ba7749de..f5edd149cf 100644 --- a/lib/new-ui/model/charts/util/chart_range.dart +++ b/lib/new-ui/model/charts/util/chart_range.dart @@ -1,4 +1,3 @@ - class ChartRange { final Duration? duration; final String displayText; @@ -16,7 +15,7 @@ class ChartRange { static const sevenDays = ChartRange._(Duration(days: 7), "7D", Duration(hours: 1)); static const thirtyDays = ChartRange._(Duration(days: 30), "30D", Duration(hours: 4)); static const oneYear = ChartRange._(Duration(days: 365), "1Y", Duration(days: 1)); - static const all = ChartRange._(null, "ALL", Duration(days: 5)); + static const all = ChartRange._(null, "ALL", Duration(days: 1)); static const ranges = [oneHour, oneDay, sevenDays, thirtyDays, oneYear, all]; } diff --git a/lib/new-ui/model/charts/util/price_change_data.dart b/lib/new-ui/model/charts/util/price_change_data.dart new file mode 100644 index 0000000000..2fc3a61f1b --- /dev/null +++ b/lib/new-ui/model/charts/util/price_change_data.dart @@ -0,0 +1,18 @@ +import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; + +class PriceChangeData implements Comparable { + final PriceChangeDirection direction; + final String amount; + final String percentage; + + @override + int compareTo(PriceChangeData other) { + final double thisValue = + double.parse(percentage) * (direction == PriceChangeDirection.up ? 1 : -1); + final double otherValue = + double.parse(other.percentage) * (other.direction == PriceChangeDirection.up ? 1 : -1); + return thisValue.compareTo(otherValue); + } + + const PriceChangeData({required this.direction, required this.amount, required this.percentage}); +} diff --git a/lib/new-ui/model/charts/util/price_change_direction.dart b/lib/new-ui/model/charts/util/price_change_direction.dart index 9c70ce8497..1857464e9d 100644 --- a/lib/new-ui/model/charts/util/price_change_direction.dart +++ b/lib/new-ui/model/charts/util/price_change_direction.dart @@ -8,4 +8,4 @@ class PriceChangeDirection { static const up = PriceChangeDirection._(Color(0xFF6FC84E), "+"); static const down = PriceChangeDirection._(Color(0xFFEA696F), "-"); -} \ No newline at end of file +} diff --git a/lib/new-ui/model/charts/util/price_data_sort_criteria.dart b/lib/new-ui/model/charts/util/price_data_sort_criteria.dart new file mode 100644 index 0000000000..890ad84955 --- /dev/null +++ b/lib/new-ui/model/charts/util/price_data_sort_criteria.dart @@ -0,0 +1,117 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_data.dart'; +import 'package:cw_core/crypto_currency.dart'; + +const List cryptoCurrenciesByMarketcap = [ + CryptoCurrency.btc, + CryptoCurrency.eth, + CryptoCurrency.usdt, + CryptoCurrency.xrp, + CryptoCurrency.bnb, + CryptoCurrency.sol, + CryptoCurrency.usdc, + CryptoCurrency.doge, + CryptoCurrency.trx, + CryptoCurrency.ton, + CryptoCurrency.bch, + CryptoCurrency.ltc, + CryptoCurrency.xmr, + CryptoCurrency.matic, + CryptoCurrency.arb, + CryptoCurrency.dai, + CryptoCurrency.paxg, + CryptoCurrency.zec, + CryptoCurrency.dcr, + CryptoCurrency.nano, + CryptoCurrency.zano, + CryptoCurrency.deuro, + CryptoCurrency.wow, +]; + +abstract class PriceDataSortCriterium { + String get name; + + String get iconPath; + + int comparator( + PriceChangeData changeDataA, PriceChangeData changeDataB, CryptoCurrency a, CryptoCurrency b); + + static const all = [ + MarketcapSortCriterium(), + GainsSortCriterium(), + LossesSortCriterium(), + AlphabeticalSortCriterium() + ]; + + const PriceDataSortCriterium(); +} + +class AlphabeticalSortCriterium extends PriceDataSortCriterium { + @override + String get name => S.current.alphabetical; + + @override + String get iconPath => "assets/new-ui/charts_sort_criteria/alpha.svg"; + + @override + int comparator(PriceChangeData changeDataA, PriceChangeData changeDataB, CryptoCurrency a, + CryptoCurrency b) => + (a.fullName ?? a.title).compareTo((b.fullName ?? b.title)); + + const AlphabeticalSortCriterium(); +} + +class MarketcapSortCriterium extends PriceDataSortCriterium { + @override + String get name => S.current.marketcap; + + @override + String get iconPath => "assets/new-ui/charts_sort_criteria/marketcap.svg"; + + @override + int comparator(PriceChangeData changeDataA, PriceChangeData changeDataB, CryptoCurrency a, + CryptoCurrency b) { + final aIndex = cryptoCurrenciesByMarketcap.indexOf(a); + final bIndex = cryptoCurrenciesByMarketcap.indexOf(b); + + if (aIndex != -1 && bIndex != -1) { + return aIndex.compareTo(bIndex); + } + if (aIndex != -1) return -1; + if (bIndex != -1) return 1; + + return (a.fullName ?? a.title).compareTo(b.fullName ?? b.title); + } + + const MarketcapSortCriterium(); +} + +class GainsSortCriterium extends PriceDataSortCriterium { + @override + String get name => S.current.gains; + + @override + String get iconPath => "assets/new-ui/charts_sort_criteria/gains.svg"; + + @override + int comparator(PriceChangeData changeDataA, PriceChangeData changeDataB, CryptoCurrency a, + CryptoCurrency b) => + changeDataB.compareTo(changeDataA); + + const GainsSortCriterium(); +} + +class LossesSortCriterium extends PriceDataSortCriterium { + @override + String get name => S.current.losses; + + @override + String get iconPath => "assets/new-ui/charts_sort_criteria/losses.svg"; + + @override + int comparator(PriceChangeData changeDataA, PriceChangeData changeDataB, CryptoCurrency a, + CryptoCurrency b) => + changeDataA.compareTo(changeDataB); + + const LossesSortCriterium(); +} diff --git a/lib/new-ui/pages/addresses_page.dart b/lib/new-ui/pages/addresses_page.dart index a7a81d282f..6319465c4b 100644 --- a/lib/new-ui/pages/addresses_page.dart +++ b/lib/new-ui/pages/addresses_page.dart @@ -3,17 +3,16 @@ import 'dart:ui'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/new-ui/long_press_popup.dart'; +import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_popup.dart'; import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart'; -import 'package:cake_wallet/new-ui/widgets/long_press_menu.dart'; +import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_menu.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; @@ -21,7 +20,6 @@ import 'package:cw_core/card_design.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:cake_wallet/utils/list_item.dart'; import 'package:mobx/mobx.dart'; diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart index 0d8d889989..38ae7082a1 100644 --- a/lib/new-ui/pages/charts_page.dart +++ b/lib/new-ui/pages/charts_page.dart @@ -2,6 +2,9 @@ import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/asset_grid.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/asset_grid_header.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/chart_header.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,40 +16,81 @@ class ChartsPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => chartsBloc, - child: BlocBuilder( - builder: (context, state) { - return Container( - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context).colorScheme.surface, - Theme.of(context).colorScheme.surfaceDim, - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - ), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 18.0), - child: Column( - spacing: 24, - children: [ - ChartHeader(), - ChartsAssetGridHeader( - onAddButtonPressed: () {}, - onSortButtonPressed: () {}, + create: (context) => chartsBloc, + child: BlocBuilder( + builder: (context, state) { + if (state is ChartsInitial) { + // if someone has a really slow device put a placeholder instead of a glitched broken page + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 24, + children: [ + CakeImageWidget( + width: 36, height: 36, imageUrl: "assets/new-ui/navbar/charts.svg"), + CupertinoActivityIndicator(), + if (kDebugMode) + Text( + "devs: if this doesn't go away in a few seconds, either your device is painfully slow or the db is messed up") + ], + ), + ); + } + + return CustomScrollView( + physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + SliverPadding( + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + sliver: CupertinoSliverRefreshControl( + refreshTriggerPullDistance: 160, + refreshIndicatorExtent: 90, + onRefresh: () async => context.read().add(PageRefreshed()), + ), ), - Expanded(child: ChartsAssetGrid()) + SliverToBoxAdapter( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surfaceDim, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: SafeArea( + child: Column( + spacing: 24, + children: [ + if (state is ChartsStateWithData) + ChartHeader( + currency: state.pinnedCurrency, + chartHeight: 100, + chartPadding: 22, + centered: false, + favorite: true, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Column( + spacing: 24, + children: [ChartsAssetGridHeader(), ChartsAssetGrid()], + ), + ), + SizedBox( + height: 96, + ) + ], + ), + ), + ), + ) ], - ), - ), + ); + }, ), ); - }, -), -); } } diff --git a/lib/new-ui/viewmodels/charts/charts_bloc.dart b/lib/new-ui/viewmodels/charts/charts_bloc.dart index 7aab5f250b..0b093493fb 100644 --- a/lib/new-ui/viewmodels/charts/charts_bloc.dart +++ b/lib/new-ui/viewmodels/charts/charts_bloc.dart @@ -1,10 +1,14 @@ import 'package:bloc/bloc.dart'; +import 'package:cake_wallet/new-ui/model/charts/charts_asset.dart'; import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_data_sort_criteria.dart'; import 'package:cake_wallet/new-ui/model/charts/price_store.dart'; import 'package:cake_wallet/new-ui/model/charts/util/chart_range.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_data.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:meta/meta.dart'; +import 'package:flutter/foundation.dart'; part 'charts_event.dart'; @@ -27,8 +31,12 @@ class ChartsBloc extends Bloc { } Future _init(Init event, Emitter emit) async { - // TODO store the config data, load it here. - emit(ChartsLoading(pinnedCurrency: CryptoCurrency.btc, currencies: [CryptoCurrency.btc, CryptoCurrency.xmr, CryptoCurrency.eth], range: ChartRange.all)); + final assets = await ChartsAsset.get(); + emit(ChartsLoading( + pinnedCurrency: assets.firstWhere((item) => item.isFavorite).asset, + currencies: assets.map((item) => item.asset).toList(), + range: ChartRange.all, + sortCriterium: PriceDataSortCriterium.all.first)); add(PageLoadStarted()); } @@ -40,7 +48,7 @@ class ChartsBloc extends Bloc { for (final curr in s.currencies) { data[curr] = await priceStore.getPrices(appStore.settingsStore.fiatCurrency, curr, s.range); } - emit(ChartsLoaded(pinnedCurrency: s.pinnedCurrency, prices: data, range: s.range)); + emit(ChartsLoaded(pinnedCurrency: s.pinnedCurrency, prices: data, range: s.range, sortCriterium: s.sortCriterium)); } else { throw Exception("attempted price load without currency data"); } @@ -51,7 +59,7 @@ class ChartsBloc extends Bloc { Emitter emit, ) async { if(state case ChartsStateWithData s) { - emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: s.currencies, range: event.newRange)); + emit(s.toLoading().copyWith(range: event.newRange)); add(PageLoadStarted()); } } @@ -60,7 +68,9 @@ class ChartsBloc extends Bloc { SortingCriteriumChanged event, Emitter emit, ) async { - //TODO sorting criteria ig? + if(state case ChartsStateWithData s) { + emit(s.copyWith(sortCriterium: event.newCriterium)); + } } Future _onCurrencyAdded( @@ -69,7 +79,8 @@ class ChartsBloc extends Bloc { ) async { if(state case ChartsStateWithData s) { final newCurrencies = s.currencies..add(event.currency); - emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: newCurrencies, range: s.range)); + await ChartsAsset(asset: event.currency, isFavorite: false).insert(); + emit(s.toLoading().copyWith(currencies: newCurrencies)); add(PageLoadStarted()); } } @@ -80,7 +91,23 @@ class ChartsBloc extends Bloc { ) async { if(state case ChartsStateWithData s) { final newCurrencies = s.currencies..remove(event.currency); - emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: newCurrencies, range: s.range)); + + if(newCurrencies.isEmpty) { + throw Exception("removed the last currency ${(kDebugMode) ? "- your ui should block this! what did you do?" : ""}"); + } + + + final CryptoCurrency newPin; + if(s.pinnedCurrency == event.currency) { + newPin = newCurrencies.first; + } else { + newPin = s.pinnedCurrency; + } + + await ChartsAsset(asset: event.currency, isFavorite: false).remove(); + + + emit(s.toLoading().copyWith(currencies: newCurrencies, pinnedCurrency: newPin)); add(PageLoadStarted()); } } @@ -90,7 +117,12 @@ class ChartsBloc extends Bloc { Emitter emit, ) async { if(state case ChartsStateWithData s) { - emit(ChartsLoading(pinnedCurrency: event.currency, currencies: s.currencies, range: s.range)); + if(s.pinnedCurrency == event.currency) { + return; + } + await ChartsAsset(asset: s.pinnedCurrency, isFavorite: false).insert(); + await ChartsAsset(asset: event.currency, isFavorite: true).insert(); + emit(s.toLoading().copyWith(pinnedCurrency: event.currency)); add(PageLoadStarted()); } } @@ -100,7 +132,7 @@ class ChartsBloc extends Bloc { Emitter emit, ) async { if(state case ChartsStateWithData s) { - emit(ChartsLoading(pinnedCurrency: s.pinnedCurrency, currencies: s.currencies, range: s.range)); + emit(s.toLoading()); add(PageLoadStarted()); } } diff --git a/lib/new-ui/viewmodels/charts/charts_event.dart b/lib/new-ui/viewmodels/charts/charts_event.dart index ca5dec7819..66dc76fac2 100644 --- a/lib/new-ui/viewmodels/charts/charts_event.dart +++ b/lib/new-ui/viewmodels/charts/charts_event.dart @@ -12,7 +12,11 @@ class RangeChanged extends ChartsEvent { const RangeChanged({required this.newRange}); } -class SortingCriteriumChanged extends ChartsEvent {} +class SortingCriteriumChanged extends ChartsEvent { + final PriceDataSortCriterium newCriterium; + + const SortingCriteriumChanged({required this.newCriterium}); +} class CurrencyAdded extends ChartsEvent { final CryptoCurrency currency; diff --git a/lib/new-ui/viewmodels/charts/charts_state.dart b/lib/new-ui/viewmodels/charts/charts_state.dart index 85b65ba9f0..7c589c80fd 100644 --- a/lib/new-ui/viewmodels/charts/charts_state.dart +++ b/lib/new-ui/viewmodels/charts/charts_state.dart @@ -11,11 +11,33 @@ final class ChartsInitial extends ChartsState { abstract final class ChartsStateWithData extends ChartsState { final CryptoCurrency pinnedCurrency; + List get currencies; + + final PriceDataSortCriterium sortCriterium; final ChartRange range; - String priceDisplayStringFor(CryptoCurrency curr); - const ChartsStateWithData({required this.pinnedCurrency, required this.range}); + String priceDisplayStringFor(CryptoCurrency curr) => "..."; + + String get fiatTicker => ""; + + bool get hasSingleCurrency => currencies.length == 1; + + ChartsStateWithData copyWith({ + CryptoCurrency? pinnedCurrency, + ChartRange? range, + PriceDataSortCriterium? sortCriterium, + }); + + ChartsLoading toLoading() => ChartsLoading( + pinnedCurrency: pinnedCurrency, + currencies: currencies, + range: range, + sortCriterium: sortCriterium, + ); + + const ChartsStateWithData( + {required this.pinnedCurrency, required this.range, required this.sortCriterium}); } final class ChartsLoading extends ChartsStateWithData { @@ -23,18 +45,74 @@ final class ChartsLoading extends ChartsStateWithData { final List currencies; @override - String priceDisplayStringFor(CryptoCurrency curr) => "..."; + ChartsLoading copyWith({ + CryptoCurrency? pinnedCurrency, + List? currencies, + ChartRange? range, + PriceDataSortCriterium? sortCriterium, + }) => ChartsLoading( + pinnedCurrency: pinnedCurrency ?? this.pinnedCurrency, + currencies: currencies ?? this.currencies, + range: range ?? this.range, + sortCriterium: sortCriterium ?? this.sortCriterium, + ); - const ChartsLoading({required super.pinnedCurrency, required this.currencies, required super.range}); + const ChartsLoading( + {required super.pinnedCurrency, + required this.currencies, + required super.range, + required super.sortCriterium}); } final class ChartsLoaded extends ChartsStateWithData { - final Map> prices; + final Map> _prices; + + @override + String get fiatTicker => _prices[_prices.keys.first]?.firstOrNull?.from.name ?? ""; + + List get currencies { + final list = _prices.keys.toList(); + list.sort((a, b) => sortCriterium.comparator(changeDataFor(a), changeDataFor(b), a, b)); + return list; + } + + @override + String priceDisplayStringFor(CryptoCurrency curr) => _prices[curr]?.lastOrNull?.price ?? "..."; + + PriceChangeData changeDataFor(CryptoCurrency curr) { + final latestPrice = double.tryParse(_prices[curr]?.lastOrNull?.price ?? "") ?? 0; + final secondLatestPrice = double.tryParse(_prices[curr]?.firstOrNull?.price ?? "") ?? 0; + + final direction = + latestPrice >= secondLatestPrice ? PriceChangeDirection.up : PriceChangeDirection.down; + final percentage = ((latestPrice - secondLatestPrice) / secondLatestPrice * 100).abs(); + final amount = (latestPrice - secondLatestPrice).abs(); + + return PriceChangeData( + direction: direction, + amount: amount.toStringAsFixed(2), + percentage: percentage.toStringAsFixed(2)); + } - List get currencies => prices.keys.toList(); + List dataFor(CryptoCurrency curr) => _prices[curr] ?? []; @override - String priceDisplayStringFor(CryptoCurrency curr) => prices[curr]?.lastOrNull?.price ?? "..."; + ChartsLoaded copyWith({ + CryptoCurrency? pinnedCurrency, + Map>? prices, + ChartRange? range, + PriceDataSortCriterium? sortCriterium, + }) => ChartsLoaded( + pinnedCurrency: pinnedCurrency ?? this.pinnedCurrency, + prices: prices ?? this._prices, + range: range ?? this.range, + sortCriterium: sortCriterium ?? this.sortCriterium, + ); - const ChartsLoaded({required super.pinnedCurrency, required this.prices, required super.range}); + const ChartsLoaded( + {required super.pinnedCurrency, + required Map> prices, + required super.range, + required super.sortCriterium}) + : _prices = prices; } diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index f6c667ad13..e6eca7b37d 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -1,9 +1,14 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_footer.dart'; +import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_popup.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_data.dart'; import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/chart_modal.dart'; +import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_menu.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/themes/core/theme_extension.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -14,28 +19,34 @@ class ChartsAssetGrid extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - builder: (context, state) { - if(state is ChartsStateWithData) { - final currencies = state.currencies; - return GridView.builder( - physics: BouncingScrollPhysics(), - itemCount: currencies.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, mainAxisExtent: 105), - itemBuilder: (context, index) { - final curr = currencies[index]; - return ChartsAssetCard( - currency: curr, - price: state.priceDisplayStringFor(curr), - ticker: "USD", - changePercentage: "4.56", - direction: PriceChangeDirection.up); - }); - } else { - return SizedBox.shrink(); - } - }, -); + builder: (context, state) { + if (state is ChartsStateWithData) { + final currencies = state.currencies; + return GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: currencies.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + mainAxisExtent: 105), + itemBuilder: (context, index) { + final curr = currencies[index]; + return ChartsAssetCard( + currency: curr, + price: state.priceDisplayStringFor(curr), + ticker: state.fiatTicker, + changeData: state is ChartsLoaded ? state.changeDataFor(curr) : null, + favorite: curr == state.pinnedCurrency, + isSingleCurrency: state.hasSingleCurrency, + ); + }); + } else { + return SizedBox.shrink(); + } + }, + ); } } @@ -45,14 +56,16 @@ class ChartsAssetCard extends StatelessWidget { required this.currency, required this.price, required this.ticker, - required this.changePercentage, - required this.direction}); + this.changeData, + required this.favorite, + required this.isSingleCurrency}); final CryptoCurrency currency; final String price; final String ticker; - final String changePercentage; - final PriceChangeDirection direction; + final PriceChangeData? changeData; + final bool favorite; + final bool isSingleCurrency; String get displayPrice { try { @@ -61,96 +74,146 @@ class ChartsAssetCard extends StatelessWidget { return priceDouble.toStringAsFixed(0); else return priceDouble.toStringAsFixed(2); - } catch(_) { + } catch (_) { return price; } - } @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - border: - Border.all(width: 1, color: Theme.of(context).colorScheme.surfaceContainerHighest), - gradient: LinearGradient(colors: [ - context.customColors.cardGradientColorPrimary, - context.customColors.cardGradientColorSecondary - ], begin: Alignment.topCenter, end: Alignment.bottomCenter)), - child: Padding( - padding: EdgeInsets.all(12), - child: Column( - spacing: 12, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return LongPressPopupBuilder( + popup: LongPressMenu(items: [ + LongPressMenuItem( + label: S.of(context).favorite, + iconPath: "assets/new-ui/favorite.svg", + onSelected: () { + context.read().add(CurrencyPinned(currency: currency)); + Navigator.of(context).pop(); + }, + color: favorite ? Theme.of(context).colorScheme.error : null), + LongPressMenuItem( + label: S.of(context).remove, + iconPath: "assets/new-ui/address_hide.svg", + onSelected: () { + if (!isSingleCurrency) { + context.read().add(CurrencyRemoved(currency: currency)); + Navigator.of(context).pop(); + } + }, + color: isSingleCurrency ? Theme.of(context).colorScheme.onSurfaceVariant : null) + ]), + footer: + isSingleCurrency ? LongPressFooter(text: S.of(context).cannot_remove_last_asset) : null, + child: GestureDetector( + onTap: () async { + final res = await showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) => ChartModal( + currency: currency, + isFavorite: favorite, + )); + if (res != null && res is bool && res) { + context.read().add(CurrencyPinned(currency: currency)); + } + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + width: 1, color: Theme.of(context).colorScheme.surfaceContainerHighest), + gradient: LinearGradient(colors: [ + context.customColors.cardGradientColorPrimary, + context.customColors.cardGradientColorSecondary + ], begin: Alignment.topCenter, end: Alignment.bottomCenter)), + child: Padding( + padding: EdgeInsets.all(12), + child: Column( + spacing: 12, children: [ - Column( + Row( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - spacing: 5, - children: [ - RotatedBox( - quarterTurns: direction == PriceChangeDirection.up ? 0 : 2, - child: CakeImageWidget( - imageUrl: "assets/new-ui/price_change_arrow.svg", - width: 8, - height: 8, - colorFilter: ColorFilter.mode(direction.color, BlendMode.srcIn), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 5, + children: [ + if (changeData != null) + RotatedBox( + quarterTurns: + changeData!.direction == PriceChangeDirection.up ? 0 : 2, + child: CakeImageWidget( + imageUrl: "assets/new-ui/price_change_arrow.svg", + width: 8, + height: 8, + colorFilter: ColorFilter.mode( + changeData!.direction.color, BlendMode.srcIn), + ), + ), + Text( + currency.title.toUpperCase(), + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ) + ], ), - ), - Text( - currency.title.toUpperCase(), - style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), - ) - ], + Text( + currency.fullName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.onSurfaceVariant), + ) + ], + ), ), - Text( - currency.fullName ?? "", - style: TextStyle( - fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant), - ) + if (changeData != null) + ChangePill( + changePercentage: changeData!.percentage, + direction: changeData!.direction) ], ), - ChangePill(changePercentage: changePercentage, direction: direction) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8, - children: [ - CakeImageWidget( - imageUrl: currency.iconSvgPath ?? currency.iconPath, - width: 24, - height: 24, - ), - Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Row( - spacing: 4, - children: [ - Text( - displayPrice, - style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), - ), - Text( - ticker, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w500, - fontSize: 20), - ) - ], + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 8, + children: [ + CakeImageWidget( + imageUrl: currency.iconSvgPath ?? currency.iconPath, + width: 24, + height: 24, ), - ), + Expanded( + child: FittedBox( + alignment: Alignment.centerRight, + fit: BoxFit.scaleDown, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + spacing: 4, + children: [ + Text( + displayPrice, + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20), + ), + Text( + ticker, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w500, + fontSize: 20), + ) + ], + ), + ), + ) + ], ) ], - ) - ], + ), + ), ), ), ); diff --git a/lib/new-ui/widgets/charts_page/asset_grid_header.dart b/lib/new-ui/widgets/charts_page/asset_grid_header.dart index d0fd6cafd7..e22edb2960 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid_header.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid_header.dart @@ -1,22 +1,81 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_popup.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_data_sort_criteria.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; +import 'package:cake_wallet/new-ui/widgets/long_press_menu/long_press_menu.dart'; import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class ChartsAssetGridHeader extends StatelessWidget { - const ChartsAssetGridHeader({super.key, required this.onAddButtonPressed, required this.onSortButtonPressed}); - - final VoidCallback onAddButtonPressed; - final VoidCallback onSortButtonPressed; + const ChartsAssetGridHeader({ + super.key, + }); @override Widget build(BuildContext context) { - return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [ - Text(S.of(context).followed_assets, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12)), - Row(spacing:8,children: [ - ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/add.svg",onPressed: onAddButtonPressed,), - ModernButton.svg(size: 36, iconSize: 16,svgPath: "assets/new-ui/sort.svg",onPressed: onSortButtonPressed,) + return BlocBuilder( + builder: (context, state) { + final bloc = context.read(); + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(S.of(context).followed_assets, + style: + TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12)), + Row( + spacing: 8, + children: [ + ModernButton.svg( + size: 36, + iconSize: 16, + svgPath: "assets/new-ui/add.svg", + onPressed: () => _showCurrencyPicker(context, bloc), + ), + LongPressPopupBuilder( + showOnTap: true, + popup: LongPressMenu( + items: PriceDataSortCriterium.all.map((item) { + final isSelected = + state is ChartsStateWithData && state.sortCriterium == item; + + return LongPressMenuItem( + label: item.name, + iconPath: item.iconPath, + color: isSelected ? Theme.of(context).colorScheme.primary : null, + onSelected: () { + bloc.add(SortingCriteriumChanged(newCriterium: item)); + Navigator.of(context).pop(); + }); + }).toList()), + child: ModernButton.svg( + size: 36, + iconSize: 12, + svgPath: "assets/new-ui/sort.svg", + onPressed: () {}, + )) + ], + ) + ], + ); + }, + ); + } - ],) - ],); + void _showCurrencyPicker(BuildContext context, ChartsBloc bloc) { + showPopUp( + context: context, + builder: (context) => CurrencyPicker( + selectedAtIndex: -1, + items: CryptoCurrency.all, + onItemSelected: (item) { + if (item is CryptoCurrency) { + bloc.add(CurrencyAdded(currency: item)); + } + })); } } diff --git a/lib/new-ui/widgets/charts_page/change_display.dart b/lib/new-ui/widgets/charts_page/change_display.dart index b024b52aca..c0e1a0e51b 100644 --- a/lib/new-ui/widgets/charts_page/change_display.dart +++ b/lib/new-ui/widgets/charts_page/change_display.dart @@ -1,31 +1,25 @@ -import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/model/charts/util/price_change_data.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/change_pill.dart'; import 'package:flutter/material.dart'; class ChangeDisplay extends StatelessWidget { - const ChangeDisplay( - {super.key, - required this.changeAmount, - required this.changePercentage, - required this.direction, - required this.ticker}); + const ChangeDisplay({super.key, required this.changeData, required this.ticker}); - final String changeAmount; - final String changePercentage; final String ticker; - final PriceChangeDirection direction; + final PriceChangeData changeData; @override Widget build(BuildContext context) { return Row( + mainAxisSize: MainAxisSize.min, spacing: 10, children: [ Text( - "${direction.symbol}${ticker} ${changeAmount}", - style: TextStyle(fontSize: 16, color: direction.color), + "${changeData.direction.symbol}${ticker} ${changeData.amount}", + style: TextStyle(fontSize: 16, color: changeData.direction.color), ), - ChangePill(changePercentage: changePercentage, direction: direction) + ChangePill(changePercentage: changeData.percentage, direction: changeData.direction) ], ); } -} \ No newline at end of file +} diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index 59836b69db..26d704afa9 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -1,15 +1,29 @@ -import 'package:cake_wallet/new-ui/model/charts/util/chart_range.dart'; -import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/change_display.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/chart_view.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/coin_header.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/price_header.dart'; import 'package:cake_wallet/new-ui/widgets/charts_page/range_selector.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class ChartHeader extends StatefulWidget { - const ChartHeader({super.key}); + const ChartHeader( + {super.key, + required this.currency, + required this.chartHeight, + required this.chartPadding, + required this.centered, + required this.favorite}); + + final CryptoCurrency currency; + final double chartHeight; + final double chartPadding; + final bool centered; + final bool favorite; @override State createState() => _ChartHeaderState(); @@ -17,62 +31,98 @@ class ChartHeader extends StatefulWidget { class _ChartHeaderState extends State { String? _viewedPrice; - ChartRange _range = ChartRange.oneDay; @override Widget build(BuildContext context) { - return Column( - spacing: 20, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 10, - children: [ - ChartViewCoinHeader(currency: CryptoCurrency.btc, isFavorite: true), - ChartViewPriceHeader( - price: _viewedPrice ?? "109437.05", - ticker: "USD", - highlight: _viewedPrice != null, - ), - Column( - children: [ - ChangeDisplay( - changeAmount: "85.6", - changePercentage: "2.31", - direction: PriceChangeDirection.up, - ticker: "USD"), - Padding( - padding: const EdgeInsets.symmetric(vertical: 22.0), - child: PriceChart( - height: 100, - prices: chartMockData, - direction: PriceChangeDirection.up, - touchCallback: (event, response) { - if (!event.isInterestedForInteractions) { - setState(() { - _viewedPrice = null; - }); - return; - } - setState(() { - _viewedPrice = response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); - }); - }, - ), - ), - Container( - width: double.infinity, - height: 1, - color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(128), + return BlocBuilder( + builder: (context, state) { + if (state case ChartsStateWithData s) { + return Column( + crossAxisAlignment: + widget.centered ? CrossAxisAlignment.center : CrossAxisAlignment.start, + spacing: 10, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 18), + child: Column( + crossAxisAlignment: + widget.centered ? CrossAxisAlignment.center : CrossAxisAlignment.start, + spacing: 10, + children: [ + if (!widget.favorite) + CakeImageWidget( + imageUrl: widget.currency.iconSvgPath ?? widget.currency.iconPath ?? "", + width: 60, + height: 60, + ), + ChartViewCoinHeader(currency: widget.currency, isFavorite: widget.favorite), + ChartViewPriceHeader( + price: _viewedPrice ?? s.priceDisplayStringFor(widget.currency), + ticker: s.fiatTicker, + highlight: _viewedPrice != null, + ), + Column( + crossAxisAlignment: + widget.centered ? CrossAxisAlignment.center : CrossAxisAlignment.start, + children: [ + SizedBox( + // has to be constant-size, otherwise will jump around when loading + height: 36, + child: (s is ChartsLoaded) + ? ChangeDisplay( + changeData: s.changeDataFor(widget.currency), ticker: "USD") + : SizedBox.shrink(), + ), + if (s is ChartsLoaded) + Padding( + padding: EdgeInsets.symmetric(vertical: widget.chartPadding), + child: PriceChart( + height: widget.chartHeight, + prices: s.dataFor(widget.currency), + direction: s.changeDataFor(widget.currency).direction, + touchCallback: (event, response) { + if (!event.isInterestedForInteractions) { + setState(() { + _viewedPrice = null; + }); + return; + } + setState(() { + _viewedPrice = + response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); + }); + }, + ), + ) + else + SizedBox( + height: widget.chartHeight + widget.chartPadding * 2, + child: Center(child: CupertinoActivityIndicator())), + ], + ), + ], ), - ChartRangeSelector(selectedRange: _range, onRangeSelected: (range)=>setState(() { - _range = range; - })) - ], - ) - ], - ) - ], + ), + Container( + width: double.infinity, + height: 1, + color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(128), + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ChartRangeSelector( + selectedRange: s.range, + onRangeSelected: (range) => + context.read().add(RangeChanged(newRange: range))) + ], + ) + ], + ); + } + return CupertinoActivityIndicator(); + }, ); } -} \ No newline at end of file +} diff --git a/lib/new-ui/widgets/charts_page/chart_modal.dart b/lib/new-ui/widgets/charts_page/chart_modal.dart new file mode 100644 index 0000000000..828a6dc286 --- /dev/null +++ b/lib/new-ui/widgets/charts_page/chart_modal.dart @@ -0,0 +1,132 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/new-ui/modal_navigator.dart'; +import 'package:cake_wallet/new-ui/pages/swap_page.dart'; +import 'package:cake_wallet/new-ui/viewmodels/charts/charts_bloc.dart'; +import 'package:cake_wallet/new-ui/widgets/charts_page/chart_header.dart'; +import 'package:cake_wallet/new-ui/widgets/coins_page/action_row/coin_action_button.dart'; +import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/buy/buy_sell_page.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; + +class ChartModal extends StatelessWidget { + const ChartModal({super.key, required this.currency, required this.isFavorite}); + + final CryptoCurrency currency; + final bool isFavorite; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => getIt.get(), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.vertical(top: Radius.circular(18))), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ModalTopBar( + title: "", + onLeadingPressed: Navigator.of(context).pop, + leadingIcon: Icon(Icons.close), + padding: EdgeInsets.only(top: 18, left: 18), + ), + ChartHeader( + currency: currency, + chartHeight: 140, + chartPadding: 102, + centered: true, + favorite: false, + ), + SizedBox( + height: 48, + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + spacing: MediaQuery.of(context).size.width * 0.05, + children: [ + CoinActionButton( + icon: CakeImageWidget( + imageUrl: "assets/new-ui/buy.svg", + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.primary, + BlendMode.srcIn, + ), + ), + label: S.of(context).buy, + action: () { + Navigator.of(context).pushNamed(Routes.buySellPage, + arguments: + BuySellPageParams(startWithSell: false, initialCurrency: currency)); + }), + CoinActionButton( + icon: CakeImageWidget( + imageUrl: "assets/new-ui/sell.svg", + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.primary, + BlendMode.srcIn, + ), + ), + label: S.of(context).sell, + action: () { + Navigator.of(context).pushNamed(Routes.buySellPage, + arguments: + BuySellPageParams(startWithSell: true, initialCurrency: currency)); + }), + CoinActionButton( + icon: CakeImageWidget( + imageUrl: "assets/new-ui/exchange.svg", + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.primary, + BlendMode.srcIn, + ), + ), + label: S.of(context).swap, + action: () { + final page = getIt.get(param2: currency); + showCupertinoModalBottomSheet( + context: context, + barrierColor: Colors.black.withAlpha(85), + builder: (context) => Material( + child: ModalNavigator( + rootPage: page, + parentContext: context, + ))); + }), + CoinActionButton( + icon: CakeImageWidget( + width: 36, + height: 36, + imageUrl: "assets/new-ui/favorite.svg", + colorFilter: ColorFilter.mode( + isFavorite + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).colorScheme.primary, + BlendMode.srcIn, + ), + ), + gradientColors: isFavorite ? [Color(0xFFDF2626), Color(0xFF980F0F)] : null, + label: S.of(context).favorite, + action: () { + Navigator.of(context).pop(true); + }) + ], + ), + SizedBox( + height: 32, + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/new-ui/widgets/charts_page/chart_view.dart b/lib/new-ui/widgets/charts_page/chart_view.dart index 591ca6aa2d..1b9f155d36 100644 --- a/lib/new-ui/widgets/charts_page/chart_view.dart +++ b/lib/new-ui/widgets/charts_page/chart_view.dart @@ -1,50 +1,9 @@ +import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; import 'package:cake_wallet/new-ui/model/charts/util/price_change_direction.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; - - -Map get chartMockData { - final DateTime now = DateTime.now(); - - return { - now.subtract(const Duration(hours: 24, minutes: 0)): "120.50", - now.subtract(const Duration(hours: 23, minutes: 15)): "135.20", - now.subtract(const Duration(hours: 22, minutes: 30)): "105.00", - now.subtract(const Duration(hours: 21, minutes: 45)): "160.75", - now.subtract(const Duration(hours: 21, minutes: 0)): "140.00", - now.subtract(const Duration(hours: 20, minutes: 15)): "210.25", - now.subtract(const Duration(hours: 19, minutes: 30)): "185.50", - now.subtract(const Duration(hours: 18, minutes: 45)): "300.00", - now.subtract(const Duration(hours: 18, minutes: 0)): "250.50", - now.subtract(const Duration(hours: 17, minutes: 15)): "310.00", - now.subtract(const Duration(hours: 16, minutes: 30)): "310.00", - now.subtract(const Duration(hours: 15, minutes: 45)): "220.25", - now.subtract(const Duration(hours: 15, minutes: 0)): "300.50", - now.subtract(const Duration(hours: 14, minutes: 15)): "260.00", - now.subtract(const Duration(hours: 13, minutes: 30)): "280.75", - now.subtract(const Duration(hours: 12, minutes: 45)): "265.00", - now.subtract(const Duration(hours: 12, minutes: 0)): "245.50", - now.subtract(const Duration(hours: 11, minutes: 15)): "245.50", - now.subtract(const Duration(hours: 10, minutes: 30)): "300.00", - now.subtract(const Duration(hours: 9, minutes: 45)): "270.25", - now.subtract(const Duration(hours: 9, minutes: 0)): "300.00", - now.subtract(const Duration(hours: 8, minutes: 15)): "250.50", - now.subtract(const Duration(hours: 7, minutes: 30)): "270.00", - now.subtract(const Duration(hours: 6, minutes: 45)): "235.75", - now.subtract(const Duration(hours: 6, minutes: 0)): "280.00", - now.subtract(const Duration(hours: 5, minutes: 15)): "320.50", - now.subtract(const Duration(hours: 4, minutes: 30)): "295.00", - now.subtract(const Duration(hours: 3, minutes: 45)): "340.25", - now.subtract(const Duration(hours: 3, minutes: 0)): "250.00", - now.subtract(const Duration(hours: 2, minutes: 15)): "280.50", - now.subtract(const Duration(hours: 1, minutes: 30)): "255.00", - now.subtract(const Duration(hours: 0, minutes: 45)): "270.25", - now: "285.50", - }; -} - class PriceChart extends StatelessWidget { const PriceChart( {super.key, @@ -53,16 +12,16 @@ class PriceChart extends StatelessWidget { required this.direction, required this.touchCallback}); - final Map prices; + final List prices; final double height; final PriceChangeDirection direction; final Function(FlTouchEvent, LineTouchResponse?) touchCallback; @override Widget build(BuildContext context) { - final chartPoints = chartMockData.entries.map((entry) { - final x = entry.key.millisecondsSinceEpoch.toDouble(); - final y = double.parse(entry.value); + final chartPoints = prices.map((entry) { + final x = entry.time.millisecondsSinceEpoch.toDouble(); + final y = double.parse(entry.price); return FlSpot(x, y); }).toList(); diff --git a/lib/new-ui/widgets/charts_page/coin_header.dart b/lib/new-ui/widgets/charts_page/coin_header.dart index d063743898..249cc6a0ff 100644 --- a/lib/new-ui/widgets/charts_page/coin_header.dart +++ b/lib/new-ui/widgets/charts_page/coin_header.dart @@ -11,17 +11,20 @@ class ChartViewCoinHeader extends StatelessWidget { @override Widget build(BuildContext context) { return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: isFavorite ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center, children: [ Row( + mainAxisAlignment: MainAxisAlignment.center, spacing: 10, children: [ + if(isFavorite) CakeImageWidget( imageUrl: currency.iconSvgPath ?? currency.iconPath ?? "", width: 30, height: 30, ), Row( + mainAxisAlignment: MainAxisAlignment.center, spacing: 5, children: [ Text( @@ -57,8 +60,6 @@ class ChartViewCoinHeader extends StatelessWidget { colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn), ) - else - SizedBox.shrink() ], ); } diff --git a/lib/new-ui/widgets/charts_page/range_selector.dart b/lib/new-ui/widgets/charts_page/range_selector.dart index e99cb0bb8d..42e5ac2475 100644 --- a/lib/new-ui/widgets/charts_page/range_selector.dart +++ b/lib/new-ui/widgets/charts_page/range_selector.dart @@ -35,7 +35,7 @@ class ChartRangeSelector extends StatelessWidget { children: ChartRange.ranges.map((item) { final selected = selectedRange == item; return GestureDetector( - onTap: ()=>onRangeSelected(item), + onTap: () => onRangeSelected(item), child: Container( width: optionSize, height: optionSize, @@ -54,4 +54,4 @@ class ChartRangeSelector extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart b/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart index de4de73c9b..7fbe2a3bed 100644 --- a/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart +++ b/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart @@ -3,7 +3,6 @@ import 'dart:math'; import 'package:cake_wallet/themes/core/theme_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_svg/svg.dart'; class CoinActionButton extends StatelessWidget { const CoinActionButton({ @@ -11,11 +10,13 @@ class CoinActionButton extends StatelessWidget { required this.icon, required this.label, required this.action, + this.gradientColors, }); final Widget icon; final String label; final VoidCallback action; + final List? gradientColors; static const sizeFactor = 0.16; @@ -32,7 +33,7 @@ class CoinActionButton extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( - colors: [ + colors: gradientColors ?? [ context.customColors.cardGradientColorPrimary, context.customColors.cardGradientColorSecondary ], diff --git a/lib/new-ui/widgets/long_press_menu/long_press_footer.dart b/lib/new-ui/widgets/long_press_menu/long_press_footer.dart new file mode 100644 index 0000000000..5ef7abd532 --- /dev/null +++ b/lib/new-ui/widgets/long_press_menu/long_press_footer.dart @@ -0,0 +1,20 @@ +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:flutter/material.dart'; + +class LongPressFooter extends StatelessWidget { + const LongPressFooter({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Padding(padding: EdgeInsets.all(12), child: Container(decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.primary.withAlpha(60),),child: Padding( + padding: EdgeInsets.all(12), + child: Row(mainAxisAlignment:MainAxisAlignment.center,spacing: 12,children: [ + CakeImageWidget(imageUrl: "assets/new-ui/info.svg", width: 18, height: 18,colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant,BlendMode.srcIn),), + Text(text, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),) + ],), + ),),); + } +} diff --git a/lib/new-ui/long_press_popup.dart b/lib/new-ui/widgets/long_press_menu/long_press_popup.dart similarity index 51% rename from lib/new-ui/long_press_popup.dart rename to lib/new-ui/widgets/long_press_menu/long_press_popup.dart index 08ae861ddd..3bacfdeac5 100644 --- a/lib/new-ui/long_press_popup.dart +++ b/lib/new-ui/widgets/long_press_menu/long_press_popup.dart @@ -5,30 +5,40 @@ import 'package:flutter/material.dart'; class LongPressPopupBuilder extends StatelessWidget { const LongPressPopupBuilder( - {super.key, required this.child, required this.popup, this.spacing = 8}); + {super.key, required this.child, required this.popup, this.spacing = 8, this.showOnTap = false, this.footer}); final Widget child; final Widget popup; + final Widget? footer; final double spacing; + final bool showOnTap; @override Widget build(BuildContext context) { return GestureDetector( - onLongPress: () { - final RenderBox renderBox = context.findRenderObject() as RenderBox; - final offset = renderBox.localToGlobal(Offset.zero); - final size = renderBox.size; - - showPopUp( - context: context, - builder: (context) => _buildPopup(context, offset, size), - ); - }, - child: child, + behavior: HitTestBehavior.translucent, + onLongPress: ()=>_showMenu(context), + onTap: showOnTap ? ()=>_showMenu(context) : null, + child: IgnorePointer(ignoring: showOnTap, child: child), ); } + void _showMenu(BuildContext context) { + final RenderBox renderBox = context.findRenderObject() as RenderBox; + final offset = renderBox.localToGlobal(Offset.zero); + final size = renderBox.size; + + showPopUp( + context: context, + builder: (context) => _buildPopup(context, offset, size), + ); + } + Widget _buildPopup(BuildContext context, Offset offset, Size size) { + final screenWidth = MediaQuery.of(context).size.width; + + final bool isOnRightHalf = offset.dx + (size.width / 2) > screenWidth / 2; + return BackdropFilter( filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: Stack( @@ -44,13 +54,16 @@ class LongPressPopupBuilder extends StatelessWidget { ), ), Positioned( - left: offset.dx * 2, + left: isOnRightHalf ? null : offset.dx, + right: isOnRightHalf ? (screenWidth - (offset.dx + size.width)) : null, top: offset.dy + size.height + spacing, child: Material( color: Colors.transparent, child: popup, ), ), + if(footer != null) + Positioned(left:0,right:0,bottom: MediaQuery.of(context).viewPadding.bottom, child: footer!,) ], ), ); diff --git a/lib/router.dart b/lib/router.dart index 6adfd313da..3ec113741c 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -89,7 +89,6 @@ import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart'; import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; -import 'package:cake_wallet/src/screens/send/send_page.dart'; import 'package:cake_wallet/src/screens/send/send_template_page.dart'; import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; @@ -683,7 +682,7 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get(param1: settings.arguments as Order)); case Routes.buySellPage: - final args = settings.arguments as bool?; + final args = settings.arguments as BuySellPageParams?; return handleRouteWithPlatformAwareness( (context) => getIt.get(param1: args), ); diff --git a/lib/src/screens/buy/buy_sell_page.dart b/lib/src/screens/buy/buy_sell_page.dart index 1bdcadba14..c5e408a21d 100644 --- a/lib/src/screens/buy/buy_sell_page.dart +++ b/lib/src/screens/buy/buy_sell_page.dart @@ -24,8 +24,24 @@ import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; +class BuySellPageParams { + final bool startWithSell; + final CryptoCurrency? initialCurrency; + + BuySellPageParams({this.startWithSell = false, this.initialCurrency}); +} + class BuySellPage extends BasePage { - BuySellPage(this.buySellViewModel); + BuySellPage(this.buySellViewModel, {BuySellPageParams? params}) { + if(params != null) { + if(buySellViewModel.isBuyAction && params.startWithSell) { + buySellViewModel.changeBuySellAction(); + } + if(params.initialCurrency != null) { + buySellViewModel.changeCryptoCurrency(currency: params.initialCurrency!); + } + } + } final BuySellViewModel buySellViewModel; final cryptoCurrencyKey = GlobalKey(); diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 518f9bf086..c27993461e 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -284,6 +284,7 @@ flutter: - assets/new-ui/changelog/icons/ - assets/new-ui/changelog/text/ - assets/new-ui/crypto_full_icons/ + - assets/new-ui/charts_sort_criteria/ fonts: - family: Lato diff --git a/res/pictures/buy.svg b/res/pictures/buy.svg new file mode 100644 index 0000000000..dee24065fa --- /dev/null +++ b/res/pictures/buy.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/pictures/charts_sort_criteria/alpha.svg b/res/pictures/charts_sort_criteria/alpha.svg new file mode 100644 index 0000000000..78bc7f441a --- /dev/null +++ b/res/pictures/charts_sort_criteria/alpha.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/pictures/charts_sort_criteria/gains.svg b/res/pictures/charts_sort_criteria/gains.svg new file mode 100644 index 0000000000..0511e4d224 --- /dev/null +++ b/res/pictures/charts_sort_criteria/gains.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/pictures/charts_sort_criteria/losses.svg b/res/pictures/charts_sort_criteria/losses.svg new file mode 100644 index 0000000000..09918e8c0f --- /dev/null +++ b/res/pictures/charts_sort_criteria/losses.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/pictures/charts_sort_criteria/marketcap.svg b/res/pictures/charts_sort_criteria/marketcap.svg new file mode 100644 index 0000000000..8969f176cb --- /dev/null +++ b/res/pictures/charts_sort_criteria/marketcap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/pictures/info.svg b/res/pictures/info.svg index 945d67e520..c4978742d3 100644 --- a/res/pictures/info.svg +++ b/res/pictures/info.svg @@ -1,3 +1,7 @@ - - + + + + + + diff --git a/res/pictures/sell.svg b/res/pictures/sell.svg new file mode 100644 index 0000000000..769343612a --- /dev/null +++ b/res/pictures/sell.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 92f2dbaa44..b50ce747fa 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -156,6 +156,7 @@ "camera_permission_is_required": "Camera permission is required. \nPlease enable it from app settings.", "cancel": "Cancel", "cannot_manage_accounts_during_sync": "You can't manage accounts while the wallet is still syncing. Please try again later.", + "cannot_remove_last_asset": "You can't remove this asset because it is your last.", "cannot_verify": "Cannot Verify", "cannot_verify_description": "This domain cannot be verified. Check the request carefully before approving.", "card_address": "Address:", @@ -465,6 +466,7 @@ "extracted_address_content": "You will be sending funds to\n${recipient_name}", "failed_authentication": "Failed authentication. ${state_error}", "faq": "FAQ", + "favorite": "Favorite", "favorite_token": "Favorite token", "favorite_token_desc": "The favorite token's balance will show on the balance card.", "features": "Features", @@ -493,6 +495,7 @@ "frozen": "Frozen", "frozen_balance": "Frozen Balance", "full_balance": "Full Balance", + "gains": "Gains", "gas_exceeds_allowance": "Gas required by transaction exceeds allowance.", "gas_price": "Gas price", "general": "General", @@ -599,6 +602,7 @@ "logout": "Logout", "long_press_edit_address": "Long press to edit address", "long_press_show_balance": "Long press card to show balance", + "losses": "Losses", "low_fee": "Low fee", "low_fee_alert": "You currently are using a low network fee priority. This could cause long waits, different rates, or canceled trades. We recommend setting a higher fee for a better experience.", "made_easy": "made easy", @@ -607,6 +611,7 @@ "manage_providers": "Manage providers", "manage_yats": "Manage Yats", "mark_as_redeemed": "Mark As Redeemed", + "marketcap": "Marketcap", "market_place": "Marketplace", "mask": "Mask", "matrix_green_dark_theme": "Matrix Green Dark Theme", From 4f1e21600b28c1fb9949c48fa777f6a889442d8b Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 03:47:00 +0200 Subject: [PATCH 10/40] remove unused import --- lib/new-ui/widgets/{ => long_press_menu}/long_press_menu.dart | 1 - 1 file changed, 1 deletion(-) rename lib/new-ui/widgets/{ => long_press_menu}/long_press_menu.dart (98%) diff --git a/lib/new-ui/widgets/long_press_menu.dart b/lib/new-ui/widgets/long_press_menu/long_press_menu.dart similarity index 98% rename from lib/new-ui/widgets/long_press_menu.dart rename to lib/new-ui/widgets/long_press_menu/long_press_menu.dart index 796d0f78fd..a1798db522 100644 --- a/lib/new-ui/widgets/long_press_menu.dart +++ b/lib/new-ui/widgets/long_press_menu/long_press_menu.dart @@ -2,7 +2,6 @@ import 'dart:ui'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; class LongPressMenuItem { final String label; From 9327a9e2d8308fa6ae34c5f51a5831506e73b08d Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 03:49:49 +0200 Subject: [PATCH 11/40] remove restricted import...? --- lib/new-ui/model/charts/price_api_client.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/new-ui/model/charts/price_api_client.dart b/lib/new-ui/model/charts/price_api_client.dart index e25b7f9b75..352e6d5b32 100644 --- a/lib/new-ui/model/charts/price_api_client.dart +++ b/lib/new-ui/model/charts/price_api_client.dart @@ -1,10 +1,11 @@ +import 'dart:convert'; + import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/new-ui/model/charts/datetime_extension.dart'; import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; import 'package:cw_core/currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; -import 'package:cw_zano/zano_wallet_api.dart'; const priceApiHost = "prices.cakewallet.com"; @@ -37,7 +38,7 @@ class PriceApiClient { return null; } try { - return jsonDecode(resp.body); + return jsonDecode(resp.body) as Map?; } catch (e) { printV("failed to decode response for ${uri.host}/${uri.path}: $e"); return null; From a7b3311a1fea21271592ab150a3967536bbcdc00 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 13:14:49 +0200 Subject: [PATCH 12/40] fix background gradient when scrolling --- lib/new-ui/pages/charts_page.dart | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/new-ui/pages/charts_page.dart b/lib/new-ui/pages/charts_page.dart index 38ae7082a1..ae929c5d0b 100644 --- a/lib/new-ui/pages/charts_page.dart +++ b/lib/new-ui/pages/charts_page.dart @@ -37,29 +37,29 @@ class ChartsPage extends StatelessWidget { ); } - return CustomScrollView( - physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), - slivers: [ - SliverPadding( - padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), - sliver: CupertinoSliverRefreshControl( - refreshTriggerPullDistance: 160, - refreshIndicatorExtent: 90, - onRefresh: () async => context.read().add(PageRefreshed()), - ), + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surfaceDim, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, ), - SliverToBoxAdapter( - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context).colorScheme.surface, - Theme.of(context).colorScheme.surfaceDim, - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), + ), + child: CustomScrollView( + physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + SliverPadding( + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + sliver: CupertinoSliverRefreshControl( + refreshTriggerPullDistance: 160, + refreshIndicatorExtent: 90, + onRefresh: () async => context.read().add(PageRefreshed()), ), + ), + SliverToBoxAdapter( child: SafeArea( child: Column( spacing: 24, @@ -85,9 +85,9 @@ class ChartsPage extends StatelessWidget { ], ), ), - ), - ) - ], + ) + ], + ), ); }, ), From bf26ddd2020cd9b227be34ad66df844f4fef45c0 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 13:15:01 +0200 Subject: [PATCH 13/40] add safeguards for broken db --- lib/new-ui/viewmodels/charts/charts_bloc.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/new-ui/viewmodels/charts/charts_bloc.dart b/lib/new-ui/viewmodels/charts/charts_bloc.dart index 0b093493fb..850636acce 100644 --- a/lib/new-ui/viewmodels/charts/charts_bloc.dart +++ b/lib/new-ui/viewmodels/charts/charts_bloc.dart @@ -32,6 +32,18 @@ class ChartsBloc extends Bloc { Future _init(Init event, Emitter emit) async { final assets = await ChartsAsset.get(); + + if(assets.isEmpty) { + // generally this shouldn't happen, but i wanna make sure we can recover from a broken db + assets.add(ChartsAsset(asset: CryptoCurrency.btc, isFavorite: true)); + assets.first.insert(); + } + + if(assets.firstWhereOrNull((item)=>item.isFavorite) == null) { + assets.first = ChartsAsset(asset: assets.first.asset, isFavorite: true); + assets.first.insert(); + } + emit(ChartsLoading( pinnedCurrency: assets.firstWhere((item) => item.isFavorite).asset, currencies: assets.map((item) => item.asset).toList(), From 71bff7e3bc55c9678c4fc4132a0fa80979c7b2fa Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 13:17:15 +0200 Subject: [PATCH 14/40] save new pin on removal --- lib/new-ui/viewmodels/charts/charts_bloc.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/new-ui/viewmodels/charts/charts_bloc.dart b/lib/new-ui/viewmodels/charts/charts_bloc.dart index 850636acce..440d92b96c 100644 --- a/lib/new-ui/viewmodels/charts/charts_bloc.dart +++ b/lib/new-ui/viewmodels/charts/charts_bloc.dart @@ -112,6 +112,8 @@ class ChartsBloc extends Bloc { final CryptoCurrency newPin; if(s.pinnedCurrency == event.currency) { newPin = newCurrencies.first; + await ChartsAsset(asset: s.pinnedCurrency, isFavorite: false).insert(); + await ChartsAsset(asset: newPin, isFavorite: true).insert(); } else { newPin = s.pinnedCurrency; } From e993efc3a3388b0cbb8faa25b3f07f1d936f593e Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 13:17:27 +0200 Subject: [PATCH 15/40] add safeguards for concurrency --- lib/new-ui/viewmodels/charts/charts_bloc.dart | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/new-ui/viewmodels/charts/charts_bloc.dart b/lib/new-ui/viewmodels/charts/charts_bloc.dart index 440d92b96c..10977d6592 100644 --- a/lib/new-ui/viewmodels/charts/charts_bloc.dart +++ b/lib/new-ui/viewmodels/charts/charts_bloc.dart @@ -1,4 +1,6 @@ import 'package:bloc/bloc.dart'; +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:cake_wallet/core/utilities.dart'; import 'package:cake_wallet/new-ui/model/charts/charts_asset.dart'; import 'package:cake_wallet/new-ui/model/charts/price_data.dart'; import 'package:cake_wallet/new-ui/model/charts/util/price_data_sort_criteria.dart'; @@ -19,13 +21,13 @@ class ChartsBloc extends Bloc { final AppStore appStore; ChartsBloc({required this.priceStore, required this.appStore}) : super(ChartsInitial()) { - on(_onRangeChanged); + on(_onRangeChanged, transformer: sequential()); on(_onSortingCriteriumChanged); - on(_onCurrencyAdded); - on(_onCurrencyRemoved); - on(_onCurrencyPinned); - on(_onPageRefreshed); - on(_onPageLoadStarted); + on(_onCurrencyAdded, transformer: sequential()); + on(_onCurrencyRemoved, transformer: sequential()); + on(_onCurrencyPinned, transformer: sequential()); + on(_onPageRefreshed, transformer: sequential()); + on(_onPageLoadStarted, transformer: restartable()); on(_init); add(Init()); } @@ -57,8 +59,10 @@ class ChartsBloc extends Bloc { Future _onPageLoadStarted(PageLoadStarted event, Emitter emit) async { if (state case ChartsStateWithData s) { final Map> data = {}; - for (final curr in s.currencies) { - data[curr] = await priceStore.getPrices(appStore.settingsStore.fiatCurrency, curr, s.range); + final currencies = s.currencies.toList(); + final range = s.range; + for (final curr in currencies) { + data[curr] = await priceStore.getPrices(appStore.settingsStore.fiatCurrency, curr, range); } emit(ChartsLoaded(pinnedCurrency: s.pinnedCurrency, prices: data, range: s.range, sortCriterium: s.sortCriterium)); } else { From b1d6a77e1ccc05a980629a6fd2a71a3f05462788 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 13:22:16 +0200 Subject: [PATCH 16/40] add haptic feedback --- lib/new-ui/widgets/charts_page/asset_grid.dart | 2 ++ lib/new-ui/widgets/charts_page/chart_header.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index e6eca7b37d..68c15c060e 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/themes/core/theme_extension.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ChartsAssetGrid extends StatelessWidget { @@ -106,6 +107,7 @@ class ChartsAssetCard extends StatelessWidget { isSingleCurrency ? LongPressFooter(text: S.of(context).cannot_remove_last_asset) : null, child: GestureDetector( onTap: () async { + HapticFeedback.mediumImpact(); final res = await showModalBottomSheet( isScrollControlled: true, context: context, diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index 26d704afa9..fe68a9aafd 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ChartHeader extends StatefulWidget { @@ -87,6 +88,7 @@ class _ChartHeaderState extends State { }); return; } + HapticFeedback.selectionClick(); setState(() { _viewedPrice = response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); From 2e88ccf5a03690405949aa58eb53afe95bf7a492 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 9 Apr 2026 13:57:43 +0200 Subject: [PATCH 17/40] fix haptic feedback --- lib/new-ui/widgets/charts_page/chart_header.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index fe68a9aafd..8ccc45e33b 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -88,11 +88,15 @@ class _ChartHeaderState extends State { }); return; } - HapticFeedback.selectionClick(); - setState(() { - _viewedPrice = - response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); - }); + + final newPrice = + response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); + if (_viewedPrice != newPrice) { + HapticFeedback.selectionClick(); + setState(() { + _viewedPrice = newPrice; + }); + } }, ), ) From 0ae4e9a4742a585d21079e88a177c10ce260ece0 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 10 Apr 2026 14:17:49 +0200 Subject: [PATCH 18/40] fix fiat ticker --- lib/new-ui/widgets/charts_page/chart_header.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index 8ccc45e33b..1fbb84d33a 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -71,7 +71,7 @@ class _ChartHeaderState extends State { height: 36, child: (s is ChartsLoaded) ? ChangeDisplay( - changeData: s.changeDataFor(widget.currency), ticker: "USD") + changeData: s.changeDataFor(widget.currency), ticker: s.fiatTicker) : SizedBox.shrink(), ), if (s is ChartsLoaded) From c81f95adbdde973caa094eaf7f6ab9e5476ebfab Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 10 Apr 2026 14:17:58 +0200 Subject: [PATCH 19/40] reduce chart padding --- lib/new-ui/widgets/charts_page/chart_modal.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/new-ui/widgets/charts_page/chart_modal.dart b/lib/new-ui/widgets/charts_page/chart_modal.dart index 828a6dc286..91f1a608b9 100644 --- a/lib/new-ui/widgets/charts_page/chart_modal.dart +++ b/lib/new-ui/widgets/charts_page/chart_modal.dart @@ -40,8 +40,8 @@ class ChartModal extends StatelessWidget { ), ChartHeader( currency: currency, - chartHeight: 140, - chartPadding: 102, + chartHeight: 220, + chartPadding: 32, centered: true, favorite: false, ), From e8f8c3d7db45a97ccd3d8b94903f72f76c8a2e15 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 10 Apr 2026 14:18:10 +0200 Subject: [PATCH 20/40] add line touch indicator --- lib/new-ui/widgets/charts_page/chart_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/new-ui/widgets/charts_page/chart_view.dart b/lib/new-ui/widgets/charts_page/chart_view.dart index 1b9f155d36..86c3f7f15d 100644 --- a/lib/new-ui/widgets/charts_page/chart_view.dart +++ b/lib/new-ui/widgets/charts_page/chart_view.dart @@ -45,11 +45,15 @@ class PriceChart extends StatelessWidget { ], lineTouchData: LineTouchData( enabled: true, + getTouchLineStart: (barData, spotIndex) => -double.infinity, + getTouchLineEnd: (barData, spotIndex) => double.infinity, getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { return spotIndexes.map((index) { return TouchedSpotIndicatorData( FlLine( - color: Colors.transparent, + strokeWidth: 1, + color: direction.color.withAlpha(80), + dashArray: [4, 4], ), FlDotData(), ); From 5386f47ae156247705f6d440dfbbf0f8540e57a8 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 10 Apr 2026 14:18:22 +0200 Subject: [PATCH 21/40] improve touch target size on range selector --- .../widgets/charts_page/range_selector.dart | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/new-ui/widgets/charts_page/range_selector.dart b/lib/new-ui/widgets/charts_page/range_selector.dart index 42e5ac2475..a640b2c667 100644 --- a/lib/new-ui/widgets/charts_page/range_selector.dart +++ b/lib/new-ui/widgets/charts_page/range_selector.dart @@ -31,22 +31,31 @@ class ChartRangeSelector extends StatelessWidget { ), duration: switchDuration), Row( - spacing: optionPadding, children: ChartRange.ranges.map((item) { final selected = selectedRange == item; + final isFirst = ChartRange.ranges.indexOf(item) == 0; return GestureDetector( - onTap: () => onRangeSelected(item), - child: Container( - width: optionSize, - height: optionSize, - child: AnimatedDefaultTextStyle( - duration: switchDuration, - style: TextStyle( - fontWeight: selected ? FontWeight.w400 : FontWeight.w500, - color: selected - ? Theme.of(context).colorScheme.onSurface - : Theme.of(context).colorScheme.onSurfaceVariant), - child: Center(child: Text(item.displayText))), + behavior: HitTestBehavior.opaque, + onTap: () { + if(!selected) { + onRangeSelected(item); + } + }, + child: Padding( + padding: EdgeInsets.only(right: optionPadding/2, + left: isFirst ? 0 : optionPadding/2), + child: Container( + width: optionSize, + height: optionSize, + child: AnimatedDefaultTextStyle( + duration: switchDuration, + style: TextStyle( + fontWeight: selected ? FontWeight.w400 : FontWeight.w500, + color: selected + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).colorScheme.onSurfaceVariant), + child: Center(child: Text(item.displayText))), + ), ), ); }).toList(), From 10c1774a4679c524c56ad367a02a7441167fdf9e Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 21 May 2026 23:27:11 +0200 Subject: [PATCH 22/40] add date/time display when viewing past amount --- .../widgets/charts_page/chart_header.dart | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index 1fbb84d33a..73b99cc2f3 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -10,6 +10,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; class ChartHeader extends StatefulWidget { const ChartHeader( @@ -32,6 +33,7 @@ class ChartHeader extends StatefulWidget { class _ChartHeaderState extends State { String? _viewedPrice; + String? _viewedTime; @override Widget build(BuildContext context) { @@ -66,12 +68,23 @@ class _ChartHeaderState extends State { crossAxisAlignment: widget.centered ? CrossAxisAlignment.center : CrossAxisAlignment.start, children: [ - SizedBox( + Container( // has to be constant-size, otherwise will jump around when loading height: 36, + alignment: widget.centered ? Alignment.center : Alignment.centerLeft, child: (s is ChartsLoaded) - ? ChangeDisplay( - changeData: s.changeDataFor(widget.currency), ticker: s.fiatTicker) + ? (_viewedPrice != null && _viewedTime != null) + ? Text( + _viewedTime!, + style: TextStyle( + fontFamily: "IBM Plex Mono", + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 16), + ) + : ChangeDisplay( + changeData: s.changeDataFor(widget.currency), + ticker: s.fiatTicker) : SizedBox.shrink(), ), if (s is ChartsLoaded) @@ -85,16 +98,19 @@ class _ChartHeaderState extends State { if (!event.isInterestedForInteractions) { setState(() { _viewedPrice = null; + _viewedTime = null; }); return; } final newPrice = response?.lineBarSpots?.firstOrNull?.y.toStringAsFixed(2); + final newTime = DateFormat("M/d/y HH:mm").format(DateTime.fromMillisecondsSinceEpoch(response?.lineBarSpots?.firstOrNull?.x.toInt()??0)); if (_viewedPrice != newPrice) { HapticFeedback.selectionClick(); setState(() { _viewedPrice = newPrice; + _viewedTime = newTime; }); } }, From 6efeace15f807cb7694ee3da451672189fe43d51 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 21 May 2026 23:33:23 +0200 Subject: [PATCH 23/40] merge --- lib/entities/default_settings_migration.dart | 2 +- lib/main.dart | 2 +- lib/new-ui/model/charts/price_data.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 3525fca75c..9197eae778 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -633,7 +633,7 @@ Future defaultSettingsMigration( oldUri: ['base.nownodes.io'], ); break; - case 65: + case 66: await createDefaultChartsData(); break; default: diff --git a/lib/main.dart b/lib/main.dart index 61dea0d6ee..30b557fd38 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -322,7 +322,7 @@ Future initializeAppConfigs({bool loadWallet = true}) async { payjoinSessionSource: payjoinSessionSource, anonpayInvoiceInfo: anonpayInvoiceInfo, havenSeedStore: havenSeedStore, - initialMigrationVersion: 65, + initialMigrationVersion: 66, ); } diff --git a/lib/new-ui/model/charts/price_data.dart b/lib/new-ui/model/charts/price_data.dart index 0f4e776115..0c14a10ae4 100644 --- a/lib/new-ui/model/charts/price_data.dart +++ b/lib/new-ui/model/charts/price_data.dart @@ -16,7 +16,7 @@ Currency currencyFromApiString(String key) { case "crypto": return CryptoCurrency.fromString(id); case "evm": - throw UnimplementedError("i promise i'll take care of this, i really want a working build"); + throw UnimplementedError(); case "sol": throw UnimplementedError(); } From 6d3cc66783405c8e0fa13f10c108e7bc8b7ab3cc Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 21 May 2026 23:39:01 +0200 Subject: [PATCH 24/40] remove iconSvgPath reference --- lib/new-ui/widgets/charts_page/asset_grid.dart | 2 +- lib/new-ui/widgets/charts_page/chart_header.dart | 2 +- lib/new-ui/widgets/charts_page/coin_header.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/new-ui/widgets/charts_page/asset_grid.dart b/lib/new-ui/widgets/charts_page/asset_grid.dart index 68c15c060e..8da31fa1de 100644 --- a/lib/new-ui/widgets/charts_page/asset_grid.dart +++ b/lib/new-ui/widgets/charts_page/asset_grid.dart @@ -184,7 +184,7 @@ class ChartsAssetCard extends StatelessWidget { spacing: 8, children: [ CakeImageWidget( - imageUrl: currency.iconSvgPath ?? currency.iconPath, + imageUrl: currency.iconPath, width: 24, height: 24, ), diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index 73b99cc2f3..0149cc81ac 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -54,7 +54,7 @@ class _ChartHeaderState extends State { children: [ if (!widget.favorite) CakeImageWidget( - imageUrl: widget.currency.iconSvgPath ?? widget.currency.iconPath ?? "", + imageUrl: widget.currency.iconPath ?? "", width: 60, height: 60, ), diff --git a/lib/new-ui/widgets/charts_page/coin_header.dart b/lib/new-ui/widgets/charts_page/coin_header.dart index 249cc6a0ff..49d51b28ba 100644 --- a/lib/new-ui/widgets/charts_page/coin_header.dart +++ b/lib/new-ui/widgets/charts_page/coin_header.dart @@ -19,7 +19,7 @@ class ChartViewCoinHeader extends StatelessWidget { children: [ if(isFavorite) CakeImageWidget( - imageUrl: currency.iconSvgPath ?? currency.iconPath ?? "", + imageUrl: currency.iconPath ?? "", width: 30, height: 30, ), From 5794b0d94c0e481fe0f63e673ca9f8bb5615a3a5 Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Thu, 21 May 2026 23:44:56 +0200 Subject: [PATCH 25/40] merge --- cw_core/lib/db/sqlite.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart index 8689894b0b..8bd738a01f 100644 --- a/cw_core/lib/db/sqlite.dart +++ b/cw_core/lib/db/sqlite.dart @@ -203,11 +203,10 @@ CREATE TABLE BalanceCardStyleSettings ( PRIMARY KEY (walletInfoId, accountIndex), FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId) ); + '''); await _createBridgeTransferTable(db); await _createTradeTable(db); _createChartsTables(db); - - '''); } ); } From 2d6394979ecfb88c36b36873e220a55d6d13fede Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Fri, 22 May 2026 00:36:45 +0200 Subject: [PATCH 26/40] fix state for date display --- lib/new-ui/widgets/charts_page/chart_header.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/new-ui/widgets/charts_page/chart_header.dart b/lib/new-ui/widgets/charts_page/chart_header.dart index 0149cc81ac..896152f960 100644 --- a/lib/new-ui/widgets/charts_page/chart_header.dart +++ b/lib/new-ui/widgets/charts_page/chart_header.dart @@ -110,6 +110,10 @@ class _ChartHeaderState extends State { HapticFeedback.selectionClick(); setState(() { _viewedPrice = newPrice; + }); + } + if (_viewedTime != newTime) { + setState(() { _viewedTime = newTime; }); } From e624694f69282fa1d0a2ffb2d324ef4a146278db Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Mon, 6 Jul 2026 22:53:42 +0200 Subject: [PATCH 27/40] merge --- cw_zcash/lib/src/pending_zcash_transaction.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/cw_zcash/lib/src/pending_zcash_transaction.dart b/cw_zcash/lib/src/pending_zcash_transaction.dart index 32d9540d17..db4f59f7eb 100644 --- a/cw_zcash/lib/src/pending_zcash_transaction.dart +++ b/cw_zcash/lib/src/pending_zcash_transaction.dart @@ -79,7 +79,6 @@ class PendingZcashTransaction with PendingTransaction { cryptoAmount: Money.zero(CryptoCurrency.zec), address: "${o1.address},${o2.address}", sendAll: false, - cryptoAmount: "", isParsedAddress: false, ); }).address, From 7abb38f05ae13f73eb30ab32214e586f008a557d Mon Sep 17 00:00:00 2001 From: Robert Malikowski Date: Tue, 14 Jul 2026 22:37:23 +0200 Subject: [PATCH 28/40] auto-reformat --- cw_bitcoin/lib/address_from_output.dart | 15 +- cw_bitcoin/lib/bitcoin_address_record.dart | 12 +- cw_bitcoin/lib/bitcoin_amount_format.dart | 4 +- .../bitcoin_commit_transaction_exception.dart | 1 - .../lib/bitcoin_transaction_priority.dart | 18 +- cw_bitcoin/lib/bitcoin_wallet.dart | 70 +- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 8 +- .../bitcoin_wallet_creation_credentials.dart | 32 +- cw_bitcoin/lib/bitcoin_wallet_keys.dart | 13 +- cw_bitcoin/lib/bitcoin_wallet_service.dart | 24 +- cw_bitcoin/lib/electrum.dart | 39 +- .../lib/electrum_transaction_history.dart | 1 - cw_bitcoin/lib/electrum_transaction_info.dart | 8 +- cw_bitcoin/lib/electrum_wallet.dart | 167 +- cw_bitcoin/lib/electrum_wallet_addresses.dart | 216 +- cw_bitcoin/lib/exceptions.dart | 2 +- cw_bitcoin/lib/hardware/bitbox_service.dart | 3 +- .../lib/hardware/litecoin_ledger_service.dart | 6 +- .../lib/lightning/lightning_wallet.dart | 32 +- .../pending_lightning_transaction.dart | 3 +- cw_bitcoin/lib/litecoin_wallet.dart | 56 +- cw_bitcoin/lib/litecoin_wallet_addresses.dart | 20 +- cw_bitcoin/lib/litecoin_wallet_service.dart | 17 +- cw_bitcoin/lib/payjoin/manager.dart | 31 +- .../lib/payjoin/payjoin_receive_worker.dart | 31 +- .../lib/payjoin/payjoin_send_worker.dart | 8 +- cw_bitcoin/lib/payjoin/storage.dart | 36 +- cw_bitcoin/lib/psbt/signer.dart | 57 +- cw_bitcoin/lib/psbt/transaction_builder.dart | 31 +- cw_bitcoin/lib/psbt/utils.dart | 4 +- cw_bitcoin/lib/psbt/v0_deserialize.dart | 7 +- cw_bitcoin/lib/psbt/v0_finalizer.dart | 7 +- cw_bitcoin/lib/utils.dart | 2 +- .../lib/src/bitcoin_cash_wallet_service.dart | 8 +- .../lib/src/exceptions/exceptions.dart | 2 +- cw_core/lib/account.dart | 2 +- cw_core/lib/account_list.dart | 1 - cw_core/lib/address_info.part.dart | 4 +- cw_core/lib/amount/amount_sanitizer.dart | 1 - cw_core/lib/amount_converter.dart | 9 +- cw_core/lib/balance_card_style_settings.dart | 30 +- cw_core/lib/card_design.dart | 233 +- cw_core/lib/crypto_amount_format.dart | 1 - cw_core/lib/crypto_currency.dart | 997 ++++- cw_core/lib/currency_for_wallet_type.dart | 3 +- cw_core/lib/db/sqlite.dart | 72 +- cw_core/lib/db/sqlite_debug.dart | 2 +- cw_core/lib/encryption_file_utils.dart | 55 +- cw_core/lib/erc20_token.part.dart | 6 +- cw_core/lib/exceptions.dart | 2 +- cw_core/lib/format_amount.dart | 6 +- cw_core/lib/format_fixed.dart | 9 +- cw_core/lib/get_height_by_date.dart | 3 +- cw_core/lib/get_height_by_date_xmr.dart | 1 - cw_core/lib/hive_type_ids.dart | 48 +- cw_core/lib/key.dart | 3 +- cw_core/lib/keyable.dart | 2 +- cw_core/lib/lnurl.dart | 15 +- cw_core/lib/monero_wallet_keys.dart | 12 +- cw_core/lib/mweb_utxo.part.dart | 4 +- cw_core/lib/nano_account.part.dart | 4 +- cw_core/lib/node.dart | 64 +- cw_core/lib/node_legacy.dart | 46 +- cw_core/lib/node_legacy.part.dart | 7 +- cw_core/lib/node_list.dart | 21 +- cw_core/lib/parseBoolFromString.dart | 2 +- cw_core/lib/parse_fixed.dart | 3 +- cw_core/lib/pathForWallet.dart | 3 +- cw_core/lib/payjoin_session.dart | 1 - cw_core/lib/payjoin_session.part.dart | 4 +- cw_core/lib/payment_uris.dart | 7 +- cw_core/lib/root_dir.dart | 28 +- cw_core/lib/sec_random_native.dart | 3 +- cw_core/lib/solana_rpc_http_service.dart | 6 +- cw_core/lib/spl_token.part.dart | 4 +- cw_core/lib/transaction_direction.dart | 8 +- cw_core/lib/transaction_history.dart | 3 +- cw_core/lib/transaction_priority.dart | 6 +- cw_core/lib/tron_token.part.dart | 4 +- cw_core/lib/utils/file.dart | 4 +- cw_core/lib/utils/proxy_logger/abstract.dart | 6 +- .../proxy_logger/memory_proxy_logger.dart | 22 +- .../lib/utils/proxy_logger/silent_logger.dart | 4 +- cw_core/lib/utils/proxy_socket/abstract.dart | 17 +- cw_core/lib/utils/proxy_socket/insecure.dart | 21 +- cw_core/lib/utils/proxy_socket/secure.dart | 18 +- cw_core/lib/utils/proxy_socket/socks.dart | 12 +- cw_core/lib/utils/proxy_wrapper.dart | 50 +- cw_core/lib/utils/tor/abstract.dart | 2 +- cw_core/lib/utils/tor/disabled.dart | 2 +- cw_core/lib/utils/tor/socks.dart | 2 +- cw_core/lib/utils/tor/torch.dart | 2 +- cw_core/lib/utils/zpub.dart | 22 +- cw_core/lib/wallet_info.dart | 242 +- cw_core/lib/wallet_info_legacy.dart | 2 +- cw_core/lib/wallet_info_legacy.part.dart | 16 +- cw_core/lib/wallet_keys_file.dart | 9 +- cw_core/lib/wallet_type.dart | 25 +- cw_core/lib/wallet_type.part.dart | 4 +- cw_core/lib/wownero_amount_format.dart | 2 +- cw_core/lib/zano_asset.part.dart | 4 +- .../test/amount/amount_sanitizer_test.dart | 10 +- cw_core/test/amount/money_test.dart | 4 +- cw_core/test/crypto_amount_format.dart | 6 +- cw_core/test/format_fixed_test.dart | 12 +- cw_core/test/lnurl_test.dart | 27 +- cw_decred/lib/wallet.dart | 14 +- cw_decred/lib/wallet_service.dart | 12 +- cw_dogecoin/lib/cw_dogecoin.dart | 1 - .../src/dogecoin_transaction_priority.dart | 7 +- cw_dogecoin/lib/src/dogecoin_wallet.dart | 3 +- .../lib/src/dogecoin_wallet_addresses.dart | 9 +- cw_dogecoin/test/cw_dogecoin_test.dart | 3 +- cw_evm/lib/clients/arbitrum_client.dart | 1 - cw_evm/lib/clients/base_client.dart | 1 - cw_evm/lib/clients/bsc_client.dart | 1 - cw_evm/lib/clients/ethereum_client.dart | 1 - cw_evm/lib/clients/evm_chain_client.dart | 15 +- cw_evm/lib/clients/polygon_client.dart | 1 - cw_evm/lib/contract/erc20.dart | 47 +- cw_evm/lib/deuro/deuro_savings.dart | 17 +- cw_evm/lib/deuro/deuro_savings_contract.dart | 6 +- .../deuro/deuro_savings_gateway_contract.dart | 110 +- cw_evm/lib/evm_chain_exceptions.dart | 13 +- cw_evm/lib/evm_chain_registry.dart | 3 +- cw_evm/lib/evm_chain_transaction_model.dart | 3 +- cw_evm/lib/evm_chain_wallet.dart | 15 +- cw_evm/lib/evm_chain_wallet_addresses.dart | 1 - cw_evm/lib/evm_erc20_balance.dart | 2 +- .../hardware/evm_chain_bitbox_service.dart | 5 +- .../evm_chain_ledger_credentials.dart | 28 +- .../hardware/evm_chain_ledger_service.dart | 6 +- cw_evm/lib/tokens/base_tokens.dart | 4 +- cw_evm/lib/tokens/polygon_tokens.dart | 4 +- cw_evm/lib/usdt0/usdt0_config.dart | 20 +- cw_evm/lib/usdt0/usdt0_service.dart | 2 +- cw_evm/lib/utils/network_chain_utils.dart | 1 - cw_monero/lib/api/account_list.dart | 2 +- .../creation_transaction_exception.dart | 4 +- .../exceptions/setup_wallet_exception.dart | 4 +- .../exceptions/wallet_creation_exception.dart | 2 +- .../exceptions/wallet_opening_exception.dart | 2 +- .../wallet_restore_from_keys_exception.dart | 4 +- .../wallet_restore_from_seed_exception.dart | 4 +- cw_monero/lib/api/monero_output.dart | 2 +- .../lib/api/structs/pending_transaction.dart | 15 +- cw_monero/lib/api/subaddress_list.dart | 32 +- cw_monero/lib/api/transaction_history.dart | 120 +- cw_monero/lib/api/wallet.dart | 59 +- cw_monero/lib/api/wallet_manager.dart | 58 +- cw_monero/lib/bip39_seed.dart | 15 +- ...monero_transaction_creation_exception.dart | 2 +- ...onero_transaction_no_inputs_exception.dart | 3 +- cw_monero/lib/ledger.dart | 13 +- .../lib/mnemonics/chinese_simplified.dart | 2 +- cw_monero/lib/mnemonics/dutch.dart | 2 +- cw_monero/lib/mnemonics/french.dart | 3258 ++++++++--------- cw_monero/lib/mnemonics/german.dart | 2 +- cw_monero/lib/mnemonics/italian.dart | 3258 ++++++++--------- cw_monero/lib/mnemonics/japanese.dart | 2 +- cw_monero/lib/mnemonics/portuguese.dart | 2 +- cw_monero/lib/mnemonics/russian.dart | 2 +- cw_monero/lib/mnemonics/spanish.dart | 2 +- cw_monero/lib/monero_account_list.dart | 25 +- cw_monero/lib/monero_subaddress_list.dart | 24 +- cw_monero/lib/monero_transaction_history.dart | 11 +- cw_monero/lib/monero_wallet.dart | 131 +- cw_monero/lib/monero_wallet_addresses.dart | 34 +- cw_monero/lib/monero_wallet_service.dart | 67 +- cw_monero/lib/pending_monero_transaction.dart | 6 +- cw_monero/lib/trezor.dart | 14 +- cw_monero/test/bip39_seed_test.dart | 19 +- .../test/monero_wallet_service_test.dart | 12 +- cw_monero/test/utils/setup_monero_c.dart | 13 +- cw_mweb/lib/cw_mweb.dart | 14 +- cw_mweb/lib/mweb_ffi.dart | 3 +- cw_mweb/lib/mwebd.pb.dart | 1398 ++++--- cw_mweb/lib/mwebd.pbgrpc.dart | 215 +- cw_nano/lib/nano_client.dart | 12 +- cw_nano/lib/nano_transaction_model.dart | 4 +- cw_nano/lib/nano_wallet.dart | 6 +- cw_nano/lib/nano_wallet_service.dart | 3 +- cw_nano/lib/pending_nano_transaction.dart | 2 +- cw_solana/lib/pending_solana_transaction.dart | 2 +- cw_solana/lib/solana_client.dart | 12 +- cw_solana/lib/solana_wallet.dart | 24 +- cw_solana/lib/solana_wallet_service.dart | 10 +- cw_tron/lib/pending_tron_transaction.dart | 2 +- cw_tron/lib/tron_balance.dart | 2 +- cw_tron/lib/tron_client.dart | 11 +- cw_tron/lib/tron_exception.dart | 3 +- cw_tron/lib/tron_http_provider.dart | 16 +- cw_tron/lib/tron_wallet.dart | 25 +- cw_zano/lib/api/consts.dart | 2 +- cw_zano/lib/api/model/asset_id_params.dart | 6 +- cw_zano/lib/api/model/balance.dart | 5 +- .../lib/api/model/create_wallet_result.dart | 7 +- cw_zano/lib/api/model/destination.dart | 13 +- cw_zano/lib/api/model/employed_entries.dart | 19 +- .../model/get_recent_txs_and_info_params.dart | 15 +- .../model/get_recent_txs_and_info_result.dart | 11 +- .../api/model/get_wallet_status_result.dart | 3 +- cw_zano/lib/api/model/recent_history.dart | 12 +- cw_zano/lib/api/model/store_result.dart | 2 +- cw_zano/lib/api/model/subtransfer.dart | 3 +- cw_zano/lib/api/model/transfer.dart | 44 +- cw_zano/lib/api/model/transfer_params.dart | 21 +- cw_zano/lib/api/model/wi_extended.dart | 18 +- cw_zano/lib/mnemonics/english.dart | 3252 ++++++++-------- .../lib/model/pending_zano_transaction.dart | 2 +- cw_zano/lib/model/zano_asset.dart | 1 + .../zano_transaction_creation_exception.dart | 2 +- .../model/zano_transaction_credentials.dart | 3 +- cw_zano/lib/model/zano_transaction_info.dart | 15 +- cw_zano/lib/model/zano_wallet_keys.dart | 8 +- cw_zano/lib/zano_formatter.dart | 54 +- cw_zano/lib/zano_transaction_history.dart | 10 +- cw_zano/lib/zano_wallet.dart | 19 +- cw_zano/lib/zano_wallet_api.dart | 61 +- cw_zano/lib/zano_wallet_exceptions.dart | 6 +- cw_zano/lib/zano_wallet_service.dart | 35 +- lib/anonpay/anonpay_api.dart | 4 +- lib/anonpay/anonpay_donation_link_info.dart | 6 +- lib/anonpay/anonpay_info_base.dart | 2 +- lib/anypay/any_pay_chain.dart | 8 +- lib/anypay/any_pay_payment.dart | 108 +- .../any_pay_payment_committed_info.dart | 24 +- lib/anypay/any_pay_payment_instruction.dart | 49 +- .../any_pay_payment_instruction_output.dart | 14 +- lib/anypay/any_pay_trasnaction.dart | 10 +- lib/anypay/anypay_api.dart | 162 +- lib/bitcoin/cw_bitcoin.dart | 38 +- lib/bitcoin_cash/cw_bitcoin_cash.dart | 8 +- lib/buy/buy_amount.dart | 13 +- lib/buy/buy_exception.dart | 3 +- lib/buy/buy_provider.dart | 17 +- lib/buy/dfx/dfx_buy_provider.dart | 17 +- lib/buy/get_buy_provider_icon.dart | 11 +- lib/buy/kryptonim/kryptonim.dart | 19 +- lib/buy/meld/meld_buy_provider.dart | 32 +- lib/buy/moonpay/moonpay_provider.dart | 23 +- lib/buy/onramper/onramper_buy_provider.dart | 72 +- lib/buy/robinhood/robinhood_buy_provider.dart | 5 +- lib/buy/sell_buy_states.dart | 3 +- lib/buy/wyre/wyre_buy_provider.dart | 16 +- .../src/auth/cake_pay_account_page.dart | 15 +- .../src/auth/cake_pay_verify_otp_page.dart | 4 +- lib/cake_pay/src/cake_pay_states.dart | 1 - .../src/cards/cake_pay_buy_card_page.dart | 17 +- .../src/cards/cake_pay_cards_page.dart | 4 +- lib/cake_pay/src/models/cake_pay_card.dart | 7 +- lib/cake_pay/src/models/cake_pay_order.dart | 31 +- .../src/models/cake_pay_user_credentials.dart | 8 +- lib/cake_pay/src/widgets/cake_pay_tile.dart | 12 +- .../widgets/denominations_amount_widget.dart | 14 +- .../src/widgets/enter_amount_widget.dart | 19 +- .../src/widgets/flip_card_widget.dart | 17 +- lib/cake_pay/src/widgets/link_extractor.dart | 2 +- .../widgets/rounded_overlay_cards_widget.dart | 15 +- .../three_checkbox_alert_content_widget.dart | 18 +- lib/cake_pay/src/widgets/user_card_item.dart | 9 +- .../address_lookup_provider.dart | 1 - .../address_resolver_service.dart | 3 +- .../address_resolver_utils.dart | 3 +- .../bip_353/bip_353_address_provider.dart | 16 +- .../bip_353/bip_353_record.dart | 3 +- lib/core/address_resolver/ens/ens_record.dart | 24 +- .../fio/fio_address_provider.dart | 30 +- .../lnurl_pay/lnurl_pay_address_provider.dart | 5 +- .../lnurl_pay/lnurlpay_record.dart | 2 +- .../mastodon/mastodon_api.dart | 2 - .../mastodon/mastodon_user.dart | 11 +- .../address_resolver/nostr/nostr_api.dart | 8 +- .../address_resolver/nostr/nostr_user.dart | 3 +- .../openalias/openalias_record.dart | 2 +- lib/core/address_resolver/parsed_address.dart | 2 +- .../twitter/twitter_address_provider.dart | 1 - .../address_resolver/twitter/twitter_api.dart | 4 +- .../unstoppable_address_provider.dart | 6 +- .../wellknown/wellknown_record.dart | 8 +- lib/core/address_resolver/yat/yat_record.dart | 2 +- .../address_resolver/yat/yat_service.dart | 15 +- lib/core/address_resolver/yat/yat_store.dart | 64 +- .../zano/zano_alias_address_provider.dart | 1 - .../zcash/zcash_names_record.dart | 3 +- lib/core/address_validator.dart | 57 +- lib/core/amount_validator.dart | 6 +- lib/core/auth_state.dart | 1 - lib/core/backup_service.dart | 47 +- lib/core/backup_service_v3.dart | 63 +- lib/core/csv_export_service.dart | 11 +- lib/core/email_validator.dart | 3 +- lib/core/execution_state.dart | 2 +- lib/core/fiat_conversion_service.dart | 18 +- lib/core/mnemonic_length.dart | 2 +- lib/core/monero_account_label_validator.dart | 8 +- .../open_cryptopay_service.dart | 22 +- lib/core/seed_validator.dart | 5 +- lib/core/selectable_option.dart | 3 - .../socks_proxy_node_address_validator.dart | 5 +- lib/core/template_validator.dart | 11 +- lib/core/universal_address_detector.dart | 2 +- lib/core/utilities.dart | 3 +- lib/core/validator.dart | 10 +- lib/core/wallet_creation_service.dart | 3 +- lib/core/wallet_creation_state.dart | 2 +- lib/core/wallet_loading_service.dart | 81 +- lib/di.dart | 486 ++- lib/dogecoin/cw_dogecoin.dart | 5 +- .../auto_generate_subaddress_status.dart | 12 +- lib/entities/balance_display_mode.dart | 9 +- lib/entities/biometric_auth.dart | 2 +- lib/entities/bitcoin_amount_display_mode.dart | 3 +- lib/entities/calculate_fiat_amount.dart | 4 +- lib/entities/calculate_fiat_amount_raw.dart | 2 +- lib/entities/contact.dart | 7 +- lib/entities/contact.part.dart | 4 +- lib/entities/contact_base.dart | 2 +- lib/entities/country.dart | 7 +- lib/entities/default_settings_migration.dart | 39 +- lib/entities/emoji_string_extension.dart | 8 +- .../evm_transaction_error_fees_handler.dart | 18 +- lib/entities/exchange_api_mode.dart | 2 +- lib/entities/fiat_currency.dart | 116 +- lib/entities/format_amount.dart | 6 +- .../require_hardware_wallet_connection.dart | 7 +- lib/entities/haven_seed_store.part.dart | 4 +- lib/entities/ios_legacy_helper.dart | 18 +- .../new_ui_entities/list_item/list_item.dart | 6 +- .../list_item/list_item_selector.dart | 2 +- .../list_item/list_item_text_field.dart | 19 +- lib/entities/node_check.dart | 23 +- lib/entities/node_list.dart | 1 + lib/entities/pin_code_required_duration.dart | 2 +- lib/entities/preferences_key.dart | 3 +- lib/entities/provider_types.dart | 26 +- lib/entities/qr_scanner.dart | 3 +- lib/entities/qr_view_data.dart | 2 +- lib/entities/seed_phrase_length.dart | 3 +- lib/entities/seed_type.dart | 3 +- lib/entities/service_status.dart | 3 +- lib/entities/sort_balance_types.dart | 2 +- lib/entities/template.part.dart | 4 +- .../transaction_creation_credentials.dart | 2 +- lib/entities/transaction_description.dart | 13 +- .../transaction_description.part.dart | 3 +- lib/entities/transaction_history.dart | 3 +- lib/entities/wallet_description.dart | 4 +- lib/entities/wallet_manager.dart | 8 +- lib/entities/wallet_nft_response.dart | 4 +- .../exchange_provider_description.dart | 118 +- .../provider/chainflip_exchange_provider.dart | 75 +- .../provider/exolix_exchange_provider.dart | 46 +- .../provider/jupiter_exchange_provider.dart | 3 +- .../near_Intents_exchange_provider.dart | 75 +- .../provider/sideshift_exchange_provider.dart | 22 +- .../simpleswap_exchange_provider.dart | 25 +- .../provider/swapsxyz_exchange_provider.dart | 103 +- .../provider/swaptrade_exchange_provider.dart | 33 +- .../provider/thorchain_exchange.provider.dart | 78 +- .../provider/xoswap_exchange_provider.dart | 56 +- lib/exchange/trade_legacy.part.dart | 10 +- lib/haven/cw_haven.dart | 1 - lib/locales/hausa_intl.dart | 9 +- lib/locales/yoruba_intl.dart | 7 +- lib/main.dart | 7 +- lib/monero/cw_monero.dart | 33 +- lib/new-ui/modal_navigator.dart | 39 +- lib/new-ui/pages/about_page.dart | 2 +- lib/new-ui/pages/account_customizer.dart | 12 +- lib/new-ui/pages/addresses_page.dart | 3 +- .../pages/bridge/bridge_confirm_sheet.dart | 6 +- .../pages/bridge/bridge_network_page.dart | 6 +- .../bridge_receive_address_input_page.dart | 8 +- lib/new-ui/pages/card_customizer.dart | 3 +- lib/new-ui/pages/coin_control_page.dart | 22 +- lib/new-ui/pages/home_page.dart | 173 +- lib/new-ui/pages/lightning_username_page.dart | 16 +- lib/new-ui/pages/receive_page.dart | 57 +- lib/new-ui/pages/scan_page.dart | 10 +- lib/new-ui/pages/send_page.dart | 63 +- lib/new-ui/pages/settings_page.dart | 65 +- lib/new-ui/pages/swap_page.dart | 47 +- .../card_customizer/card_customizer_bloc.dart | 18 +- .../card_customizer_event.dart | 2 - .../card_customizer_state.dart | 32 +- lib/new-ui/viewmodels/charts/charts_bloc.dart | 45 +- .../viewmodels/charts/charts_event.dart | 3 +- .../viewmodels/charts/charts_state.dart | 26 +- .../lightning_username_bloc.dart | 6 +- .../lightning_username_event.dart | 2 +- .../widgets/addresses_page/address_info.dart | 3 +- .../addresses_page/address_label_input.dart | 3 +- lib/new-ui/widgets/animated_dropdown.dart | 33 +- lib/new-ui/widgets/apps_widget.dart | 14 +- .../widgets/bridge/confirm_details_card.dart | 5 +- .../widgets/bridge/transfer_history_row.dart | 6 +- lib/new-ui/widgets/changelog_modal.dart | 2 +- .../widgets/charts_page/asset_grid.dart | 2 +- .../widgets/charts_page/change_pill.dart | 2 +- .../widgets/charts_page/chart_header.dart | 4 +- .../widgets/charts_page/chart_view.dart | 4 +- .../widgets/charts_page/coin_header.dart | 16 +- .../widgets/charts_page/price_header.dart | 2 +- .../widgets/charts_page/range_selector.dart | 6 +- .../coin_control_list_item.dart | 136 +- .../action_row/coin_action_button.dart | 9 +- .../action_row/coin_action_row.dart | 135 +- .../coins_page/assets_history/asset_tile.dart | 22 +- .../assets_history_section.dart | 44 +- .../assets_history/assets_section.dart | 102 +- .../assets_history/assets_top_bar.dart | 107 +- .../assets_history/history_filters_page.dart | 9 +- .../assets_history/history_modal.dart | 6 +- .../assets_history/history_order_tile.dart | 26 +- .../assets_history/history_section.dart | 330 +- .../history_swap_providers_page.dart | 6 +- .../assets_history/history_tile.dart | 60 +- .../assets_history/history_tile_base.dart | 49 +- .../assets_history/history_top_bar.dart | 20 +- .../assets_history/history_trade_tile.dart | 21 +- .../transaction_details_modal.dart | 70 +- .../coins_page/cards/balance_card.dart | 82 +- .../widgets/coins_page/cards/cards_view.dart | 84 +- lib/new-ui/widgets/coins_page/mweb_ad.dart | 4 +- .../coins_page/top_bar_widget/chain_icon.dart | 6 +- .../top_bar_widget/lightning_switcher.dart | 8 +- .../top_bar_widget/pulsing_dot.dart | 12 +- .../coins_page/top_bar_widget/sync_bar.dart | 25 +- .../coins_page/top_bar_widget/top_bar.dart | 6 +- .../unconfirmed_balance_widget.dart | 127 +- .../widgets/coins_page/wallet_info.dart | 14 +- lib/new-ui/widgets/confirm_swiper.dart | 16 +- lib/new-ui/widgets/copy_wrapper.dart | 2 +- .../currency_picker/currency_picker_args.dart | 8 +- .../picker_recents_loader.dart | 4 +- .../single_network_currency_picker.dart | 6 +- lib/new-ui/widgets/dropdown_row.dart | 3 +- lib/new-ui/widgets/keyboard_hide_overlay.dart | 3 +- lib/new-ui/widgets/line_tab_switcher.dart | 80 +- .../long_press_menu/long_press_footer.dart | 38 +- .../long_press_menu/long_press_menu.dart | 3 +- .../long_press_menu/long_press_popup.dart | 34 +- lib/new-ui/widgets/modal_header.dart | 12 +- lib/new-ui/widgets/modal_page_wrapper.dart | 16 +- lib/new-ui/widgets/modern_button.dart | 2 +- lib/new-ui/widgets/new_primary_button.dart | 33 +- lib/new-ui/widgets/picker.dart | 147 +- .../receive_page/payjoin_copy_modal.dart | 2 +- .../receive_page/receive_address_type.dart | 3 +- .../receive_address_type_selector.dart | 11 +- .../receive_page/receive_amount_display.dart | 108 +- .../receive_page/receive_amount_modal.dart | 16 +- .../receive_page/receive_bottom_buttons.dart | 49 +- .../receive_page/receive_info_box.dart | 45 +- .../receive_page/receive_label_modal.dart | 12 +- .../receive_page/receive_label_widget.dart | 3 +- .../receive_large_amount_preview.dart | 3 +- .../widgets/receive_page/receive_qr_code.dart | 14 +- .../receive_page/receive_token_display.dart | 11 +- .../widgets/receive_page/receive_top_bar.dart | 50 +- .../widgets/send_page/fiat_amount_bar.dart | 16 +- .../send_page/floating_icon_button.dart | 16 +- .../send_page/l2_send_external_modal.dart | 39 +- .../widgets/send_page/recipient_dot_row.dart | 26 +- .../widgets/send_page/send_address_input.dart | 2 +- .../widgets/send_page/send_amount_input.dart | 30 +- .../send_page/send_confirm_bottom_widget.dart | 4 +- .../widgets/send_page/send_confirm_sheet.dart | 131 +- .../widgets/send_page/send_memo_input.dart | 3 +- .../send_page/send_syncing_indicator.dart | 35 +- .../swap_page/provider_options_page.dart | 14 +- .../swap_page/refund_address_modal.dart | 8 +- .../swap_address_selection_modal.dart | 81 +- .../widgets/swap_page/swap_options_page.dart | 17 +- ...wap_provider_initial_preference_modal.dart | 2 +- .../swap_page/swap_send_external_modal.dart | 10 +- .../trocador_providers_settings.dart | 3 +- lib/order/order.part.dart | 4 +- lib/order/order_provider_description.dart | 4 +- lib/order/order_source_description.dart | 2 +- lib/reactions/bootstrap.dart | 2 +- lib/reactions/fiat_rate_update.dart | 3 +- .../on_authentication_state_change.dart | 3 +- .../on_current_fiat_api_mode_change.dart | 17 +- lib/reactions/on_current_fiat_change.dart | 17 +- lib/reactions/on_current_wallet_change.dart | 3 +- lib/router.dart | 34 +- lib/solana/cw_solana.dart | 4 +- lib/src/screens/Info_page.dart | 2 +- lib/src/screens/auth/auth_page.dart | 8 +- lib/src/screens/backup/backup_page.dart | 11 +- .../backup/edit_backup_password_page.dart | 5 +- lib/src/screens/buy/buy_sell_page.dart | 27 +- lib/src/screens/buy/webview_page.dart | 5 +- .../connect_device/connect_device_page.dart | 3 +- .../monero_hardware_wallet_options_page.dart | 2 +- .../connect_device/widgets/device_tile.dart | 6 +- .../screens/contact/contact_list_page.dart | 60 +- lib/src/screens/contact/contact_page.dart | 33 +- lib/src/screens/dashboard/dashboard_page.dart | 7 +- .../dashboard/desktop_dashboard_page.dart | 2 +- .../desktop_action_button.dart | 14 +- .../desktop_dashboard_actions.dart | 114 +- .../desktop_dashboard_navbar.dart | 3 +- .../desktop_sidebar/side_menu.dart | 2 +- .../desktop_wallet_selection_dropdown.dart | 35 +- .../dashboard/favorite_token_modal.dart | 5 +- .../dashboard/pages/balance/balance_page.dart | 3 +- .../pages/balance/crypto_balance_widget.dart | 10 +- .../dashboard/pages/cake_features_page.dart | 66 +- .../dashboard/pages/navigation_dock.dart | 27 +- .../dashboard/pages/nft_listing_page.dart | 6 +- .../dashboard/pages/transactions_page.dart | 9 +- lib/src/screens/dashboard/sign_page.dart | 14 +- .../dashboard/widgets/action_button.dart | 3 +- .../dashboard/widgets/date_section_raw.dart | 4 +- .../dashboard/widgets/filter_tile.dart | 2 +- .../dashboard/widgets/filter_widget.dart | 19 +- .../screens/dashboard/widgets/header_row.dart | 8 +- .../widgets/new_main_navbar_widget.dart | 193 +- .../screens/dashboard/widgets/order_row.dart | 20 +- .../dashboard/widgets/page_indicator.dart | 64 +- .../screens/dashboard/widgets/sign_form.dart | 10 +- .../widgets/solana_nft_tile_widget.dart | 14 +- .../dashboard/widgets/sync_indicator.dart | 3 +- .../dashboard/widgets/transaction_raw.dart | 17 +- .../dashboard/widgets/verify_form.dart | 2 +- lib/src/screens/dev/moneroc_cache_debug.dart | 45 +- .../screens/dev/moneroc_call_profiler.dart | 25 +- lib/src/screens/dev/network_requests.dart | 58 +- lib/src/screens/dev/qr_tools_page.dart | 17 +- .../screens/dev/secure_preferences_page.dart | 22 +- .../screens/dev/shared_preferences_page.dart | 73 +- .../screens/disclaimer/disclaimer_page.dart | 2 +- lib/src/screens/exchange/exchange_page.dart | 10 +- .../widgets/currency_picker_widget.dart | 2 +- .../mobile_exchange_cards_section.dart | 4 +- .../screens/exchange/widgets/picker_item.dart | 5 +- .../exchange_trade_external_send_page.dart | 6 +- .../exchange_trade/exchange_trade_item.dart | 2 +- .../exchange_trade/exchange_trade_page.dart | 19 +- .../exchange_trade/widgets/timer_widget.dart | 6 +- .../integrations/deuro/savings_page.dart | 6 +- .../integrations/deuro/widgets/numpad.dart | 14 +- .../monero_accounts/widgets/account_tile.dart | 2 +- .../wallet_group_description_page.dart | 2 +- ..._group_existing_seed_description_page.dart | 17 +- .../new_wallet/widgets/select_button.dart | 20 +- .../nodes/node_create_or_edit_page.dart | 104 +- .../nodes/pow_node_create_or_edit_page.dart | 43 +- lib/src/screens/nodes/widgets/node_form.dart | 183 +- .../screens/nodes/widgets/node_list_row.dart | 53 +- lib/src/screens/pin_code/pin_code.dart | 8 +- lib/src/screens/pin_code/pin_code_widget.dart | 6 +- .../widgets/anonpay_status_section.dart | 24 +- .../receive/widgets/copy_link_item.dart | 4 +- .../release_notes/release_notes_screen.dart | 20 +- lib/src/screens/rescan/rescan_page.dart | 1 - .../restore/restore_from_backup_page.dart | 16 +- .../screens/restore/restore_options_page.dart | 10 +- .../wallet_restore_choose_derivation.dart | 7 +- .../wallet_restore_from_keys_form.dart | 38 +- .../wallet_restore_from_seed_form.dart | 2 +- .../screens/restore/wallet_restore_page.dart | 5 +- lib/src/screens/root/root.dart | 2 - .../seed_verification_success_view.dart | 16 +- lib/src/screens/seed/wallet_seed_page.dart | 19 +- lib/src/screens/send/send_page.dart | 665 ++-- .../widgets/choose_yat_address_alert.dart | 11 +- lib/src/screens/send/widgets/send_card.dart | 17 +- lib/src/screens/settings/attributes.dart | 2 +- .../settings/connection_sync_page.dart | 311 +- .../desktop_settings_page.dart | 12 +- .../settings/display_settings_page.dart | 350 +- .../screens/settings/domain_lookups_page.dart | 12 +- .../screens/settings/items/item_headers.dart | 2 +- .../screens/settings/manage_nodes_page.dart | 21 +- lib/src/screens/settings/mweb_logs_page.dart | 12 +- lib/src/screens/settings/mweb_node_page.dart | 41 +- lib/src/screens/settings/mweb_settings.dart | 2 +- .../screens/settings/other_settings_page.dart | 137 +- lib/src/screens/settings/privacy_page.dart | 92 +- .../settings/security_backup_page.dart | 21 +- .../settings/silent_payments_logs_page.dart | 9 +- .../settings/silent_payments_settings.dart | 25 +- .../widgets/settings_choices_cell.dart | 7 +- .../settings/widgets/settings_picker_row.dart | 2 +- .../widgets/settings_theme_choice.dart | 52 +- .../widgets/wallet_connect_button.dart | 4 +- .../setup_2fa/setup_2fa_enter_code_page.dart | 12 +- .../widgets/popup_cancellable_alert.dart | 11 +- .../setup_pin_code/setup_pin_code.dart | 13 +- lib/src/screens/splash/splash_page.dart | 6 +- lib/src/screens/start_tor/start_tor_page.dart | 4 +- .../support_chat/support_chat_page.dart | 24 +- .../support_chat/widgets/chatwoot_widget.dart | 7 +- .../trade_details/track_trade_list_item.dart | 5 +- .../trade_details_list_card.dart | 12 +- .../trade_details/trade_details_page.dart | 8 +- .../trade_details_status_item.dart | 3 +- .../address_list_item.dart | 2 +- .../confirmations_list_item.dart | 6 +- .../transaction_details/rbf_details_page.dart | 3 +- .../textfield_list_item.dart | 2 +- .../transaction_details_page.dart | 32 +- .../transaction_expandable_list_item.dart | 2 +- .../unspent_coins_list_page.dart | 82 +- .../widgets/unspent_coins_list_item.dart | 48 +- lib/src/screens/ur/animated_ur_page.dart | 12 +- .../widgets/qr_format_info_bottom_sheet.dart | 5 +- .../ur/widgets/qr_selection_dialog.dart | 9 +- lib/src/screens/ur/widgets/urqr.dart | 14 +- .../eth/evm_supported_methods.dart | 2 +- .../services/walletkit_service.dart | 39 +- .../wallet_connect/utils/method_utils.dart | 13 +- .../utils/wc_permissions_mapper.dart | 3 +- .../wc_connections_listing_view.dart | 2 +- .../enter_wallet_connect_uri_widget.dart | 4 +- .../wallet_connect/widgets/wc_hero_card.dart | 1 - .../screens/wallet_keys/wallet_keys_page.dart | 2 +- .../screens/wallet_list/wallet_list_page.dart | 86 +- .../wallet_unlock_arguments.dart | 5 +- .../welcome/create_pin_welcome_page.dart | 3 +- lib/src/screens/welcome/welcome_page.dart | 10 +- .../yat/widgets/first_introduction.dart | 95 +- .../yat/widgets/second_introduction.dart | 64 +- .../yat/widgets/third_introduction.dart | 22 +- lib/src/screens/yat/widgets/yat_bar.dart | 20 +- .../yat/widgets/yat_page_indicator.dart | 10 +- lib/src/widgets/adaptable_page_view.dart | 3 +- lib/src/widgets/alert_with_picker_option.dart | 1 - lib/src/widgets/base_alert_dialog.dart | 97 +- lib/src/widgets/base_text_form_field.dart | 3 +- lib/src/widgets/blockchain_height_widget.dart | 2 +- ...ake_pay_transaction_sent_bottom_sheet.dart | 21 +- .../confirm_sending_bottom_sheet_widget.dart | 3 +- .../info_bottom_sheet_widget.dart | 4 +- .../info_steps_bottom_sheet_widget.dart | 22 +- .../payment_confirmation_bottom_sheet.dart | 3 +- .../swap_confirmation_bottom_sheet.dart | 3 +- .../swap_details_bottom_sheet.dart | 11 +- .../token_selection_bottom_sheet.dart | 13 +- lib/src/widgets/check_box_picker.dart | 11 +- lib/src/widgets/checkbox_widget.dart | 1 + lib/src/widgets/evm_switcher.dart | 3 +- .../widgets/haven_wallet_removal_popup.dart | 20 +- lib/src/widgets/index.dart | 1 + .../new_list_row/list_Item_style_wrapper.dart | 40 +- .../list_item_checkbox_widget.dart | 64 +- .../list_item_regular_row_widget.dart | 2 +- .../list_item_selector_widget.dart | 13 +- .../list_item_text_field_widget.dart | 5 +- .../new_list_row/list_item_toggle_widget.dart | 3 +- .../new_list_row/new_list_section.dart | 17 +- lib/src/widgets/number_text_fild_widget.dart | 10 +- lib/src/widgets/picker.dart | 3 +- .../widgets/picker_inner_wrapper_widget.dart | 10 +- lib/src/widgets/provider_optoin_tile.dart | 4 +- lib/src/widgets/rounded_checkbox.dart | 28 +- lib/src/widgets/rounded_icon_button.dart | 2 +- .../scrollable_with_bottom_section.dart | 2 +- lib/src/widgets/seed_widget.dart | 13 +- lib/src/widgets/seedphrase_grid_widget.dart | 8 +- lib/src/widgets/simple_checkbox.dart | 6 +- lib/src/widgets/standard_checkbox.dart | 11 +- lib/src/widgets/standard_list.dart | 19 +- lib/src/widgets/standard_list_status_row.dart | 6 +- .../widgets/standard_slide_button_widget.dart | 4 +- .../validable_annotated_editable_text.dart | 19 +- lib/src/widgets/vulnerable_seeds_popup.dart | 21 +- lib/store/app_store.dart | 1 - lib/store/dashboard/order_filter_store.dart | 5 +- .../dashboard/payjoin_transactions_store.dart | 3 +- lib/store/dashboard/trade_filter_store.dart | 52 +- lib/store/node_list_store.dart | 1 + lib/store/seed_settings_store.dart | 1 - lib/store/settings_store.dart | 160 +- lib/store/templates/send_template_store.dart | 6 +- lib/store/wallet_list_store.dart | 2 +- lib/themes/core/theme_store.dart | 4 +- .../light_theme_custom_colors.dart | 12 +- lib/tron/cw_tron.dart | 3 +- lib/utils/address_formatter.dart | 15 +- lib/utils/brightness_util.dart | 2 +- lib/utils/clipboard_util.dart | 3 +- lib/utils/date_picker.dart | 26 +- lib/utils/debounce.dart | 6 +- lib/utils/device_info.dart | 4 +- lib/utils/feature_flag.dart | 3 +- lib/utils/item_cell.dart | 4 +- lib/utils/list_item.dart | 2 +- lib/utils/list_section.dart | 2 +- lib/utils/mobx.dart | 12 +- lib/utils/package_info.dart | 77 +- lib/utils/tor.dart | 16 +- lib/utils/totp_utils.dart | 2 +- lib/view_model/animated_ur_model.dart | 5 +- lib/view_model/auth_state.dart | 1 - lib/view_model/auth_view_model.dart | 2 +- lib/view_model/backup_view_model.dart | 6 +- .../bridge/bridge_details_view_model.dart | 4 +- lib/view_model/bridge/bridge_view_model.dart | 20 +- lib/view_model/buy/buy_amount_view_model.dart | 4 +- lib/view_model/buy/buy_item.dart | 5 +- lib/view_model/buy/buy_sell_view_model.dart | 18 +- .../cake_pay_buy_card_view_model.dart | 7 +- .../cake_pay_cards_list_view_model.dart | 1 - .../contact_list/contact_view_model.dart | 26 +- .../dashboard/action_list_item.dart | 2 +- .../dashboard/dashboard_view_model.dart | 33 +- lib/view_model/dashboard/filter_item.dart | 19 +- .../dashboard/home_settings_view_model.dart | 24 +- lib/view_model/dashboard/order_list_item.dart | 2 +- .../dashboard/receive_option_view_model.dart | 1 - .../dashboard/transaction_list_item.dart | 4 +- lib/view_model/dashboard/wallet_balance.dart | 2 +- .../dev/background_sync_logs_view_model.dart | 8 +- .../exchange_provider_logs_view_model.dart | 5 +- .../dev/network_requests_view_model.dart | 2 +- lib/view_model/dev/qr_tools_view_model.dart | 26 +- lib/view_model/dev/secure_preferences.dart | 19 +- .../dev/send_network_requests_view_model.dart | 5 +- lib/view_model/dev/shared_preferences.dart | 9 +- .../dev/socket_health_logs_view_model.dart | 1 - .../edit_backup_password_view_model.dart | 9 +- .../exchange/exchange_trade_view_model.dart | 38 +- .../exchange/exchange_view_model.dart | 93 +- .../hardware_wallet/bitbox_view_model.dart | 5 +- .../hardware_wallet_view_model.dart | 1 - .../hardware_wallet/ledger_view_model.dart | 20 +- .../trezor_connect_view_model.dart | 17 +- .../integrations/deuro_view_model.dart | 5 +- .../account_list_item.dart | 3 +- ...ero_account_edit_or_create_view_model.dart | 23 +- .../monero_account_list_view_model.dart | 43 +- .../node_create_or_edit_view_model.dart | 31 +- .../node_list/node_list_view_model.dart | 34 +- .../node_list/pow_node_list_view_model.dart | 1 + .../payjoin_details_view_model.dart | 6 +- lib/view_model/rescan_view_model.dart | 2 +- lib/view_model/restore/restore_mode.dart | 2 +- .../restore_from_backup_view_model.dart | 5 +- lib/view_model/seed_settings_view_model.dart | 3 +- lib/view_model/send/output.dart | 15 +- .../send/send_template_view_model.dart | 4 +- lib/view_model/send/send_view_model.dart | 144 +- .../send/send_view_model_state.dart | 3 + .../settings/connection_sync_view_model.dart | 6 +- .../settings/display_settings_view_model.dart | 5 +- lib/view_model/settings/link_list_item.dart | 12 +- .../settings/other_settings_view_model.dart | 18 +- .../settings/privacy_settings_view_model.dart | 6 +- .../settings/regular_list_item.dart | 2 +- .../security_settings_view_model.dart | 7 +- .../settings/switcher_list_item.dart | 5 +- .../trocador_providers_view_model.dart | 5 +- .../settings/version_list_item.dart | 2 +- lib/view_model/setup_pin_code_view_model.dart | 5 +- lib/view_model/start_tor_view_model.dart | 13 +- lib/view_model/trade_details_view_model.dart | 24 +- .../transaction_details_view_model.dart | 52 +- .../unspent_coins_details_view_model.dart | 3 +- .../unspent_coins/unspent_coins_item.dart | 28 +- .../unspent_coins_list_view_model.dart | 10 +- .../unspent_coins_switch_item.dart | 13 +- .../wallet_account_list_header.dart | 2 +- .../wallet_address_hidden_list_header.dart | 2 +- .../wallet_address_list_item.dart | 30 +- .../wallet_address_list_view_model.dart | 27 +- .../wallet_address_util.dart | 10 +- lib/view_model/wallet_creation_vm.dart | 13 +- .../wallet_groups_display_view_model.dart | 23 +- .../wallet_hardware_restore_view_model.dart | 17 +- .../wallet_list/wallet_list_view_model.dart | 7 +- lib/view_model/wallet_restore_view_model.dart | 26 +- lib/view_model/wallet_seed_view_model.dart | 1 - lib/wallet_type_utils.dart | 18 +- lib/wownero/cw_wownero.dart | 12 +- lib/zano/cw_zano.dart | 34 +- lib/zcash/cw_zcash.dart | 39 +- 781 files changed, 15084 insertions(+), 14256 deletions(-) diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart index 0d985b2370..072a92dc29 100644 --- a/cw_bitcoin/lib/address_from_output.dart +++ b/cw_bitcoin/lib/address_from_output.dart @@ -17,21 +17,16 @@ BitcoinBaseAddress addressFromScript(Script script, switch (addressType) { case P2pkhAddressType.p2pkh: - return P2pkhAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2pkhAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case P2shAddressType.p2pkhInP2sh: case P2shAddressType.p2pkInP2sh: - return P2shAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2shAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case SegwitAddresType.p2wpkh: - return P2wpkhAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2wpkhAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case SegwitAddresType.p2wsh: - return P2wshAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2wshAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); case SegwitAddresType.p2tr: - return P2trAddress.fromScriptPubkey( - script: script, network: BitcoinNetwork.mainnet); + return P2trAddress.fromScriptPubkey(script: script, network: BitcoinNetwork.mainnet); } throw ArgumentError("Invalid script"); diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index d6de05051b..65b728904b 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -72,17 +72,16 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { required super.type, String? scriptHash, required super.network, - }) { + }) { try { this.scriptHash = scriptHash ?? - (network != null ? BitcoinAddressUtils.scriptHash(address, network: network!) : null); + (network != null ? BitcoinAddressUtils.scriptHash(address, network: network!) : null); } catch (e) { printV(e); } -} + } static bool _legacyDefaultForType(BitcoinAddressType type) { - // Some address types (p2wpkh, p2wsh) were historically derived from the same account/path as our new standard // but using legacy formats. For these, we default to legacy = false. if (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh) return false; @@ -154,6 +153,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { } return scriptHash!; } + @override String get derivationPath { if (type == SegwitAddresType.mweb) { @@ -162,9 +162,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { final coinType = _coinTypeForNetwork(); final purpose = _purposeForType(type); - final accountPath = isLegacyDerivation - ? electrum_path - : "m/$purpose'/$coinType'/0'"; + final accountPath = isLegacyDerivation ? electrum_path : "m/$purpose'/$coinType'/0'"; final chain = isHidden ? 1 : 0; return "$accountPath/$chain/$index"; diff --git a/cw_bitcoin/lib/bitcoin_amount_format.dart b/cw_bitcoin/lib/bitcoin_amount_format.dart index d5a42d984b..dd73635695 100644 --- a/cw_bitcoin/lib/bitcoin_amount_format.dart +++ b/cw_bitcoin/lib/bitcoin_amount_format.dart @@ -7,8 +7,8 @@ final bitcoinAmountFormat = NumberFormat() ..maximumFractionDigits = bitcoinAmountLength ..minimumFractionDigits = 1; -String bitcoinAmountToString({required int amount}) => bitcoinAmountFormat.format( - cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); +String bitcoinAmountToString({required int amount}) => + bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); double bitcoinAmountToDouble({required int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider); diff --git a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart index 7bf488f3f1..ffadb65ae3 100644 --- a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart +++ b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart @@ -5,4 +5,3 @@ class BitcoinCommitTransactionException implements Exception { @override String toString() => errorMessage; } - diff --git a/cw_bitcoin/lib/bitcoin_transaction_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index 46e4ca8e43..e38cbce305 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -2,7 +2,8 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:flutter/foundation.dart'; class BitcoinTransactionPriority extends TransactionPriority { - const BitcoinTransactionPriority({required String title, required int raw, String? description, String? hint}) + const BitcoinTransactionPriority( + {required String title, required int raw, String? description, String? hint}) : super(title: title, raw: raw, description: description, hint: hint); static const List all = [fast, medium, slow, custom]; @@ -10,10 +11,10 @@ class BitcoinTransactionPriority extends TransactionPriority { BitcoinTransactionPriority(title: 'Slow', description: "2 sat/byte", hint: "~ 24 h", raw: 0); static const BitcoinTransactionPriority medium = BitcoinTransactionPriority(title: 'Medium', description: "3 sat/byte", hint: "~ 1 h", raw: 1); - static const BitcoinTransactionPriority fast = - BitcoinTransactionPriority(title: 'Fast', description: "4 sat/byte", hint: "~ 30 min", raw: 2); + static const BitcoinTransactionPriority fast = BitcoinTransactionPriority( + title: 'Fast', description: "4 sat/byte", hint: "~ 30 min", raw: 2); static const BitcoinTransactionPriority custom = - BitcoinTransactionPriority(title: 'Custom', raw: 3); + BitcoinTransactionPriority(title: 'Custom', raw: 3); static BitcoinTransactionPriority deserialize({required int raw}) { switch (raw) { @@ -116,19 +117,19 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { return label; } - } + class BitcoinCashTransactionPriority extends BitcoinTransactionPriority { const BitcoinCashTransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); static const List all = [fast, medium, slow]; static const BitcoinCashTransactionPriority slow = - BitcoinCashTransactionPriority(title: 'Slow', raw: 0); + BitcoinCashTransactionPriority(title: 'Slow', raw: 0); static const BitcoinCashTransactionPriority medium = - BitcoinCashTransactionPriority(title: 'Medium', raw: 1); + BitcoinCashTransactionPriority(title: 'Medium', raw: 1); static const BitcoinCashTransactionPriority fast = - BitcoinCashTransactionPriority(title: 'Fast', raw: 2); + BitcoinCashTransactionPriority(title: 'Fast', raw: 2); static BitcoinCashTransactionPriority deserialize({required int raw}) { switch (raw) { @@ -170,4 +171,3 @@ class BitcoinCashTransactionPriority extends BitcoinTransactionPriority { return label; } } - diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index a0d88ce9ac..7e76132a88 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -140,7 +140,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { autorun((_) { this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; }); - + reaction((_) => this.useLightning, (bool useLightning) { if (useLightning && LightningWallet.isAvailable) { if (mnemonic != null) { @@ -306,28 +306,28 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } return BitcoinWallet( - mnemonic: mnemonic, - xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null, - password: password, - passphrase: passphrase, - walletInfo: walletInfo, - derivationInfo: derivationInfo, - unspentCoinsInfo: unspentCoinsInfo, - initialAddresses: snp?.addresses, - initialSilentAddresses: snp?.silentAddresses, - initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, - initialBalance: snp?.balance, - initialLightningBalance: snp?.lightningBalance, - encryptionFileUtils: encryptionFileUtils, - seedBytes: seedBytes, - initialRegularAddressIndex: snp?.regularAddressIndex, - initialChangeAddressIndex: snp?.changeAddressIndex, - addressPageType: snp?.addressPageType, - networkParam: network, - alwaysScan: snp?.alwaysScan, - useLightning: snp?.useLightning, - cachedLightningAddress: snp?.cachedLightningAddress, - payjoinBox: payjoinBox, + mnemonic: mnemonic, + xpub: keysData.xPub != null ? convertZpubToXpub(keysData.xPub!) : null, + password: password, + passphrase: passphrase, + walletInfo: walletInfo, + derivationInfo: derivationInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp?.addresses, + initialSilentAddresses: snp?.silentAddresses, + initialSilentAddressIndex: snp?.silentAddressIndex ?? 0, + initialBalance: snp?.balance, + initialLightningBalance: snp?.lightningBalance, + encryptionFileUtils: encryptionFileUtils, + seedBytes: seedBytes, + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: snp?.addressPageType, + networkParam: network, + alwaysScan: snp?.alwaysScan, + useLightning: snp?.useLightning, + cachedLightningAddress: snp?.cachedLightningAddress, + payjoinBox: payjoinBox, ); } @@ -346,12 +346,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } try { - final lBalance = await lightningWallet!.getBalance(); + final lBalance = await lightningWallet!.getBalance(); - this.balance[CryptoCurrency.btcln] = ElectrumBalance( - confirmed: lBalance, - unconfirmed: Money.zero(CryptoCurrency.btcln), - frozen: Money.zero(CryptoCurrency.btcln)); + this.balance[CryptoCurrency.btcln] = ElectrumBalance( + confirmed: lBalance, + unconfirmed: Money.zero(CryptoCurrency.btcln), + frozen: Money.zero(CryptoCurrency.btcln)); } catch (e) { printV("Error fetching lightning balance: $e"); } @@ -494,7 +494,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { @override Future createTransaction(Object credentials) async { credentials = credentials as BitcoinTransactionCredentials; - final lnAddr = credentials.outputs.first.isParsedAddress ? credentials.outputs.first.extractedAddress! : credentials.outputs.first.address; + final lnAddr = credentials.outputs.first.isParsedAddress + ? credentials.outputs.first.extractedAddress! + : credentials.outputs.first.address; final isLNCompatible = await lightningWallet?.isCompatible(lnAddr); if ((credentials.coinTypeToSpendFrom == UnspentCoinType.lightning && lightningWallet != null) || @@ -506,12 +508,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { amount = credentials.outputs.first.cryptoAmount; } - return lightningWallet!.createTransaction( - lnAddr, - amount.amount > BigInt.zero ? amount.amount : null, - credentials.priority, - credentials.outputs.first.sendAll,); + lnAddr, + amount.amount > BigInt.zero ? amount.amount : null, + credentials.priority, + credentials.outputs.first.sendAll, + ); } final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction; diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 6abce9689a..0bc7f8c9c8 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -137,7 +137,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S } @override - bool containsAddress(String address) => super.containsAddress(address) || address == lightningAddress; + bool containsAddress(String address) => + super.containsAddress(address) || address == lightningAddress; @override String get addressForBuy { @@ -153,12 +154,9 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S @override String get addressForExchange { - final current = getFreshAddress(); final availableReceiveAddresses = receiveAddresses.where((element) => - !element.isUsed && - !element.isHidden && - !hiddenAddresses.contains(element.address)); + !element.isUsed && !element.isHidden && !hiddenAddresses.contains(element.address)); final bool isSilentPaymentsPage = addressPageType == SilentPaymentsAddresType.p2sp; final bool isLightningPage = addressPageType == LightningAddressType.p2l; diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 479cf64164..658969c3e1 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -59,27 +59,27 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { } class BitcoinWalletFromKeysCredentials extends WalletCredentials { - BitcoinWalletFromKeysCredentials({ - required String name, - required String password, - required this.xpub, - WalletInfo? walletInfo, - super.hardwareWalletType - }) : super(name: name, password: password, walletInfo: walletInfo); + BitcoinWalletFromKeysCredentials( + {required String name, + required String password, + required this.xpub, + WalletInfo? walletInfo, + super.hardwareWalletType}) + : super(name: name, password: password, walletInfo: walletInfo); final String xpub; } class LitecoinWalletFromKeysCredentials extends WalletCredentials { - LitecoinWalletFromKeysCredentials({ - required String name, - required String password, - required this.xpub, - required this.scanSecret, - required this.spendPubkey, - WalletInfo? walletInfo, - super.hardwareWalletType - }) : super(name: name, password: password, walletInfo: walletInfo); + LitecoinWalletFromKeysCredentials( + {required String name, + required String password, + required this.xpub, + required this.scanSecret, + required this.spendPubkey, + WalletInfo? walletInfo, + super.hardwareWalletType}) + : super(name: name, password: password, walletInfo: walletInfo); final String xpub; final String scanSecret; diff --git a/cw_bitcoin/lib/bitcoin_wallet_keys.dart b/cw_bitcoin/lib/bitcoin_wallet_keys.dart index 4ed0da49cc..9a9bdcba46 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_keys.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_keys.dart @@ -1,15 +1,12 @@ class BitcoinWalletKeys { - const BitcoinWalletKeys({required this.wif, required this.privateKey, required this.publicKey, required this.xpub}); + const BitcoinWalletKeys( + {required this.wif, required this.privateKey, required this.publicKey, required this.xpub}); final String wif; final String privateKey; final String publicKey; final String xpub; - Map toJson() => { - 'wif': wif, - 'privateKey': privateKey, - 'publicKey': publicKey, - 'xpub': xpub - }; -} \ No newline at end of file + Map toJson() => + {'wif': wif, 'privateKey': privateKey, 'publicKey': publicKey, 'xpub': xpub}; +} diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 8a034d8cfe..836b006d26 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -21,8 +21,7 @@ class BitcoinWalletService extends WalletService< BitcoinRestoreWalletFromSeedCredentials, BitcoinWalletFromKeysCredentials, BitcoinRestoreWalletFromHardware> { - BitcoinWalletService(this.unspentCoinsInfoSource, - this.payjoinSessionSource, this.isDirect); + BitcoinWalletService(this.unspentCoinsInfoSource, this.payjoinSessionSource, this.isDirect); final Box unspentCoinsInfoSource; final Box payjoinSessionSource; @@ -38,9 +37,12 @@ class BitcoinWalletService extends WalletService< final String mnemonic; final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); - derivationInfo.derivationType = credentials.derivationInfo?.derivationType ?? derivationInfo.derivationType; - derivationInfo.derivationPath = credentials.derivationInfo?.derivationPath ?? derivationInfo.derivationPath; - derivationInfo.description = credentials.derivationInfo?.description ?? derivationInfo.description; + derivationInfo.derivationType = + credentials.derivationInfo?.derivationType ?? derivationInfo.derivationType; + derivationInfo.derivationPath = + credentials.derivationInfo?.derivationPath ?? derivationInfo.derivationPath; + derivationInfo.description = + credentials.derivationInfo?.description ?? derivationInfo.description; derivationInfo.scriptType = credentials.derivationInfo?.scriptType ?? derivationInfo.scriptType; await derivationInfo.save(); switch (derivationInfo.derivationType) { @@ -119,8 +121,9 @@ class BitcoinWalletService extends WalletService< } await WalletInfo.delete(walletInfo); - final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( - (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); + final unspentCoinsToDelete = unspentCoinsInfoSource.values + .where((unspentCoin) => unspentCoin.walletId == walletInfo.id) + .toList(); final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList(); @@ -135,11 +138,10 @@ class BitcoinWalletService extends WalletService< final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; credentials.walletInfo?.network = network.value; final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); - derivationInfo.derivationPath = - credentials.hwAccountData.derivationPath; - + derivationInfo.derivationPath = credentials.hwAccountData.derivationPath; + final xpub = convertAnyToXpub(credentials.hwAccountData.xpub!); - + await credentials.walletInfo!.save(); final wallet = await BitcoinWallet( password: credentials.password!, diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 052d18ef0f..44aa096c4e 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -304,9 +304,9 @@ class ElectrumClient { }); Future>>> getBatchHistory( - List scriptHashes, { - int timeout = 10000, - }) async { + List scriptHashes, { + int timeout = 10000, + }) async { final paramsList = scriptHashes.map((h) => [h]).toList(growable: false); final batchResults = await callBatchWithTimeout( @@ -342,9 +342,9 @@ class ElectrumClient { } Future>>> getBatchUnspent( - List scriptHashes, { - int timeout = 10000, - }) async { + List scriptHashes, { + int timeout = 10000, + }) async { final paramsList = scriptHashes.map((h) => [h]).toList(growable: false); final batchResults = await callBatchWithTimeout( @@ -380,9 +380,9 @@ class ElectrumClient { } Future>> getBatchBalance( - List scriptHashes, { - int timeout = 10000, - }) async { + List scriptHashes, { + int timeout = 10000, + }) async { final paramsList = scriptHashes.map((h) => [h]).toList(growable: false); final batchResults = await callBatchWithTimeout( @@ -416,9 +416,9 @@ class ElectrumClient { } Future>> getBatchTransactionVerbose( - List hashes, { - int timeout = 10000, - }) async { + List hashes, { + int timeout = 10000, + }) async { final result = >{}; if (hashes.isEmpty) return result; @@ -443,9 +443,9 @@ class ElectrumClient { } Future> getBatchTransactionHex( - List hashes, { - int timeout = 10000, - }) async { + List hashes, { + int timeout = 10000, + }) async { final result = {}; if (hashes.isEmpty) return result; @@ -483,12 +483,8 @@ class ElectrumClient { // Build the Batch Array final List> batchPayload = []; for (int i = 0; i < paramsList.length; i++) { - batchPayload.add({ - "jsonrpc": "2.0", - "method": method, - "params": paramsList[i], - "id": "$batchBaseId-$i" - }); + batchPayload.add( + {"jsonrpc": "2.0", "method": method, "params": paramsList[i], "id": "$batchBaseId-$i"}); } // Register the task @@ -775,7 +771,6 @@ class ElectrumClient { } void _handleResponse(dynamic response) { - // Handle batch response if (response is List) { if (response.isEmpty) return; diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index 6457832ec9..5de131c77c 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -1,6 +1,5 @@ import 'dart:convert'; - import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/pathForWallet.dart'; diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index a58829e009..48c006ae24 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -184,15 +184,13 @@ class ElectrumTransactionInfo extends TransactionInfo { // MWEB HogEx final isHogExTx = (BtcTransaction tx) { - if (tx.inputs.isEmpty || tx.inputs.first.txIndex > 0 || tx.outputs.isEmpty) - return false; + if (tx.inputs.isEmpty || tx.inputs.first.txIndex > 0 || tx.outputs.isEmpty) return false; final b = tx.outputs.first.scriptPubKey.toBytes(); return b.length == 34 && b[0] == 88 && b[1] == 32; }; final firstInput = bundle.ins.isNotEmpty ? bundle.ins.first : null; - final isHogEx = firstInput != null && - isHogExTx(bundle.originalTransaction) && - isHogExTx(firstInput); + final isHogEx = + firstInput != null && isHogExTx(bundle.originalTransaction) && isHogExTx(firstInput); final fee = hasMissingInputTx ? null : inputAmount - totalOutAmount; final walletCurrency = walletTypeToCryptoCurrency(type); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index ccd41d010f..bc7d45ec44 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -156,7 +156,6 @@ abstract class ElectrumWalletBase sideHdByType[type] = sideHd; } } - } int _purposeForType(BitcoinAddressType type) { @@ -195,7 +194,6 @@ abstract class ElectrumWalletBase /// For LEGACY addresses, returns the wallet's legacy derivation base (derivationInfo.derivationPath) /// which is already the account path used historically (e.g. m/0' or m/84'/0'/0'). String _accountDerivationPathForRecord(BaseBitcoinAddressRecord record) { - if (derivationInfo.derivationType == DerivationType.electrum) { return derivationInfo.derivationPath ?? electrum_path; // m/0' } @@ -807,7 +805,7 @@ abstract class ElectrumWalletBase node!.isElectrs = true; // TODO figure out why condition was needed // if (node!.isInBox) { - node!.save(); + node!.save(); // } return node!.isElectrs!; } @@ -873,7 +871,8 @@ abstract class ElectrumWalletBase BigInt get networkDustAmount => BigInt.from(546); - bool _isBelowDust(BigInt amount) => amount <= networkDustAmount && network != BitcoinNetwork.testnet; + bool _isBelowDust(BigInt amount) => + amount <= networkDustAmount && network != BitcoinNetwork.testnet; UtxoDetails _createUTXOS({ required bool sendAll, @@ -961,8 +960,7 @@ abstract class ElectrumWalletBase final baseDerivationPath = _accountDerivationPathForRecord(utx.bitcoinAddressRecord); - final derivationPath = - "${_hardenedDerivationPath(baseDerivationPath)}" + final derivationPath = "${_hardenedDerivationPath(baseDerivationPath)}" "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}" "/${utx.bitcoinAddressRecord.index}"; publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); @@ -1139,7 +1137,8 @@ abstract class ElectrumWalletBase utxoDetails.utxos.length == utxoDetails.availableInputs.length - utxoDetails.unconfirmedCoins.length; - final amountLeftForChangeAndFee = utxoDetails.allInputsAmount - credentialsAmount.amount.toInt(); + final amountLeftForChangeAndFee = + utxoDetails.allInputsAmount - credentialsAmount.amount.toInt(); if (amountLeftForChangeAndFee <= 0) { if (!spendingAllCoins) { @@ -1175,11 +1174,9 @@ abstract class ElectrumWalletBase isChange: true, )); - // Must match the address' account root (purpose/coinType) and legacy derivation when applicable. final changeBaseDerivationPath = _accountDerivationPathForRecord(changeAddress); - final changeDerivationPath = - "${_hardenedDerivationPath(changeBaseDerivationPath)}" + final changeDerivationPath = "${_hardenedDerivationPath(changeBaseDerivationPath)}" "/${changeAddress.isHidden ? "1" : "0"}" "/${changeAddress.index}"; utxoDetails.publicKeys[address.pubKeyHash()] = @@ -1242,7 +1239,8 @@ abstract class ElectrumWalletBase inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, vinOutpoints: utxoDetails.vinOutpoints, ); - final leftover = utxoDetails.allInputsAmount - credentialsAmount.amount.toInt() - feeNoChange; + final leftover = + utxoDetails.allInputsAmount - credentialsAmount.amount.toInt() - feeNoChange; if (leftover >= 0) { final finalFee = feeNoChange + leftover; // absorb tiny remainder @@ -1827,16 +1825,15 @@ abstract class ElectrumWalletBase } Future?>> _fetchUnspentsRegular( - List addresses, - ) async { + List addresses, + ) async { final addressFutures = addresses.map((address) => fetchUnspent(address)).toList(); return Future.wait(addressFutures); } - Future?>> _fetchUnspentsBatch( - List addresses, - ) async { + List addresses, + ) async { final byScriptHash = { for (final address in addresses) address.getScriptHash(network): address, }; @@ -1845,7 +1842,7 @@ abstract class ElectrumWalletBase try { final unspentByScriptHash = - await _processChunksToMap>>( + await _processChunksToMap>>( items: scriptHashes, chunkSize: addressHistoryChunkSize, processChunk: _getListUnspentBatch, @@ -2131,10 +2128,7 @@ abstract class ElectrumWalletBase final hd = _hdFor(record: addressRecord); - final privkey = generateECPrivate( - hd: hd, - index: addressRecord.index, - network: network); + final privkey = generateECPrivate(hd: hd, index: addressRecord.index, network: network); privateKeys.add(privkey); @@ -2197,8 +2191,7 @@ abstract class ElectrumWalletBase final deduction = (outputAmount - networkDustAmount >= remainingFee) ? remainingFee : outputAmount - networkDustAmount; - outputs[i] = BitcoinOutput( - address: output.address, value: outputAmount - deduction); + outputs[i] = BitcoinOutput(address: output.address, value: outputAmount - deduction); remainingFee -= deduction; if (remainingFee <= BigInt.zero) break; @@ -2464,7 +2457,8 @@ abstract class ElectrumWalletBase @override Future> fetchTransactions() async { try { - final Map historiesWithDetails = {};; + final Map historiesWithDetails = {}; + ; printV('[BATCH_TEST] Fetching transactions with batch: $shouldUseBatchFetching'); @@ -2473,19 +2467,16 @@ abstract class ElectrumWalletBase ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) : fetchTransactionsForAddressType(historiesWithDetails, type))); } else if (type == WalletType.bitcoinCash) { - await Future.wait(BITCOIN_CASH_ADDRESS_TYPES - .map((type) => shouldUseBatchFetching + await Future.wait(BITCOIN_CASH_ADDRESS_TYPES.map((type) => shouldUseBatchFetching ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) : fetchTransactionsForAddressType(historiesWithDetails, type))); } else if (type == WalletType.litecoin) { - await Future.wait(LITECOIN_ADDRESS_TYPES - .where((type) => type != SegwitAddresType.mweb) - .map((type) => shouldUseBatchFetching - ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) - : fetchTransactionsForAddressType(historiesWithDetails, type))); + await Future.wait(LITECOIN_ADDRESS_TYPES.where((type) => type != SegwitAddresType.mweb).map( + (type) => shouldUseBatchFetching + ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) + : fetchTransactionsForAddressType(historiesWithDetails, type))); } else if (type == WalletType.dogecoin) { - await Future.wait(DOGECOIN_ADDRESS_TYPES - .map((type) => shouldUseBatchFetching + await Future.wait(DOGECOIN_ADDRESS_TYPES.map((type) => shouldUseBatchFetching ? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type) : fetchTransactionsForAddressType(historiesWithDetails, type))); } @@ -2633,8 +2624,7 @@ abstract class ElectrumWalletBase } Future fetchTransactionsForAddressTypeBatch( - Map historiesWithDetails, - BitcoinAddressType type) async { + Map historiesWithDetails, BitcoinAddressType type) async { final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type).toList(); final receiveAddresses = addressesByType.where((addr) => !addr.isHidden).toList(); @@ -2676,14 +2666,13 @@ abstract class ElectrumWalletBase ); } - Future fetchTransactionsForAddressesBranchBatch( - Map historiesWithDetails, - BitcoinAddressType type, - List branchAddresses, { - required bool isHidden, - required bool isLegacyDerivation, - }) async { + Map historiesWithDetails, + BitcoinAddressType type, + List branchAddresses, { + required bool isHidden, + required bool isLegacyDerivation, + }) async { if (branchAddresses.isEmpty) return; final tip = await getCurrentChainTip(); @@ -2714,7 +2703,6 @@ abstract class ElectrumWalletBase if (!shouldDiscover) return; - final newAddresses = await walletAddresses.discoverAddressesBatch( currentBranch, isHidden, @@ -2756,9 +2744,7 @@ abstract class ElectrumWalletBase } Future> _fetchBatchAddressHistory( - List addressRecords, - int? currentHeight, - int historyChunkSize) async { + List addressRecords, int? currentHeight, int historyChunkSize) async { String lastTxId = ''; bool didUpdateHistory = false; @@ -2769,11 +2755,8 @@ abstract class ElectrumWalletBase final scriptHashes = addressRecords.map((a) => a.getScriptHash(network)).toList(); final historyByScriptHash = - await _processChunksToMap>>( - items: scriptHashes, - chunkSize: historyChunkSize, - processChunk: _getHistoryBatch - ); + await _processChunksToMap>>( + items: scriptHashes, chunkSize: historyChunkSize, processChunk: _getHistoryBatch); // Map scriptHash -> addressRecord final byScriptHash = {}; @@ -2869,8 +2852,7 @@ abstract class ElectrumWalletBase .toList(growable: false); final heightsByHash = { - for (final e in chunkHistory) - (e['tx_hash'] as String): (e['height'] as int?), + for (final e in chunkHistory) (e['tx_hash'] as String): (e['height'] as int?), }; final infosByHash = await fetchTransactionInfoBatch( @@ -2909,40 +2891,35 @@ abstract class ElectrumWalletBase } } - Future>> _getTransactionVerboseBatch( - List hashes) { + Future>> _getTransactionVerboseBatch(List hashes) { return electrumClient.getBatchTransactionVerbose( hashes, timeout: transactionBatchTimeoutMs, ); } - Future> _getTransactionHexBatch( - List hashes) { + Future> _getTransactionHexBatch(List hashes) { return electrumClient.getBatchTransactionHex( hashes, timeout: transactionBatchTimeoutMs, ); } - Future>>> _getHistoryBatch( - List scriptHashes) { + Future>>> _getHistoryBatch(List scriptHashes) { return electrumClient.getBatchHistory( scriptHashes, timeout: transactionBatchTimeoutMs, ); } - Future>>> _getListUnspentBatch( - List scriptHashes) { + Future>>> _getListUnspentBatch(List scriptHashes) { return electrumClient.getBatchUnspent( scriptHashes, timeout: transactionBatchTimeoutMs, ); } - Future>> _getBalanceBatch( - List scriptHashes) { + Future>> _getBalanceBatch(List scriptHashes) { return electrumClient.getBatchBalance( scriptHashes, timeout: transactionBatchTimeoutMs, @@ -2956,8 +2933,7 @@ abstract class ElectrumWalletBase Duration retryDelay = const Duration(seconds: 2), }) async { final result = {}; - final uniqueHashes = - hashes.map((h) => h.trim()).where((h) => h.isNotEmpty).toSet().toList(); + final uniqueHashes = hashes.map((h) => h.trim()).where((h) => h.isNotEmpty).toSet().toList(); if (uniqueHashes.isEmpty) return result; @@ -2990,9 +2966,8 @@ abstract class ElectrumWalletBase required Map? heightsByHash, }) async { for (var i = 0; i < txIds.length; i += transactionChunkSize) { - final end = (i + transactionChunkSize < txIds.length) - ? i + transactionChunkSize - : txIds.length; + final end = + (i + transactionChunkSize < txIds.length) ? i + transactionChunkSize : txIds.length; final chunk = txIds.sublist(i, end); final bundlesByHash = await getTransactionExpandedBatch( @@ -3024,9 +2999,8 @@ abstract class ElectrumWalletBase } } - Future> getTransactionExpandedBatch({ - required List hashes, - Map? heightsByHash}) async { + Future> getTransactionExpandedBatch( + {required List hashes, Map? heightsByHash}) async { final bundles = {}; if (hashes.isEmpty) return bundles; @@ -3062,8 +3036,8 @@ abstract class ElectrumWalletBase Future>> _fetchTransactionVerboseBatch( List txIds) async { - - final verboseTransactionByHash = await _processChunksToMap>( + final verboseTransactionByHash = + await _processChunksToMap>( items: txIds, chunkSize: transactionChunkSize, processChunk: _getTransactionVerboseBatch, @@ -3100,8 +3074,8 @@ abstract class ElectrumWalletBase } Map _parseTransactions( - Map> verboseByHash, - ) { + Map> verboseByHash, + ) { final result = {}; for (final entry in verboseByHash.entries) { @@ -3116,10 +3090,9 @@ abstract class ElectrumWalletBase return result; } - Map> _collectInputTxIdsByHash( - Map originalByHash, - ) { + Map originalByHash, + ) { final inputTxIdsByHash = >{}; for (final entry in originalByHash.entries) { @@ -3201,8 +3174,8 @@ abstract class ElectrumWalletBase } Future> _fetchBlockTimestampsFromMempoolByHeights( - Set heights, - ) async { + Set heights, + ) async { final out = {}; if (heights.isEmpty) return out; if (!(await checkIfMempoolAPIIsEnabled())) return out; @@ -3212,10 +3185,10 @@ abstract class ElectrumWalletBase try { final blockHashResp = await ProxyWrapper() .get( - clearnetUri: Uri.parse( - 'https://mempool.cakewallet.com/api/v1/block-height/$h', - ), - ) + clearnetUri: Uri.parse( + 'https://mempool.cakewallet.com/api/v1/block-height/$h', + ), + ) .timeout(const Duration(seconds: 15)); if (blockHashResp.statusCode != 200 || blockHashResp.body.isEmpty) return; @@ -3225,10 +3198,10 @@ abstract class ElectrumWalletBase final blockResp = await ProxyWrapper() .get( - clearnetUri: Uri.parse( - 'https://mempool.cakewallet.com/api/v1/block/$blockHash', - ), - ) + clearnetUri: Uri.parse( + 'https://mempool.cakewallet.com/api/v1/block/$blockHash', + ), + ) .timeout(const Duration(seconds: 15)); if (blockResp.statusCode != 200 || blockResp.body.isEmpty) return; @@ -3276,7 +3249,6 @@ abstract class ElectrumWalletBase return result; } - Future updateTransactions() async { printV("updateTransactions() called!"); try { @@ -3368,8 +3340,7 @@ abstract class ElectrumWalletBase } try { - final balancesByScriptHash = - await _processChunksToMap>( + final balancesByScriptHash = await _processChunksToMap>( items: scriptHashes, chunkSize: addressHistoryChunkSize, processChunk: _getBalanceBatch, @@ -3415,7 +3386,8 @@ abstract class ElectrumWalletBase ? await fetchBalancesBatch(addresses) : await fetchBalancesRegular(addresses); - printV('Fetched balances for ${addresses.length} addresses. Batch fetching: $shouldUseBatchFetching'); + printV( + 'Fetched balances for ${addresses.length} addresses. Batch fetching: $shouldUseBatchFetching'); var totalFrozen = 0; var totalConfirmed = 0; @@ -3544,17 +3516,14 @@ abstract class ElectrumWalletBase // that matches this received transaction, mark it as being from a peg out: for (final tx2 in transactionHistory.transactions.values) { final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs(); - // this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other - if (tx2.additionalInfo["isPegOut"] == true && - tx2.amount == tx.amount && - heightDiff <= 5) { + // this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other + if (tx2.additionalInfo["isPegOut"] == true && tx2.amount == tx.amount && heightDiff <= 5) { tx.additionalInfo["fromPegOut"] = true; } } } Future checkIfBatchSupported() async { - if (_isBatchSupported != null) { printV('[BATCH_TEST] Already checked: $_isBatchSupported'); return; @@ -3580,9 +3549,7 @@ abstract class ElectrumWalletBase ); final hasError = result.any((item) => - item is Map && - item.containsKey('error') && - item['error'] != null); + item is Map && item.containsKey('error') && item['error'] != null); if (hasError) { _isBatchSupported = false; diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 239fa491d8..239977ecbd 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -195,13 +195,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } if (addressPageType == LightningAddressType.p2l) { - return lightningAddress ?? - "Error: Unable to fetch your Lightning address, please check your network connection."; + return lightningAddress ?? + "Error: Unable to fetch your Lightning address, please check your network connection."; } - final typeMatchingAddressesAll = _addresses - .where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)) - .toList(); + final typeMatchingAddressesAll = + _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList(); // Prefer standard derivation addresses for the current/active address, // but keep legacy addresses present in the overall address lists. @@ -210,8 +209,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ...typeMatchingAddressesAll.where((a) => a.isLegacyDerivation), ]; - final typeMatchingReceiveAddressesAll = - typeMatchingAddressesAll.where((addr) => !addr.isUsed && !hiddenAddresses.contains(addr.address)).toList(); + final typeMatchingReceiveAddressesAll = typeMatchingAddressesAll + .where((addr) => !addr.isUsed && !hiddenAddresses.contains(addr.address)) + .toList(); final typeMatchingReceiveAddresses = [ ...typeMatchingReceiveAddressesAll.where((a) => !a.isLegacyDerivation), ...typeMatchingReceiveAddressesAll.where((a) => a.isLegacyDerivation), @@ -354,34 +354,35 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return acc; }); - @override - Future init() async { - if (walletInfo.type == WalletType.bitcoinCash) { - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - } else if (walletInfo.type == WalletType.litecoin) { - await _generateInitialAddresses(type: SegwitAddresType.p2wpkh); - if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { - await _generateInitialAddresses(type: SegwitAddresType.mweb); - } - } else if (walletInfo.type == WalletType.dogecoin) { + @override + Future init() async { + if (walletInfo.type == WalletType.bitcoinCash) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); + } else if (walletInfo.type == WalletType.litecoin) { + await _generateInitialAddresses(type: SegwitAddresType.p2wpkh); + if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { + await _generateInitialAddresses(type: SegwitAddresType.mweb); + } + } else if (walletInfo.type == WalletType.dogecoin) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); + } else if (walletInfo.type == WalletType.bitcoin) { + await _generateInitialAddresses(isLegacyDerivation: true); + await _generateInitialAddresses(); + if (!isHardwareWallet) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh, isLegacyDerivation: true); await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - } else if (walletInfo.type == WalletType.bitcoin) { - await _generateInitialAddresses(isLegacyDerivation: true); - await _generateInitialAddresses(); - if (!isHardwareWallet) { - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh, isLegacyDerivation: true); - await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); - await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh, isLegacyDerivation: true); - await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); + await _generateInitialAddresses( + type: P2shAddressType.p2wpkhInP2sh, isLegacyDerivation: true); + await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); - await _generateInitialAddresses(type: SegwitAddresType.p2tr, isLegacyDerivation: true); - await _generateInitialAddresses(type: SegwitAddresType.p2tr); + await _generateInitialAddresses(type: SegwitAddresType.p2tr, isLegacyDerivation: true); + await _generateInitialAddresses(type: SegwitAddresType.p2tr); - await _generateInitialAddresses(type: SegwitAddresType.p2wsh, isLegacyDerivation: true); - await _generateInitialAddresses(type: SegwitAddresType.p2wsh); - } + await _generateInitialAddresses(type: SegwitAddresType.p2wsh, isLegacyDerivation: true); + await _generateInitialAddresses(type: SegwitAddresType.p2wsh); } + } updateAddressesByMatch(); updateReceiveAddresses(); @@ -680,9 +681,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action void updateReceiveAddresses() { receiveAddresses.removeRange(0, receiveAddresses.length); - final newAddresses = _addresses.where((addressRecord) => - !addressRecord.isHidden && - !addressRecord.isUsed); + final newAddresses = + _addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed); receiveAddresses.addAll(newAddresses); } @@ -699,12 +699,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future discoverAddresses( - List addressList, - bool isHidden, - Future Function(BitcoinAddressRecord) getAddressHistory, { - BitcoinAddressType type = SegwitAddresType.p2wpkh, - required bool isLegacyDerivation, - }) async { + List addressList, + bool isHidden, + Future Function(BitcoinAddressRecord) getAddressHistory, { + BitcoinAddressType type = SegwitAddresType.p2wpkh, + required bool isLegacyDerivation, + }) async { final newAddresses = await _createNewAddresses( gap, startIndex: addressList.length, @@ -716,8 +716,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { addAddresses(newAddresses); addressList.addAll(newAddresses); - final addressesWithHistory = - await Future.wait(newAddresses.map(getAddressHistory)); + final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory)); final isLastAddressUsed = addressesWithHistory.last != null; if (isLastAddressUsed) { @@ -733,12 +732,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future> discoverAddressesBatch( - List addressList, - bool isHidden, - Future> Function(List) getUsedAddresses, { - BitcoinAddressType type = SegwitAddresType.p2wpkh, - required bool isLegacyDerivation, - }) async { + List addressList, + bool isHidden, + Future> Function(List) getUsedAddresses, { + BitcoinAddressType type = SegwitAddresType.p2wpkh, + required bool isLegacyDerivation, + }) async { final newAddresses = await _createNewAddresses( gap, startIndex: addressList.length, @@ -750,8 +749,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final usedAddresses = await getUsedAddresses(newAddresses); - final hasUsedAddressInGap = newAddresses.any( - (addressRecord) => usedAddresses.contains(addressRecord.address)); + final hasUsedAddressInGap = + newAddresses.any((addressRecord) => usedAddresses.contains(addressRecord.address)); if (!hasUsedAddressInGap) { return newAddresses; @@ -770,64 +769,69 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return [...newAddresses, ...moreNewAddresses]; } - Future _generateInitialAddresses( - {BitcoinAddressType type = SegwitAddresType.p2wpkh, - bool isLegacyDerivation = false }) async { - - // Legacy derivation produces the same addresses as standard for these types. - // Don't generate a legacy set to avoid duplicates. - if (isLegacyDerivation && (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh)) { - return; - } + {BitcoinAddressType type = SegwitAddresType.p2wpkh, bool isLegacyDerivation = false}) async { + // Legacy derivation produces the same addresses as standard for these types. + // Don't generate a legacy set to avoid duplicates. + if (isLegacyDerivation && (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh)) { + return; + } - var countOfReceiveAddresses = 0; - var countOfHiddenAddresses = 0; + var countOfReceiveAddresses = 0; + var countOfHiddenAddresses = 0; - _addresses.forEach((addr) { - if (addr.type == type && addr.isLegacyDerivation == isLegacyDerivation) { - if (addr.isHidden) { - countOfHiddenAddresses += 1; - } else { - countOfReceiveAddresses += 1; - } + _addresses.forEach((addr) { + if (addr.type == type && addr.isLegacyDerivation == isLegacyDerivation) { + if (addr.isHidden) { + countOfHiddenAddresses += 1; + } else { + countOfReceiveAddresses += 1; } - }); - - if (countOfReceiveAddresses < defaultReceiveAddressesCount) { - final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; - final newAddresses = await _createNewAddresses(addressesCount, - startIndex: countOfReceiveAddresses, isHidden: false, type: type, isLegacyDerivation: isLegacyDerivation); - addAddresses(newAddresses); } + }); - if (countOfHiddenAddresses < defaultChangeAddressesCount) { - final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; - final newAddresses = await _createNewAddresses(addressesCount, - startIndex: countOfHiddenAddresses, isHidden: true, type: type, isLegacyDerivation: isLegacyDerivation); - addAddresses(newAddresses); - } + if (countOfReceiveAddresses < defaultReceiveAddressesCount) { + final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses; + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfReceiveAddresses, + isHidden: false, + type: type, + isLegacyDerivation: isLegacyDerivation); + addAddresses(newAddresses); } - Future> _createNewAddresses(int count, - {int startIndex = 0, bool isHidden = false, BitcoinAddressType? type, bool isLegacyDerivation = false}) async { - final list = []; - - for (var i = startIndex; i < count + startIndex; i++) { - - final addrType = type ?? addressPageType; - final hd = _hdFor(isHidden: isHidden, type: addrType, isLegacyDerivation: isLegacyDerivation); + if (countOfHiddenAddresses < defaultChangeAddressesCount) { + final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses; + final newAddresses = await _createNewAddresses(addressesCount, + startIndex: countOfHiddenAddresses, + isHidden: true, + type: type, + isLegacyDerivation: isLegacyDerivation); + addAddresses(newAddresses); + } + } - final address = BitcoinAddressRecord( - await getAddressAsync(index: i, hd: hd, addressType: addrType), - index: i, - isHidden: isHidden, - isLegacyDerivation: isLegacyDerivation, - type: addrType, - network: network, - ); - list.add(address); - } + Future> _createNewAddresses(int count, + {int startIndex = 0, + bool isHidden = false, + BitcoinAddressType? type, + bool isLegacyDerivation = false}) async { + final list = []; + + for (var i = startIndex; i < count + startIndex; i++) { + final addrType = type ?? addressPageType; + final hd = _hdFor(isHidden: isHidden, type: addrType, isLegacyDerivation: isLegacyDerivation); + + final address = BitcoinAddressRecord( + await getAddressAsync(index: i, hd: hd, addressType: addrType), + index: i, + isHidden: isHidden, + isLegacyDerivation: isLegacyDerivation, + type: addrType, + network: network, + ); + list.add(address); + } return list; } @@ -868,8 +872,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return; } - final mainHd = _hdFor(isHidden: false, type: element.type, isLegacyDerivation: element.isLegacyDerivation); - final sideHd = _hdFor(isHidden: true, type: element.type, isLegacyDerivation: element.isLegacyDerivation); + final mainHd = _hdFor( + isHidden: false, type: element.type, isLegacyDerivation: element.isLegacyDerivation); + final sideHd = _hdFor( + isHidden: true, type: element.type, isLegacyDerivation: element.isLegacyDerivation); if (!element.isHidden && element.address != await getAddressAsync(index: element.index, hd: mainHd, addressType: element.type)) { @@ -897,7 +903,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return _isAddressByType(addressRecord, addressPageType); } - bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; + bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => !addr.isHidden && !addr.isUsed && addr.type == type; @@ -907,9 +913,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { final addressRecord = silentAddresses.firstWhere((addressRecord) => addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address); - silentAddresses.remove(addressRecord); - updateAddressesByMatch(); - } + silentAddresses.remove(addressRecord); + updateAddressesByMatch(); + } Bip32Slip10Secp256k1 _hdFor({ required bool isHidden, @@ -923,7 +929,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (hd == null) throw Exception("HD not found for type $type"); return hd; } - + @action Future setLightningAddress(String walletName, {String newAddress = ""}) async { if (lightningWallet == null) return; diff --git a/cw_bitcoin/lib/exceptions.dart b/cw_bitcoin/lib/exceptions.dart index 9bdb66eef0..d43ce823a7 100644 --- a/cw_bitcoin/lib/exceptions.dart +++ b/cw_bitcoin/lib/exceptions.dart @@ -30,7 +30,7 @@ class BitcoinTransactionCommitFailed extends TransactionCommitFailed { @override String toString() { - return errorMessage??"unknown error"; + return errorMessage ?? "unknown error"; } } diff --git a/cw_bitcoin/lib/hardware/bitbox_service.dart b/cw_bitcoin/lib/hardware/bitbox_service.dart index 3f47c5c621..a71dcacbc2 100644 --- a/cw_bitcoin/lib/hardware/bitbox_service.dart +++ b/cw_bitcoin/lib/hardware/bitbox_service.dart @@ -67,7 +67,8 @@ class BitcoinBitboxService extends HardwareWalletService with BitcoinHardwareWal Future getMasterFingerprint() => manager.getMasterFingerprint(); } -class LitecoinBitboxService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService { +class LitecoinBitboxService extends HardwareWalletService + with BitcoinHardwareWalletService, LitecoinHardwareWalletService { LitecoinBitboxService(this.manager); final BitboxManager manager; diff --git a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart index 9ede5714e3..aa73fc106e 100644 --- a/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart +++ b/cw_bitcoin/lib/hardware/litecoin_ledger_service.dart @@ -12,7 +12,8 @@ import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_litecoin/ledger_litecoin.dart'; -class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWalletService, LitecoinHardwareWalletService { +class LitecoinLedgerService extends HardwareWalletService + with BitcoinHardwareWalletService, LitecoinHardwareWalletService { LitecoinLedgerService(this.ledgerConnection) : litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection); @@ -53,7 +54,6 @@ class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWa required List inputs, required Map publicKeys, }) { - final readyInputs = []; for (final utxo in inputs) { final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; @@ -77,7 +77,7 @@ class LitecoinLedgerService extends HardwareWalletService with BitcoinHardwareWa inputs: readyInputs, outputs: outputs .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value, - Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) + Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) .toList(), changePath: changePath, sigHashType: 0x01, diff --git a/cw_bitcoin/lib/lightning/lightning_wallet.dart b/cw_bitcoin/lib/lightning/lightning_wallet.dart index 42dda7cb22..ea9399c6ab 100644 --- a/cw_bitcoin/lib/lightning/lightning_wallet.dart +++ b/cw_bitcoin/lib/lightning/lightning_wallet.dart @@ -90,8 +90,7 @@ class LightningWallet { lnurlDomain: lnurlDomain, apiKey: apiKey, privateEnabledDefault: true, - maxDepositClaimFee: MaxFee.rate(satPerVbyte: BigInt.from(5)) - ); + maxDepositClaimFee: MaxFee.rate(satPerVbyte: BigInt.from(5))); final connectRequest = ConnectRequest( config: config, @@ -105,8 +104,7 @@ class LightningWallet { _logStream ??= initLogging().asBroadcastStream(); try { - final logFile = File("$appPath/lightning.log") - ..createSync(); + final logFile = File("$appPath/lightning.log")..createSync(); _subscribeToLogStream(logFile); } catch (e) { printV(e); @@ -198,15 +196,17 @@ class LightningWallet { } } - Future createTransaction( - String address, BigInt? amountSats, BitcoinTransactionPriority? priority, bool feesIncluded) async { + Future createTransaction(String address, BigInt? amountSats, + BitcoinTransactionPriority? priority, bool feesIncluded) async { final inputType = await sdk.parse(input: address); final feePolicy = feesIncluded ? FeePolicy.feesIncluded : FeePolicy.feesExcluded; if (inputType is InputType_Bolt11Invoice) { final request = PrepareSendPaymentRequest( - paymentRequest: inputType.field0.invoice.bolt11, amount: amountSats, feePolicy: feePolicy); + paymentRequest: inputType.field0.invoice.bolt11, + amount: amountSats, + feePolicy: feePolicy); final prepareResponse = await sdk.prepareSendPayment(request: request); final paymentMethod = prepareResponse.paymentMethod; @@ -392,14 +392,16 @@ class LightningWallet { return _getElectrumTransactionInfoFromPayment(response.payment); } - Future refundDeposit(String txId, int vout, String destinationAddress, - BigInt feeRate) async { - final response = await sdk.refundDeposit(request: RefundDepositRequest( - txid: txId, - vout: vout, - destinationAddress: destinationAddress, - fee: Fee.rate(satPerVbyte: feeRate), - ),); + Future refundDeposit( + String txId, int vout, String destinationAddress, BigInt feeRate) async { + final response = await sdk.refundDeposit( + request: RefundDepositRequest( + txid: txId, + vout: vout, + destinationAddress: destinationAddress, + fee: Fee.rate(satPerVbyte: feeRate), + ), + ); return response.txHex; } diff --git a/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart b/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart index d2bcabb1fa..237a6b824d 100644 --- a/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart +++ b/cw_bitcoin/lib/lightning/pending_lightning_transaction.dart @@ -10,10 +10,9 @@ class PendingLightningTransaction with PendingTransaction { required this.commitOverride, }); - final bool isSendAll; Future Function() commitOverride; - final List _listeners =[]; + final List _listeners = []; @override String id; diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 2f9ae0b3c4..23959374c4 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -175,7 +175,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @override bool get hasRescan => true; - final String? scanSecretOverride; final String? spendPubkeyOverride; List get scanSecret => (scanSecretOverride != null && scanSecretOverride?.isNotEmpty == true) @@ -232,8 +231,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } @override - WalletKeysData get walletKeysData => - WalletKeysData(mnemonic: seed, xPub: xpub, passphrase: passphrase, scanSecret: scanSecretOverride, spendPubkey: spendPubkeyOverride); + WalletKeysData get walletKeysData => WalletKeysData( + mnemonic: seed, + xPub: xpub, + passphrase: passphrase, + scanSecret: scanSecretOverride, + spendPubkey: spendPubkeyOverride); static Future open({ required String name, @@ -1106,15 +1109,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { for (final utxo in transaction.utxos) { if (utxo.utxo.scriptType != SegwitAddresType.mweb) { inputs.add(utxo.utxo.toInput()); - txouts.add(TxOut(value: Int64(utxo.utxo.value.toInt()), - pkScript: utxo.ownerDetails.address.toScriptPubKey().toBytes())); + txouts.add(TxOut( + value: Int64(utxo.utxo.value.toInt()), + pkScript: utxo.ownerDetails.address.toScriptPubKey().toBytes())); } } var resp = await CwMweb.psbtCreate(PsbtCreateRequest( - rawTx: inputs.isEmpty ? null : BtcTransaction( - inputs: inputs, - outputs: isMweb ? [] : transaction.outputs, - ).toBytes(), + rawTx: inputs.isEmpty + ? null + : BtcTransaction( + inputs: inputs, + outputs: isMweb ? [] : transaction.outputs, + ).toBytes(), witnessUtxo: txouts, )); for (final utxo in transaction.utxos) { @@ -1127,17 +1133,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { )); } } - if (isMweb) for (final output in transaction.outputs) { - var address = addressFromOutputScript(output.scriptPubKey, LitecoinNetwork.mainnet); - if (output.scriptPubKey.getAddressType() == SegwitAddresType.mweb) { - address = SegwitBech32Encoder.encode("ltcmweb", 0, output.scriptPubKey.toBytes()); + if (isMweb) + for (final output in transaction.outputs) { + var address = addressFromOutputScript(output.scriptPubKey, LitecoinNetwork.mainnet); + if (output.scriptPubKey.getAddressType() == SegwitAddresType.mweb) { + address = SegwitBech32Encoder.encode("ltcmweb", 0, output.scriptPubKey.toBytes()); + } + resp = await CwMweb.psbtAddRecipient(PsbtAddRecipientRequest( + psbtB64: resp.psbtB64, + recipient: PsbtRecipient(address: address, value: Int64(output.amount.toInt())), + feeRatePerKb: Int64.parseInt(transaction.feeRate) * 1000, + )); } - resp = await CwMweb.psbtAddRecipient(PsbtAddRecipientRequest( - psbtB64: resp.psbtB64, - recipient: PsbtRecipient(address: address, value: Int64(output.amount.toInt())), - feeRatePerKb: Int64.parseInt(transaction.feeRate) * 1000, - )); - } return base64.decode(resp.psbtB64); } @@ -1203,7 +1210,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { for (final utxo in tx.utxos) { if (utxo.utxo.scriptType == SegwitAddresType.mweb) { hasMwebInput = true; - } else { // check if any of the inputs of this transaction are hog-ex: // this list is only non-mweb inputs: @@ -1256,9 +1262,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final utxo = unspentCoins .firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex); final key = generateECPrivate( - hd: utxo.bitcoinAddressRecord.isHidden - ? sideHd - : mainHd, + hd: utxo.bitcoinAddressRecord.isHidden ? sideHd : mainHd, index: utxo.bitcoinAddressRecord.index, network: network); final digest = tx2.getTransactionSegwitDigit( @@ -1284,8 +1288,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } } - void addTransactionListener(PendingBitcoinTransaction tx, - List inputAddresses, bool isPegIn, bool isPegOut) { + void addTransactionListener( + PendingBitcoinTransaction tx, List inputAddresses, bool isPegIn, bool isPegOut) { tx.addListener((transaction) async { final addresses = {}; transaction.inputAddresses?.addAll(inputAddresses); @@ -1575,7 +1579,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { rawTx: rawTx, ownerDetails: utxo.ownerDetails, ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, - ownerMasterFingerprint:masterFingerprint, + ownerMasterFingerprint: masterFingerprint, ownerPublicKey: publicKeyAndDerivationPath.publicKey, )); } diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index a4a4b7590f..fe85cd7823 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -59,9 +59,11 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with ? hex.decode(scanSecretOverride!) : mwebHd?.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw ?? List.filled(32, 0); - List get spendPubkey => (spendPubkeyOverride != null && spendPubkeyOverride?.isNotEmpty == true) - ? hex.decode(spendPubkeyOverride!) - : mwebHd?.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed ?? List.filled(32, 0); + List get spendPubkey => + (spendPubkeyOverride != null && spendPubkeyOverride?.isNotEmpty == true) + ? hex.decode(spendPubkeyOverride!) + : mwebHd?.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed ?? + List.filled(32, 0); @override Future init() async { @@ -81,7 +83,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with return null; } if ((scanSecret.length < 1 || scanSecret.reduce((a, b) => a + b) == 0) && - (spendPubkey.length < 1 || spendPubkey.reduce((a, b) => a + b) == 0)) { + (spendPubkey.length < 1 || spendPubkey.reduce((a, b) => a + b) == 0)) { return null; } @@ -230,12 +232,16 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with // don't use mweb addresses for exchange refund address: final current = getFreshAddress(); - final bool isMweb = receiveAddresses - .any((e) => e.address == current && e.type == SegwitAddresType.mweb); + final bool isMweb = + receiveAddresses.any((e) => e.address == current && e.type == SegwitAddresType.mweb); if (isMweb) { final segwit = receiveAddresses - .where((e) => e.type == SegwitAddresType.p2wpkh && !e.isUsed && !e.isHidden && !hiddenAddresses.contains(e.address)) + .where((e) => + e.type == SegwitAddresType.p2wpkh && + !e.isUsed && + !e.isHidden && + !hiddenAddresses.contains(e.address)) .map((e) => e.address) .toList(); diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart index 0156ee6724..09ab924d1f 100644 --- a/cw_bitcoin/lib/litecoin_wallet_service.dart +++ b/cw_bitcoin/lib/litecoin_wallet_service.dart @@ -48,7 +48,8 @@ class LitecoinWalletService extends WalletService< password: credentials.password!, passphrase: credentials.passphrase, walletInfo: credentials.walletInfo!, - derivationInfo: credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), + derivationInfo: + credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); @@ -64,7 +65,6 @@ class LitecoinWalletService extends WalletService< @override Future openWallet(String name, String password) async { - final walletInfo = await WalletInfo.get(name, getType()); if (walletInfo == null) { throw Exception('Wallet not found'); @@ -125,8 +125,9 @@ class LitecoinWalletService extends WalletService< } } - final unspentCoinsToDelete = unspentCoinsInfoSource.values.where( - (unspentCoin) => unspentCoin.walletId == walletInfo.id).toList(); + final unspentCoinsToDelete = unspentCoinsInfoSource.values + .where((unspentCoin) => unspentCoin.walletId == walletInfo.id) + .toList(); final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList(); @@ -150,8 +151,7 @@ class LitecoinWalletService extends WalletService< final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; credentials.walletInfo?.network = network.value; final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); - derivationInfo.derivationPath = - credentials.hwAccountData.derivationPath; + derivationInfo.derivationPath = credentials.hwAccountData.derivationPath; await derivationInfo.save(); credentials.walletInfo!.save(); @@ -170,7 +170,7 @@ class LitecoinWalletService extends WalletService< @override Future restoreFromKeys(LitecoinWalletFromKeysCredentials credentials, - {bool? isTestnet}) async { + {bool? isTestnet}) async { final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet; credentials.walletInfo?.network = network.value; @@ -202,7 +202,8 @@ class LitecoinWalletService extends WalletService< passphrase: credentials.passphrase, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo!, - derivationInfo: credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), + derivationInfo: + credentials.derivationInfo ?? (await credentials.walletInfo!.getDerivationInfo()), unspentCoinsInfo: unspentCoinsInfoSource, encryptionFileUtils: encryptionFileUtilsFor(isDirect), ); diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart index 4fa2e2a63a..efa120f997 100644 --- a/cw_bitcoin/lib/payjoin/manager.dart +++ b/cw_bitcoin/lib/payjoin/manager.dart @@ -108,16 +108,14 @@ class PayjoinManager { Future initSender( String pjUriString, String originalPsbt, int networkFeesSatPerVb) async { try { - final pjUri = - (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); + final pjUri = (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250); final senderBuilder = await SenderBuilder.fromPsbtAndUri( psbtBase64: originalPsbt, pjUri: pjUri, ); final persister = PayjoinSenderPersister.impl(); - final newSender = - await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); + final newSender = await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); final senderToken = await newSender.persist(persister: persister); return Sender.load(token: senderToken, persister: persister); @@ -133,8 +131,7 @@ class PayjoinManager { bool isTestnet = false, }) async { final pjUri = Uri.parse(pjUrl).queryParameters['pj']!; - await _payjoinStorage.insertSenderSession( - sender, pjUri, _wallet.id, amount); + await _payjoinStorage.insertSenderSession(sender, pjUri, _wallet.id, amount); return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri); } @@ -207,8 +204,7 @@ class PayjoinManager { return completer.future; } - Future getUnusedReceiver(String address, - [bool isTestnet = false]) async { + Future getUnusedReceiver(String address, [bool isTestnet = false]) async { final session = _payjoinStorage.getUnusedActiveReceiverSession(_wallet.id); if (session != null) { @@ -220,7 +216,8 @@ class PayjoinManager { return initReceiver(address); } - Future initReceiver(String address, [bool isTestnet = false, int retryCount = 0]) async { + Future initReceiver(String address, + [bool isTestnet = false, int retryCount = 0]) async { if (retryCount > 0) writePayjoinLog("Retrying initReceiver ${retryCount + 1} attempt"); try { @@ -245,7 +242,6 @@ class PayjoinManager { } catch (e) { writePayjoinLog(e.toString()); if (e.toString().contains("error sending request for url") && retryCount < 5) { - return initReceiver(address, isTestnet, ++retryCount); } else { rethrow; @@ -273,13 +269,11 @@ class PayjoinManager { rawAmount = getOutputAmountFromTx(tx, _wallet); break; case PayjoinReceiverRequestTypes.checkIsOwned: - (_wallet.walletAddresses as BitcoinWalletAddresses) - .newPayjoinReceiver(); + (_wallet.walletAddresses as BitcoinWalletAddresses).newPayjoinReceiver(); _payjoinStorage.markReceiverSessionInProgress(receiver.id()); final inputScript = message['input_script'] as Uint8List; - final isOwned = - _wallet.isMine(Script.fromRaw(byteData: inputScript)); + final isOwned = _wallet.isMine(Script.fromRaw(byteData: inputScript)); mainToIsolateSendPort?.send({ 'requestId': message['requestId'], 'result': isOwned, @@ -288,8 +282,7 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.checkIsReceiverOutput: final outputScript = message['output_script'] as Uint8List; - final isReceiverOutput = - _wallet.isMine(Script.fromRaw(byteData: outputScript)); + final isReceiverOutput = _wallet.isMine(Script.fromRaw(byteData: outputScript)); mainToIsolateSendPort?.send({ 'requestId': message['requestId'], 'result': isReceiverOutput, @@ -310,7 +303,8 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.processPsbt: final psbt = message['psbt'] as String; - writePayjoinLog("Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.processPsbt: $psbt"); + writePayjoinLog( + "Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.processPsbt: $psbt"); final signedPsbt = await _wallet.signPsbt(psbt, utxos); mainToIsolateSendPort?.send({ @@ -322,7 +316,8 @@ class PayjoinManager { case PayjoinReceiverRequestTypes.proposalSent: _cleanupSession(receiver.id()); final psbt = message['psbt'] as String; - writePayjoinLog("Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.proposalSent: $psbt"); + writePayjoinLog( + "Receiver(${receiver.id()}) PayjoinReceiverRequestTypes.proposalSent: $psbt"); await _payjoinStorage.markReceiverSessionComplete( receiver.id(), getTxIdFromPsbtV0(psbt), rawAmount); diff --git a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart index 641399504c..f1980e39e8 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart @@ -47,8 +47,7 @@ class PayjoinReceiverWorker { try { final receiver = Receiver.fromJson(json: receiverJson); - final uncheckedProposal = - await worker.receiveUncheckedProposal(receiver); + final uncheckedProposal = await worker.receiveUncheckedProposal(receiver); final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast(); sendPort.send({ @@ -112,8 +111,7 @@ class PayjoinReceiverWorker { final httpRequest = await client.post(url, headers: {'Content-Type': request.contentType}, body: request.body); - final proposal = await session.processRes( - body: httpRequest.bodyBytes, ctx: extractReq.$2); + final proposal = await session.processRes(body: httpRequest.bodyBytes, ctx: extractReq.$2); if (proposal != null) return proposal; sleep(Duration(seconds: 2)); } @@ -140,8 +138,7 @@ class PayjoinReceiverWorker { return await finalProposal.psbt(); } - Future processPayjoinProposal( - UncheckedProposal proposal) async { + Future processPayjoinProposal(UncheckedProposal proposal) async { await proposal.extractTxToScheduleBroadcast(); // TODO Handle this. send to the main port on a timer? @@ -174,20 +171,17 @@ class PayjoinReceiverWorker { ); final pj5 = await pj4.commitOutputs(); - final listUnspent = - await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); + final listUnspent = await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); final unspent = listUnspent as List; if (unspent.isEmpty) throw RecoverableError('No unspent outputs available'); - final candidateInputs = - await Future.wait(unspent.map(_inputPairFromUtxo)); + final candidateInputs = await Future.wait(unspent.map(_inputPairFromUtxo)); // Prefer a UTXO that avoids the Unnecessary Input Heuristic (UIH2); // fall back to the first candidate if none preserves privacy. InputPair selectedUtxo = candidateInputs.first; try { - selectedUtxo = - await pj5.tryPreservingPrivacy(candidateInputs: candidateInputs); + selectedUtxo = await pj5.tryPreservingPrivacy(candidateInputs: candidateInputs); } catch (_) {} final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]); @@ -196,8 +190,8 @@ class PayjoinReceiverWorker { // Finalize proposal final payjoinProposal = await pj7.finalizeProposal( processPsbt: (String psbt) async { - final result = await _sendRequest( - PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); + final result = + await _sendRequest(PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt}); return result as String; }, // TODO set maxFeeRateSatPerVb @@ -213,15 +207,12 @@ class PayjoinReceiverWorker { Future _inputPairFromUtxo(UtxoWithPrivateKey utxo) async { final txout = TxOut( value: utxo.utxo.value, - scriptPubkey: Uint8List.fromList( - utxo.ownerDetails.address.toScriptPubKey().toBytes()), + scriptPubkey: Uint8List.fromList(utxo.ownerDetails.address.toScriptPubKey().toBytes()), ); - final psbtin = - PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); + final psbtin = PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null); - final previousOutput = - OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); + final previousOutput = OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout); final txin = TxIn( previousOutput: previousOutput, diff --git a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart index 75f58a4e36..0d46d1f62a 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart @@ -46,11 +46,11 @@ class PayjoinSenderWorker { sendPort.send(e); } } + final client = ProxyWrapper().getHttpIOClient(); /// Run a payjoin sender (V2 protocol first, fallback to V1). Future runSender(Sender sender) async { - try { return await _runSenderV2(sender); } catch (e) { @@ -70,13 +70,11 @@ class PayjoinSenderWorker { Future _runSenderV2(Sender sender) async { try { final postRequest = await sender.extractV2( - ohttpProxyUrl: - await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()), + ohttpProxyUrl: await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()), ); final postResult = await _postRequest(postRequest.$1); - final getContext = - await postRequest.$2.processResponse(response: postResult); + final getContext = await postRequest.$2.processResponse(response: postResult); sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted, "pj": pjUrl}); diff --git a/cw_bitcoin/lib/payjoin/storage.dart b/cw_bitcoin/lib/payjoin/storage.dart index 5fb9d57161..e4132ebfa9 100644 --- a/cw_bitcoin/lib/payjoin/storage.dart +++ b/cw_bitcoin/lib/payjoin/storage.dart @@ -23,16 +23,14 @@ class PayjoinStorage { ), ); - PayjoinSession? getUnusedActiveReceiverSession(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - session.status == PayjoinSessionStatus.created.name && - !session.isSenderSession) - .firstOrNull; - - Future markReceiverSessionComplete( - String sessionId, String txId, String amount) async { + PayjoinSession? getUnusedActiveReceiverSession(String walletId) => _payjoinSessionSources.values + .where((session) => + session.walletId == walletId && + session.status == PayjoinSessionStatus.created.name && + !session.isSenderSession) + .firstOrNull; + + Future markReceiverSessionComplete(String sessionId, String txId, String amount) async { final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; session.status = PayjoinSessionStatus.success.name; @@ -41,8 +39,7 @@ class PayjoinStorage { await session.save(); } - Future markReceiverSessionUnrecoverable( - String sessionId, String reason) async { + Future markReceiverSessionUnrecoverable(String sessionId, String reason) async { final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; session.status = PayjoinSessionStatus.unrecoverable.name; @@ -92,13 +89,10 @@ class PayjoinStorage { await session.save(); } - List readAllOpenSessions(String walletId) => - _payjoinSessionSources.values - .where((session) => - session.walletId == walletId && - ![ - PayjoinSessionStatus.success.name, - PayjoinSessionStatus.unrecoverable.name - ].contains(session.status)) - .toList(); + List readAllOpenSessions(String walletId) => _payjoinSessionSources.values + .where((session) => + session.walletId == walletId && + ![PayjoinSessionStatus.success.name, PayjoinSessionStatus.unrecoverable.name] + .contains(session.status)) + .toList(); } diff --git a/cw_bitcoin/lib/psbt/signer.dart b/cw_bitcoin/lib/psbt/signer.dart index c46517e665..fbd00b8427 100644 --- a/cw_bitcoin/lib/psbt/signer.dart +++ b/cw_bitcoin/lib/psbt/signer.dart @@ -40,8 +40,7 @@ extension PsbtSigner on PsbtV2 { return tx.buffer(); } - Future signWithUTXO( - List utxos, UTXOSignerCallBack signer, + Future signWithUTXO(List utxos, UTXOSignerCallBack signer, [UTXOGetterCallBack? getTaprootPair]) async { final raw = BytesUtils.toHexString(extractUnsignedTX(getSegwit: false)); final tx = BtcTransaction.fromRaw(raw); @@ -53,8 +52,8 @@ extension PsbtSigner on PsbtV2 { if (utxos.any((e) => e.utxo.isP2tr())) { for (final input in tx.inputs) { - final utxo = utxos.firstWhereOrNull( - (u) => u.utxo.txHash == input.txId && u.utxo.vout == input.txIndex); + final utxo = utxos + .firstWhereOrNull((u) => u.utxo.txHash == input.txId && u.utxo.vout == input.txIndex); if (utxo == null) { final trPair = await getTaprootPair!.call(input.txId, input.txIndex); @@ -81,8 +80,8 @@ extension PsbtSigner on PsbtV2 { : BitcoinOpCodeConst.SIGHASH_ALL; /// We generate transaction digest for current input - final digest = _generateTransactionDigest( - script, i, utxo.utxo, tx, taprootAmounts, taprootScripts); + final digest = + _generateTransactionDigest(script, i, utxo.utxo, tx, taprootAmounts, taprootScripts); /// now we need sign the transaction digest final sig = signer(digest, utxo, utxo.privateKey, sighash); @@ -90,21 +89,14 @@ extension PsbtSigner on PsbtV2 { if (utxo.utxo.isP2tr()) { setInputTapKeySig(i, Uint8List.fromList(BytesUtils.fromHexString(sig))); } else { - setInputPartialSig( - i, - Uint8List.fromList(BytesUtils.fromHexString(utxo.public().toHex())), + setInputPartialSig(i, Uint8List.fromList(BytesUtils.fromHexString(utxo.public().toHex())), Uint8List.fromList(BytesUtils.fromHexString(sig))); } } } - List _generateTransactionDigest( - Script scriptPubKeys, - int input, - BitcoinUtxo utxo, - BtcTransaction transaction, - List taprootAmounts, - List with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin output.fiatAmount, (String amount) { @@ -991,8 +992,8 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin - NewListSections( - sections: { - "": [ - if (FeatureFlag.isInAppTorEnabled) - ListItemToggle( - keyValue: "enable_builtin_tor", - label: S.of(context).enable_builtin_tor, - value: _connectionSyncViewModel.builtinTor, - onChanged: (val) { - _connectionSyncViewModel.setBuiltinTor(val, context); - }), - ListItemToggle( - keyValue: "disable_automatic_exchange_status_updates", - label: S.of(context).disable_automatic_exchange_status_updates, - value: _connectionSyncViewModel.disableAutomaticExchangeStatusUpdates, - onChanged: (val) { - _connectionSyncViewModel.setDisableAutomaticExchangeStatusUpdates(val); - }), - if (_connectionSyncViewModel.canUseBlinkProtection) - ListItemToggle( - keyValue: "can_use_blink_protection", - label: S.of(context).use_blink_protection, - value: _connectionSyncViewModel.useBlinkProtection, - onChanged: (val) { - _connectionSyncViewModel.setUseBlinkProtection(val); - }), - if (_connectionSyncViewModel.canUseEtherscan) - ListItemToggle( - keyValue: "can_use_etherscan", - label: S.of(context).etherscan_history, - value: _connectionSyncViewModel.useEtherscan, - onChanged: (val) { - _connectionSyncViewModel.setUseEtherscan(val); - }), - if (_connectionSyncViewModel.canUsePolygonScan) - ListItemToggle( - keyValue: "can_use_polygonscan", - label: S.of(context).polygonscan_history, - value: _connectionSyncViewModel.usePolygonScan, - onChanged: (val) { - _connectionSyncViewModel.setUsePolygonScan(val); - }), - if (_connectionSyncViewModel.canUseBaseScan) - ListItemToggle( - keyValue: "can_use_basescan", - label: S.of(context).basescan_history, - value: _connectionSyncViewModel.canUseBaseScan, - onChanged: (val) { - _connectionSyncViewModel.setUseBaseScan(val); - }), - if (_connectionSyncViewModel.canUseArbiScan) - ListItemToggle( - keyValue: "can_use_arbiscan", - label: S.of(context).arbiscan_history, - value: _connectionSyncViewModel.useArbiScan, - onChanged: (val) { - _connectionSyncViewModel.setUseArbiScan(val); - }), - if (_connectionSyncViewModel.canUseBscScan) - ListItemToggle( - keyValue: "can_use_bscscan", - label: S.of(context).bscscan_history, - value: _connectionSyncViewModel.useBscScan, - onChanged: (val) { - _connectionSyncViewModel.setUseBscScan(val); - }), - if (_connectionSyncViewModel.canUseTronGrid) - ListItemToggle( - keyValue: "can_use_trongrid", - label: S.of(context).trongrid_history, - value: _connectionSyncViewModel.useTronGrid, - onChanged: (val) { - _connectionSyncViewModel.setUseTronGrid(val); - }), - if (_connectionSyncViewModel.canUseMempoolFeeAPI) - ListItemToggle( - keyValue: "enable_mempool_api", - label: S.of(context).enable_mempool_api, - value: _connectionSyncViewModel.useMempoolFeeAPI, - onChanged: (bool isEnabled) async { - if (!isEnabled) { - final bool confirmation = await showPopUp( + builder: (context) => NewListSections(sections: { + "": [ + if (FeatureFlag.isInAppTorEnabled) + ListItemToggle( + keyValue: "enable_builtin_tor", + label: S.of(context).enable_builtin_tor, + value: _connectionSyncViewModel.builtinTor, + onChanged: (val) { + _connectionSyncViewModel.setBuiltinTor(val, context); + }), + ListItemToggle( + keyValue: "disable_automatic_exchange_status_updates", + label: S.of(context).disable_automatic_exchange_status_updates, + value: _connectionSyncViewModel.disableAutomaticExchangeStatusUpdates, + onChanged: (val) { + _connectionSyncViewModel.setDisableAutomaticExchangeStatusUpdates(val); + }), + if (_connectionSyncViewModel.canUseBlinkProtection) + ListItemToggle( + keyValue: "can_use_blink_protection", + label: S.of(context).use_blink_protection, + value: _connectionSyncViewModel.useBlinkProtection, + onChanged: (val) { + _connectionSyncViewModel.setUseBlinkProtection(val); + }), + if (_connectionSyncViewModel.canUseEtherscan) + ListItemToggle( + keyValue: "can_use_etherscan", + label: S.of(context).etherscan_history, + value: _connectionSyncViewModel.useEtherscan, + onChanged: (val) { + _connectionSyncViewModel.setUseEtherscan(val); + }), + if (_connectionSyncViewModel.canUsePolygonScan) + ListItemToggle( + keyValue: "can_use_polygonscan", + label: S.of(context).polygonscan_history, + value: _connectionSyncViewModel.usePolygonScan, + onChanged: (val) { + _connectionSyncViewModel.setUsePolygonScan(val); + }), + if (_connectionSyncViewModel.canUseBaseScan) + ListItemToggle( + keyValue: "can_use_basescan", + label: S.of(context).basescan_history, + value: _connectionSyncViewModel.canUseBaseScan, + onChanged: (val) { + _connectionSyncViewModel.setUseBaseScan(val); + }), + if (_connectionSyncViewModel.canUseArbiScan) + ListItemToggle( + keyValue: "can_use_arbiscan", + label: S.of(context).arbiscan_history, + value: _connectionSyncViewModel.useArbiScan, + onChanged: (val) { + _connectionSyncViewModel.setUseArbiScan(val); + }), + if (_connectionSyncViewModel.canUseBscScan) + ListItemToggle( + keyValue: "can_use_bscscan", + label: S.of(context).bscscan_history, + value: _connectionSyncViewModel.useBscScan, + onChanged: (val) { + _connectionSyncViewModel.setUseBscScan(val); + }), + if (_connectionSyncViewModel.canUseTronGrid) + ListItemToggle( + keyValue: "can_use_trongrid", + label: S.of(context).trongrid_history, + value: _connectionSyncViewModel.useTronGrid, + onChanged: (val) { + _connectionSyncViewModel.setUseTronGrid(val); + }), + if (_connectionSyncViewModel.canUseMempoolFeeAPI) + ListItemToggle( + keyValue: "enable_mempool_api", + label: S.of(context).enable_mempool_api, + value: _connectionSyncViewModel.useMempoolFeeAPI, + onChanged: (bool isEnabled) async { + if (!isEnabled) { + final bool confirmation = await showPopUp( context: context, builder: (BuildContext context) { return AlertWithTwoActions( @@ -133,91 +131,88 @@ class ConnectionSyncPage extends BasePage { alertContent: S.of(context).disable_fee_api_warning, rightButtonText: S.of(context).confirm, leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.of(context).pop(true), - actionLeftButton: () => Navigator.of(context).pop(false)); + actionRightButton: () => + Navigator.of(context).pop(true), + actionLeftButton: () => + Navigator.of(context).pop(false)); }) ?? - false; - if (confirmation) { - _connectionSyncViewModel.setUseMempoolFeeAPI(isEnabled); - } - return; - } - + false; + if (confirmation) { _connectionSyncViewModel.setUseMempoolFeeAPI(isEnabled); - }), - if (Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) - ListItemRegularRow( - keyValue: "background_sync", - label: S.of(context).background_sync, - onTap: () => Navigator.of(context).pushNamed(Routes.backgroundSync) - ), - if (_connectionSyncViewModel.hasPowNodes) - ListItemRegularRow( - keyValue: "manage_pow_nodes", - label: S.of(context).manage_pow_nodes, - onTap: () => Navigator.of(context).pushNamed(Routes.managePowNodes), - ), - ListItemSelector( - keyValue: "fiat_api", - label: S.of(context).fiat_api, - options: [_connectionSyncViewModel.fiatApiMode.title], - onTap: () async { - final items = FiatApiMode.all; + } + return; + } + + _connectionSyncViewModel.setUseMempoolFeeAPI(isEnabled); + }), + if (Platform.isAndroid && FeatureFlag.isBackgroundSyncEnabled) + ListItemRegularRow( + keyValue: "background_sync", + label: S.of(context).background_sync, + onTap: () => Navigator.of(context).pushNamed(Routes.backgroundSync)), + if (_connectionSyncViewModel.hasPowNodes) + ListItemRegularRow( + keyValue: "manage_pow_nodes", + label: S.of(context).manage_pow_nodes, + onTap: () => Navigator.of(context).pushNamed(Routes.managePowNodes), + ), + ListItemSelector( + keyValue: "fiat_api", + label: S.of(context).fiat_api, + options: [_connectionSyncViewModel.fiatApiMode.title], + onTap: () async { + final items = FiatApiMode.all; - final selectedAtIndex = + final selectedAtIndex = items.indexOf(_connectionSyncViewModel.fiatApiMode); - await showPopUp( - context: context, - builder: (_) => Picker( - items: items, - selectedAtIndex: selectedAtIndex, - mainAxisAlignment: MainAxisAlignment.start, - onItemSelected: (FiatApiMode fiatApiMode) { - _connectionSyncViewModel.setFiatMode(fiatApiMode); - }, - isSeparated: false, - ), - ); - }), - ListItemSelector( - keyValue: "swap", - label: S.of(context).swap, - options: [_connectionSyncViewModel.exchangeStatus.title], - onTap: () async { - final items = ExchangeApiMode.all; + await showPopUp( + context: context, + builder: (_) => Picker( + items: items, + selectedAtIndex: selectedAtIndex, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: (FiatApiMode fiatApiMode) { + _connectionSyncViewModel.setFiatMode(fiatApiMode); + }, + isSeparated: false, + ), + ); + }), + ListItemSelector( + keyValue: "swap", + label: S.of(context).swap, + options: [_connectionSyncViewModel.exchangeStatus.title], + onTap: () async { + final items = ExchangeApiMode.all; - final selectedAtIndex = + final selectedAtIndex = items.indexOf(_connectionSyncViewModel.exchangeStatus); - await showPopUp( - context: context, - builder: (_) => Picker( - items: items, - selectedAtIndex: selectedAtIndex, - mainAxisAlignment: MainAxisAlignment.start, - onItemSelected: (ExchangeApiMode mode) { - _connectionSyncViewModel.setExchangeApiMode(mode); - }, - isSeparated: false, - ), - ); - }), - ListItemRegularRow( - keyValue: "domain_lookups", - label: S.of(context).domain_looks_up, - onTap: () => Navigator.of(context).pushNamed(Routes.domainLookupsPage) - ), - if(_connectionSyncViewModel.hasRescan) - ListItemRegularRow( + await showPopUp( + context: context, + builder: (_) => Picker( + items: items, + selectedAtIndex: selectedAtIndex, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: (ExchangeApiMode mode) { + _connectionSyncViewModel.setExchangeApiMode(mode); + }, + isSeparated: false, + ), + ); + }), + ListItemRegularRow( + keyValue: "domain_lookups", + label: S.of(context).domain_looks_up, + onTap: () => Navigator.of(context).pushNamed(Routes.domainLookupsPage)), + if (_connectionSyncViewModel.hasRescan) + ListItemRegularRow( keyValue: "rescan", label: S.of(context).rescan, - onTap: ()=>Navigator.of(context).pushNamed(Routes.rescan) - ) - ], - } - ) - ), + onTap: () => Navigator.of(context).pushNamed(Routes.rescan)) + ], + })), ], ), ); diff --git a/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart b/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart index 09c3bb0b9e..a792560a1c 100644 --- a/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart +++ b/lib/src/screens/settings/desktop_settings/desktop_settings_page.dart @@ -61,9 +61,9 @@ class _DesktopSettingsPageState extends State { } if ((!widget.dashboardViewModel.isMoneroViewOnly && - item.name(context) == S.of(context).export_outputs) || - (!widget.dashboardViewModel.hasMweb && - item.name(context) == S.of(context).litecoin_mweb_settings)) { + item.name(context) == S.of(context).export_outputs) || + (!widget.dashboardViewModel.hasMweb && + item.name(context) == S.of(context).litecoin_mweb_settings)) { return Container(); } @@ -103,11 +103,9 @@ class _DesktopSettingsPageState extends State { key: _settingsNavigatorKey, initialRoute: Routes.empty_no_route, onGenerateRoute: (settings) => Router.createRoute(settings), - onGenerateInitialRoutes: - (NavigatorState navigator, String initialRouteName) { + onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) { return [ - navigator - .widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! + navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! ]; }, ), diff --git a/lib/src/screens/settings/display_settings_page.dart b/lib/src/screens/settings/display_settings_page.dart index be46befc3b..5a04b86e29 100644 --- a/lib/src/screens/settings/display_settings_page.dart +++ b/lib/src/screens/settings/display_settings_page.dart @@ -27,7 +27,6 @@ import 'package:image_picker/image_picker.dart'; class DisplaySettingsPage extends StatelessWidget { DisplaySettingsPage(this._displaySettingsViewModel); - final DisplaySettingsViewModel _displaySettingsViewModel; @override @@ -38,198 +37,197 @@ class DisplaySettingsPage extends StatelessWidget { leadingIcon: Icon(Icons.arrow_back_ios_new), onLeadingPressed: () => Navigator.of(context).pop(), ), - content: Column( - spacing: 16, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (responsiveLayoutUtil.shouldRenderMobileUI && - DeviceInfo.instance.isMobile) ...[ - Padding( - padding: const EdgeInsets.only(left: 14, top: 14), - child: Text( - S.of(context).appearance, - style: Theme.of(context).textTheme.labelLarge?.copyWith( + content: Column( + spacing: 16, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (responsiveLayoutUtil.shouldRenderMobileUI && DeviceInfo.instance.isMobile) ...[ + Padding( + padding: const EdgeInsets.only(left: 14, top: 14), + child: Text( + S.of(context).appearance, + style: Theme.of(context).textTheme.labelLarge?.copyWith( height: 0.2, color: Theme.of(context).colorScheme.onSurfaceVariant, ), + ), + ), + Container( + decoration: ShapeDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHigh, + shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(18))), + child: Column( + children: [ + SettingsChoicesCell( + ChoicesListItem( + title: "", + items: ThemeMode.values, + selectedItem: _displaySettingsViewModel.themeMode, + onItemSelected: (ThemeMode themeMode) => + _displaySettingsViewModel.setThemeMode(themeMode), + displayItem: (ThemeMode themeMode) { + return themeMode.name[0].toUpperCase() + + themeMode.name.substring(1).toLowerCase(); + }, + ), + useGenericColor: false, + padding: EdgeInsets.all(14), ), - ), - Container( - decoration: ShapeDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - shape: RoundedSuperellipseBorder( - borderRadius: BorderRadius.circular(18))), - child: Column( - children: [ - SettingsChoicesCell( - ChoicesListItem( - title: "", - items: ThemeMode.values, - selectedItem: _displaySettingsViewModel.themeMode, - onItemSelected: (ThemeMode themeMode) => - _displaySettingsViewModel.setThemeMode(themeMode), - displayItem: (ThemeMode themeMode) { - return themeMode.name[0].toUpperCase() + - themeMode.name.substring(1).toLowerCase(); - }, + Container( + decoration: ShapeDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(18))), + child: Column( + children: [ + Semantics( + label: S.of(context).color_theme, + child: SettingsThemeChoicesCell(_displaySettingsViewModel), ), - useGenericColor: false, - padding: EdgeInsets.all(14), - ), - Container( - decoration: ShapeDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, - shape: RoundedSuperellipseBorder( - borderRadius: BorderRadius.circular(18))), - child: Column( - children: [ - Semantics( - label: S.of(context).color_theme, - child: SettingsThemeChoicesCell(_displaySettingsViewModel), - ), - ], - ), - ), - ], + ], + ), ), - ), - ], - Observer( - builder: (_) => NewListSections( - sections: { - "": [ - ListItemToggle( - keyValue: "apps", - label: S.of(context).apps, - value: _displaySettingsViewModel.shouldShowMarketPlaceInDashboard, - onChanged: (val) { - _displaySettingsViewModel.setShouldShowMarketPlaceInDashbaord(val); - }), - ListItemToggle( - keyValue: "display_settings_show_address_book_popup", - label: S.of(context).show_address_book_popup, - value: _displaySettingsViewModel.showAddressBookPopup, - onChanged: (val) { - _displaySettingsViewModel.setShowAddressBookPopup(val); - }), - ListItemToggle( - keyValue: "display_settings_disable_buy_button", - label: S.of(context).disable_buy, - value: _displaySettingsViewModel.disableTradeOption, - onChanged: (val) { - _displaySettingsViewModel.setDisableTradeOption(val); - }), - if (_displaySettingsViewModel.showZcashCardSetting) - ListItemToggle( - keyValue: "display_settings_show_zcashcard", - label: S.of(context).show_zcash_card, - value: _displaySettingsViewModel.showZcashCard, - onChanged: (val) { - _displaySettingsViewModel.setShowZcashCard(val); - }), - ListItemSelector( - keyValue: "display_settings_sync_status_display", - label: S.of(context).sync_status_display_mode, - options: [_displaySettingsViewModel.syncStatusDisplayMode.title], - onTap: () async { - final items = SyncStatusDisplayMode.values.toList(); + ], + ), + ), + ], + Observer( + builder: (_) => NewListSections( + sections: { + "": [ + ListItemToggle( + keyValue: "apps", + label: S.of(context).apps, + value: _displaySettingsViewModel.shouldShowMarketPlaceInDashboard, + onChanged: (val) { + _displaySettingsViewModel.setShouldShowMarketPlaceInDashbaord(val); + }), + ListItemToggle( + keyValue: "display_settings_show_address_book_popup", + label: S.of(context).show_address_book_popup, + value: _displaySettingsViewModel.showAddressBookPopup, + onChanged: (val) { + _displaySettingsViewModel.setShowAddressBookPopup(val); + }), + ListItemToggle( + keyValue: "display_settings_disable_buy_button", + label: S.of(context).disable_buy, + value: _displaySettingsViewModel.disableTradeOption, + onChanged: (val) { + _displaySettingsViewModel.setDisableTradeOption(val); + }), + if (_displaySettingsViewModel.showZcashCardSetting) + ListItemToggle( + keyValue: "display_settings_show_zcashcard", + label: S.of(context).show_zcash_card, + value: _displaySettingsViewModel.showZcashCard, + onChanged: (val) { + _displaySettingsViewModel.setShowZcashCard(val); + }), + ListItemSelector( + keyValue: "display_settings_sync_status_display", + label: S.of(context).sync_status_display_mode, + options: [_displaySettingsViewModel.syncStatusDisplayMode.title], + onTap: () async { + final items = SyncStatusDisplayMode.values.toList(); - final selectedAtIndex = + final selectedAtIndex = items.indexOf(_displaySettingsViewModel.syncStatusDisplayMode); - await showPopUp( - context: context, - builder: (_) => Picker( - items: items, - selectedAtIndex: selectedAtIndex, - mainAxisAlignment: MainAxisAlignment.start, - onItemSelected: (SyncStatusDisplayMode mode) { - _displaySettingsViewModel.setSyncStatusDisplayMode(mode); - }, - displayItem: (SyncStatusDisplayMode mode) => mode.title, - isSeparated: false, - ), - ); - }), - if (_displaySettingsViewModel.showDisplayAmountsInSatoshiSetting) - ListItemRegularRow( - keyValue: "display_settings_bitcoin_amount_display", - label: S.of(context).bitcoin_amount_display, - trailingText: _displaySettingsViewModel.displayAmountsInSatoshi.title, - onTap: () async { - final items = BitcoinAmountDisplayMode.all; + await showPopUp( + context: context, + builder: (_) => Picker( + items: items, + selectedAtIndex: selectedAtIndex, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: (SyncStatusDisplayMode mode) { + _displaySettingsViewModel.setSyncStatusDisplayMode(mode); + }, + displayItem: (SyncStatusDisplayMode mode) => mode.title, + isSeparated: false, + ), + ); + }), + if (_displaySettingsViewModel.showDisplayAmountsInSatoshiSetting) + ListItemRegularRow( + keyValue: "display_settings_bitcoin_amount_display", + label: S.of(context).bitcoin_amount_display, + trailingText: _displaySettingsViewModel.displayAmountsInSatoshi.title, + onTap: () async { + final items = BitcoinAmountDisplayMode.all; - final selectedAtIndex = + final selectedAtIndex = items.indexOf(_displaySettingsViewModel.displayAmountsInSatoshi); - await showPopUp( - context: context, - builder: (_) => Picker( - items: items, - selectedAtIndex: selectedAtIndex, - mainAxisAlignment: MainAxisAlignment.start, - onItemSelected: _displaySettingsViewModel.setDisplayAmountsInSatoshi, - displayItem: (BitcoinAmountDisplayMode mode) => mode.title, - isSeparated: false, - ), - ); - }), - if (!_displaySettingsViewModel.disabledFiatApiMode) - ListItemSelector( - keyValue: "display_settings_fiat_currency", - label: S.of(context).settings_currency, - options: [_displaySettingsViewModel.fiatCurrency.title], - onTap: () => FiatCurrencyPickerSheet.show( - context: context, - selected: _displaySettingsViewModel.fiatCurrency, - onSelected: _displaySettingsViewModel.setFiatCurrency, - )), - ListItemSelector( - keyValue: "display_settings_language", - label: S.of(context).settings_change_language, - options: [LanguageService.list[_displaySettingsViewModel.languageCode] ?? ''], - onTap: () async { - final items = LanguageService.list.keys.toList(); + await showPopUp( + context: context, + builder: (_) => Picker( + items: items, + selectedAtIndex: selectedAtIndex, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: _displaySettingsViewModel.setDisplayAmountsInSatoshi, + displayItem: (BitcoinAmountDisplayMode mode) => mode.title, + isSeparated: false, + ), + ); + }), + if (!_displaySettingsViewModel.disabledFiatApiMode) + ListItemSelector( + keyValue: "display_settings_fiat_currency", + label: S.of(context).settings_currency, + options: [_displaySettingsViewModel.fiatCurrency.title], + onTap: () => FiatCurrencyPickerSheet.show( + context: context, + selected: _displaySettingsViewModel.fiatCurrency, + onSelected: _displaySettingsViewModel.setFiatCurrency, + )), + ListItemSelector( + keyValue: "display_settings_language", + label: S.of(context).settings_change_language, + options: [LanguageService.list[_displaySettingsViewModel.languageCode] ?? ''], + onTap: () async { + final items = LanguageService.list.keys.toList(); - final selectedAtIndex = + final selectedAtIndex = items.indexOf(_displaySettingsViewModel.languageCode); - await showPopUp( - context: context, - builder: (_) => Picker( - displayItem: (dynamic code) { - return LanguageService.list[code] ?? ''; - }, - items: items, - selectedAtIndex: selectedAtIndex, - mainAxisAlignment: MainAxisAlignment.start, - onItemSelected: _displaySettingsViewModel.onLanguageSelected, - images: LanguageService.list.keys - .map((e) => Image.asset( + await showPopUp( + context: context, + builder: (_) => Picker( + displayItem: (dynamic code) { + return LanguageService.list[code] ?? ''; + }, + items: items, + selectedAtIndex: selectedAtIndex, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: _displaySettingsViewModel.onLanguageSelected, + images: LanguageService.list.keys + .map((e) => Image.asset( "assets/images/flags/${LanguageService.localeCountryCode[e]}.png")) - .toList(), - hintText: S.of(context).search_language, - matchingCriteria: (String code, String searchText) { - return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false; - }, - isSeparated: true, - - ), - ); - }), - ], - }, - ), - ), - if (FeatureFlag.customBackgroundEnabled) - StandardListRow( - title: "Custom background", - isSelected: false, - onTap: (_) => _pickImage(context), - ), - ], + .toList(), + hintText: S.of(context).search_language, + matchingCriteria: (String code, String searchText) { + return LanguageService.list[code] + ?.toLowerCase() + .contains(searchText) ?? + false; + }, + isSeparated: true, + ), + ); + }), + ], + }, + ), ), - ); + if (FeatureFlag.customBackgroundEnabled) + StandardListRow( + title: "Custom background", + isSelected: false, + onTap: (_) => _pickImage(context), + ), + ], + ), + ); } // Function to pick an image from the gallery diff --git a/lib/src/screens/settings/domain_lookups_page.dart b/lib/src/screens/settings/domain_lookups_page.dart index e8528939e2..9a61a8e6fc 100644 --- a/lib/src/screens/settings/domain_lookups_page.dart +++ b/lib/src/screens/settings/domain_lookups_page.dart @@ -25,11 +25,13 @@ class DomainLookupsPage extends BasePage { .map( (source) => SettingsSwitcherCell( title: source.label, - leading: source.iconPath.isNotEmpty ? CakeImageWidget( - imageUrl: source.iconPath, - width: 24, - height: 24, - ) : SizedBox(width: 24, height: 24), + leading: source.iconPath.isNotEmpty + ? CakeImageWidget( + imageUrl: source.iconPath, + width: 24, + height: 24, + ) + : SizedBox(width: 24, height: 24), value: _connectionsSyncViewModel.lookupValue(source), onValueChange: (_, bool value) => _connectionsSyncViewModel.setLookupValue(source, value), diff --git a/lib/src/screens/settings/items/item_headers.dart b/lib/src/screens/settings/items/item_headers.dart index cc8a3b9aa3..eca0576f46 100644 --- a/lib/src/screens/settings/items/item_headers.dart +++ b/lib/src/screens/settings/items/item_headers.dart @@ -15,4 +15,4 @@ class ItemHeaders { static const termsAndConditions = 'Terms and conditions'; static const faq = 'FAQ'; static const version = 'Version'; -} \ No newline at end of file +} diff --git a/lib/src/screens/settings/manage_nodes_page.dart b/lib/src/screens/settings/manage_nodes_page.dart index 75c384374f..c750397e03 100644 --- a/lib/src/screens/settings/manage_nodes_page.dart +++ b/lib/src/screens/settings/manage_nodes_page.dart @@ -55,10 +55,10 @@ class _ManageNodesPageState extends State { ModernButton( size: 36, icon: Icon(Icons.add), - onPressed: ()async { + onPressed: () async { final res = await Navigator.of(context) - .pushNamed(widget.isPow ? Routes.newPowNode : Routes.newNode); - if(res != null && res is Node) { + .pushNamed(widget.isPow ? Routes.newPowNode : Routes.newNode); + if (res != null && res is Node) { widget.nodeListViewModel.nodes.add(res); } }) @@ -75,12 +75,11 @@ class _ManageNodesPageState extends State { // horizontal: 0, node: widget.nodeListViewModel.currentNode, speed: widget.nodeListViewModel.nodeSpeedFor(widget.nodeListViewModel.currentNode), - onEditComplete: (res)async{ - if(res != null && res is Node) { - widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id); + onEditComplete: (res) async { + if (res != null && res is Node) { + widget.nodeListViewModel.nodes.removeWhere((item) => item.id == res.id); widget.nodeListViewModel.nodes.add(res); } - }, onTap: () {}, isSelected: true, @@ -93,7 +92,7 @@ class _ManageNodesPageState extends State { color: Theme.of(context).colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(18)), child: ClipRRect( - borderRadius: BorderRadius.circular(18), + borderRadius: BorderRadius.circular(18), child: Observer( builder: (BuildContext context) { int itemsCount = widget.nodeListViewModel.nonCurrentNodes.length; @@ -119,11 +118,11 @@ class _ManageNodesPageState extends State { isPow: widget.isPow, speed: widget.nodeListViewModel.nodeSpeedFor(node), onEditComplete: (res) async { - if(res != null && res is Node) { - widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id); + if (res != null && res is Node) { + widget.nodeListViewModel.nodes + .removeWhere((item) => item.id == res.id); widget.nodeListViewModel.nodes.add(res); } - }, onTap: () async { await showPopUp( diff --git a/lib/src/screens/settings/mweb_logs_page.dart b/lib/src/screens/settings/mweb_logs_page.dart index 6fd6ead3e1..baa12a82ff 100644 --- a/lib/src/screens/settings/mweb_logs_page.dart +++ b/lib/src/screens/settings/mweb_logs_page.dart @@ -38,7 +38,8 @@ class MwebLogsPage extends BasePage { padding: EdgeInsets.all(16.0), child: Text( snapshot.data!, - style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontFamily: 'Monospace'), + style: + Theme.of(context).textTheme.bodyMedium!.copyWith(fontFamily: 'Monospace'), ), ), ); @@ -103,11 +104,10 @@ class MwebLogsPage extends BasePage { } Future _saveFile() async { - String? outputFile = await FilePicker.platform - .saveFile( - dialogTitle: 'Save Your File to desired location', - fileName: "debug.log", - lockParentWindow: true); + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Save Your File to desired location', + fileName: "debug.log", + lockParentWindow: true); if (outputFile == null) return; diff --git a/lib/src/screens/settings/mweb_node_page.dart b/lib/src/screens/settings/mweb_node_page.dart index 45b89ed9da..f5be67ee0e 100644 --- a/lib/src/screens/settings/mweb_node_page.dart +++ b/lib/src/screens/settings/mweb_node_page.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart'; import 'package:flutter/material.dart'; + class MwebNodePage extends StatefulWidget { const MwebNodePage(this.mwebSettingsViewModelBase, {super.key}); @@ -30,30 +31,28 @@ class _MwebNodePageState extends State { Widget build(BuildContext context) { return SafeArea( child: ModalPageWrapper( - topBar: ModalTopBar( + topBar: ModalTopBar( title: S.current.litecoin_mweb_settings, onLeadingPressed: Navigator.of(context).pop, leadingIcon: Icon(Icons.arrow_back_ios_new)), - content: Container( - child: NewListSections( - controllers: { - widget.mwebSettingsViewModelBase.mwebNodeUri: _nodeUriController, - }, - sections: { - 'main': [ - ListItemTextField( - keyValue: widget.mwebSettingsViewModelBase.mwebNodeUri, - label: S.current.node_address, - validator: NodePathValidator(), - ), - ] - }), - ), - bottomContent: LoadingPrimaryButton( - onPressed: () => save(context), - text: S.of(context).save, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, + content: Container( + child: NewListSections(controllers: { + widget.mwebSettingsViewModelBase.mwebNodeUri: _nodeUriController, + }, sections: { + 'main': [ + ListItemTextField( + keyValue: widget.mwebSettingsViewModelBase.mwebNodeUri, + label: S.current.node_address, + validator: NodePathValidator(), + ), + ] + }), + ), + bottomContent: LoadingPrimaryButton( + onPressed: () => save(context), + text: S.of(context).save, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, ), ), ); diff --git a/lib/src/screens/settings/mweb_settings.dart b/lib/src/screens/settings/mweb_settings.dart index ffe6184b79..5fef27436f 100644 --- a/lib/src/screens/settings/mweb_settings.dart +++ b/lib/src/screens/settings/mweb_settings.dart @@ -41,7 +41,7 @@ class MwebSettingsPage extends BasePage { title: S.current.litecoin_mweb_scanning, handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan), ), - SettingsCellWithArrow( + SettingsCellWithArrow( title: S.current.litecoin_mweb_logs, handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.mwebLogs), ), diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 9593f8b6b0..7a08d6d860 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -149,7 +149,8 @@ class OtherSettingsPage extends BasePage { Navigator.of(context).pushNamed(Routes.signPage); }), ], - if (_otherSettingsViewModel.walletType == WalletType.bitcoin) "btc_logging": [ + if (_otherSettingsViewModel.walletType == WalletType.bitcoin) + "btc_logging": [ ListItemRegularRow( keyValue: "export_lightning_logs", label: S.of(context).export_lightning_logs, @@ -158,72 +159,74 @@ class OtherSettingsPage extends BasePage { keyValue: "export_payjoin_logs", label: S.of(context).export_payjoin_logs, onTap: () => onExportPJLog(context)), - ], - "dev": FeatureFlag.hasDevOptions == false ? [] : [ - if (_otherSettingsViewModel.walletType == WalletType.monero) - ListItemRegularRow( - keyValue: "[dev] monero background sync", - label: "[dev] monero background sync", - onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync)), - if ([WalletType.monero, WalletType.wownero, WalletType.zano] - .contains(_otherSettingsViewModel.walletType)) - ListItemRegularRow( - keyValue: "[dev] xmr call profiler", - label: "[dev] xmr call profiler", - onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler)), - if ([WalletType.monero].contains(_otherSettingsViewModel.walletType)) - ListItemRegularRow( - keyValue: '[dev] xmr wallet cache debug', - label: '[dev] xmr wallet cache debug', - onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug)), - ListItemRegularRow( - keyValue: '[dev] shared preferences', - label: '[dev] shared preferences', - onTap: () => Navigator.of(context).pushNamed(Routes.devSharedPreferences)), - ListItemRegularRow( - keyValue: '[dev] secure storage preferences', - label: '[dev] secure storage preferences', - onTap: () => Navigator.of(context).pushNamed(Routes.devSecurePreferences)), - ListItemRegularRow( - keyValue: '[dev] background sync logs', - label: '[dev] background sync logs', - onTap: () => Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs)), - ListItemRegularRow( - keyValue: '[dev] socket health logs', - label: '[dev] socket health logs', - onTap: () => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs)), - ListItemRegularRow( - keyValue: '[dev] network requests logs', - label: '[dev] network requests logs', - onTap: () => Navigator.of(context).pushNamed(Routes.devNetworkRequests)), - ListItemRegularRow( - keyValue: '[dev] exchange provider logs', - label: '[dev] exchange provider logs', - onTap: () => Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs)), - ListItemRegularRow( - keyValue: '[dev] *QR tools', - label: '[dev] *QR tools', - onTap: () => Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs)), - ListItemRegularRow( - keyValue: '[dev] browse sqlite db', - label: '[dev] browse sqlite db', - onTap: () async { - final data = await dumpDb(); - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => JsonExplorerPage(data: data, title: 'sqlite db'), - ), - ); - }), - ListItemRegularRow( - keyValue: '[dev] fake corrupt sqlite db', - label: '[dev] fake corrupt sqlite db', - onTap: () async { - final dbDebugMarker = await sqliteDebugMarkerFile(); - dbDebugMarker.create(); - } - ), - ] + ], + "dev": FeatureFlag.hasDevOptions == false + ? [] + : [ + if (_otherSettingsViewModel.walletType == WalletType.monero) + ListItemRegularRow( + keyValue: "[dev] monero background sync", + label: "[dev] monero background sync", + onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync)), + if ([WalletType.monero, WalletType.wownero, WalletType.zano] + .contains(_otherSettingsViewModel.walletType)) + ListItemRegularRow( + keyValue: "[dev] xmr call profiler", + label: "[dev] xmr call profiler", + onTap: () => Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler)), + if ([WalletType.monero].contains(_otherSettingsViewModel.walletType)) + ListItemRegularRow( + keyValue: '[dev] xmr wallet cache debug', + label: '[dev] xmr wallet cache debug', + onTap: () => + Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug)), + ListItemRegularRow( + keyValue: '[dev] shared preferences', + label: '[dev] shared preferences', + onTap: () => Navigator.of(context).pushNamed(Routes.devSharedPreferences)), + ListItemRegularRow( + keyValue: '[dev] secure storage preferences', + label: '[dev] secure storage preferences', + onTap: () => Navigator.of(context).pushNamed(Routes.devSecurePreferences)), + ListItemRegularRow( + keyValue: '[dev] background sync logs', + label: '[dev] background sync logs', + onTap: () => Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs)), + ListItemRegularRow( + keyValue: '[dev] socket health logs', + label: '[dev] socket health logs', + onTap: () => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs)), + ListItemRegularRow( + keyValue: '[dev] network requests logs', + label: '[dev] network requests logs', + onTap: () => Navigator.of(context).pushNamed(Routes.devNetworkRequests)), + ListItemRegularRow( + keyValue: '[dev] exchange provider logs', + label: '[dev] exchange provider logs', + onTap: () => Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs)), + ListItemRegularRow( + keyValue: '[dev] *QR tools', + label: '[dev] *QR tools', + onTap: () => Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs)), + ListItemRegularRow( + keyValue: '[dev] browse sqlite db', + label: '[dev] browse sqlite db', + onTap: () async { + final data = await dumpDb(); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => JsonExplorerPage(data: data, title: 'sqlite db'), + ), + ); + }), + ListItemRegularRow( + keyValue: '[dev] fake corrupt sqlite db', + label: '[dev] fake corrupt sqlite db', + onTap: () async { + final dbDebugMarker = await sqliteDebugMarkerFile(); + dbDebugMarker.create(); + }), + ] }), ); } diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index 8838146173..3d8b651a1a 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -38,44 +38,43 @@ class PrivacyPage extends BasePage { return Column( mainAxisSize: MainAxisSize.min, children: [ - NewListSections( - sections: { - "1": [ - if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible) - ListItemToggle( - keyValue: "auto_generate_subaddresses", - label: _privacySettingsViewModel.isMoneroWallet - ? S.of(context).auto_generate_subaddresses - : S.of(context).auto_generate_addresses, - value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled, - onChanged: (val) { - _privacySettingsViewModel.setAutoGenerateSubaddresses(val); - }), + NewListSections(sections: { + "1": [ + if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible) ListItemToggle( - keyValue: "save_recipient_address", - label: S.of(context).settings_save_recipient_address, - value: _privacySettingsViewModel.shouldSaveRecipientAddress, + keyValue: "auto_generate_subaddresses", + label: _privacySettingsViewModel.isMoneroWallet + ? S.of(context).auto_generate_subaddresses + : S.of(context).auto_generate_addresses, + value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled, onChanged: (val) { - _privacySettingsViewModel.setShouldSaveRecipientAddress(val); + _privacySettingsViewModel.setAutoGenerateSubaddresses(val); }), - if (_privacySettingsViewModel.canUsePayjoin) - ListItemToggle( - keyValue: "use_payjoin", - label: S.of(context).use_payjoin, - value: _privacySettingsViewModel.usePayjoin, - onChanged: (val) { - _privacySettingsViewModel.setUsePayjoin(val); - }), - if (_privacySettingsViewModel.canUseLightning) - ListItemToggle( - keyValue: "enable_lightning", - label: S.of(context).enable_lightning, - value: _privacySettingsViewModel.useLightning, - onChanged: (val) { - _privacySettingsViewModel.setUseLightning(val); - }), - ], - "": [ + ListItemToggle( + keyValue: "save_recipient_address", + label: S.of(context).settings_save_recipient_address, + value: _privacySettingsViewModel.shouldSaveRecipientAddress, + onChanged: (val) { + _privacySettingsViewModel.setShouldSaveRecipientAddress(val); + }), + if (_privacySettingsViewModel.canUsePayjoin) + ListItemToggle( + keyValue: "use_payjoin", + label: S.of(context).use_payjoin, + value: _privacySettingsViewModel.usePayjoin, + onChanged: (val) { + _privacySettingsViewModel.setUsePayjoin(val); + }), + if (_privacySettingsViewModel.canUseLightning) + ListItemToggle( + keyValue: "enable_lightning", + label: S.of(context).enable_lightning, + value: _privacySettingsViewModel.useLightning, + onChanged: (val) { + _privacySettingsViewModel.setUseLightning(val); + }), + ], + "": [ if (_privacySettingsViewModel.isBitcoin) ListItemRegularRow( iconPath: "assets/new-ui/settings_row_icons/silent-payments.svg", @@ -83,23 +82,22 @@ class PrivacyPage extends BasePage { label: S.of(context).silent_payments, onTap: () => Navigator.of(context).pushNamed(Routes.silentPaymentsSettings)), - if (_privacySettingsViewModel.hasMWEB) - ListItemRegularRow( - iconPath: "assets/new-ui/settings_row_icons/mweb.svg", - keyValue: "mweb", - label: "MWEB", - onTap: () => - Navigator.of(context).pushNamed(Routes.mwebSettings)), - if (_privacySettingsViewModel.hasCoinControl) + if (_privacySettingsViewModel.hasMWEB) + ListItemRegularRow( + iconPath: "assets/new-ui/settings_row_icons/mweb.svg", + keyValue: "mweb", + label: "MWEB", + onTap: () => Navigator.of(context).pushNamed(Routes.mwebSettings)), + if (_privacySettingsViewModel.hasCoinControl) ListItemRegularRow( iconPath: "assets/new-ui/settings_row_icons/coin-control.svg", keyValue: "coin_control", label: "Coin Control", - onTap: () => - Navigator.of(context).pushNamed(Routes.unspentCoinsList, arguments: CoinControlPageArgs(canEdit: false, coinTypeToSpendFrom: null))), + onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList, + arguments: + CoinControlPageArgs(canEdit: false, coinTypeToSpendFrom: null))), ], - } - ), + }), ], ); }), diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index 37fb7c7105..fc8f5a1bec 100644 --- a/lib/src/screens/settings/security_backup_page.dart +++ b/lib/src/screens/settings/security_backup_page.dart @@ -73,8 +73,8 @@ class SecurityBackupPage extends BasePage { isAuthenticatedSuccessfully); } } else { - _securitySettingsViewModel.setAllowBiometricalAuthentication( - isAuthenticatedSuccessfully); + _securitySettingsViewModel + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } }, conditionToDetermineIfToUse2FA: _securitySettingsViewModel @@ -85,13 +85,13 @@ class SecurityBackupPage extends BasePage { } }), if (DeviceInfo.instance.isMobile) - ListItemToggle( - keyValue: "display_settings_prevent_screen_capture", - label: S.of(context).prevent_screenshots, - value: _securitySettingsViewModel.isAppSecure, - onChanged: (val) { - _securitySettingsViewModel.setIsAppSecure(val); - }), + ListItemToggle( + keyValue: "display_settings_prevent_screen_capture", + label: S.of(context).prevent_screenshots, + value: _securitySettingsViewModel.isAppSecure, + onChanged: (val) { + _securitySettingsViewModel.setIsAppSecure(val); + }), if (FeatureFlag.duressPinEnabled) ListItemToggle( keyValue: "security_backup_page_duress_pin_button_key", @@ -114,8 +114,7 @@ class SecurityBackupPage extends BasePage { if (confirmation) { Navigator.of(context).pushNamed( Routes.setupDuressPin, - arguments: - (PinCodeState pinCtx, String _) async { + arguments: (PinCodeState pinCtx, String _) async { pinCtx.close(); _securitySettingsViewModel.setEnableDuressPin(true); }, diff --git a/lib/src/screens/settings/silent_payments_logs_page.dart b/lib/src/screens/settings/silent_payments_logs_page.dart index 102755f4e9..71bf5d85dc 100644 --- a/lib/src/screens/settings/silent_payments_logs_page.dart +++ b/lib/src/screens/settings/silent_payments_logs_page.dart @@ -105,11 +105,10 @@ class SilentPaymentsLogPage extends BasePage { } Future _saveFile() async { - String? outputFile = await FilePicker.platform - .saveFile( - dialogTitle: 'Save Your File to desired location', - fileName: "debug.log", - lockParentWindow: true); + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Save Your File to desired location', + fileName: "debug.log", + lockParentWindow: true); if (outputFile == null) return; diff --git a/lib/src/screens/settings/silent_payments_settings.dart b/lib/src/screens/settings/silent_payments_settings.dart index 023c8f649a..15184d5e51 100644 --- a/lib/src/screens/settings/silent_payments_settings.dart +++ b/lib/src/screens/settings/silent_payments_settings.dart @@ -19,7 +19,11 @@ class SilentPaymentsSettingsPage extends StatelessWidget { color: Theme.of(context).colorScheme.surface, child: Column( children: [ - ModalTopBar(title: S.current.silent_payments_settings,leadingIcon: Icon(Icons.arrow_back_ios_new),onLeadingPressed: Navigator.of(context).pop,), + ModalTopBar( + title: S.current.silent_payments_settings, + leadingIcon: Icon(Icons.arrow_back_ios_new), + onLeadingPressed: Navigator.of(context).pop, + ), Expanded( child: SingleChildScrollView( child: Observer(builder: (_) { @@ -27,14 +31,14 @@ class SilentPaymentsSettingsPage extends StatelessWidget { padding: EdgeInsets.only(top: 10), child: Column( children: [ - if(!FeatureFlag.hasNewUi) - SettingsSwitcherCell( - title: S.current.silent_payments_display_card, - value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay, - onValueChange: (_, bool value) { - _silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value); - }, - ), + if (!FeatureFlag.hasNewUi) + SettingsSwitcherCell( + title: S.current.silent_payments_display_card, + value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay, + onValueChange: (_, bool value) { + _silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value); + }, + ), SettingsSwitcherCell( title: S.current.silent_payments_always_scan, value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan, @@ -44,7 +48,8 @@ class SilentPaymentsSettingsPage extends StatelessWidget { ), SettingsCellWithArrow( title: S.current.silent_payments_scanning, - handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan), + handler: (BuildContext context) => + Navigator.of(context).pushNamed(Routes.rescan), ), SettingsCellWithArrow( title: S.current.silent_payments_logs, diff --git a/lib/src/screens/settings/widgets/settings_choices_cell.dart b/lib/src/screens/settings/widgets/settings_choices_cell.dart index fdf81d5695..1e470dfc0a 100644 --- a/lib/src/screens/settings/widgets/settings_choices_cell.dart +++ b/lib/src/screens/settings/widgets/settings_choices_cell.dart @@ -44,9 +44,8 @@ class SettingsChoicesCell extends StatelessWidget { color: Theme.of(context).colorScheme.surfaceContainerHighest, width: 1.5, ), - color: useGenericColor - ? Theme.of(context).colorScheme.surfaceContainerHighest - : null, + color: + useGenericColor ? Theme.of(context).colorScheme.surfaceContainerHighest : null, ), child: LayoutBuilder( builder: (context, constraints) { @@ -106,4 +105,4 @@ class SettingsChoicesCell extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/src/screens/settings/widgets/settings_picker_row.dart b/lib/src/screens/settings/widgets/settings_picker_row.dart index 462dedb83b..9115c19c8c 100644 --- a/lib/src/screens/settings/widgets/settings_picker_row.dart +++ b/lib/src/screens/settings/widgets/settings_picker_row.dart @@ -6,4 +6,4 @@ class SettingsPickerRaw extends StatelessWidget { // TODO: implement build throw UnimplementedError(); } -} \ No newline at end of file +} diff --git a/lib/src/screens/settings/widgets/settings_theme_choice.dart b/lib/src/screens/settings/widgets/settings_theme_choice.dart index c96743eb3e..1276ef1d55 100644 --- a/lib/src/screens/settings/widgets/settings_theme_choice.dart +++ b/lib/src/screens/settings/widgets/settings_theme_choice.dart @@ -58,11 +58,13 @@ class SettingsThemeChoicesCell extends StatelessWidget { curve: Curves.easeInOut, margin: EdgeInsets.only(right: 24), decoration: ShapeDecoration( - shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(cellRadius), - side: BorderSide( - color: isSelected ? Theme.of(context).colorScheme.primary : Colors.transparent, - strokeAlign: BorderSide.strokeAlignOutside) - )), + shape: RoundedSuperellipseBorder( + borderRadius: BorderRadius.circular(cellRadius), + side: BorderSide( + color: isSelected + ? Theme.of(context).colorScheme.primary + : Colors.transparent, + strokeAlign: BorderSide.strokeAlignOutside))), child: ClipRRect( borderRadius: BorderRadius.circular(cellRadius), child: CakeImageWidget( @@ -97,8 +99,8 @@ class SettingsThemeChoicesCell extends StatelessWidget { children: [ Padding( padding: EdgeInsets.only(top: 14), - child: Container(height: 1, color: Theme.of(context).colorScheme.outlineVariant) - ), + child: Container( + height: 1, color: Theme.of(context).colorScheme.outlineVariant)), SizedBox(height: cellHeight), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -123,10 +125,14 @@ class SettingsThemeChoicesCell extends StatelessWidget { duration: Duration(milliseconds: 350), opacity: isSelected ? 1 : 0, child: Container( - width:28,height:28,decoration: BoxDecoration(borderRadius: BorderRadius.circular(99999999),border: Border.all(color:Theme.of(context) - .colorScheme - .onSurface)) - ), + width: 28, + height: 28, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(99999999), + border: Border.all( + color: Theme.of(context) + .colorScheme + .onSurface))), ), AnimatedScale( duration: Duration(milliseconds: 350), @@ -153,19 +159,19 @@ class SettingsThemeChoicesCell extends StatelessWidget { if (_displaySettingsViewModel.currentTheme is BlackTheme) Padding( padding: EdgeInsets.only(top: 12, bottom: 4), - child: Container(height: 1, color: Theme.of(context).colorScheme.outlineVariant) - ), + child: + Container(height: 1, color: Theme.of(context).colorScheme.outlineVariant)), if (_displaySettingsViewModel.currentTheme is BlackTheme) - SettingsSwitcherCell( - height: 40, - title: S.current.oled_mode, - value: _displaySettingsViewModel.isBlackThemeOledEnabled, - onValueChange: (_, bool value) { - _displaySettingsViewModel.setBlackThemeOled(value); - }, - padding: EdgeInsets.zero, - switchBackgroundColor: currentTheme.colorScheme.secondaryContainer, - ), + SettingsSwitcherCell( + height: 40, + title: S.current.oled_mode, + value: _displaySettingsViewModel.isBlackThemeOledEnabled, + onValueChange: (_, bool value) { + _displaySettingsViewModel.setBlackThemeOled(value); + }, + padding: EdgeInsets.zero, + switchBackgroundColor: currentTheme.colorScheme.secondaryContainer, + ), ], ), ); diff --git a/lib/src/screens/settings/widgets/wallet_connect_button.dart b/lib/src/screens/settings/widgets/wallet_connect_button.dart index 855c399da0..66b2072cff 100644 --- a/lib/src/screens/settings/widgets/wallet_connect_button.dart +++ b/lib/src/screens/settings/widgets/wallet_connect_button.dart @@ -26,8 +26,8 @@ class WalletConnectTile extends StatelessWidget { child: Text( S.current.walletConnect, style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, - ), + color: Theme.of(context).colorScheme.onSurface, + ), ), ), Image.asset( diff --git a/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart b/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart index 4ecfc6fd47..a9f9927873 100644 --- a/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart +++ b/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart @@ -115,7 +115,6 @@ class TOTPEnterCode extends StatefulWidget { required this.isClosable, }); - final Setup2FAViewModel setup2FAViewModel; final bool isForSetup; final bool isClosable; @@ -154,7 +153,6 @@ class _TOTPEnterCodeState extends State { ), child: Column( children: [ - BaseTextFormField( textAlign: TextAlign.left, hintText: S.current.totp_code, @@ -180,15 +178,17 @@ class _TOTPEnterCodeState extends State { return PrimaryButton( isDisabled: widget.setup2FAViewModel.enteredOTPCode.length != 8, onPressed: () async { - final result = - await widget.setup2FAViewModel.totp2FAAuth(totpController.text, widget.isForSetup); - final bannedState = widget.setup2FAViewModel.state is AuthenticationBanned; + final result = await widget.setup2FAViewModel + .totp2FAAuth(totpController.text, widget.isForSetup); + final bannedState = + widget.setup2FAViewModel.state is AuthenticationBanned; await showPopUp( context: context, builder: (BuildContext context) { return PopUpCancellableAlertDialog( - contentText: _textDisplayedInPopupOnResult(result, bannedState, context), + contentText: + _textDisplayedInPopupOnResult(result, bannedState, context), actionButtonText: S.of(context).ok, buttonAction: () { result ? widget.setup2FAViewModel.success() : null; diff --git a/lib/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart b/lib/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart index a12e64c15b..98b05794ae 100644 --- a/lib/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart +++ b/lib/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart @@ -25,12 +25,11 @@ class PopUpCancellableAlertDialog extends StatelessWidget { contentText, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.normal, - - color: Theme.of(context).colorScheme.onSurface, - decoration: TextDecoration.none, - ), + fontSize: 16, + fontWeight: FontWeight.normal, + color: Theme.of(context).colorScheme.onSurface, + decoration: TextDecoration.none, + ), ); } diff --git a/lib/src/screens/setup_pin_code/setup_pin_code.dart b/lib/src/screens/setup_pin_code/setup_pin_code.dart index 07b66dcf7d..69e254f798 100644 --- a/lib/src/screens/setup_pin_code/setup_pin_code.dart +++ b/lib/src/screens/setup_pin_code/setup_pin_code.dart @@ -8,7 +8,7 @@ import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; class SetupPinCodePage extends BasePage { - SetupPinCodePage(this.pinCodeViewModel,{this.onSuccessfulPinSetup, this.isDuressPin = false}) + SetupPinCodePage(this.pinCodeViewModel, {this.onSuccessfulPinSetup, this.isDuressPin = false}) : pinCodeStateKey = GlobalKey(); final SetupPinCodeViewModel pinCodeViewModel; @@ -24,8 +24,7 @@ class SetupPinCodePage extends BasePage { key: pinCodeStateKey, hasLengthSwitcher: true, onFullPin: (String pin, PinCodeState state) async { - if (pinCodeViewModel.isOriginalPinCodeFull && - !pinCodeViewModel.isRepeatedPinCodeFull) { + if (pinCodeViewModel.isOriginalPinCodeFull && !pinCodeViewModel.isRepeatedPinCodeFull) { state.title = S.current.enter_your_pin_again; state.clear(); return; @@ -64,7 +63,7 @@ class SetupPinCodePage extends BasePage { if (pinCodeStateKey.currentState != null) { onSuccessfulPinSetup?.call(pinCodeStateKey.currentState!, pin); } - + state.reset(); }, alertBarrierDismissible: false, @@ -76,8 +75,7 @@ class SetupPinCodePage extends BasePage { builder: (BuildContext context) { return AlertWithOneAction( alertTitle: isDuressPin ? S.current.durres_PIN : S.current.setup_pin, - alertContent: - '${S.current.setup_pin_is_failed} ${e.toString()}', + alertContent: '${S.current.setup_pin_is_failed} ${e.toString()}', buttonText: S.of(context).ok, buttonAction: () => Navigator.of(context).pop(), alertBarrierDismissible: false, @@ -108,7 +106,6 @@ class SetupPinCodePage extends BasePage { pinCodeViewModel.reset(); } }, - onChangedPinLength: (int length) => - pinCodeViewModel.pinCodeLength = length, + onChangedPinLength: (int length) => pinCodeViewModel.pinCodeLength = length, initialPinLength: pinCodeViewModel.pinCodeLength); } diff --git a/lib/src/screens/splash/splash_page.dart b/lib/src/screens/splash/splash_page.dart index 07bc1119d6..daaaaa915d 100644 --- a/lib/src/screens/splash/splash_page.dart +++ b/lib/src/screens/splash/splash_page.dart @@ -3,8 +3,6 @@ import 'package:flutter/material.dart'; class SplashPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: Container() - ); + return Scaffold(body: Container()); } -} \ No newline at end of file +} diff --git a/lib/src/screens/start_tor/start_tor_page.dart b/lib/src/screens/start_tor/start_tor_page.dart index 97c5c65999..93d5c08879 100644 --- a/lib/src/screens/start_tor/start_tor_page.dart +++ b/lib/src/screens/start_tor/start_tor_page.dart @@ -34,7 +34,7 @@ class StartTorPage extends BasePage { CircularProgressIndicator(), SizedBox(height: 20), _buildWaitingText(context), - ], + ], if (startTorViewModel.showOptions) ...[ _buildOptionsButtons(context), ], @@ -93,4 +93,4 @@ class StartTorPage extends BasePage { ], ); } -} \ No newline at end of file +} diff --git a/lib/src/screens/support_chat/support_chat_page.dart b/lib/src/screens/support_chat/support_chat_page.dart index f15b248a4e..d2a3cae764 100644 --- a/lib/src/screens/support_chat/support_chat_page.dart +++ b/lib/src/screens/support_chat/support_chat_page.dart @@ -11,15 +11,14 @@ class SupportChatPage extends StatelessWidget { final SupportViewModel supportViewModel; final SecureStorage secureStorage; - @override Widget build(BuildContext context) => Container( - color: Theme.of(context).colorScheme.surface, - child: SafeArea( - child: Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), - child: Column( - children: [ + color: Theme.of(context).colorScheme.surface, + child: SafeArea( + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + children: [ ModalTopBar( title: S.current.settings_support, leadingIcon: Icon(Icons.arrow_back_ios_new), @@ -42,12 +41,11 @@ class SupportChatPage extends StatelessWidget { return Container(); }, ), - ], + ], + ), + ), ), - ), - ), - ); + ); - Future getCookie() async => - await secureStorage.read(key: COOKIE_KEY) ?? ""; + Future getCookie() async => await secureStorage.read(key: COOKIE_KEY) ?? ""; } diff --git a/lib/src/screens/support_chat/widgets/chatwoot_widget.dart b/lib/src/screens/support_chat/widgets/chatwoot_widget.dart index 217d0a1c63..baf2650f35 100644 --- a/lib/src/screens/support_chat/widgets/chatwoot_widget.dart +++ b/lib/src/screens/support_chat/widgets/chatwoot_widget.dart @@ -41,11 +41,10 @@ class ChatwootWidgetState extends State { controller.addWebMessageListener( WebMessageListener( jsObjectName: 'ReactNativeWebView', - onPostMessage: (WebMessage? message, WebUri? sourceOrigin, - bool isMainFrame, PlatformJavaScriptReplyProxy replyProxy) { + onPostMessage: (WebMessage? message, WebUri? sourceOrigin, bool isMainFrame, + PlatformJavaScriptReplyProxy replyProxy) { final shortenedMessage = message?.data.toString().substring(16); - if (shortenedMessage != null && - _isJsonString(shortenedMessage)) { + if (shortenedMessage != null && _isJsonString(shortenedMessage)) { final parsedMessage = jsonDecode(shortenedMessage); final eventType = parsedMessage["event"]; if (eventType == 'loaded') { diff --git a/lib/src/screens/trade_details/track_trade_list_item.dart b/lib/src/screens/trade_details/track_trade_list_item.dart index 916dd72da7..ed6f592d60 100644 --- a/lib/src/screens/trade_details/track_trade_list_item.dart +++ b/lib/src/screens/trade_details/track_trade_list_item.dart @@ -1,10 +1,7 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; class TrackTradeListItem extends StandartListItem { - TrackTradeListItem({ - required String title, - required String value, - required this.onTap}) + TrackTradeListItem({required String title, required String value, required this.onTap}) : super(title: title, value: value); final Function() onTap; } diff --git a/lib/src/screens/trade_details/trade_details_list_card.dart b/lib/src/screens/trade_details/trade_details_list_card.dart index 64f727b91e..c69bdc19cc 100644 --- a/lib/src/screens/trade_details/trade_details_list_card.dart +++ b/lib/src/screens/trade_details/trade_details_list_card.dart @@ -19,13 +19,11 @@ class TradeDetailsListCardItem extends StandartListItem { required CryptoCurrency to, required void Function(BuildContext) onTap, String? extraId}) { - - - final extraIdTitle = from == CryptoCurrency.xrp - ? S.current.destination_tag - : from == CryptoCurrency.xlm - ? S.current.memo - : S.current.extra_id; + final extraIdTitle = from == CryptoCurrency.xrp + ? S.current.destination_tag + : from == CryptoCurrency.xlm + ? S.current.memo + : S.current.extra_id; return TradeDetailsListCardItem( id: '${S.current.trade_details_id} ${formatAsText(id)}', diff --git a/lib/src/screens/trade_details/trade_details_page.dart b/lib/src/screens/trade_details/trade_details_page.dart index 371bd265f0..f4fc4c5b1a 100644 --- a/lib/src/screens/trade_details/trade_details_page.dart +++ b/lib/src/screens/trade_details/trade_details_page.dart @@ -74,10 +74,10 @@ class TradeDetailsPageBodyState extends State { child: Text( '${item.value}', style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, - ), + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, + ), ), ), image: GestureDetector( diff --git a/lib/src/screens/trade_details/trade_details_status_item.dart b/lib/src/screens/trade_details/trade_details_status_item.dart index b1fd89e3ef..399b6baea6 100644 --- a/lib/src/screens/trade_details/trade_details_status_item.dart +++ b/lib/src/screens/trade_details/trade_details_status_item.dart @@ -1,8 +1,7 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; class DetailsListStatusItem extends StandartListItem { - DetailsListStatusItem( - {required String title, required String value, this.status}) + DetailsListStatusItem({required String title, required String value, this.status}) : super(title: title, value: value); final String? status; // waiting, action required, created, fetching, finished, success diff --git a/lib/src/screens/transaction_details/address_list_item.dart b/lib/src/screens/transaction_details/address_list_item.dart index 1e969f581d..850a8dca3e 100644 --- a/lib/src/screens/transaction_details/address_list_item.dart +++ b/lib/src/screens/transaction_details/address_list_item.dart @@ -2,4 +2,4 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_ class AddressListItem extends TransactionDetailsListItem { AddressListItem({required super.title, required super.value, super.key}); -} \ No newline at end of file +} diff --git a/lib/src/screens/transaction_details/confirmations_list_item.dart b/lib/src/screens/transaction_details/confirmations_list_item.dart index a1c7d44148..3870b57153 100644 --- a/lib/src/screens/transaction_details/confirmations_list_item.dart +++ b/lib/src/screens/transaction_details/confirmations_list_item.dart @@ -6,7 +6,7 @@ class ConfirmationsListItem extends TransactionDetailsListItem { ConfirmationsListItem({required super.title, required super.value, super.key}) { final parts = value.split("/"); - current = int.tryParse(parts.first)??0; - needed = int.tryParse(parts.last)??0; + current = int.tryParse(parts.first) ?? 0; + needed = int.tryParse(parts.last) ?? 0; } -} \ No newline at end of file +} diff --git a/lib/src/screens/transaction_details/rbf_details_page.dart b/lib/src/screens/transaction_details/rbf_details_page.dart index 30de3892cf..0ec4d5acca 100644 --- a/lib/src/screens/transaction_details/rbf_details_page.dart +++ b/lib/src/screens/transaction_details/rbf_details_page.dart @@ -105,7 +105,8 @@ class RBFDetailsPage extends BasePage { text: S.of(context).send, isLoading: transactionDetailsViewModel.sendViewModel.state is IsExecutingState, - isDisabled: transactionDetailsViewModel.sendViewModel.state is ExecutedSuccessfullyState, + isDisabled: transactionDetailsViewModel.sendViewModel.state + is ExecutedSuccessfullyState, color: Theme.of(context).colorScheme.primary, textColor: Theme.of(context).colorScheme.onPrimary, ))), diff --git a/lib/src/screens/transaction_details/textfield_list_item.dart b/lib/src/screens/transaction_details/textfield_list_item.dart index 846f9acd5d..bc151626cd 100644 --- a/lib/src/screens/transaction_details/textfield_list_item.dart +++ b/lib/src/screens/transaction_details/textfield_list_item.dart @@ -14,4 +14,4 @@ class TextFieldListItem extends TransactionDetailsListItem { ); final Function(String value) onSubmitted; -} \ No newline at end of file +} diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index 9679b8531b..8709dfb29e 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -103,8 +103,10 @@ class TransactionDetailsPage extends BasePage { child: SelectButton( text: S.of(context).bump_fee, onTap: () async { - Navigator.of(context).pushNamed(Routes.bumpFeePage, - arguments: [transactionDetailsViewModel.transactionInfo, transactionDetailsViewModel.rawTransaction]); + Navigator.of(context).pushNamed(Routes.bumpFeePage, arguments: [ + transactionDetailsViewModel.transactionInfo, + transactionDetailsViewModel.rawTransaction + ]); }, ), ); @@ -123,25 +125,17 @@ class TransactionDetailsPage extends BasePage { required WalletType walletType, }) { final textStyle = Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, - ); + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, + ); final List children = []; final bool hasDoubleNewline = value.contains('\n\n'); if (hasDoubleNewline) { - final blocks = value - .split('\n\n') - .map((b) => b.trim()) - .where((b) => b.isNotEmpty) - .toList(); + final blocks = value.split('\n\n').map((b) => b.trim()).where((b) => b.isNotEmpty).toList(); for (final block in blocks) { - final lines = block - .split('\n') - .map((l) => l.trim()) - .where((l) => l.isNotEmpty) - .toList(); + final lines = block.split('\n').map((l) => l.trim()).where((l) => l.isNotEmpty).toList(); if (lines.length > 1) { children.add(Text(lines.first, style: textStyle)); for (int i = 1; i < lines.length; i++) { @@ -165,11 +159,7 @@ class TransactionDetailsPage extends BasePage { children.add(SizedBox(height: 8)); } } else { - final lines = value - .split('\n') - .map((l) => l.trim()) - .where((l) => l.isNotEmpty) - .toList(); + final lines = value.split('\n').map((l) => l.trim()).where((l) => l.isNotEmpty).toList(); bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20); int startIndex = 0; if (firstLineIsContactName) { diff --git a/lib/src/screens/transaction_details/transaction_expandable_list_item.dart b/lib/src/screens/transaction_details/transaction_expandable_list_item.dart index db6cf22ae6..84149e84ab 100644 --- a/lib/src/screens/transaction_details/transaction_expandable_list_item.dart +++ b/lib/src/screens/transaction_details/transaction_expandable_list_item.dart @@ -7,6 +7,6 @@ class StandardExpandableListItem extends TransactionDetailsListItem { required this.expandableItems, Key? key, }) : super(title: title, value: '', key: key); - + final List expandableItems; } diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index 33ee821a6a..d162a593da 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -121,14 +121,14 @@ class UnspentCoinsListFormState extends State { canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { if (didPop) return; - if(mounted) - await widget.handleOnPopInvoked(context); + if (mounted) await widget.handleOnPopInvoked(context); }, child: FutureBuilder( future: _initialization, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CircularProgressIndicator( + return Center( + child: CircularProgressIndicator( color: Theme.of(context).colorScheme.primary, )); } @@ -153,10 +153,10 @@ class UnspentCoinsListFormState extends State { Text( S.current.all_coins, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.onSurface, - ), + fontSize: 16, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + ), ), ], ), @@ -165,46 +165,44 @@ class UnspentCoinsListFormState extends State { child: unspentCoinsListViewModel.items.isEmpty ? Center( child: Text( - 'No unspent coins available', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ) - ) + 'No unspent coins available', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + )) : ListView.separated( itemCount: unspentCoinsListViewModel.items.length, separatorBuilder: (_, __) => SizedBox(height: 15), itemBuilder: (_, int index) { final item = unspentCoinsListViewModel.items[index]; - return Observer( - builder: (_) { - final fiatAmount = unspentCoinsListViewModel.fiatAmounts[item.amount] ?? ''; - return GestureDetector( - onTap: () => Navigator.of(context).pushNamed( - Routes.unspentCoinsDetails, - arguments: [item, unspentCoinsListViewModel], - ), - child: UnspentCoinsListItem( - note: item.note, - amount: item.amount, - fiatAmount: fiatAmount, - address: item.address, - isSending: item.isSending, - isFrozen: item.isFrozen, - isChange: item.isChange, - isSilentPayment: item.isSilentPayment, - onCheckBoxTap: item.isFrozen - ? null - : () async { - item.isSending = !item.isSending; - await unspentCoinsListViewModel - .saveUnspentCoinInfo(item); - }, - ), - ); - } - ); + return Observer(builder: (_) { + final fiatAmount = + unspentCoinsListViewModel.fiatAmounts[item.amount] ?? ''; + return GestureDetector( + onTap: () => Navigator.of(context).pushNamed( + Routes.unspentCoinsDetails, + arguments: [item, unspentCoinsListViewModel], + ), + child: UnspentCoinsListItem( + note: item.note, + amount: item.amount, + fiatAmount: fiatAmount, + address: item.address, + isSending: item.isSending, + isFrozen: item.isFrozen, + isChange: item.isChange, + isSilentPayment: item.isSilentPayment, + onCheckBoxTap: item.isFrozen + ? null + : () async { + item.isSending = !item.isSending; + await unspentCoinsListViewModel + .saveUnspentCoinInfo(item); + }, + ), + ); + }); }, ), ), diff --git a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart index d95fd13474..72d3ad4324 100644 --- a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart +++ b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart @@ -73,19 +73,19 @@ class UnspentCoinsListItem extends StatelessWidget { AutoSizeText( note, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: amountColor, - fontSize: 15, - fontWeight: FontWeight.w600, - ), + color: amountColor, + fontSize: 15, + fontWeight: FontWeight.w600, + ), maxLines: 1, ), AutoSizeText( amount, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: amountColor, - fontSize: 15, - fontWeight: FontWeight.w600, - ), + color: amountColor, + fontSize: 15, + fontWeight: FontWeight.w600, + ), maxLines: 1, ) ], @@ -111,21 +111,21 @@ class UnspentCoinsListItem extends StatelessWidget { ], ), if (fiatAmount.isNotEmpty) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AutoSizeText( - fiatAmount, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: amountColor, - fontSize: 1, - fontWeight: FontWeight.w600, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AutoSizeText( + fiatAmount, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: amountColor, + fontSize: 1, + fontWeight: FontWeight.w600, + ), + maxLines: 1, ), - maxLines: 1, - ), - ], - ), + ], + ), Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -134,8 +134,8 @@ class UnspentCoinsListItem extends StatelessWidget { AutoSizeText( '${address.substring(0, 5)}...${address.substring(address.length - 5)}', // ToDo: Maybe use address label style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: addressColor, - ), + color: addressColor, + ), maxLines: 1, ), Row( diff --git a/lib/src/screens/ur/animated_ur_page.dart b/lib/src/screens/ur/animated_ur_page.dart index 06e34bfded..b6a57f4915 100644 --- a/lib/src/screens/ur/animated_ur_page.dart +++ b/lib/src/screens/ur/animated_ur_page.dart @@ -64,8 +64,7 @@ class AnimatedURPage extends BasePage { hardwareWalletType: animatedURmodel.wallet.hardwareWalletType, ), ), - if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header] - .contains(urQrType)) ...{ + if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header].contains(urQrType)) ...{ Padding( padding: const EdgeInsets.all(16.0), child: SizedBox( @@ -89,8 +88,7 @@ class AnimatedURPage extends BasePage { case "ur:xmr-txunsigned": // ur:xmr-txsigned final ur = await presentQRScanner(context, showManualInput: false); if (ur == null) return; - final result = - await monero!.commitTransactionUR(animatedURmodel.wallet, ur); + final result = await monero!.commitTransactionUR(animatedURmodel.wallet, ur); if (result) { Navigator.of(context).pop(true); } @@ -98,8 +96,7 @@ class AnimatedURPage extends BasePage { case "ur:xmr-output": // xmr-keyimage final ur = await presentQRScanner(context, showManualInput: false); if (ur == null) return; - final result = - await monero!.importKeyImagesUR(animatedURmodel.wallet, ur); + final result = await monero!.importKeyImagesUR(animatedURmodel.wallet, ur); if (result) { Navigator.of(context).pop(true); } @@ -107,8 +104,7 @@ class AnimatedURPage extends BasePage { case "ur:psbt": // psbt final ur = await presentQRScanner(context, showManualInput: false); if (ur == null) return; - await bitcoin! - .commitPsbtUR(animatedURmodel.wallet, ur.trim().split("\n")); + await bitcoin!.commitPsbtUR(animatedURmodel.wallet, ur.trim().split("\n")); Navigator.of(context).pop(true); default: throw UnimplementedError("unable to handle UR: ${urQrType}"); diff --git a/lib/src/screens/ur/widgets/qr_format_info_bottom_sheet.dart b/lib/src/screens/ur/widgets/qr_format_info_bottom_sheet.dart index 3b634c7fa1..0a26f913e8 100644 --- a/lib/src/screens/ur/widgets/qr_format_info_bottom_sheet.dart +++ b/lib/src/screens/ur/widgets/qr_format_info_bottom_sheet.dart @@ -24,10 +24,7 @@ class QRFormatInfoBottomSheet extends StatelessWidget { width: 40, height: 4, decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.3), + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3), borderRadius: BorderRadius.circular(2), ), ), diff --git a/lib/src/screens/ur/widgets/qr_selection_dialog.dart b/lib/src/screens/ur/widgets/qr_selection_dialog.dart index 2d0f818bf6..8a5cf5adf2 100644 --- a/lib/src/screens/ur/widgets/qr_selection_dialog.dart +++ b/lib/src/screens/ur/widgets/qr_selection_dialog.dart @@ -90,9 +90,7 @@ class QRFormatSelectionDialog extends BaseAlertDialog { fontSize: 14, fontWeight: FontWeight.w600, color: isSelected - ? Theme.of(context) - .colorScheme - .onPrimaryContainer + ? Theme.of(context).colorScheme.onPrimaryContainer : Theme.of(context).colorScheme.onSurface, ), ), @@ -105,10 +103,7 @@ class QRFormatSelectionDialog extends BaseAlertDialog { .colorScheme .onPrimaryContainer .withOpacity(0.6) - : Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.6), + : Theme.of(context).colorScheme.onSurface.withOpacity(0.6), ), ), ], diff --git a/lib/src/screens/ur/widgets/urqr.dart b/lib/src/screens/ur/widgets/urqr.dart index c12b7c9e8e..7ac4ecdd06 100644 --- a/lib/src/screens/ur/widgets/urqr.dart +++ b/lib/src/screens/ur/widgets/urqr.dart @@ -53,19 +53,17 @@ class _URQRState extends State { String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length]; void next() => setState(() { - final keys = widget.urqr.keys.toList(); + final keys = widget.urqr.keys.toList(); - selectedInt++; - selected = keys[(selectedInt) % keys.length]; - }); + selectedInt++; + selected = keys[(selectedInt) % keys.length]; + }); late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first; - List get frames => widget.urqr[selected]?.split("\n") ?? []; void _nextFrame() => setState(() => frame++); - @override Widget build(BuildContext context) { return Column( @@ -85,9 +83,7 @@ class _URQRState extends State { ), ), if (widget.urqr.values.length > 1) - widget.walletType == WalletType.monero - ? _legacySwitch(context) - : _newSwitch(context), + widget.walletType == WalletType.monero ? _legacySwitch(context) : _newSwitch(context), if (FeatureFlag.hasDevOptions) ...{ TextButton( onPressed: () { diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart index 32e00570c0..77544b9820 100644 --- a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_supported_methods.dart @@ -28,4 +28,4 @@ enum EVMSupportedMethods { return 'eth_sendTransaction'; } } -} \ No newline at end of file +} diff --git a/lib/src/screens/wallet_connect/services/walletkit_service.dart b/lib/src/screens/wallet_connect/services/walletkit_service.dart index a758b8b9c7..aa3c26307a 100644 --- a/lib/src/screens/wallet_connect/services/walletkit_service.dart +++ b/lib/src/screens/wallet_connect/services/walletkit_service.dart @@ -133,9 +133,9 @@ abstract class WalletKitServiceBase with Store { if (!isInitialized) { try { await _walletKit.init().timeout( - const Duration(seconds: 8), - onTimeout: () => throw TimeoutException('walletKit init timed out'), - ); + const Duration(seconds: 8), + onTimeout: () => throw TimeoutException('walletKit init timed out'), + ); debugPrint('Initialized'); isInitialized = true; } catch (e) { @@ -202,14 +202,16 @@ abstract class WalletKitServiceBase with Store { namespaces: session.namespaces, ); if (events.contains('accountsChanged')) { - await _walletKit.emitSessionEvent( - topic: session.topic, - chainId: chainID, - event: SessionEventParams( - name: 'accountsChanged', - data: [chain.publicKey], - ), - ).timeout(const Duration(seconds: 3)); + await _walletKit + .emitSessionEvent( + topic: session.topic, + chainId: chainID, + event: SessionEventParams( + name: 'accountsChanged', + data: [chain.publicKey], + ), + ) + .timeout(const Duration(seconds: 3)); } } on ReownSignError catch (e) { if (e.code == 6) { @@ -433,12 +435,10 @@ abstract class WalletKitServiceBase with Store { } final requesterMetadata = args.requester.metadata; - final requesterIcon = requesterMetadata.icons.isNotEmpty - ? requesterMetadata.icons.first - : null; + final requesterIcon = + requesterMetadata.icons.isNotEmpty ? requesterMetadata.icons.first : null; final chainKeysForAuth = walletKeyService.getKeysForChain(appStore.wallet!); - final addressForAuth = - chainKeysForAuth.isNotEmpty ? chainKeysForAuth.first.publicKey : ''; + final addressForAuth = chainKeysForAuth.isNotEmpty ? chainKeysForAuth.first.publicKey : ''; final combinedMessageBody = formattedMessages.map((m) => m.values.first as String).join('\n\n'); @@ -521,8 +521,7 @@ abstract class WalletKitServiceBase with Store { @action Future deletePairing({required String topic}) async { - final topicSessions = - sessions.where((element) => element.pairingTopic == topic).toList(); + final topicSessions = sessions.where((element) => element.pairingTopic == topic).toList(); await _walletKit.core.pairing.disconnect(topic: topic); for (var session in topicSessions) { @@ -557,7 +556,7 @@ abstract class WalletKitServiceBase with Store { reason: Errors.getSdkError(Errors.USER_DISCONNECTED).toSignError(), ); } catch (e) { - printV('disconnectSession: $e'); + printV('disconnectSession: $e'); } sessions.clear(); @@ -622,7 +621,7 @@ abstract class WalletKitServiceBase with Store { @action List getSessionsForPairingInfo(PairingInfo pairing) { - return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); + return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); } String getKeyForStoringTopicsForWallet() { diff --git a/lib/src/screens/wallet_connect/utils/method_utils.dart b/lib/src/screens/wallet_connect/utils/method_utils.dart index b22c572597..78a11f8712 100644 --- a/lib/src/screens/wallet_connect/utils/method_utils.dart +++ b/lib/src/screens/wallet_connect/utils/method_utils.dart @@ -13,7 +13,7 @@ import 'package:reown_walletkit/reown_walletkit.dart'; class MethodsUtils { static final walletKit = getIt.get().walletKit; static final bottomSheetService = getIt.get(); - + static const _transactionMethods = { 'eth_sendTransaction', 'eth_signTransaction', @@ -34,18 +34,13 @@ class MethodsUtils { }) async { final appStore = getIt.get(); final pending = walletKit.pendingRequests.getAll(); - final session = pending.isNotEmpty - ? walletKit.sessions.get(pending.last.topic) - : null; + final session = pending.isNotEmpty ? walletKit.sessions.get(pending.last.topic) : null; final dAppMetadata = session?.peer.metadata; final isTransaction = method != null && _transactionMethods.contains(method); final resolvedTitle = title ?? - (isTransaction - ? S.current.wc_approve_request_title - : S.current.wc_signing_request_title); - final swipeLabel = - isTransaction ? S.current.wc_swipe_to_approve : S.current.wc_swipe_to_sign; + (isTransaction ? S.current.wc_approve_request_title : S.current.wc_signing_request_title); + final swipeLabel = isTransaction ? S.current.wc_swipe_to_approve : S.current.wc_swipe_to_sign; final extraRows = []; if (method != null && method.isNotEmpty) { diff --git a/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart index 6a84f826e8..fceb5714ac 100644 --- a/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart +++ b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart @@ -39,7 +39,8 @@ class WCPermissionsMapper { } final permissions = [ - WCPermission(iconUrl: "assets/new-ui/global_view.svg", label: S.current.wc_permission_view_balance), + WCPermission( + iconUrl: "assets/new-ui/global_view.svg", label: S.current.wc_permission_view_balance), ]; final wantsTransactionApproval = methods.any(_transactionMethods.contains); diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart index a6caff932a..1a2e4f44a7 100644 --- a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart +++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart @@ -227,4 +227,4 @@ class WalletConnectConnectionsView extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart b/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart index 036fdd6dca..b73d738ff3 100644 --- a/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart @@ -78,12 +78,12 @@ class EnterWalletConnectURIWidget extends BaseAlertDialog { width: 36, height: 36, padding: EdgeInsets.only(top: 0), - child: Semantics( + child: Semantics( label: S.of(context).paste, child: InkWell( onTap: () => _pasteWalletConnectURI(), child: Container( - padding: EdgeInsets.all(8), + padding: EdgeInsets.all(8), decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(6)), ), diff --git a/lib/src/screens/wallet_connect/widgets/wc_hero_card.dart b/lib/src/screens/wallet_connect/widgets/wc_hero_card.dart index ebb69a347b..8d77ca9e3e 100644 --- a/lib/src/screens/wallet_connect/widgets/wc_hero_card.dart +++ b/lib/src/screens/wallet_connect/widgets/wc_hero_card.dart @@ -1,4 +1,3 @@ - import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index c2b736d1bc..ebd4e7496a 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -184,7 +184,7 @@ class _WalletKeysPageBodyState extends State Widget _buildSeedTab(BuildContext context, bool isLegacySeed) { return Column( children: [ - if (isLegacySeedOnly || isLegacySeed ||widget.walletKeysViewModel.shouldShowHeightBox) ...[ + if (isLegacySeedOnly || isLegacySeed || widget.walletKeysViewModel.shouldShowHeightBox) ...[ _buildHeightBox(), const SizedBox(height: 20), ], diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index d94808ad9f..f227af07c1 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -143,7 +143,7 @@ class WalletListBodyState extends State { @override Widget build(BuildContext context) { return GradientBackground( - scaffold: Container( + scaffold: Container( height: double.infinity, padding: EdgeInsets.only(top: 16), child: Stack( @@ -339,45 +339,45 @@ class WalletListBodyState extends State { Stack( alignment: Alignment.bottomCenter, children: [ - !FeatureFlag.hasNewUi - ? IgnorePointer( - child: Container( - alignment: Alignment.bottomCenter, - height: 185, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Theme.of(context).colorScheme.surface.withAlpha(10), - Theme.of(context).colorScheme.surface, - Theme.of(context).colorScheme.surface, - Theme.of(context).colorScheme.surface - ], - ), - ), - ), - ) - : IgnorePointer( - child: Container( - height: 275, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Theme.of(context).colorScheme.surfaceDim.withAlpha(10), - Theme.of(context).colorScheme.surfaceDim.withAlpha(150), - Theme.of(context).colorScheme.surfaceDim.withAlpha(255), - Theme.of(context).colorScheme.surfaceDim.withAlpha(255), - Theme.of(context).colorScheme.surfaceDim.withAlpha(255), - Theme.of(context).colorScheme.surfaceDim.withAlpha(255), - Theme.of(context).colorScheme.surfaceDim.withAlpha(255) - ], + !FeatureFlag.hasNewUi + ? IgnorePointer( + child: Container( + alignment: Alignment.bottomCenter, + height: 185, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Theme.of(context).colorScheme.surface.withAlpha(10), + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surface + ], + ), + ), + ), + ) + : IgnorePointer( + child: Container( + height: 275, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Theme.of(context).colorScheme.surfaceDim.withAlpha(10), + Theme.of(context).colorScheme.surfaceDim.withAlpha(150), + Theme.of(context).colorScheme.surfaceDim.withAlpha(255), + Theme.of(context).colorScheme.surfaceDim.withAlpha(255), + Theme.of(context).colorScheme.surfaceDim.withAlpha(255), + Theme.of(context).colorScheme.surfaceDim.withAlpha(255), + Theme.of(context).colorScheme.surfaceDim.withAlpha(255) + ], + ), + ), + ), ), - ), - ), - ), Container( height: 240, width: MediaQuery.of(context).size.width, @@ -464,8 +464,7 @@ class WalletListBodyState extends State { color: Theme.of(context).colorScheme.primary, textColor: Theme.of(context).colorScheme.onPrimary, ), - if(FeatureFlag.hasNewUi) - SizedBox(height:52.0) + if (FeatureFlag.hasNewUi) SizedBox(height: 52.0) ], ), ), @@ -578,9 +577,8 @@ class WalletListBodyState extends State { if (_progressBar != null) { _progressBar!.dismiss(); } - _progressBar = createBar(text, context, duration: null) - ..show(context); - }catch(e){} + _progressBar = createBar(text, context, duration: null)..show(context); + } catch (e) {} } Future hideProgressText() async { diff --git a/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart b/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart index 5b6d4dd162..e73d211a30 100644 --- a/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart +++ b/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart @@ -5,10 +5,7 @@ typedef AuthPasswordHandler = Future Function(String); class WalletUnlockArguments { WalletUnlockArguments( - {required this.callback, - this.walletName, - this.walletType, - this.authPasswordHandler}); + {required this.callback, this.walletName, this.walletType, this.authPasswordHandler}); final OnAuthenticationFinished callback; final AuthPasswordHandler? authPasswordHandler; diff --git a/lib/src/screens/welcome/create_pin_welcome_page.dart b/lib/src/screens/welcome/create_pin_welcome_page.dart index f8312fc798..a2c4a67559 100644 --- a/lib/src/screens/welcome/create_pin_welcome_page.dart +++ b/lib/src/screens/welcome/create_pin_welcome_page.dart @@ -228,7 +228,8 @@ class CreatePinWelcomePage extends BasePage { child: PrimaryButton( key: ValueKey('create_pin_welcome_page_create_a_pin_button_key'), onPressed: () => Navigator.pushNamed(context, Routes.welcomeWallet), - text: isWalletPasswordDirectInput ? S.current.set_up_a_wallet : S.current.set_a_pin, + text: + isWalletPasswordDirectInput ? S.current.set_up_a_wallet : S.current.set_a_pin, color: Theme.of(context).colorScheme.primary, textColor: Theme.of(context).colorScheme.onPrimary, ), diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index d9890cd417..3b618f4908 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -22,17 +22,19 @@ class WelcomePage extends BasePage { @override Widget Function(BuildContext, Widget) get rootWrapper => - (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); + (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); @override bool get resizeToAvoidBottomInset => false; @override Widget trailing(BuildContext context) { - final Uri _url = - Uri.parse('https://docs.cakewallet.com/get-started/setup/'); + final Uri _url = Uri.parse('https://docs.cakewallet.com/get-started/setup/'); return IconButton( - icon: Icon(Icons.info_outline, size: 26,), + icon: Icon( + Icons.info_outline, + size: 26, + ), onPressed: () async { await launchUrl(_url); }, diff --git a/lib/src/screens/yat/widgets/first_introduction.dart b/lib/src/screens/yat/widgets/first_introduction.dart index a007b408d1..be76063768 100644 --- a/lib/src/screens/yat/widgets/first_introduction.dart +++ b/lib/src/screens/yat/widgets/first_introduction.dart @@ -25,65 +25,44 @@ class FirstIntroduction extends StatelessWidget { color: Theme.of(context).colorScheme.surfaceContainer, child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(top: 40, bottom: 40), - content: Column( - children: [ - Container( - height: 45, - padding: EdgeInsets.only(left: 24, right: 24), - child: YatBar(onClose: () => Navigator.of(context).pop()) - ), - animation, - Container( - padding: EdgeInsets.only(left: 30, right: 30), - child: Column( - children: [ - Text( - S.of(context).yat_alert_title, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 24, - fontWeight: FontWeight.bold, - + content: Column(children: [ + Container( + height: 45, + padding: EdgeInsets.only(left: 24, right: 24), + child: YatBar(onClose: () => Navigator.of(context).pop())), + animation, + Container( + padding: EdgeInsets.only(left: 30, right: 30), + child: Column(children: [ + Text(S.of(context).yat_alert_title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + decoration: TextDecoration.none, + )), + Padding( + padding: EdgeInsets.only(top: 20), + child: Text(S.of(context).yat_alert_content, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 16, + fontWeight: FontWeight.normal, color: Theme.of(context).colorScheme.onSurface, decoration: TextDecoration.none, - ) - ), - Padding( - padding: EdgeInsets.only(top: 20), - child: Text( - S.of(context).yat_alert_content, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.normal, - - color: Theme.of(context).colorScheme.onSurface, - decoration: TextDecoration.none, - ) - ) - ) - ] - ) - ) - ] - ), + ))) + ])) + ]), bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24), - bottomSection: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - PrimaryButton( - text: S.of(context).restore_next, - textColor: Theme.of(context).colorScheme.onPrimary, - color: Theme.of(context).colorScheme.primary, - onPressed: onNext - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: YatPageIndicator(filled: 0) - ) - ] - ), - ) - ); + bottomSection: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ + PrimaryButton( + text: S.of(context).restore_next, + textColor: Theme.of(context).colorScheme.onPrimary, + color: Theme.of(context).colorScheme.primary, + onPressed: onNext), + Padding(padding: EdgeInsets.only(top: 24), child: YatPageIndicator(filled: 0)) + ]), + )); } -} \ No newline at end of file +} diff --git a/lib/src/screens/yat/widgets/second_introduction.dart b/lib/src/screens/yat/widgets/second_introduction.dart index 4482e5f05b..2d1fe3e086 100644 --- a/lib/src/screens/yat/widgets/second_introduction.dart +++ b/lib/src/screens/yat/widgets/second_introduction.dart @@ -29,60 +29,42 @@ class SecondIntroduction extends StatelessWidget { Container( height: 45, padding: EdgeInsets.only(left: 24, right: 24), - child: YatBar(onClose: onClose) - ), + child: YatBar(onClose: onClose)), animation, Padding( padding: EdgeInsets.only(top: 40, left: 30, right: 30), - child: Column( - children: [ - Text( - S.of(context).second_intro_title, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + child: Column(children: [ + Text(S.of(context).second_intro_title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 24, fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.onSurface, decoration: TextDecoration.none, - ) - ), - Padding( - padding: EdgeInsets.only(top: 20), - child: Text( - S.of(context).second_intro_content, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + )), + Padding( + padding: EdgeInsets.only(top: 20), + child: Text(S.of(context).second_intro_content, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 16, fontWeight: FontWeight.normal, - color: Theme.of(context).colorScheme.onSurface, decoration: TextDecoration.none, - ) - ) - ) - ] - ), + ))) + ]), ), ], ), bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24), - bottomSection: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - PrimaryButton( - text: S.of(context).restore_next, - textColor: Theme.of(context).colorScheme.onPrimary, - color: Theme.of(context).colorScheme.primary, - onPressed: onNext - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: YatPageIndicator(filled: 1) - ) - ] - ), - ) - ); + bottomSection: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ + PrimaryButton( + text: S.of(context).restore_next, + textColor: Theme.of(context).colorScheme.onPrimary, + color: Theme.of(context).colorScheme.primary, + onPressed: onNext), + Padding(padding: EdgeInsets.only(top: 24), child: YatPageIndicator(filled: 1)) + ]), + )); } -} \ No newline at end of file +} diff --git a/lib/src/screens/yat/widgets/third_introduction.dart b/lib/src/screens/yat/widgets/third_introduction.dart index 63fcdcd186..43fb37efa1 100644 --- a/lib/src/screens/yat/widgets/third_introduction.dart +++ b/lib/src/screens/yat/widgets/third_introduction.dart @@ -39,23 +39,21 @@ class ThirdIntroduction extends StatelessWidget { Text(S.of(context).third_intro_title, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 24, - fontWeight: FontWeight.bold, - - color: Theme.of(context).colorScheme.onSurface, - decoration: TextDecoration.none, - )), + fontSize: 24, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + decoration: TextDecoration.none, + )), Padding( padding: EdgeInsets.only(top: 20), child: Text(S.of(context).third_intro_content, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.normal, - - color: Theme.of(context).colorScheme.onSurface, - decoration: TextDecoration.none, - ))) + fontSize: 16, + fontWeight: FontWeight.normal, + color: Theme.of(context).colorScheme.onSurface, + decoration: TextDecoration.none, + ))) ])), ], ), diff --git a/lib/src/screens/yat/widgets/yat_bar.dart b/lib/src/screens/yat/widgets/yat_bar.dart index 32312bf64f..a605e360c6 100644 --- a/lib/src/screens/yat/widgets/yat_bar.dart +++ b/lib/src/screens/yat/widgets/yat_bar.dart @@ -9,19 +9,9 @@ class YatBar extends StatelessWidget { @override Widget build(BuildContext context) { - return Stack( - alignment: Alignment.bottomCenter, - children: [ - Positioned( - top: 0, - right: 0, - child: YatCloseButton(onClose: onClose) - ), - Positioned( - top: 16, - child: image - ) - ] - ); + return Stack(alignment: Alignment.bottomCenter, children: [ + Positioned(top: 0, right: 0, child: YatCloseButton(onClose: onClose)), + Positioned(top: 16, child: image) + ]); } -} \ No newline at end of file +} diff --git a/lib/src/screens/yat/widgets/yat_page_indicator.dart b/lib/src/screens/yat/widgets/yat_page_indicator.dart index ec1d405841..a5fd2d08ee 100644 --- a/lib/src/screens/yat/widgets/yat_page_indicator.dart +++ b/lib/src/screens/yat/widgets/yat_page_indicator.dart @@ -22,11 +22,7 @@ class YatPageIndicator extends StatelessWidget { shape: BoxShape.circle, color: isFilled ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.1) - ) - ); - }) - ) - ); + : Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.1))); + }))); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/adaptable_page_view.dart b/lib/src/widgets/adaptable_page_view.dart index fc2506e01b..8ef9a20682 100644 --- a/lib/src/widgets/adaptable_page_view.dart +++ b/lib/src/widgets/adaptable_page_view.dart @@ -156,7 +156,6 @@ class _RenderSizingContainer extends RenderProxyBox { final double t = (page - floorPage).clamp(0.0, 1.0); final double height = lerpDouble(a.height, b.height, t) ?? a.height; - child.layout( constraints.copyWith(minHeight: height, maxHeight: height), parentUsesSize: true, @@ -214,4 +213,4 @@ class _RenderSizeAware extends RenderProxyBox { ), ); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/alert_with_picker_option.dart b/lib/src/widgets/alert_with_picker_option.dart index fdc8f0b981..79a7b204b9 100644 --- a/lib/src/widgets/alert_with_picker_option.dart +++ b/lib/src/widgets/alert_with_picker_option.dart @@ -48,7 +48,6 @@ class AlertWithPickerOption extends BaseAlertDialog { style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 10, fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, decoration: TextDecoration.none, ), diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 25c08ff6af..c87ab99ce4 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -5,36 +5,30 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:flutter/material.dart'; - class AlertButtonStyle { final Color backgroundColor; final Color textColor; final FontWeight fontWeight; - const AlertButtonStyle({ - required this.backgroundColor, - required this.textColor, - this.fontWeight = FontWeight.w400 - }); + const AlertButtonStyle( + {required this.backgroundColor, required this.textColor, this.fontWeight = FontWeight.w400}); factory AlertButtonStyle.primary(BuildContext context) => AlertButtonStyle( - backgroundColor: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - ); + backgroundColor: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ); factory AlertButtonStyle.secondary(BuildContext context) => AlertButtonStyle( - backgroundColor: Theme.of(context).colorScheme.surfaceContainer, - textColor: Theme.of(context).colorScheme.primary, - ); + backgroundColor: Theme.of(context).colorScheme.surfaceContainer, + textColor: Theme.of(context).colorScheme.primary, + ); factory AlertButtonStyle.error(BuildContext context) => AlertButtonStyle( - backgroundColor: Theme.of(context).colorScheme.errorContainer, - textColor: Theme.of(context).colorScheme.error, - fontWeight: FontWeight.w500 - ); + backgroundColor: Theme.of(context).colorScheme.errorContainer, + textColor: Theme.of(context).colorScheme.error, + fontWeight: FontWeight.w500); } - class BaseAlertDialog extends StatelessWidget { String? get headerText => ''; @@ -67,7 +61,7 @@ class BaseAlertDialog extends StatelessWidget { Key? rightActionButtonKey; Key? dialogKey; - + AlertButtonStyle? get leftAlertButtonStyle => null; AlertButtonStyle? get rightAlertButtonStyle => null; @@ -129,29 +123,30 @@ class BaseAlertDialog extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, - spacing:8, + spacing: 8, children: [ - if(showLeftButton) - Expanded( - child: GestureDetector( - key: leftActionButtonKey, - onTap: actionLeft, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(999999), - color: leftButtonStyle.backgroundColor - ), - child: Padding( - padding: EdgeInsets.symmetric(vertical: 16, horizontal: 8), - child: AutoSizeText( - maxLines:1, - leftActionButtonText, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16, color: leftButtonStyle.textColor, fontWeight: leftButtonStyle.fontWeight) + if (showLeftButton) + Expanded( + child: GestureDetector( + key: leftActionButtonKey, + onTap: actionLeft, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(999999), + color: leftButtonStyle.backgroundColor), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16, horizontal: 8), + child: AutoSizeText( + maxLines: 1, + leftActionButtonText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: leftButtonStyle.textColor, + fontWeight: leftButtonStyle.fontWeight)), ), - ), - )), - ), + )), + ), Expanded( child: GestureDetector( key: rightActionButtonKey, @@ -159,16 +154,17 @@ class BaseAlertDialog extends StatelessWidget { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(999999), - color: rightButtonStyle.backgroundColor - ), + color: rightButtonStyle.backgroundColor), child: Padding( padding: EdgeInsets.symmetric(vertical: 16, horizontal: 8), child: AutoSizeText( - maxLines: 1, - rightActionButtonText, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16, color: rightButtonStyle.textColor, fontWeight: rightButtonStyle.fontWeight) - ), + maxLines: 1, + rightActionButtonText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: rightButtonStyle.textColor, + fontWeight: rightButtonStyle.fontWeight)), ), )), ), @@ -185,7 +181,7 @@ class BaseAlertDialog extends StatelessWidget { radius: 50, backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, child: ClipOval( - child: CakeImageWidget (imageUrl: imageUrl, width: 100, height: 100, fit: BoxFit.cover), + child: CakeImageWidget(imageUrl: imageUrl, width: 100, height: 100, fit: BoxFit.cover), ), ), ); @@ -201,8 +197,7 @@ class BaseAlertDialog extends StatelessWidget { child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), child: Container( - decoration: - BoxDecoration(color: Theme.of(context).colorScheme.onSurface.withAlpha(25)), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.onSurface.withAlpha(25)), child: Center( child: Padding( padding: const EdgeInsets.all(16.0), @@ -229,9 +224,7 @@ class BaseAlertDialog extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ if (headerText?.isNotEmpty ?? false) headerTitle(context), - titleText != null - ? title(context) - : SizedBox(height: 16), + titleText != null ? title(context) : SizedBox(height: 16), isDividerExists ? Padding( padding: EdgeInsets.only(top: 16, bottom: 8), diff --git a/lib/src/widgets/base_text_form_field.dart b/lib/src/widgets/base_text_form_field.dart index f6402bea01..7329be545b 100644 --- a/lib/src/widgets/base_text_form_field.dart +++ b/lib/src/widgets/base_text_form_field.dart @@ -47,7 +47,8 @@ class BaseTextFormField extends StatelessWidget { this.suffixIconConstraints, super.key, this.suffixText, - this.borderRadius = const BorderRadius.all(Radius.circular(18)), this.onEditingComplete, + this.borderRadius = const BorderRadius.all(Radius.circular(18)), + this.onEditingComplete, }); final TextEditingController? controller; diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart index 122806ded3..c4cafb5a2a 100644 --- a/lib/src/widgets/blockchain_height_widget.dart +++ b/lib/src/widgets/blockchain_height_widget.dart @@ -195,7 +195,7 @@ class BlockchainHeightState extends State { height = decred!.heightByDate(date); } else if (widget.walletType == WalletType.monero) { height = monero!.getHeightByDate(date: date); - } else if (widget.walletType == WalletType.wownero){ + } else if (widget.walletType == WalletType.wownero) { height = wownero!.getHeightByDate(date: date); } else if (widget.walletType == WalletType.zcash) { height = await zcash!.getHeightByDate(date); diff --git a/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart index e98ead96a2..b546de0b47 100644 --- a/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/cake_pay_transaction_sent_bottom_sheet.dart @@ -49,8 +49,7 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { color: Theme.of(ctx).colorScheme.onSurfaceVariant, ); - Widget _buildHeader(BuildContext ctx) => - Column( + Widget _buildHeader(BuildContext ctx) => Column( children: [ const SizedBox(height: 12), Container( @@ -81,8 +80,7 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { ], ); - Widget _buildBody(BuildContext context) => - Padding( + Widget _buildBody(BuildContext context) => Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Column( children: [ @@ -123,7 +121,6 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { copyButtonOnPressed: () async => await Clipboard.setData(ClipboardData(text: paymentIdValue)), ), - Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Column( @@ -157,10 +154,7 @@ class CakePayTransactionSentBottomSheet extends StatelessWidget { return ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), child: Material( - color: Theme - .of(context) - .colorScheme - .surface, + color: Theme.of(context).colorScheme.surface, child: ConstrainedBox( constraints: BoxConstraints(maxHeight: maxHeight), child: SingleChildScrollView( @@ -210,17 +204,14 @@ class _StandardTile extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: Theme - .of(context) - .colorScheme - .surfaceContainerLowest - .withAlpha(80)), + color: Theme.of(context).colorScheme.surfaceContainerLowest.withAlpha(80)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Row( - mainAxisAlignment: copyButton ? MainAxisAlignment.spaceBetween : MainAxisAlignment.start, + mainAxisAlignment: + copyButton ? MainAxisAlignment.spaceBetween : MainAxisAlignment.start, children: [ if (imagePath != null) Padding( diff --git a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart index 9653652850..91581fe945 100644 --- a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart @@ -168,7 +168,8 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { final batchContactTitle = '${index + 1}/${outputs.length} - ${contactName.isEmpty ? 'Address' : contactName}'; final _address = item.isParsedAddress ? item.extractedAddress : item.address; - final _amount = '${item.cryptoAmount.sanitized()} ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}'; + final _amount = + '${item.cryptoAmount.sanitized()} ${amountParsingProxy?.getCryptoSymbol(currency) ?? currency.title}'; return isBatchSending || (contactName.isNotEmpty && !isCakePayName) ? ExpansionAddressTile( contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact, diff --git a/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart index 08705e18b7..d343bc5f4f 100644 --- a/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart @@ -100,7 +100,9 @@ class InfoBottomSheet extends BaseBottomSheet { ) else Container(), - SizedBox(height: 24,), + SizedBox( + height: 24, + ), if (content != null) Expanded( flex: 2, diff --git a/lib/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart index 1e78e41702..d14dab0952 100644 --- a/lib/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart @@ -34,7 +34,7 @@ class InfoStepsBottomSheet extends BaseBottomSheet { ), child: SingleChildScrollView( child: Column( - children: [ + children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 28.0), child: Column( @@ -96,18 +96,18 @@ class InfoStepsBottomSheet extends BaseBottomSheet { )) .toList(), ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: PrimaryButton( - text: S.of(context).close, - color: context.currentTheme.colorScheme.primary, + ), + Padding( + padding: const EdgeInsets.all(16), + child: PrimaryButton( + text: S.of(context).close, + color: context.currentTheme.colorScheme.primary, textColor: context.currentTheme.colorScheme.onPrimary, onPressed: () => Navigator.of(context).pop(), - ), - ) - ], - ), + ), + ) + ], + ), ), ), ); diff --git a/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart index 9540566c90..3c1dfde4f0 100644 --- a/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart @@ -139,8 +139,7 @@ class _PaymentConfirmationContent extends StatelessWidget { clipBehavior: Clip.none, children: [ Image.asset( - paymentFlowResult.addressDetectionResult?.detectedCurrency?.iconPath ?? - '', + paymentFlowResult.addressDetectionResult?.detectedCurrency?.iconPath ?? '', width: 70, height: 70, errorBuilder: (context, error, stackTrace) => Icon( diff --git a/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart index 3b2a9e630e..2e69513546 100644 --- a/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart @@ -288,7 +288,8 @@ class SwapConfirmationContentState extends State { SwapConfirmationTextfield( key: ValueKey('swap_confirmation_bottomsheet_address_textfield_key'), isAddress: true, - walletType: cryptoCurrencyOrTokenToWalletType(widget.exchangeViewModel.receiveCurrency), + walletType: + cryptoCurrencyOrTokenToWalletType(widget.exchangeViewModel.receiveCurrency), hintText: 'Destination Address', focusNode: _addressFocus, controller: _addressController, diff --git a/lib/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart index 12544c8b6e..ea563cab2b 100644 --- a/lib/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart @@ -112,7 +112,7 @@ class _SwapDetailsBottomSheetState extends State { buttonAction: () { _showingFailureDialog = false; Navigator.of(popupContext).pop(); - if(mounted) { + if (mounted) { Navigator.of(context, rootNavigator: true).pop(); } }, @@ -318,15 +318,13 @@ class _SwapDetailsContent extends StatelessWidget { children: [ _SwapDetailsTile( label: 'You Send', - value: - '${trade.amount} ${trade.from?.title ?? ''}', + value: '${trade.amount} ${trade.from?.title ?? ''}', valueFiatFormatted: exchangeTradeViewModel.sendAmountFiatFormatted, ), const SizedBox(height: 8), _SwapDetailsTile( label: 'You Get', - value: - '${trade.receiveAmount ?? '0'} ${trade.to?.title ?? ''}', + value: '${trade.receiveAmount ?? '0'} ${trade.to?.title ?? ''}', valueFiatFormatted: exchangeTradeViewModel .getReceiveAmountFiatFormatted(trade.receiveAmount ?? '0.0'), ), @@ -351,7 +349,8 @@ class _SwapDetailsContent extends StatelessWidget { const SizedBox(height: 4), AddressFormatter.buildSegmentedAddress( address: trade.payoutAddress ?? '', - walletType: trade.to != null ? cryptoCurrencyOrTokenToWalletType(trade.to!) : null, + walletType: + trade.to != null ? cryptoCurrencyOrTokenToWalletType(trade.to!) : null, evenTextStyle: Theme.of(context) .textTheme .bodyMedium! diff --git a/lib/src/widgets/bottom_sheet/token_selection_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/token_selection_bottom_sheet.dart index 3910f876c5..c260453338 100644 --- a/lib/src/widgets/bottom_sheet/token_selection_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/token_selection_bottom_sheet.dart @@ -78,7 +78,8 @@ class _TokenSelectionContentState extends State<_TokenSelectionContent> { void initState() { super.initState(); final baseNetwork = widget.fixedNetwork ?? WalletType.ethereum; - selectedNetwork = _resolveGenericETHDetectionResultToSpecificChain(baseNetwork, widget.paymentRequest.scheme.isNotEmpty); + selectedNetwork = _resolveGenericETHDetectionResultToSpecificChain( + baseNetwork, widget.paymentRequest.scheme.isNotEmpty); _autoSelectToken(); } @@ -101,9 +102,10 @@ class _TokenSelectionContentState extends State<_TokenSelectionContent> { return null; } - WalletType _resolveGenericETHDetectionResultToSpecificChain(WalletType network, bool hasURIScheme) { - if(hasURIScheme || network != WalletType.ethereum) return network; - + WalletType _resolveGenericETHDetectionResultToSpecificChain( + WalletType network, bool hasURIScheme) { + if (hasURIScheme || network != WalletType.ethereum) return network; + final current = widget.paymentViewModel.currentWalletType; if (isEVMCompatibleChain(current)) return current; return network; @@ -113,7 +115,8 @@ class _TokenSelectionContentState extends State<_TokenSelectionContent> { final initialNetwork = selectedNetwork; if (initialNetwork == null || !mounted) return; - final network = _resolveGenericETHDetectionResultToSpecificChain(initialNetwork, widget.paymentRequest.scheme.isNotEmpty); + final network = _resolveGenericETHDetectionResultToSpecificChain( + initialNetwork, widget.paymentRequest.scheme.isNotEmpty); setState(() { selectedNetwork = network; diff --git a/lib/src/widgets/check_box_picker.dart b/lib/src/widgets/check_box_picker.dart index 67aefba146..a43e501abc 100644 --- a/lib/src/widgets/check_box_picker.dart +++ b/lib/src/widgets/check_box_picker.dart @@ -40,12 +40,11 @@ class CheckBoxPickerState extends State { widget.title, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18, - - fontWeight: FontWeight.bold, - decoration: TextDecoration.none, - color: Theme.of(context).colorScheme.onSurface, - ), + fontSize: 18, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Theme.of(context).colorScheme.onSurface, + ), ), ), Padding( diff --git a/lib/src/widgets/checkbox_widget.dart b/lib/src/widgets/checkbox_widget.dart index f891de0927..aa0ad9731e 100644 --- a/lib/src/widgets/checkbox_widget.dart +++ b/lib/src/widgets/checkbox_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + class CheckboxWidget extends StatefulWidget { CheckboxWidget({required this.value, required this.caption, required this.onChanged}); diff --git a/lib/src/widgets/evm_switcher.dart b/lib/src/widgets/evm_switcher.dart index fb0cc96263..0e9799af5a 100644 --- a/lib/src/widgets/evm_switcher.dart +++ b/lib/src/widgets/evm_switcher.dart @@ -83,8 +83,7 @@ class _EvmSwitcherState extends State { .toList(growable: false); } - bool _hiddenSetsEqual(Set a, Set b) => - a.length == b.length && a.containsAll(b); + bool _hiddenSetsEqual(Set a, Set b) => a.length == b.length && a.containsAll(b); int get _selectedIndex { if (widget.currentChain == null) return -1; diff --git a/lib/src/widgets/haven_wallet_removal_popup.dart b/lib/src/widgets/haven_wallet_removal_popup.dart index e4c1767a79..4e3793aba7 100644 --- a/lib/src/widgets/haven_wallet_removal_popup.dart +++ b/lib/src/widgets/haven_wallet_removal_popup.dart @@ -36,12 +36,11 @@ class HavenWalletRemovalPopup extends StatelessWidget { alignment: Alignment.bottomCenter, child: DefaultTextStyle( style: Theme.of(context).textTheme.bodyMedium!.copyWith( - decoration: TextDecoration.none, - fontSize: 24.0, - fontWeight: FontWeight.bold, - - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + decoration: TextDecoration.none, + fontSize: 24.0, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), child: Text("Emergency Notice"), ), ), @@ -61,11 +60,10 @@ class HavenWalletRemovalPopup extends StatelessWidget { child: Text( "It looks like you have Haven wallets in your list. Haven is getting removed in next release of Cake Wallet, and you currently have Haven in the following wallets:\n\n[${affectedWalletNames.join(", ")}]\n\nPlease move your funds to other wallet, as you will lose access to your Haven funds in next update.\n\nFor assistance, please use the in-app support or email support@cakewallet.com", style: Theme.of(context).textTheme.bodyMedium!.copyWith( - decoration: TextDecoration.none, - fontSize: 16.0, - - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + decoration: TextDecoration.none, + fontSize: 16.0, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ), ) ], diff --git a/lib/src/widgets/index.dart b/lib/src/widgets/index.dart index e69de29bb2..8b13789179 100644 --- a/lib/src/widgets/index.dart +++ b/lib/src/widgets/index.dart @@ -0,0 +1 @@ + diff --git a/lib/src/widgets/new_list_row/list_Item_style_wrapper.dart b/lib/src/widgets/new_list_row/list_Item_style_wrapper.dart index 110bf01e08..600ff42690 100644 --- a/lib/src/widgets/new_list_row/list_Item_style_wrapper.dart +++ b/lib/src/widgets/new_list_row/list_Item_style_wrapper.dart @@ -45,26 +45,28 @@ class ListItemStyleWrapper extends StatelessWidget { bottom: Radius.circular(isLastInSection ? 18 : 0), ); - return ClipRSuperellipse( - borderRadius: radius, - child: Column( - children: [ - Container( - height: height, - decoration: ShapeDecoration( - shape: RoundedSuperellipseBorder( - borderRadius: radius, - ), - color: backgroundColor ?? theme.colorScheme.surfaceContainer, + return ClipRSuperellipse( + borderRadius: radius, + child: Column( + children: [ + Container( + height: height, + decoration: ShapeDecoration( + shape: RoundedSuperellipseBorder( + borderRadius: radius, ), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 12, vertical: height == null ? 11 : 0), - child: builder(context, textStyle, labelStyle))))), - if(iconPath != null && isLastInSection == false) Container( + color: backgroundColor ?? theme.colorScheme.surfaceContainer, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 12, vertical: height == null ? 11 : 0), + child: builder(context, textStyle, labelStyle))))), + if (iconPath != null && isLastInSection == false) + Container( color: theme.colorScheme.surfaceContainer, child: Padding( padding: const EdgeInsets.only(left: 50, right: 13), diff --git a/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart b/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart index a2a75d5524..6c4d769ba4 100644 --- a/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart +++ b/lib/src/widgets/new_list_row/list_item_checkbox_widget.dart @@ -14,7 +14,10 @@ class ListItemCheckboxWidget extends StatefulWidget { this.subtitleColor, this.onTap, this.isFirstInSection = false, - this.isLastInSection = false, this.subtitle, this.iconPath, this.showArrow = false, + this.isLastInSection = false, + this.subtitle, + this.iconPath, + this.showArrow = false, }); final String keyValue; @@ -34,15 +37,14 @@ class ListItemCheckboxWidget extends StatefulWidget { } class _ListItemCheckboxWidgetState extends State { - - @override Widget build(BuildContext context) { return ListItemStyleWrapper( iconPath: widget.iconPath, - onTap: widget.onTap ?? () { - widget.onChanged(!widget.value); - }, + onTap: widget.onTap ?? + () { + widget.onChanged(!widget.value); + }, isFirstInSection: widget.isFirstInSection, height: widget.subtitle != null ? 64 : 50, isLastInSection: widget.isLastInSection, @@ -56,7 +58,7 @@ class _ListItemCheckboxWidgetState extends State { children: [ if (widget.iconPath != null) widget.iconPath!.toLowerCase().endsWith("svg") - ? CakeImageWidget(imageUrl:widget.iconPath!, height: 26, width: 26) + ? CakeImageWidget(imageUrl: widget.iconPath!, height: 26, width: 26) : Image.asset( widget.iconPath!, width: 26, @@ -64,29 +66,31 @@ class _ListItemCheckboxWidgetState extends State { ), Expanded( child: Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - children: [ - Flexible(child: Text(widget.label)), - if (widget.showArrow) - Icon( - Icons.chevron_right, - size: 18, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ) - ], - ), - if (widget.subtitle != null) - Text( - widget.subtitle!, - style: TextStyle( - fontSize: 12, color: widget.subtitleColor ?? Theme.of(context).colorScheme.onSurfaceVariant), - ) - ], - ), + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Flexible(child: Text(widget.label)), + if (widget.showArrow) + Icon( + Icons.chevron_right, + size: 18, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ) + ], + ), + if (widget.subtitle != null) + Text( + widget.subtitle!, + style: TextStyle( + fontSize: 12, + color: widget.subtitleColor ?? + Theme.of(context).colorScheme.onSurfaceVariant), + ) + ], + ), ), ], ), diff --git a/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart b/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart index b2301b8814..21c242063f 100644 --- a/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart +++ b/lib/src/widgets/new_list_row/list_item_regular_row_widget.dart @@ -85,7 +85,7 @@ class ListItemRegularRowWidget extends StatelessWidget { final imageWidget = badgeIconPath != null ? Stack( - clipBehavior: Clip.none, + clipBehavior: Clip.none, children: [ leadingIcon!, Positioned( diff --git a/lib/src/widgets/new_list_row/list_item_selector_widget.dart b/lib/src/widgets/new_list_row/list_item_selector_widget.dart index c0b9b98096..fc70714450 100644 --- a/lib/src/widgets/new_list_row/list_item_selector_widget.dart +++ b/lib/src/widgets/new_list_row/list_item_selector_widget.dart @@ -12,7 +12,8 @@ class ListItemSelectorWidget extends StatelessWidget { required this.selectedIndex, required this.onChanged, this.isFirstInSection = false, - this.isLastInSection = false, this.onTap, + this.isLastInSection = false, + this.onTap, }); final String keyValue; @@ -45,16 +46,16 @@ class ListItemSelectorWidget extends StatelessWidget { options[selectedIndex], style: labelStyle, ), - CakeImageWidget(imageUrl: - "assets/new-ui/chooser.svg", + CakeImageWidget( + imageUrl: "assets/new-ui/chooser.svg", colorFilter: - ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn), + ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn), ), ], ), ), ], - ); + ); }); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/new_list_row/list_item_text_field_widget.dart b/lib/src/widgets/new_list_row/list_item_text_field_widget.dart index fd66df2c06..df91970dcc 100644 --- a/lib/src/widgets/new_list_row/list_item_text_field_widget.dart +++ b/lib/src/widgets/new_list_row/list_item_text_field_widget.dart @@ -26,8 +26,7 @@ class ListItemTextFieldWidget extends StatefulWidget { final bool isLastInSection; @override - State createState() => - _ListItemTextFieldWidgetState(); + State createState() => _ListItemTextFieldWidgetState(); } class _ListItemTextFieldWidgetState extends State { @@ -36,7 +35,7 @@ class _ListItemTextFieldWidgetState extends State { return ListItemStyleWrapper( isFirstInSection: widget.isFirstInSection, isLastInSection: widget.isLastInSection, - height:50, + height: 50, builder: (context, textStyle, labelStyle) { return Row( children: [ diff --git a/lib/src/widgets/new_list_row/list_item_toggle_widget.dart b/lib/src/widgets/new_list_row/list_item_toggle_widget.dart index 899fcd2467..c1743445d5 100644 --- a/lib/src/widgets/new_list_row/list_item_toggle_widget.dart +++ b/lib/src/widgets/new_list_row/list_item_toggle_widget.dart @@ -27,7 +27,6 @@ class ListItemToggleWidget extends StatefulWidget { } class _ListItemToggleWidgetState extends State { - @override void initState() { super.initState(); @@ -52,7 +51,7 @@ class _ListItemToggleWidgetState extends State { Flexible( child: Text(widget.label, style: textStyle, softWrap: true), ), - if(widget.leadingEndWidget != null) widget.leadingEndWidget! + if (widget.leadingEndWidget != null) widget.leadingEndWidget! ], ), ), diff --git a/lib/src/widgets/new_list_row/new_list_section.dart b/lib/src/widgets/new_list_row/new_list_section.dart index ad78013c8a..3dad8979d3 100644 --- a/lib/src/widgets/new_list_row/new_list_section.dart +++ b/lib/src/widgets/new_list_row/new_list_section.dart @@ -14,15 +14,14 @@ import 'package:cake_wallet/src/widgets/new_list_row/list_item_toggle_widget.dar import 'package:flutter/material.dart'; class NewListSections extends StatelessWidget { - const NewListSections({ - super.key, - required this.sections, - this.controllers = const {}, - this.tapHandlers = const {}, - this.getCheckboxValue, - this.updateCheckboxValue, - this.showHeader = false - }); + const NewListSections( + {super.key, + required this.sections, + this.controllers = const {}, + this.tapHandlers = const {}, + this.getCheckboxValue, + this.updateCheckboxValue, + this.showHeader = false}); final Map> sections; final Map controllers; diff --git a/lib/src/widgets/number_text_fild_widget.dart b/lib/src/widgets/number_text_fild_widget.dart index 03c5a3cd15..6c5c1c8f57 100644 --- a/lib/src/widgets/number_text_fild_widget.dart +++ b/lib/src/widgets/number_text_fild_widget.dart @@ -56,7 +56,7 @@ class _NumberTextFieldState extends State { @override Widget build(BuildContext context) => TextField( - style: Theme.of(context).textTheme.titleMedium!, + style: Theme.of(context).textTheme.titleMedium!, enableInteractiveSelection: false, textAlign: TextAlign.center, textAlignVertical: TextAlignVertical.bottom, @@ -78,16 +78,16 @@ class _NumberTextFieldState extends State { type: MaterialType.transparency, child: InkWell( child: Container( - width: widget.arrowsWidth, + width: widget.arrowsWidth, alignment: Alignment.bottomCenter, - child: Icon(Icons.keyboard_arrow_left_outlined ,size: widget.arrowsWidth)), + child: Icon(Icons.keyboard_arrow_left_outlined, size: widget.arrowsWidth)), onTap: _canGoDown ? () => _update(false) : null)), suffixIcon: Material( type: MaterialType.transparency, child: InkWell( child: Container( - width: widget.arrowsWidth, - alignment: Alignment.bottomCenter, + width: widget.arrowsWidth, + alignment: Alignment.bottomCenter, child: Icon(Icons.keyboard_arrow_right_outlined, size: widget.arrowsWidth)), onTap: _canGoUp ? () => _update(true) : null))), maxLines: 1, diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index 4934010c22..117c15d953 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -345,7 +345,8 @@ class _PickerState extends State> { Flexible( child: Text( key: ValueKey('picker_items_index_${itemName}_text_key'), - widget.displayItem?.call(item) ?? (item == CryptoCurrency.btcln ? "BTC (LN)" : item.toString()), + widget.displayItem?.call(item) ?? + (item == CryptoCurrency.btcln ? "BTC (LN)" : item.toString()), softWrap: true, style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontWeight: FontWeight.w600, diff --git a/lib/src/widgets/picker_inner_wrapper_widget.dart b/lib/src/widgets/picker_inner_wrapper_widget.dart index c8a5f81e90..5714719f46 100644 --- a/lib/src/widgets/picker_inner_wrapper_widget.dart +++ b/lib/src/widgets/picker_inner_wrapper_widget.dart @@ -3,8 +3,7 @@ import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; class PickerInnerWrapperWidget extends StatelessWidget { - PickerInnerWrapperWidget( - {required this.children, this.title, this.itemsHeight}); + PickerInnerWrapperWidget({required this.children, this.title, this.itemsHeight}); final List children; final String? title; @@ -48,10 +47,9 @@ class PickerInnerWrapperWidget extends StatelessWidget { color: Theme.of(context).colorScheme.surfaceContainerHighest, child: ConstrainedBox( constraints: BoxConstraints( - maxHeight: - itemsHeight != null && itemsHeight! <= containerHeight - ? itemsHeight! - : containerHeight, + maxHeight: itemsHeight != null && itemsHeight! <= containerHeight + ? itemsHeight! + : containerHeight, maxWidth: ResponsiveLayoutUtilBase.kPopupWidth, ), child: Column( diff --git a/lib/src/widgets/provider_optoin_tile.dart b/lib/src/widgets/provider_optoin_tile.dart index 45557309c4..7bd7f70d0d 100644 --- a/lib/src/widgets/provider_optoin_tile.dart +++ b/lib/src/widgets/provider_optoin_tile.dart @@ -105,8 +105,8 @@ class ProviderOptionTile extends StatelessWidget { children: [ Row( children: [ - ImageUtil.getImageFromPath(imagePath:imagePath, - height: imageHeight, width: imageWidth), + ImageUtil.getImageFromPath( + imagePath: imagePath, height: imageHeight, width: imageWidth), SizedBox(width: 8), Expanded( child: Container( diff --git a/lib/src/widgets/rounded_checkbox.dart b/lib/src/widgets/rounded_checkbox.dart index 8cd24e602b..97a5ffda41 100644 --- a/lib/src/widgets/rounded_checkbox.dart +++ b/lib/src/widgets/rounded_checkbox.dart @@ -9,19 +9,19 @@ class RoundedCheckbox extends StatelessWidget { @override Widget build(BuildContext context) { - return value - ? Container( - height: 20.0, - width: 20.0, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(50.0)), - color: Theme.of(context).colorScheme.primary, - ), - child: Icon( - Icons.check, - color: Theme.of(context).colorScheme.surface, - size: 14.0, - )) - : Offstage(); + return value + ? Container( + height: 20.0, + width: 20.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(50.0)), + color: Theme.of(context).colorScheme.primary, + ), + child: Icon( + Icons.check, + color: Theme.of(context).colorScheme.surface, + size: 14.0, + )) + : Offstage(); } } diff --git a/lib/src/widgets/rounded_icon_button.dart b/lib/src/widgets/rounded_icon_button.dart index 72d5491f38..912e67d494 100644 --- a/lib/src/widgets/rounded_icon_button.dart +++ b/lib/src/widgets/rounded_icon_button.dart @@ -27,7 +27,7 @@ class RoundedIconButton extends StatelessWidget { onPressed: onPressed, fillColor: fillColor ?? colorScheme.surfaceContainerHighest, elevation: 0, - constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30), + constraints: BoxConstraints.tightFor(width: width ?? 30, height: height ?? 30), padding: EdgeInsets.zero, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: shape ?? const CircleBorder(), diff --git a/lib/src/widgets/scrollable_with_bottom_section.dart b/lib/src/widgets/scrollable_with_bottom_section.dart index 295e5ca2d7..07a0702049 100644 --- a/lib/src/widgets/scrollable_with_bottom_section.dart +++ b/lib/src/widgets/scrollable_with_bottom_section.dart @@ -52,4 +52,4 @@ class ScrollableWithBottomSectionState extends State { cursorColor: Theme.of(context).colorScheme.primary, backgroundCursorColor: Theme.of(context).colorScheme.primary, validStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - backgroundColor: Colors.transparent, - fontWeight: FontWeight.normal, - fontSize: 16, - ), + color: Theme.of(context).colorScheme.onSurfaceVariant, + backgroundColor: Colors.transparent, + fontWeight: FontWeight.normal, + fontSize: 16, + ), invalidStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.errorContainer, backgroundColor: Colors.transparent, @@ -157,7 +157,8 @@ class SeedWidgetState extends State { padding: EdgeInsets.all(6), decoration: ShapeDecoration( color: Theme.of(context).colorScheme.surface, - shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(18)), + shape: RoundedSuperellipseBorder( + borderRadius: BorderRadius.circular(18)), ), child: Image.asset( 'assets/images/paste_ios.png', diff --git a/lib/src/widgets/seedphrase_grid_widget.dart b/lib/src/widgets/seedphrase_grid_widget.dart index a32cf1c1c6..be1d34f43f 100644 --- a/lib/src/widgets/seedphrase_grid_widget.dart +++ b/lib/src/widgets/seedphrase_grid_widget.dart @@ -9,8 +9,6 @@ class SeedPhraseGridWidget extends StatelessWidget { final List list; - - @override Widget build(BuildContext context) { int minTiles = 1; @@ -23,7 +21,6 @@ class SeedPhraseGridWidget extends StatelessWidget { int crossAxisCount = ((screenWidth + spacing - (2 * padding)) / (desiredTileWidth + spacing)).floor(); - if (crossAxisCount > maxTiles) crossAxisCount = maxTiles; if (crossAxisCount < minTiles) crossAxisCount = minTiles; @@ -44,9 +41,8 @@ class SeedPhraseGridWidget extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8), alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).colorScheme.surfaceContainerHigh - ), + borderRadius: BorderRadius.circular(8), + color: Theme.of(context).colorScheme.surfaceContainerHigh), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/src/widgets/simple_checkbox.dart b/lib/src/widgets/simple_checkbox.dart index 850d9bac1c..93d7b8abcd 100644 --- a/lib/src/widgets/simple_checkbox.dart +++ b/lib/src/widgets/simple_checkbox.dart @@ -26,9 +26,9 @@ class _SimpleCheckboxState extends State { checkColor: Theme.of(context).textTheme.titleLarge!.color, activeColor: Colors.transparent, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - side: WidgetStateBorderSide.resolveWith((states) => BorderSide( - color: Theme.of(context).textTheme.titleLarge!.color!, width: 1.0)), + side: WidgetStateBorderSide.resolveWith((states) => + BorderSide(color: Theme.of(context).textTheme.titleLarge!.color!, width: 1.0)), ), ); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/standard_checkbox.dart b/lib/src/widgets/standard_checkbox.dart index 64de5337d2..d67e368acf 100644 --- a/lib/src/widgets/standard_checkbox.dart +++ b/lib/src/widgets/standard_checkbox.dart @@ -66,12 +66,11 @@ class StandardCheckbox extends StatelessWidget { caption, softWrap: true, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16.0, - - fontWeight: FontWeight.normal, - color: captionColor ?? Theme.of(context).colorScheme.onSurface, - decoration: TextDecoration.none, - ), + fontSize: 16.0, + fontWeight: FontWeight.normal, + color: captionColor ?? Theme.of(context).colorScheme.onSurface, + decoration: TextDecoration.none, + ), ), ), ) diff --git a/lib/src/widgets/standard_list.dart b/lib/src/widgets/standard_list.dart index f0afdcde51..52a06a7409 100644 --- a/lib/src/widgets/standard_list.dart +++ b/lib/src/widgets/standard_list.dart @@ -50,7 +50,6 @@ class StandardListRow extends StatelessWidget { Widget? buildLeading(BuildContext context) => null; Widget buildCenter(BuildContext context, {required bool hasLeftOffset}) { - return Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.start, @@ -84,17 +83,17 @@ class StandardListRow extends StatelessWidget { ), ), ) - else - Align( - alignment: Alignment.centerLeft, - child: Text( - title, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: titleColor(context), - fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400, + else + Align( + alignment: Alignment.centerLeft, + child: Text( + title, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: titleColor(context), + fontWeight: isSelected ? FontWeight.w800 : FontWeight.w400, + ), ), ), - ), ], ), ) diff --git a/lib/src/widgets/standard_list_status_row.dart b/lib/src/widgets/standard_list_status_row.dart index 42032593d6..7a84cb87a8 100644 --- a/lib/src/widgets/standard_list_status_row.dart +++ b/lib/src/widgets/standard_list_status_row.dart @@ -47,9 +47,9 @@ class StandardListStatusRow extends StatelessWidget { Text( value, style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 16, - fontWeight: FontWeight.w500, - ), + fontSize: 16, + fontWeight: FontWeight.w500, + ), ) ], ), diff --git a/lib/src/widgets/standard_slide_button_widget.dart b/lib/src/widgets/standard_slide_button_widget.dart index 5a7c019c94..4f143c05fa 100644 --- a/lib/src/widgets/standard_slide_button_widget.dart +++ b/lib/src/widgets/standard_slide_button_widget.dart @@ -113,7 +113,9 @@ class StandardSlideButtonState extends State { child: Icon( key: ValueKey('standard_slide_button_widget_slider_icon_key'), Icons.arrow_forward, - color: widget.isDisabled ? Theme.of(context).colorScheme.onSurface.withOpacity(0.2) : Theme.of(context).colorScheme.onSurface, + color: widget.isDisabled + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.2) + : Theme.of(context).colorScheme.onSurface, ), ), ), diff --git a/lib/src/widgets/validable_annotated_editable_text.dart b/lib/src/widgets/validable_annotated_editable_text.dart index 7a01506d7d..91400811dd 100644 --- a/lib/src/widgets/validable_annotated_editable_text.dart +++ b/lib/src/widgets/validable_annotated_editable_text.dart @@ -65,7 +65,6 @@ class ValidatableAnnotatedEditableText extends EditableText { backgroundCursorColor: backgroundCursorColor, onChanged: onChanged, onSubmitted: onSubmitted, - toolbarOptions: const ToolbarOptions( copy: true, cut: true, @@ -120,17 +119,17 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState { annotation = Annotation( range: TextRange(start: 0, end: item.range.start), style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - backgroundColor: Colors.transparent, - ), + color: Theme.of(context).colorScheme.onSurfaceVariant, + backgroundColor: Colors.transparent, + ), ); } else if (prev.range.end < item.range.start) { annotation = Annotation( range: TextRange(start: prev.range.end, end: item.range.start), style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onError, - backgroundColor: Colors.transparent, - ), + color: Theme.of(context).colorScheme.onError, + backgroundColor: Colors.transparent, + ), ); } @@ -146,9 +145,9 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState { Annotation( range: TextRange(start: result.last.range.end, end: text.length), style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - backgroundColor: Colors.transparent, - ), + color: Theme.of(context).colorScheme.onSurfaceVariant, + backgroundColor: Colors.transparent, + ), ), ); } diff --git a/lib/src/widgets/vulnerable_seeds_popup.dart b/lib/src/widgets/vulnerable_seeds_popup.dart index 2326360ae9..b4c2dc3e85 100644 --- a/lib/src/widgets/vulnerable_seeds_popup.dart +++ b/lib/src/widgets/vulnerable_seeds_popup.dart @@ -36,12 +36,11 @@ class VulnerableSeedsPopup extends StatelessWidget { alignment: Alignment.bottomCenter, child: DefaultTextStyle( style: Theme.of(context).textTheme.bodyMedium!.copyWith( - decoration: TextDecoration.none, - fontSize: 24.0, - fontWeight: FontWeight.bold, - - color: Theme.of(context).colorScheme.onSurface, - ), + decoration: TextDecoration.none, + fontSize: 24.0, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + ), child: Text("Emergency Notice"), ), ), @@ -61,12 +60,10 @@ class VulnerableSeedsPopup extends StatelessWidget { child: Text( "Your Bitcoin wallet(s) below use a legacy seed format that is vulnerable, which MAY result in you losing money from these wallet(s) if no action is taken.\nWe recommend that you IMMEDIATELY create wallet(s) in Cake Wallet and immediately transfer the funds to these wallet(s).\nVulnerable wallet name(s):\n\n[${affectedWalletNames.join(", ")}]\n\nFor assistance, please use the in-app support or email support@cakewallet.com", style: Theme.of(context).textTheme.bodyMedium!.copyWith( - decoration: TextDecoration.none, - fontSize: 16.0, - - color: Theme.of(context) - .colorScheme.onSurface, - ), + decoration: TextDecoration.none, + fontSize: 16.0, + color: Theme.of(context).colorScheme.onSurface, + ), ), ) ], diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index 70ff844d8c..b3ac158d9e 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -48,7 +48,6 @@ abstract class AppStoreBase with Store { SettingsStore settingsStore; - ThemeStore themeStore; @observable diff --git a/lib/store/dashboard/order_filter_store.dart b/lib/store/dashboard/order_filter_store.dart index d7f856d720..f82d4241b6 100644 --- a/lib/store/dashboard/order_filter_store.dart +++ b/lib/store/dashboard/order_filter_store.dart @@ -30,8 +30,7 @@ abstract class OrderFilterStoreBase with Store { required List orders, required WalletBase wallet, }) { - final walletOrders = - orders.where((item) => item.order.walletId == wallet.id).toList(); + final walletOrders = orders.where((item) => item.order.walletId == wallet.id).toList(); final cakePayOrders = walletOrders.where((item) { final order = item.order; @@ -43,4 +42,4 @@ abstract class OrderFilterStoreBase with Store { if (!displayCakePay) return []; return cakePayOrders; } -} \ No newline at end of file +} diff --git a/lib/store/dashboard/payjoin_transactions_store.dart b/lib/store/dashboard/payjoin_transactions_store.dart index 9a22921bff..37841e412d 100644 --- a/lib/store/dashboard/payjoin_transactions_store.dart +++ b/lib/store/dashboard/payjoin_transactions_store.dart @@ -8,8 +8,7 @@ import 'package:mobx/mobx.dart'; part 'payjoin_transactions_store.g.dart'; -class PayjoinTransactionsStore = PayjoinTransactionsStoreBase - with _$PayjoinTransactionsStore; +class PayjoinTransactionsStore = PayjoinTransactionsStoreBase with _$PayjoinTransactionsStore; abstract class PayjoinTransactionsStoreBase with Store { PayjoinTransactionsStoreBase({ diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index 57e3149eee..18d4038e71 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -71,20 +71,20 @@ abstract class TradeFilterStoreBase with Store { @computed int get enabledProvidersCount => [ - displayChangeNow, - displaySideShift, - displaySimpleSwap, - displayTrocador, - displayExolix, - displayChainflip, - displayThorChain, - displayLetsExchange, - displayStealthEx, - displayXOSwap, - displaySwapTrade, - displaySwapXyz, - displayNearIntents - ].where((item) => item).length; + displayChangeNow, + displaySideShift, + displaySimpleSwap, + displayTrocador, + displayExolix, + displayChainflip, + displayThorChain, + displayLetsExchange, + displayStealthEx, + displayXOSwap, + displaySwapTrade, + displaySwapXyz, + displayNearIntents + ].where((item) => item).length; @computed bool get displayAllTrades => @@ -189,12 +189,12 @@ abstract class TradeFilterStoreBase with Store { } List filtered({required List trades, required WalletBase wallet}) { - final _trades = trades - .where((item) { - final isSameChain = item.trade.chainId != null ? item.trade.chainId == wallet.chainId : true; // returning default as true here so it falls back to the default checks if there's no chainId - return item.trade.walletId == wallet.id && isTradeInAccount(item, wallet) && isSameChain; - }) - .toList(); + final _trades = trades.where((item) { + final isSameChain = item.trade.chainId != null + ? item.trade.chainId == wallet.chainId + : true; // returning default as true here so it falls back to the default checks if there's no chainId + return item.trade.walletId == wallet.id && isTradeInAccount(item, wallet) && isSameChain; + }).toList(); final needToFilter = !displayAllTrades; return needToFilter @@ -217,12 +217,14 @@ abstract class TradeFilterStoreBase with Store { item.trade.provider == ExchangeProviderDescription.thorChain) || (displayLetsExchange && item.trade.provider == ExchangeProviderDescription.letsExchange) || - (displayStealthEx && item.trade.provider == ExchangeProviderDescription.stealthEx) || + (displayStealthEx && + item.trade.provider == ExchangeProviderDescription.stealthEx) || (displayXOSwap && item.trade.provider == ExchangeProviderDescription.xoSwap) || - (displaySwapTrade && item.trade.provider == ExchangeProviderDescription.swapTrade) || - (displaySwapXyz && - item.trade.provider == ExchangeProviderDescription.swapsXyz) || - (displayNearIntents && item.trade.provider == ExchangeProviderDescription.nearIntents)) + (displaySwapTrade && + item.trade.provider == ExchangeProviderDescription.swapTrade) || + (displaySwapXyz && item.trade.provider == ExchangeProviderDescription.swapsXyz) || + (displayNearIntents && + item.trade.provider == ExchangeProviderDescription.nearIntents)) .toList() : _trades; } diff --git a/lib/store/node_list_store.dart b/lib/store/node_list_store.dart index e69de29bb2..8b13789179 100644 --- a/lib/store/node_list_store.dart +++ b/lib/store/node_list_store.dart @@ -0,0 +1 @@ + diff --git a/lib/store/seed_settings_store.dart b/lib/store/seed_settings_store.dart index 90c02ba978..dff167cb58 100644 --- a/lib/store/seed_settings_store.dart +++ b/lib/store/seed_settings_store.dart @@ -5,7 +5,6 @@ part 'seed_settings_store.g.dart'; class SeedSettingsStore = SeedSettingsStoreBase with _$SeedSettingsStore; abstract class SeedSettingsStoreBase with Store { - @observable String? passphrase; } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index b06e2acb62..d67c1f045b 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -247,7 +247,6 @@ abstract class SettingsStoreBase with Store { priority[WalletType.ethereum] = initialEthereumTransactionPriority; } - if (initialPolygonTransactionPriority != null) { priority[WalletType.polygon] = initialPolygonTransactionPriority; } @@ -302,7 +301,8 @@ abstract class SettingsStoreBase with Store { reaction((_) => shouldShowRepWarning, (bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val)); - reaction((_)=>mwebAdDismissed, (val)=>sharedPreferences.setBool(PreferencesKey.mwebAdDismissed, val)); + reaction((_) => mwebAdDismissed, + (val) => sharedPreferences.setBool(PreferencesKey.mwebAdDismissed, val)); priority.observe((change) { final String? key; @@ -570,42 +570,40 @@ abstract class SettingsStoreBase with Store { reaction( (_) => lookupsZcashNames, - (bool looksUpZcashNames) => _sharedPreferences.setBool( - PreferencesKey.lookupsZcashNames, looksUpZcashNames)); + (bool looksUpZcashNames) => + _sharedPreferences.setBool(PreferencesKey.lookupsZcashNames, looksUpZcashNames)); reaction( - (_) => lookupsZcashAddress, - (bool lookupsZcashAddress) => _sharedPreferences.setBool( - PreferencesKey.lookupsZcashAddress, lookupsZcashAddress)); + (_) => lookupsZcashAddress, + (bool lookupsZcashAddress) => + _sharedPreferences.setBool(PreferencesKey.lookupsZcashAddress, lookupsZcashAddress)); reaction( (_) => lookupsWellKnown, (bool looksUpWellKnown) => _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown)); - reaction( - (_) => lookupsFio, - (bool lookupsFio) => - _sharedPreferences.setBool(PreferencesKey.lookupsFio, lookupsFio)); + reaction((_) => lookupsFio, + (bool lookupsFio) => _sharedPreferences.setBool(PreferencesKey.lookupsFio, lookupsFio)); reaction( - (_) => lookupsNostr, - (bool lookupsNostr) => + (_) => lookupsNostr, + (bool lookupsNostr) => _sharedPreferences.setBool(PreferencesKey.lookupsNostr, lookupsNostr)); reaction( - (_) => lookupsThorChain, - (bool lookupsThorChain) => + (_) => lookupsThorChain, + (bool lookupsThorChain) => _sharedPreferences.setBool(PreferencesKey.lookupsThorChain, lookupsThorChain)); reaction( - (_) => lookupsBip353, - (bool lookupsBip353) => + (_) => lookupsBip353, + (bool lookupsBip353) => _sharedPreferences.setBool(PreferencesKey.lookupsBip353, lookupsBip353)); reaction( - (_) => lookupsLNUrl, - (bool lookupsLNUrl) => + (_) => lookupsLNUrl, + (bool lookupsLNUrl) => _sharedPreferences.setBool(PreferencesKey.lookupsLNUrl, lookupsLNUrl)); reaction((_) => usePayjoin, @@ -726,8 +724,8 @@ abstract class SettingsStoreBase with Store { reaction( (_) => showZcashMissingFundsCard, - (bool showZcashMissingFundsCard) => - _sharedPreferences.setBool(PreferencesKey.showZcashMissingFundsCard, showZcashMissingFundsCard)); + (bool showZcashMissingFundsCard) => _sharedPreferences.setBool( + PreferencesKey.showZcashMissingFundsCard, showZcashMissingFundsCard)); reaction((_) => mwebEnabled, (bool mwebEnabled) => _sharedPreferences.setBool(PreferencesKey.mwebEnabled, mwebEnabled)); @@ -765,8 +763,8 @@ abstract class SettingsStoreBase with Store { reaction( (_) => balanceHideCounter, - (int balanceHideCounter) => _sharedPreferences.setInt(PreferencesKey.balanceHideCounter, balanceHideCounter) - ); + (int balanceHideCounter) => + _sharedPreferences.setInt(PreferencesKey.balanceHideCounter, balanceHideCounter)); this.nodes.observe((change) { if (change.newValue != null && change.key != null) { @@ -1147,7 +1145,8 @@ abstract class SettingsStoreBase with Store { return priority[walletType]; } - void setPriority(WalletType walletType, TransactionPriority priority, {int? chainId}) => this.priority[walletType] = priority; + void setPriority(WalletType walletType, TransactionPriority priority, {int? chainId}) => + this.priority[walletType] = priority; bool isBitcoinBuyEnabled; @@ -1158,8 +1157,7 @@ abstract class SettingsStoreBase with Store { _sharedPreferences.setBool(PreferencesKey.shouldShowReceiveWarning, value); static Future load( - { - required bool isBitcoinBuyEnabled, + {required bool isBitcoinBuyEnabled, FiatCurrency initialFiatCurrency = FiatCurrency.usd, BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance}) async { final sharedPreferences = await getIt.getAsync(); @@ -1281,10 +1279,14 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true; final showAddressBookPopupEnabled = sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ?? true; - final forceDecentralizedExchanges = await sharedPreferences.getBool(PreferencesKey.forceDecentralizedExchanges) ?? false; - final decentralizedExchangesPromptDismissed = await sharedPreferences.getBool(PreferencesKey.decentralizedExchangesPromptDismissed) ?? false; + final forceDecentralizedExchanges = + await sharedPreferences.getBool(PreferencesKey.forceDecentralizedExchanges) ?? false; + final decentralizedExchangesPromptDismissed = + await sharedPreferences.getBool(PreferencesKey.decentralizedExchangesPromptDismissed) ?? + false; final syncStatusDisplayMode = SyncStatusDisplayModeExtension.fromString( - sharedPreferences.getString(PreferencesKey.syncStatusDisplayMode) ?? SyncStatusDisplayMode.blocksRemaining.name); + sharedPreferences.getString(PreferencesKey.syncStatusDisplayMode) ?? + SyncStatusDisplayMode.blocksRemaining.name); final exchangeStatus = ExchangeApiMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ?? ExchangeApiMode.enabled.raw); @@ -1323,7 +1325,8 @@ abstract class SettingsStoreBase with Store { final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; final lookupsZcashNames = sharedPreferences.getBool(PreferencesKey.lookupsZcashNames) ?? true; - final lookupsZcashAddress = sharedPreferences.getBool(PreferencesKey.lookupsZcashAddress) ?? true; + final lookupsZcashAddress = + sharedPreferences.getBool(PreferencesKey.lookupsZcashAddress) ?? true; final lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true; final lookupsFio = sharedPreferences.getBool(PreferencesKey.lookupsFio) ?? true; final lookupsNostr = sharedPreferences.getBool(PreferencesKey.lookupsNostr) ?? true; @@ -1337,7 +1340,8 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; final mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false; final mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true; - final showZcashMissingFundsCard = sharedPreferences.getBool(PreferencesKey.showZcashMissingFundsCard) ?? true; + final showZcashMissingFundsCard = + sharedPreferences.getBool(PreferencesKey.showZcashMissingFundsCard) ?? true; final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false; final hasEnabledMwebBefore = sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false; @@ -1376,7 +1380,6 @@ abstract class SettingsStoreBase with Store { final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); final dogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey); - final nodeSource = await Node.getAll(); final powNodeSource = await Node.getAllPow(); @@ -1389,52 +1392,36 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.firstWhereOrNull((e) => e.id == litecoinElectrumServerId) ?? nodeSource.firstWhereOrNull((e) => e.uriRaw == cakeWalletLitecoinElectrumUri); - final ethereumNode = - nodeSource.firstWhereOrNull((e) => e.id == ethereumNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == ethereumDefaultNodeUri); - final polygonNode = - nodeSource.firstWhereOrNull((e) => e.id == polygonNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri); - final baseNode = - nodeSource.firstWhereOrNull((e) => e.id == baseNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == baseDefaultNodeUri); - final arbitrumNode = - nodeSource.firstWhereOrNull((e) => e.id == arbitrumNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == arbitrumDefaultNodeUri); + final ethereumNode = nodeSource.firstWhereOrNull((e) => e.id == ethereumNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == ethereumDefaultNodeUri); + final polygonNode = nodeSource.firstWhereOrNull((e) => e.id == polygonNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri); + final baseNode = nodeSource.firstWhereOrNull((e) => e.id == baseNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == baseDefaultNodeUri); + final arbitrumNode = nodeSource.firstWhereOrNull((e) => e.id == arbitrumNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == arbitrumDefaultNodeUri); final bitcoinCashElectrumServer = nodeSource.firstWhereOrNull((e) => e.id == bitcoinCashElectrumServerId) ?? - nodeSource.firstWhereOrNull( - (e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri); - final nanoNode = - nodeSource.firstWhereOrNull((e) => e.id == nanoNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == nanoDefaultNodeUri); - final decredNode = - nodeSource.firstWhereOrNull((e) => e.id == decredNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == decredDefaultUri); - final nanoPowNode = - powNodeSource.firstWhereOrNull((e) => e.id == nanoPowNodeId) ?? - powNodeSource.firstWhereOrNull( - (e) => e.uriRaw == nanoDefaultPowNodeUri); - final solanaNode = - nodeSource.firstWhereOrNull((e) => e.id == solanaNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == solanaDefaultNodeUri); - final tronNode = - nodeSource.firstWhereOrNull((e) => e.id == tronNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == tronDefaultNodeUri); - final wowneroNode = - nodeSource.firstWhereOrNull((e) => e.id == wowneroNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == wowneroDefaultNodeUri); - final zanoNode = - nodeSource.firstWhereOrNull((e) => e.id == zanoNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == zanoDefaultNodeUri); - final dogecoinNode = - nodeSource.firstWhereOrNull((e) => e.id == dogecoinNodeId) ?? - nodeSource.firstWhereOrNull((e) => e.uriRaw == dogecoinDefaultNodeUri); - final zcashNode = - nodeSource.firstWhereOrNull((e) => e.id == zcashNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri); + final nanoNode = nodeSource.firstWhereOrNull((e) => e.id == nanoNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == nanoDefaultNodeUri); + final decredNode = nodeSource.firstWhereOrNull((e) => e.id == decredNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == decredDefaultUri); + final nanoPowNode = powNodeSource.firstWhereOrNull((e) => e.id == nanoPowNodeId) ?? + powNodeSource.firstWhereOrNull((e) => e.uriRaw == nanoDefaultPowNodeUri); + final solanaNode = nodeSource.firstWhereOrNull((e) => e.id == solanaNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == solanaDefaultNodeUri); + final tronNode = nodeSource.firstWhereOrNull((e) => e.id == tronNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == tronDefaultNodeUri); + final wowneroNode = nodeSource.firstWhereOrNull((e) => e.id == wowneroNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == wowneroDefaultNodeUri); + final zanoNode = nodeSource.firstWhereOrNull((e) => e.id == zanoNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == zanoDefaultNodeUri); + final dogecoinNode = nodeSource.firstWhereOrNull((e) => e.id == dogecoinNodeId) ?? + nodeSource.firstWhereOrNull((e) => e.uriRaw == dogecoinDefaultNodeUri); + final zcashNode = nodeSource.firstWhereOrNull((e) => e.id == zcashNodeId) ?? nodeSource.firstWhereOrNull((e) => e.uriRaw == zcashDefaultNodeUri); - final bscNode = - nodeSource.firstWhereOrNull((e) => e.id == bscNodeId) ?? + final bscNode = nodeSource.firstWhereOrNull((e) => e.id == bscNodeId) ?? nodeSource.firstWhereOrNull((e) => e.uriRaw == bscDefaultNodeUri); final packageInfo = await PackageInfo.fromPlatform(); @@ -1654,7 +1641,8 @@ abstract class SettingsStoreBase with Store { final mwebAdDismissed = await sharedPreferences.getBool(PreferencesKey.mwebAdDismissed) ?? false; - final balanceHideCounter = await sharedPreferences.getInt(PreferencesKey.balanceHideCounter) ?? 0; + final balanceHideCounter = + await sharedPreferences.getInt(PreferencesKey.balanceHideCounter) ?? 0; return SettingsStore( secureStorage: secureStorage, @@ -1899,7 +1887,8 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ?? showAddressBookPopupEnabled; syncStatusDisplayMode = SyncStatusDisplayModeExtension.fromString( - sharedPreferences.getString(PreferencesKey.syncStatusDisplayMode) ?? SyncStatusDisplayMode.blocksRemaining.name); + sharedPreferences.getString(PreferencesKey.syncStatusDisplayMode) ?? + SyncStatusDisplayMode.blocksRemaining.name); exchangeStatus = ExchangeApiMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ?? ExchangeApiMode.enabled.raw); @@ -1950,15 +1939,15 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; - lookupsZcashNames = - sharedPreferences.getBool(PreferencesKey.lookupsZcashNames) ?? true; + lookupsZcashNames = sharedPreferences.getBool(PreferencesKey.lookupsZcashNames) ?? true; lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true; customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; silentPaymentsCardDisplay = sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false; mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true; - showZcashMissingFundsCard = sharedPreferences.getBool(PreferencesKey.showZcashMissingFundsCard) ?? true; + showZcashMissingFundsCard = + sharedPreferences.getBool(PreferencesKey.showZcashMissingFundsCard) ?? true; mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false; hasEnabledMwebBefore = sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false; final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); @@ -2001,7 +1990,6 @@ abstract class SettingsStoreBase with Store { final decredNode = await Node.get(decredNodeId ?? -1); final dogecoinNode = await Node.get(dogecoinNodeId ?? -1); - if (moneroNode != null) { nodes[WalletType.monero] = moneroNode; } @@ -2173,15 +2161,12 @@ abstract class SettingsStoreBase with Store { } Future _saveCurrentNode(Node node, WalletType walletType) async { - switch (walletType) { case WalletType.bitcoin: - await _sharedPreferences.setInt( - PreferencesKey.currentBitcoinElectrumSererIdKey, node.id); + await _sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, node.id); break; case WalletType.litecoin: - await _sharedPreferences.setInt( - PreferencesKey.currentLitecoinElectrumSererIdKey, node.id); + await _sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, node.id); break; case WalletType.monero: await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, node.id); @@ -2200,8 +2185,7 @@ abstract class SettingsStoreBase with Store { nodes[node.type] = node; break; case WalletType.bitcoinCash: - await _sharedPreferences.setInt( - PreferencesKey.currentBitcoinCashNodeIdKey, node.id); + await _sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.id); break; case WalletType.nano: await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.id); diff --git a/lib/store/templates/send_template_store.dart b/lib/store/templates/send_template_store.dart index 3ffa1dedab..a1fff698ef 100644 --- a/lib/store/templates/send_template_store.dart +++ b/lib/store/templates/send_template_store.dart @@ -8,8 +8,7 @@ part 'send_template_store.g.dart'; class SendTemplateStore = SendTemplateBase with _$SendTemplateStore; abstract class SendTemplateBase with Store { - SendTemplateBase({required this.templateSource}) - : templates = ObservableList