diff --git a/lib/pages/churning/churning_progress_view.dart b/lib/pages/churning/churning_progress_view.dart index b8b152e41..8e732e643 100644 --- a/lib/pages/churning/churning_progress_view.dart +++ b/lib/pages/churning/churning_progress_view.dart @@ -186,8 +186,11 @@ class _ChurningProgressViewState extends ConsumerState { height: 20, ), ProgressItem( - iconAsset: Assets.svg.peers, - label: "Waiting for balance to unlock", + iconAsset: Assets.svg.alertCircle, + label: "Waiting for balance to unlock ${ref.watch( + pChurningService(widget.walletId) + .select((s) => s.confirmsInfo), + ) ?? ""}", status: ref.watch( pChurningService(widget.walletId) .select((s) => s.waitingForUnlockedBalance), @@ -197,7 +200,7 @@ class _ChurningProgressViewState extends ConsumerState { height: 12, ), ProgressItem( - iconAsset: Assets.svg.fusing, + iconAsset: Assets.svg.churn, label: "Creating churn transaction", status: ref.watch( pChurningService(widget.walletId) diff --git a/lib/pages/churning/churning_view.dart b/lib/pages/churning/churning_view.dart index dad480778..d59042e5e 100644 --- a/lib/pages/churning/churning_view.dart +++ b/lib/pages/churning/churning_view.dart @@ -17,6 +17,7 @@ import '../../widgets/custom_buttons/checkbox_text_button.dart'; import '../../widgets/desktop/primary_button.dart'; import '../../widgets/rounded_container.dart'; import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_text_field.dart'; import 'churning_progress_view.dart'; import 'churning_rounds_selection_sheet.dart'; @@ -111,7 +112,23 @@ class _ChurnViewState extends ConsumerState { .topNavIconPrimary, ), onPressed: () async { - //' TODO show about? + await showDialog( + context: context, + builder: (context) => const StackOkDialog( + title: "What is churning?", + message: "Churning in a Monero wallet involves" + " sending Monero to oneself in multiple" + " transactions, which can enhance privacy" + " by making it harder for observers to " + "link your transactions. This process" + " re-mixes the funds within the network," + " helping obscure transaction history. " + "Churning is optional and mainly beneficial" + " in scenarios where maximum privacy is" + " desired or if you received the Monero from" + " a source from which you'd like to disassociate.", + ), + ); }, ), ), diff --git a/lib/pages_desktop_specific/churning/desktop_churning_view.dart b/lib/pages_desktop_specific/churning/desktop_churning_view.dart index fdd34b3f3..e3a7b0736 100644 --- a/lib/pages_desktop_specific/churning/desktop_churning_view.dart +++ b/lib/pages_desktop_specific/churning/desktop_churning_view.dart @@ -134,6 +134,9 @@ class _DesktopChurning extends ConsumerState { Assets.svg.churn, width: 32, height: 32, + color: Theme.of(context) + .extension()! + .textSubtitle1, ), const SizedBox( width: 12, @@ -206,7 +209,17 @@ class _DesktopChurning extends ConsumerState { height: 16, ), Text( - "Churning info text", + "Churning in a Monero wallet involves" + " sending Monero to oneself in multiple" + " transactions, which can enhance privacy" + " by making it harder for observers to " + "link your transactions. This process" + " re-mixes the funds within the network," + " helping obscure transaction history. " + "Churning is optional and mainly beneficial" + " in scenarios where maximum privacy is" + " desired or if you received the Monero from" + " a source from which you'd like to disassociate.", style: STextStyles.desktopTextMedium( context, diff --git a/lib/pages_desktop_specific/churning/sub_widgets/churning_dialog.dart b/lib/pages_desktop_specific/churning/sub_widgets/churning_dialog.dart index 3c7d96b1f..44615e244 100644 --- a/lib/pages_desktop_specific/churning/sub_widgets/churning_dialog.dart +++ b/lib/pages_desktop_specific/churning/sub_widgets/churning_dialog.dart @@ -236,8 +236,11 @@ class _ChurnDialogViewState extends ConsumerState { height: 20, ), ProgressItem( - iconAsset: Assets.svg.peers, - label: "Waiting for balance to unlock", + iconAsset: Assets.svg.alertCircle, + label: "Waiting for balance to unlock ${ref.watch( + pChurningService(widget.walletId) + .select((s) => s.confirmsInfo), + ) ?? ""}", status: ref.watch( pChurningService(widget.walletId) .select((s) => s.waitingForUnlockedBalance), @@ -247,7 +250,7 @@ class _ChurnDialogViewState extends ConsumerState { height: 12, ), ProgressItem( - iconAsset: Assets.svg.fusing, + iconAsset: Assets.svg.churn, label: "Creating churn transaction", status: ref.watch( pChurningService(widget.walletId) diff --git a/lib/services/churning_service.dart b/lib/services/churning_service.dart index fb3bca6e1..0aecefe08 100644 --- a/lib/services/churning_service.dart +++ b/lib/services/churning_service.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:cs_monero/cs_monero.dart'; import 'package:flutter/cupertino.dart'; @@ -42,6 +43,56 @@ class ChurningService extends ChangeNotifier { } } + String? confirmsInfo; + Future _updateConfirmsInfo() async { + final currentHeight = wallet.currentKnownChainHeight; + if (currentHeight < 1) { + return; + } + + final outputs = await csWallet.getOutputs(refresh: true); + final required = wallet.cryptoCurrency.minConfirms; + + int lowestNumberOfConfirms = required; + + for (final output in outputs.where((e) => !e.isFrozen && !e.spent)) { + final confirms = currentHeight - output.height; + + lowestNumberOfConfirms = min(lowestNumberOfConfirms, confirms); + } + + final bool shouldNotify; + if (lowestNumberOfConfirms == required) { + shouldNotify = confirmsInfo != null; + confirmsInfo = null; + } else { + final prev = confirmsInfo; + confirmsInfo = "($lowestNumberOfConfirms/$required)"; + shouldNotify = confirmsInfo != prev; + } + + if (_running && _timerRunning && shouldNotify) { + notifyListeners(); + } + } + + Timer? _confirmsTimer; + bool _timerRunning = false; + void _stopConfirmsTimer() { + _timerRunning = false; + _confirmsTimer?.cancel(); + confirmsInfo = null; + _confirmsTimer = null; + } + + void _startConfirmsTimer() { + _confirmsTimer?.cancel(); + _confirmsTimer = Timer.periodic( + const Duration(seconds: 5), + (_) => _updateConfirmsInfo(), + ); + } + final _pause = Mutex(); bool get isPaused => _pause.isLocked; void unpause() { @@ -75,6 +126,7 @@ class ChurningService extends ChangeNotifier { notifyListeners(); try { + _stopConfirmsTimer(); Logging.log?.i("Doing churn #${roundsCompleted + 1}"); await _churnTxSimple(); waitingForUnlockedBalance = ChurnStatus.success; @@ -106,6 +158,7 @@ class ChurningService extends ChangeNotifier { } if (!complete() && _running) { + _startConfirmsTimer(); waitingForUnlockedBalance = ChurnStatus.running; makingChurnTransaction = ChurnStatus.waiting; completedStatus = ChurnStatus.waiting;