Skip to content

Commit 7ca990f

Browse files
committed
Update UI and handling of connecting to Monero nodes
1 parent 4003432 commit 7ca990f

8 files changed

Lines changed: 294 additions & 71 deletions

File tree

Components/Pages/Settings.razor

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,21 @@
1515
<p class="regular-p-grey">Please wait</p>
1616
</Modal>
1717

18-
<Modal Title="Backup" ModalButtonOptions="ModalButtonOptions.CANCEL" @bind-IsOpen="IsBackingUp" OnCancelPressed="() => { try{ BackupCts?.Cancel(); } catch{} }" IsCloseable="false">
18+
<div>
19+
<Modal Title="Connect to Monero node" ModalButtonOptions="ModalButtonOptions.OK_CANCEL" @bind-IsOpen="ShowConnectToMoneroNodeModal" OnOkPressed="async () => await ConnectToMoneroNodeAsync(MoneroNodeToConnectTo)" OkButtonText="Connect">
20+
<p class="regular-p-grey">Are you sure you want to connect to this Monero node?</p>
21+
<a class="external-link">Url: @MoneroNodeToConnectTo</a>
22+
</Modal>
23+
</div>
24+
25+
<div>
26+
<Modal Title="Remove Monero node" ModalButtonOptions="ModalButtonOptions.OK_CANCEL" @bind-IsOpen="ShowRemoveMoneroNodeModal" OnOkPressed="async () => await RemoveMoneroNodeAsync(MoneroNodeToRemove)" OkButtonText="Remove">
27+
<p class="regular-p-grey">Are you sure you want to remove to this Monero node?</p>
28+
<a class="external-link">Url: @MoneroNodeToRemove</a>
29+
</Modal>
30+
</div>
31+
32+
<Modal Title="Backup" ModalButtonOptions="ModalButtonOptions.CANCEL" @bind-IsOpen="IsBackingUp" OnCancelPressed="() => { try { BackupCts?.Cancel(); } catch { } }" IsCloseable="false">
1933
<p class="regular-p-grey">Please wait</p>
2034
<p class="regular-p-grey">Note: This can take a long time when using a remote node</p>
2135
</Modal>
@@ -39,22 +53,26 @@
3953
<Modal Title="Error" ModalButtonOptions="ModalButtonOptions.OK" IsOpen="@(!string.IsNullOrEmpty(ConnectionError))" IsOpenChanged="(isOpen) => { if (!isOpen) ConnectionError = null; }">
4054
<p class="regular-p-grey">@ConnectionError</p>
4155
</Modal>
42-
<Modal Title="Configure Monero node" ModalButtonOptions="ModalButtonOptions.OK_CANCEL" @bind-IsOpen="ShowConnectToMoneroNodeModal" OnOkPressed="ConnectToMoneroNodeAsync" OkButtonText="Connect">
56+
<Modal Title="Add Monero node" ModalButtonOptions="ModalButtonOptions.OK_CANCEL" @bind-IsOpen="ShowAddMoneroNodeModal" OnOkPressed="AddMoneroNodeAsync" OkButtonText="Add node">
4357
<div class="configure">
4458
<div>
4559
<label>Url</label>
4660
<input @bind="MoneroNodeUrl" @bind:event="oninput" />
4761
<p class="note">Example: http://192.168.0.1:18081</p>
4862
<p class="note">@IsMoneroNodeUrlInvalidReason</p>
4963
</div>
50-
<div>
51-
<label>Username</label>
52-
<input @bind="MoneroNodeUsername" />
53-
</div>
54-
<div>
55-
<label>Password</label>
56-
<input @bind="MoneroNodePassword" />
57-
</div>
64+
<Toggle @bind-IsToggled="AuthenticationRequired" Text="Authentication required"></Toggle>
65+
@if (AuthenticationRequired)
66+
{
67+
<div>
68+
<label>Username</label>
69+
<input @bind="MoneroNodeUsername" />
70+
</div>
71+
<div>
72+
<label>Password</label>
73+
<input @bind="MoneroNodePassword" />
74+
</div>
75+
}
5876
</div>
5977
</Modal>
6078
<div class="settings-main">
@@ -90,15 +108,45 @@
90108
} *@
91109
</div>
92110
<br />
93-
<p class="bold-p">Monero node</p>
111+
<p class="bold-p">Monero network</p>
94112
@if (DaemonInstallOption == DaemonInstallOptions.RemoteNode)
95113
{
96114
// Always false when using standalone?
97115
<p>Status: @(IsXmrNodeOnline ? "Running" : "Stopped")</p>
98116
}
99-
<p class="url">Url: @(string.IsNullOrEmpty(ConnectedMoneroNodeUrl) ? "Loading..." : ConnectedMoneroNodeUrl)</p>
117+
@if (UrlConnections.Count == 0)
118+
{
119+
<p>No Monero nodes</p>
120+
}
121+
else
122+
{
123+
<ul class="url-connections">
124+
@foreach (var urlConnection in UrlConnections.Where(x => !x.Url.Contains("127.0.0.1")).OrderByDescending(x => x.Url == ConnectedMoneroNodeUrl).ThenBy(x => x.Priority))
125+
{
126+
<li class=@(urlConnection.Url == ConnectedMoneroNodeUrl ? "connection connected" : "connection")>
127+
@switch (urlConnection.OnlineStatus)
128+
{
129+
case OnlineStatus.UNKNOWN:
130+
<div class="online-status unknown"></div>
131+
break;
132+
case OnlineStatus.ONLINE:
133+
<div class="online-status online"></div>
134+
break;
135+
case OnlineStatus.OFFLINE:
136+
<div class="online-status offline"></div>
137+
break;
138+
}
139+
<p>@urlConnection.Url</p>
140+
<i class="bi bi-arrow-right-circle connect-node" @onclick="() => { ShowConnectToMoneroNodeModal = true; MoneroNodeToConnectTo = urlConnection.Url; }" />
141+
<i class="bi bi-x-circle remove-node" @onclick="() => { ShowRemoveMoneroNodeModal = true; MoneroNodeToRemove = urlConnection.Url; }" />
142+
</li>
143+
}
144+
</ul>
145+
}
100146
</div>
101-
<button @onclick="() => ShowConnectToMoneroNodeModal = true">Configure Monero node</button>
147+
<button @onclick="() => ShowAddMoneroNodeModal = true">Add Monero node</button>
148+
<Toggle OnToggledChanged="SetAutoSwitchAsync" @bind-IsToggled="IsAutoSwitchEnabled" Text="Use any Monero node"></Toggle>
149+
<p class="note">Note: disable this to ensure the app only uses the selected node</p>
102150
<br/>
103151
<p class="bold-p">Preferences</p>
104152
<div class="dropdown">
@@ -112,7 +160,7 @@
112160
<button disabled="@(DaemonInstallOption == DaemonInstallOptions.RemoteNode)" class="backup-btn" @onclick="() => ShowRestoreModal = true">Restore account</button>
113161
</div>
114162

115-
<Toggle OnToggledChanged="HandleToggleAsync" @bind-IsToggled="IsNotificationsToggled" Text="Notifications" />
163+
<Toggle OnToggledChanged="HandleNotificationsToggleAsync" @bind-IsToggled="IsNotificationsToggled" Text="Notifications" />
116164
@if (DaemonInstallOption == DaemonInstallOptions.Standalone)
117165
{
118166
<Toggle OnToggledChanged="HandleWakeLockToggle" @bind-IsToggled="IsWakeLockToggled" Text="Keep offers open in background" />
@@ -132,6 +180,5 @@
132180
<p class="note">Note: you need to have Orbot installed</p>
133181
<button @onclick="ConnectToRemoteNodeAsync">Connect to remote node</button>
134182
</div>
135-
<Toggle Disabled="true" OnToggledChanged="HandleRemoteNodeToggleAsync" @bind-IsToggled="IsRemoteNodeToggled" Text="Use remote node" />
136183
}
137184
</div>

Components/Pages/Settings.razor.cs

Lines changed: 116 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using AndroidX.Core.Content;
33
using CommunityToolkit.Maui.Storage;
44
using Grpc.Net.Client.Web;
5+
using HavenoSharp.Models;
56
using HavenoSharp.Services;
67
using HavenoSharp.Singletons;
78
using Manta.Helpers;
@@ -72,13 +73,33 @@ public partial class Settings : ComponentBase, IDisposable
7273

7374
public bool ShowRestoreModal { get; set; }
7475

75-
public bool ShowConnectToMoneroNodeModal { get; set; }
76+
public bool ShowAddMoneroNodeModal { get; set; }
7677

7778
Timer? ValidateMoneroNodeUrlTimer { get; set; }
7879

7980
public string? IsMoneroNodeUrlInvalidReason { get; set; }
8081
public bool IsMoneroNodeUrlInvalid { get; set; }
8182

83+
public bool ShowRemoveMoneroNodeModal { get; set; }
84+
public string? MoneroNodeToRemove { get; set; }
85+
public bool ShowConnectToMoneroNodeModal { get; set; }
86+
public string? MoneroNodeToConnectTo { get; set; }
87+
88+
public bool IsAutoSwitchEnabled { get; set; }
89+
90+
public bool AuthenticationRequired
91+
{
92+
get;
93+
set
94+
{
95+
field = value;
96+
MoneroNodeUsername = null;
97+
MoneroNodePassword = null;
98+
}
99+
}
100+
101+
public List<UrlConnection> UrlConnections { get; set; } = [];
102+
82103
public string? MoneroNodePassword { get; set { field = value?.Trim(); } }
83104
public string? MoneroNodeUrl
84105
{
@@ -103,6 +124,9 @@ public async Task ValidateUrl(string field)
103124
{
104125
try
105126
{
127+
if (field is null)
128+
return;
129+
106130
if (field == string.Empty)
107131
{
108132
IsMoneroNodeUrlInvalidReason = null;
@@ -401,23 +425,95 @@ public async Task ScanQRCodeAsync()
401425
await current.MainPage.Navigation.PopModalAsync(true);
402426
}
403427

404-
public async Task ConnectToMoneroNodeAsync()
428+
public async Task SetAutoSwitchAsync(bool value)
405429
{
406-
// Should show error etc
407-
if (MoneroNodeUrl is null)
408-
return;
430+
await HavenoXmrNodeService.SetAutoSwitchAsync(value);
431+
}
432+
433+
public async Task AddMoneroNodeAsync()
434+
{
435+
if (string.IsNullOrEmpty(MoneroNodeUrl))
436+
throw new Exception("Url cannot be empty.");
437+
438+
MoneroNodeUsername ??= string.Empty;
439+
MoneroNodePassword ??= string.Empty;
440+
441+
await HavenoXmrNodeService.AddConnectionAsync(MoneroNodeUrl, MoneroNodeUsername, MoneroNodePassword, 0);
442+
443+
// Not ideal but Haveno requires the username/password again when setting the node even if it is saved
444+
var savedNodes = AppPreferences.Get<List<MoneroNodeInfo>>(AppPreferences.SavedXmrNodes) ?? [];
445+
MoneroNodeInfo? moneroNodeInfo = savedNodes.FirstOrDefault(x => x.Url == MoneroNodeUrl);
446+
if (moneroNodeInfo is null)
447+
{
448+
moneroNodeInfo = new MoneroNodeInfo
449+
{
450+
Url = MoneroNodeUrl,
451+
Password = MoneroNodePassword,
452+
Username = MoneroNodeUsername
453+
};
454+
455+
savedNodes.Add(moneroNodeInfo);
456+
}
457+
else
458+
{
459+
// This shouldn't happen?
460+
461+
moneroNodeInfo.Password = MoneroNodePassword;
462+
moneroNodeInfo.Username = MoneroNodeUsername;
463+
}
464+
465+
AppPreferences.Set(AppPreferences.SavedXmrNodes, savedNodes);
466+
467+
UrlConnections = await HavenoXmrNodeService.GetConnectionsAsync();
468+
469+
MoneroNodeUrl = null;
470+
MoneroNodeUsername = null;
471+
MoneroNodePassword = null;
472+
473+
AuthenticationRequired = false;
474+
}
475+
476+
public async Task RemoveMoneroNodeAsync(string url)
477+
{
478+
var connectedNode = await HavenoXmrNodeService.GetMoneroNodeAsync();
479+
if (connectedNode.Url == url)
480+
throw new Exception("Cannot remove the selected node as it is currently in use.");
481+
482+
await HavenoXmrNodeService.RemoveConnectionAsync(url);
483+
484+
var savedNodes = AppPreferences.Get<List<MoneroNodeInfo>>(AppPreferences.SavedXmrNodes) ?? [];
485+
MoneroNodeInfo? moneroNodeInfo = savedNodes.FirstOrDefault(x => x.Url == url);
486+
if (moneroNodeInfo is not null)
487+
{
488+
savedNodes.Remove(moneroNodeInfo);
489+
AppPreferences.Set(AppPreferences.SavedXmrNodes, savedNodes);
490+
}
491+
492+
UrlConnections = await HavenoXmrNodeService.GetConnectionsAsync();
493+
}
409494

495+
public async Task ConnectToMoneroNodeAsync(string url)
496+
{
410497
MoneroNodeConnectCancellationTokenSource = new(10_000);
411498
IsConnectingToMoneroNode = true;
412499

413500
try
414501
{
415-
MoneroNodeUsername ??= string.Empty;
416-
MoneroNodePassword ??= string.Empty;
502+
var moneroNodeUsername = string.Empty;
503+
var moneroNodePassword = string.Empty;
504+
505+
var savedNodes = AppPreferences.Get<List<MoneroNodeInfo>>(AppPreferences.SavedXmrNodes) ?? [];
506+
MoneroNodeInfo? moneroNodeInfo = savedNodes.FirstOrDefault(x => x.Url == url);
507+
if (moneroNodeInfo is not null)
508+
{
509+
moneroNodeUsername = moneroNodeInfo.Username;
510+
moneroNodePassword = moneroNodeInfo.Password;
511+
}
417512

418513
// Does not throw an exception if fails?
514+
IsAutoSwitchEnabled = false;
419515
await HavenoXmrNodeService.SetAutoSwitchAsync(false);
420-
await HavenoXmrNodeService.SetMoneroNodeAsync(MoneroNodeUrl, MoneroNodeUsername, MoneroNodePassword, 0);
516+
await HavenoXmrNodeService.SetMoneroNodeAsync(url, moneroNodeUsername, moneroNodePassword, 0);
421517

422518
var response = await HavenoXmrNodeService.GetMoneroNodeAsync();
423519
ConnectedMoneroNodeUrl = response.Url;
@@ -433,7 +529,7 @@ public async Task ConnectToMoneroNodeAsync()
433529
break;
434530
}
435531

436-
if (response.OnlineStatus == HavenoSharp.Models.OnlineStatus.ONLINE)
532+
if (response.OnlineStatus == OnlineStatus.ONLINE)
437533
break;
438534

439535
await Task.Delay(500);
@@ -442,34 +538,29 @@ public async Task ConnectToMoneroNodeAsync()
442538
if (MoneroNodeConnectCancellationTokenSource.IsCancellationRequested)
443539
{
444540
await HavenoXmrNodeService.SetAutoSwitchAsync(true);
445-
await HavenoXmrNodeService.RemoveConnectionAsync(MoneroNodeUrl);
446-
447-
var moneroNodes = await HavenoXmrNodeService.GetConnectionsAsync();
448-
var previousAuthedNode = moneroNodes.FirstOrDefault(x => x.AuthenticationStatus == HavenoSharp.Models.AuthenticationStatus.AUTHENTICATED && x.OnlineStatus == HavenoSharp.Models.OnlineStatus.ONLINE);
541+
IsAutoSwitchEnabled = true;
449542

450-
if (previousAuthedNode is not null) // Hope that previous node did not require auth
451-
await HavenoXmrNodeService.SetMoneroNodeAsync(previousAuthedNode.Url, string.Empty, string.Empty, 0);
543+
var moneroNode = await HavenoXmrNodeService.GetBestConnectionAsync();
544+
await HavenoXmrNodeService.SetMoneroNodeAsync(moneroNode.Url, string.Empty, string.Empty, 0);
452545

453546
response = await HavenoXmrNodeService.GetMoneroNodeAsync();
454547
ConnectedMoneroNodeUrl = response.Url;
455548

456-
throw new Exception("Failed to connect to node.");
457-
}
458-
else
459-
{
460-
AppPreferences.Set(AppPreferences.CustomXmrNode, ConnectedMoneroNodeUrl);
549+
throw new Exception("Failed to connect to node. Please check that the node is online and that the login details are correct.");
461550
}
462551
}
463552
finally
464553
{
465-
ShowConnectToMoneroNodeModal = false;
554+
ShowAddMoneroNodeModal = false;
466555
MoneroNodeUrl = string.Empty;
467556
MoneroNodeUsername = string.Empty;
468557
MoneroNodePassword = string.Empty;
469558

470559
MoneroNodeConnectCancellationTokenSource.Dispose();
471560
MoneroNodeConnectCancellationTokenSource = null;
472561
IsConnectingToMoneroNode = false;
562+
563+
UrlConnections = await HavenoXmrNodeService.GetConnectionsAsync();
473564
}
474565
}
475566

@@ -528,29 +619,7 @@ public async Task ConnectToRemoteNodeAsync()
528619
ConnectionError = "Could not connect to remote node. Make sure Orbot is installed and configured.";
529620
}
530621

531-
public async Task HandleRemoteNodeToggleAsync(bool isToggled)
532-
{
533-
// Prompt that account won't be synced and that if running, local daemon, termux etc needs to be stopped
534-
if (!isToggled)
535-
{
536-
// Theres a small issue if orbot is running at the same time as it listens to the same ports that the Termux tor instance listens on, however users should not be regularly switching hosting modes
537-
await SecureStorageHelper.SetAsync("daemon-installation-type", DaemonInstallOptions.Standalone);
538-
539-
if ((await HavenoDaemonService.GetIsDaemonInstalledAsync()).Item1)
540-
{
541-
await HavenoDaemonService.TryStartLocalHavenoDaemonAsync(Guid.NewGuid().ToString(), "http://127.0.0.1:3201");
542-
}
543-
else
544-
{
545-
NavigationManager.NavigateTo("/");
546-
}
547-
548-
Password = null;
549-
Host = null;
550-
}
551-
}
552-
553-
public async Task HandleToggleAsync(bool isToggled)
622+
public async Task HandleNotificationsToggleAsync(bool isToggled)
554623
{
555624
#if ANDROID
556625
if (isToggled)
@@ -581,16 +650,6 @@ public async Task HandleWakeLockToggle(bool isToggled)
581650
#endif
582651
}
583652

584-
protected override async Task OnAfterRenderAsync(bool firstRender)
585-
{
586-
if (firstRender)
587-
{
588-
589-
}
590-
591-
await base.OnAfterRenderAsync(firstRender);
592-
}
593-
594653
private async void HandleDaemonInfoFetch(bool isFetching)
595654
{
596655
await InvokeAsync(() => {
@@ -637,6 +696,9 @@ protected override async Task OnInitializedAsync()
637696
IsXmrNodeOnline = DaemonInfoSingleton.IsXmrNodeOnline;
638697
ConnectedMoneroNodeUrl = DaemonInfoSingleton.ConnectedMoneroNodeUrl;
639698

699+
IsAutoSwitchEnabled = await HavenoXmrNodeService.GetAutoSwitchAsync();
700+
UrlConnections = await HavenoXmrNodeService.GetConnectionsAsync();
701+
640702
DaemonInfoSingleton.OnDaemonInfoFetch += HandleDaemonInfoFetch;
641703
DaemonConnectionSingleton.OnConnectionChanged += HandleDaemonConnectionChanged;
642704

0 commit comments

Comments
 (0)