diff --git a/src/aoWebWallet.Tests/StorageServiceTests.cs b/src/aoWebWallet.Tests/StorageServiceTests.cs new file mode 100644 index 0000000..49955d9 --- /dev/null +++ b/src/aoWebWallet.Tests/StorageServiceTests.cs @@ -0,0 +1,39 @@ +using aoWebWallet.Models; +using aoWebWallet.Services; +using ArweaveAO; +using ArweaveAO.Models; +using Microsoft.Extensions.Options; + +namespace aoWebWallet.Tests +{ + [TestClass] + public class StorageServiceTests + { + [TestMethod] + public async Task TestBuildInTokenData() + { + List result = new(); + + StorageService.AddSystemTokens(result); + + TokenClient tokenClient = new TokenClient(Options.Create(new ArweaveConfig()), new HttpClient()); + + foreach(var token in result) + { + //Get live data + var data = await tokenClient.GetTokenMetaData(token.TokenId); + + Assert.IsNotNull(token.TokenData); + Assert.IsNotNull(data); + + Assert.AreEqual(token.TokenId, data.TokenId); + Assert.AreEqual(token.TokenData.TokenId, data.TokenId); + Assert.AreEqual(token.TokenData.Name, data.Name); + Assert.AreEqual(token.TokenData.Ticker, data.Ticker); + Assert.AreEqual(token.TokenData.Denomination, data.Denomination); + Assert.AreEqual(token.TokenData.Logo, data.Logo); + + } + } + } +} \ No newline at end of file diff --git a/src/aoWebWallet.Tests/aoWebWallet.Tests.csproj b/src/aoWebWallet.Tests/aoWebWallet.Tests.csproj new file mode 100644 index 0000000..02f6a5e --- /dev/null +++ b/src/aoWebWallet.Tests/aoWebWallet.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/src/aoWebWallet.sln b/src/aoWebWallet.sln index a90b2ae..b9bea89 100644 --- a/src/aoWebWallet.sln +++ b/src/aoWebWallet.sln @@ -9,11 +9,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libs", "Libs", "{06E5BC39-7 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webvNext.DataLoader", "webvNext.DataLoader\webvNext.DataLoader.csproj", "{17CA4374-64D0-4618-852F-8A76D0A57166}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoww.Services", "aoww.Services\aoww.Services.csproj", "{178C3213-D574-4B39-A2DA-1FB1D2806242}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aoww.Services", "aoww.Services\aoww.Services.csproj", "{178C3213-D574-4B39-A2DA-1FB1D2806242}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{89AC47DF-65AD-4870-AA1D-74ABF1F3D8FE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoww.Services.Tests", "aoww.Services.Tests\aoww.Services.Tests.csproj", "{322F4807-05CF-431D-B400-7420E1B29936}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aoww.Services.Tests", "aoww.Services.Tests\aoww.Services.Tests.csproj", "{322F4807-05CF-431D-B400-7420E1B29936}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aoWebWallet.Tests", "aoWebWallet.Tests\aoWebWallet.Tests.csproj", "{12E9E40E-96D1-4501-A9A4-EBE4D4F43D8D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -37,13 +39,19 @@ Global {322F4807-05CF-431D-B400-7420E1B29936}.Debug|Any CPU.Build.0 = Debug|Any CPU {322F4807-05CF-431D-B400-7420E1B29936}.Release|Any CPU.ActiveCfg = Release|Any CPU {322F4807-05CF-431D-B400-7420E1B29936}.Release|Any CPU.Build.0 = Release|Any CPU + {12E9E40E-96D1-4501-A9A4-EBE4D4F43D8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12E9E40E-96D1-4501-A9A4-EBE4D4F43D8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12E9E40E-96D1-4501-A9A4-EBE4D4F43D8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12E9E40E-96D1-4501-A9A4-EBE4D4F43D8D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {17CA4374-64D0-4618-852F-8A76D0A57166} = {06E5BC39-764A-48B9-B4F9-F48387A2C965} + {178C3213-D574-4B39-A2DA-1FB1D2806242} = {06E5BC39-764A-48B9-B4F9-F48387A2C965} {322F4807-05CF-431D-B400-7420E1B29936} = {89AC47DF-65AD-4870-AA1D-74ABF1F3D8FE} + {12E9E40E-96D1-4501-A9A4-EBE4D4F43D8D} = {89AC47DF-65AD-4870-AA1D-74ABF1F3D8FE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {432E3F8E-53FF-4D9C-869D-48449BD3B8B4} diff --git a/src/aoWebWallet/Extensions/WalletExtensions.cs b/src/aoWebWallet/Extensions/WalletExtensions.cs new file mode 100644 index 0000000..946e7cf --- /dev/null +++ b/src/aoWebWallet/Extensions/WalletExtensions.cs @@ -0,0 +1,17 @@ +using aoWebWallet.Models; + +namespace aoWebWallet.Extensions +{ + public static class WalletExtensions + { + public static string ToAutocompleteDisplay(this Wallet wallet) + { + if (string.IsNullOrWhiteSpace(wallet.Name)) + { + return wallet.Address; + } + + return $"{wallet.Name} ({wallet.Address})"; + } + } +} diff --git a/src/aoWebWallet/Layout/MainLayout.razor b/src/aoWebWallet/Layout/MainLayout.razor index ec207fb..2c20bf9 100644 --- a/src/aoWebWallet/Layout/MainLayout.razor +++ b/src/aoWebWallet/Layout/MainLayout.razor @@ -4,7 +4,7 @@ string? versionHash = Program.GetVersionHash(); } - + @@ -12,7 +12,7 @@ + Width="100" Class="pt-2" Alt="AOWW"/> @@ -27,12 +27,13 @@ @Body -
+
- - - - +
+ + +
Version: @Program.GetVersionWithoutHash() @if (!string.IsNullOrEmpty(versionHash)) @@ -40,7 +41,8 @@ -@versionHash } - zsXSvJtHVSK4QyPch4Uf0JMiZi9uEhgVvyz6qeEJcfY + +
diff --git a/src/aoWebWallet/Layout/MainLayout.razor.cs b/src/aoWebWallet/Layout/MainLayout.razor.cs index 6e274a8..cd38a56 100644 --- a/src/aoWebWallet/Layout/MainLayout.razor.cs +++ b/src/aoWebWallet/Layout/MainLayout.razor.cs @@ -11,8 +11,6 @@ public partial class MainLayout protected override void OnInitialized() { - BindingContext.PropertyChanged += BindingContext_PropertyChanged; - base.OnInitialized(); } @@ -27,17 +25,8 @@ protected override async Task OnAfterRenderAsync(bool firstRender) await base.OnAfterRenderAsync(firstRender); } - private void BindingContext_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(MainViewModel.IsDarkMode)) - { - this.StateHasChanged(); - } - } - public virtual void Dispose() { - BindingContext.PropertyChanged -= BindingContext_PropertyChanged; } } } diff --git a/src/aoWebWallet/Models/ActionParam.cs b/src/aoWebWallet/Models/ActionParam.cs new file mode 100644 index 0000000..8394e15 --- /dev/null +++ b/src/aoWebWallet/Models/ActionParam.cs @@ -0,0 +1,178 @@ + +using System.Text; + +namespace aoWebWallet.Models +{ + public class AoAction + { + public List Params { get; set; } = new(); + + public ActionParam? Target => Params.Where(x => x.ParamType == ActionParamType.Target).FirstOrDefault(); + public IEnumerable AllWithoutTarget => Params.Where(x => x.ParamType != ActionParamType.Target); + public IEnumerable Filled => Params.Where(x => x.ParamType == ActionParamType.Filled); + public IEnumerable AllInputs => Params.Where(x => + x.ParamType != ActionParamType.Filled + && x.ParamType != ActionParamType.Target); + + public string? IsValid() + { + if (Target == null) + return "No Target process specified."; + + foreach(var input in AllInputs) + { + if (string.IsNullOrEmpty(input.Value)) + return $"Please enter a value for {input.Key}"; + } + + return null; + } + + public List ToEvalTags() + { + return Params.Select(x => new ArweaveBlazor.Models.Tag { Name = x.Key, Value = x.Value ?? string.Empty }).ToList(); + } + + public List ToTags() + { + return AllWithoutTarget.Select(x => new ArweaveBlazor.Models.Tag { Name = x.Key, Value = x.Value ?? string.Empty }).ToList(); + } + + public List ToDryRunTags() + { + return AllWithoutTarget.Select(x => new ArweaveAO.Models.Tag { Name = x.Key, Value = x.Value ?? string.Empty }).ToList(); + } + + + public string ToQueryString() + { + if (Target == null) + return string.Empty; + + StringBuilder sb = new StringBuilder(); + + sb.Append($"{Target.Key}={Target.Value}&"); + + foreach (var param in this.Filled) + { + sb.Append($"{param.Key}={param.Value}&"); + } + + foreach (var param in this.AllInputs) + { + var args = string.Join(';', param.Args); + if (args.Length > 0) + { + sb.Append($"X-{param.ParamType}={param.Key};{args}&"); + } + else + { + sb.Append($"X-{param.ParamType}={param.Key}&"); + } + } + + return sb.ToString().TrimEnd('&'); + } + + public static AoAction CreateFromQueryString(string qstring) + { + // Parsing query string + var queryStringValues = System.Web.HttpUtility.ParseQueryString(qstring); + + AoAction action = new AoAction(); + + foreach (var key in queryStringValues.AllKeys) + { + if (key == null) + continue; + + var values = queryStringValues.GetValues(key); + if (values == null || !values.Any()) + continue; + + foreach (var val in values) + { + string actionKey = key; + string? actionValue = val.ToString(); + ActionParamType actionParamType = ActionParamType.Filled; + + var actionValueSplit = actionValue.Split(';', StringSplitOptions.RemoveEmptyEntries); + actionValue = actionValueSplit.First(); + List args = actionValueSplit.Skip(1).ToList(); + + if (key.Equals("Target", StringComparison.InvariantCultureIgnoreCase)) + actionParamType = ActionParamType.Target; + if (key.Equals("X-Quantity", StringComparison.InvariantCultureIgnoreCase)) + actionParamType = ActionParamType.Quantity; + if (key.Equals("X-Balance", StringComparison.InvariantCultureIgnoreCase)) + actionParamType = ActionParamType.Balance; + else if (key.Equals("X-Process", StringComparison.InvariantCultureIgnoreCase)) + actionParamType = ActionParamType.Process; + else if (key.Equals("X-Integer", StringComparison.InvariantCultureIgnoreCase)) + actionParamType = ActionParamType.Integer; + else if (key.Equals("X-Input", StringComparison.InvariantCultureIgnoreCase)) + actionParamType = ActionParamType.Input; + + if (actionParamType != ActionParamType.Filled + && actionParamType != ActionParamType.Target) + { + actionKey = actionValue; + actionValue = null; + } + + action.Params.Add(new ActionParam + { + Key = actionKey, + Value = actionValue, + Args = args, + ParamType = actionParamType + }); + + } + } + + return action; + } + + public static AoAction CreateForTokenTransaction(string tokenId) + { + return new AoAction + { + Params = new List + { + new ActionParam { Key= "Target", ParamType = ActionParamType.Target, Value= tokenId }, + new ActionParam { Key= "Action", ParamType = ActionParamType.Filled, Value= "Transfer" }, + new ActionParam { Key= "Recipient", ParamType = ActionParamType.Process }, + new ActionParam { Key= "Quantity", ParamType = ActionParamType.Balance, Args = new List { tokenId } } + } + + }; + } + } + + public class ActionParam + { + public required string Key { get; set; } + public string? Value { get; set; } + + /// + /// Arguments (like TokenId) + /// + public List Args { get; set; } = new(); + + public ActionParamType ParamType { get; set; } + + } + + public enum ActionParamType + { + None = 0, + Target, + Filled, + Input, + Integer, + Process, + Balance, //Arg1: TokenId //Must have balance + Quantity, //Arg1: TokenId //Does not care about balance + } +} diff --git a/src/aoWebWallet/Models/GatewayConfig.cs b/src/aoWebWallet/Models/GatewayConfig.cs new file mode 100644 index 0000000..9b75fb1 --- /dev/null +++ b/src/aoWebWallet/Models/GatewayConfig.cs @@ -0,0 +1,7 @@ +namespace aoWebWallet.Models +{ + public class GatewayConfig + { + public string GatewayUrl { get; set; } = "https://arweave.net"; + } +} diff --git a/src/aoWebWallet/ViewModels/Transaction.cs b/src/aoWebWallet/Models/Transaction.cs similarity index 72% rename from src/aoWebWallet/ViewModels/Transaction.cs rename to src/aoWebWallet/Models/Transaction.cs index 3c8e316..78000c7 100644 --- a/src/aoWebWallet/ViewModels/Transaction.cs +++ b/src/aoWebWallet/Models/Transaction.cs @@ -1,4 +1,4 @@ -namespace aoWebWallet.ViewModels +namespace aoWebWallet.Models { public class Transaction { diff --git a/src/aoWebWallet/Models/UserSettings.cs b/src/aoWebWallet/Models/UserSettings.cs index 6446d8d..29d4430 100644 --- a/src/aoWebWallet/Models/UserSettings.cs +++ b/src/aoWebWallet/Models/UserSettings.cs @@ -2,9 +2,12 @@ { public class UserSettings { - public bool? IsDarkMode { get; set; } = true; - public string? ComputeUnitUrl { get; set; } - public string? GraphqlApiUrl { get; set; } + //public bool? IsDarkMode { get; set; } = true; + public string GatewayUrl { get; set; } = "https://arweave.net"; + public string GraphqlUrl { get; set; } = "https://arweave.net/graphql"; + public string ComputeUnitUrl { get; set; } = "https://cu.ao-testnet.xyz"; + public string MessengerUnitUrl { get; set; } = "https://mu.ao-testnet.xyz"; + public bool Claimed1 { get; set; } public bool Claimed2 { get; set; } diff --git a/src/aoWebWallet/Pages/About.razor b/src/aoWebWallet/Pages/About.razor index 91875c6..354b01f 100644 --- a/src/aoWebWallet/Pages/About.razor +++ b/src/aoWebWallet/Pages/About.razor @@ -47,10 +47,9 @@ - - - - + + Contribute to developers address
+ zsXSvJtHVSK4QyPch4Uf0JMiZi9uEhgVvyz6qeEJcfY
diff --git a/src/aoWebWallet/Pages/ActionPage.razor b/src/aoWebWallet/Pages/ActionPage.razor new file mode 100644 index 0000000..92b840f --- /dev/null +++ b/src/aoWebWallet/Pages/ActionPage.razor @@ -0,0 +1,201 @@ +@page "/action" +@using aoWebWallet.Models +@inherits MvvmComponentBase +@inject IDialogService DialogService +@inject ISnackbar Snackbar +@inject NavigationManager NavigationManager +@inject TokenDataService dataService +@inject TransactionService transactionService; +@inject WalletDetailViewModel WalletDetailViewModel + +@Program.PageTitlePostFix + + + + New transaction + + + @if (BindingContext.WalletList.Data != null) + { + var sendWallets = BindingContext.WalletList.Data.Where(x => !x.IsReadOnly).ToList(); + if (!sendWallets.Any()) + { + Add Wallet + } + else + { + + @foreach (var wallet in sendWallets ?? new()) + { + + + @* *@ + +
+ + @wallet.Address + +
+
+ @wallet.Name +
+
+
+ +
+ } +
+ } + } + +
+ + @if(readOnly) + { + Please review your transaction: + } + + + + @if (!readOnly && !string.IsNullOrEmpty(selectedWallet)) + { + Preview + @validation + } + else if (!started && !string.IsNullOrEmpty(selectedWallet) && string.IsNullOrEmpty(transactionService.LastTransaction.Data?.Id)) + { + + if (transactionService.DryRunResult.Data != null) + { + + + Preview Result + @foreach (var msg in transactionService.DryRunResult.Data.Messages) + { + var error = msg.Tags.Where(x => x.Name == "Error").Select(x => x.Value).FirstOrDefault(); + + Message for: @msg.Target + @msg.Data + @error +
+ } +
+ @* + Learn More + *@ +
+ } + + Cancel + Submit + } + + @if (transactionService.LastTransaction.DataLoader != null) + { + + if (!string.IsNullOrEmpty(transactionService.LastTransaction.Data?.Id)) + { + + Transfer success! + Message Id + + @transactionService.LastTransaction.Data?.Id + + + + Return to wallet + @* View Transaction *@ + } + } + + +
+ +@code +{ + private string? validation; + private string? selectedWallet; + private Wallet? selectedWalletObj; + private bool readOnly = false; + private bool started = false; + + + private async void Preview() + { + //transactionService.LastTransaction.Data = new Transaction() { Id = "test" }; + + validation = AoAction.IsValid(); + readOnly = string.IsNullOrEmpty(validation); + + if (BindingContext.WalletList.Data == null) + return; + + var wallet = BindingContext.WalletList.Data.Where(x => x.Address == selectedWallet).FirstOrDefault(); + if (wallet == null) + { + if (selectedWalletObj?.Address == selectedWallet) + { + wallet = selectedWalletObj; + } + } + + if (wallet == null) + return; + + //Do we need the owner wallet? + //Wallet? ownerWallet = BindingContext.WalletList.Data.Where(x => x.Address == wallet.OwnerAddress).FirstOrDefault(); + + transactionService.DryRunAction(wallet, AoAction); + + } + private void Cancel() + { + readOnly = false; + } + + private void ReturnToWallet() + { + NavigationManager.NavigateTo($"/wallet/{selectedWallet}"); + } + + private void ViewTransaction() + { + if (!string.IsNullOrEmpty(transactionService.LastTransaction.Data?.Id)) + NavigationManager.NavigateTo($"/transaction/{transactionService.LastTransaction.Data?.Id}"); + else + ReturnToWallet(); + + } + + private async Task Submit() + { + if (BindingContext.WalletList.Data == null) + return; + + var wallet = BindingContext.WalletList.Data.Where(x => x.Address == selectedWallet).FirstOrDefault(); + if(wallet == null) + { + if(selectedWalletObj?.Address == selectedWallet) + { + wallet = selectedWalletObj; + } + } + + if (wallet == null) + return; + + //Do we need the owner wallet? + Wallet? ownerWallet = BindingContext.WalletList.Data.Where(x => x.Address == wallet.OwnerAddress).FirstOrDefault(); + + started = true; + await transactionService.SendAction(wallet, ownerWallet, AoAction); + } + + private void OpenDialog() + { + NavigationManager.NavigateTo("/start"); + // var options = new DialogOptions { CloseOnEscapeKey = true }; + // DialogService.Show("Add Wallet", options); + } + +} diff --git a/src/aoWebWallet/Pages/ActionPage.razor.cs b/src/aoWebWallet/Pages/ActionPage.razor.cs new file mode 100644 index 0000000..a9e0dc0 --- /dev/null +++ b/src/aoWebWallet/Pages/ActionPage.razor.cs @@ -0,0 +1,93 @@ +using aoWebWallet.Models; +using aoWebWallet.ViewModels; +using Microsoft.AspNetCore.Components.Routing; + +namespace aoWebWallet.Pages +{ + public partial class ActionPage : MvvmComponentBase + { + public AoAction AoAction { get; set; } = new(); + + protected override void OnInitialized() + { + transactionService.Reset(); + + GetQueryStringValues(); + //WatchDataLoaderVM(BindingContext.TokenList); + WatchDataLoaderVM(BindingContext.WalletList); + WatchDataLoaderVM(transactionService.LastTransaction); + WatchDataLoaderVM(transactionService.DryRunResult); + + //Auto select wallet + if (!string.IsNullOrEmpty(WalletDetailViewModel.SelectedWallet?.Wallet.Address)) + { + selectedWalletObj = WalletDetailViewModel.SelectedWallet?.Wallet; + selectedWallet = selectedWalletObj?.Address; + + } + + NavigationManager.LocationChanged += NavigationManager_LocationChanged; + + base.OnInitialized(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await BindingContext.CheckHasArConnectExtension(); + + await BindingContext.LoadWalletList(); + //await dataService.LoadTokenList(); + } + + await base.OnAfterRenderAsync(firstRender); + } + + private void NavigationManager_LocationChanged(object? sender, LocationChangedEventArgs e) + { + GetQueryStringValues(); + StateHasChanged(); + } + + private async void GetQueryStringValues() + { + var uri = new Uri(NavigationManager.Uri); + var query = uri.Query; + + AoAction = AoAction.CreateFromQueryString(query); + + //Add and load tokens + var tokens = AoAction + .AllInputs + .Where(x => x.ParamType == ActionParamType.Balance || x.ParamType == ActionParamType.Quantity) + .Select(x => x.Args.FirstOrDefault()) + .Where(x => x != null) + .Distinct() + .Select(x => x!) + .ToList(); + + await dataService.TryAddTokenIds(tokens); + + StateHasChanged(); + } + + + + public override void Dispose() + { + NavigationManager.LocationChanged -= NavigationManager_LocationChanged; + + base.Dispose(); + } + + //protected override async Task LoadDataAsync() + //{ + // await BindingContext.LoadTokenList(); + + // await base.LoadDataAsync(); + + //} + + } +} diff --git a/src/aoWebWallet/Pages/AddressBook.razor b/src/aoWebWallet/Pages/AddressBook.razor new file mode 100644 index 0000000..78f3f88 --- /dev/null +++ b/src/aoWebWallet/Pages/AddressBook.razor @@ -0,0 +1,133 @@ +@page "/address-book" +@using aoWebWallet.Models +@inherits MvvmComponentBase +@inject IDialogService DialogService +@inject ISnackbar Snackbar +@inject ClipboardService ClipboardService + +Address Book - @Program.PageTitlePostFix + + + + + + Address Book + + + @if (BindingContext.WalletList.Data != null) + { + + + + } + + + + @if (BindingContext.WalletList.Data != null) + { + if(BindingContext.WalletList.Data.Where(x => x.IsReadOnly).Any()) + { + int logoCount = 1; + foreach (var wallet in BindingContext.WalletList.Data.Where(x => x.IsReadOnly)) + { + string logoUrl = $"images/account--{logoCount}.svg"; + string detailUrl = $"wallet/{wallet.Address}"; + + + + + +
+ + @wallet.Address + + +
+
+ @wallet.Name +
+
+ + @if(BindingContext.ProcessesDataList?.Data?.Where(x => x.Data?.Address == wallet.Address && (x.Data?.Processes?.Any() ?? false)).Any() ?? false) + { + AOS + } + + + +
+
+ + logoCount++; + if (logoCount > 5) + logoCount = 1; + } + } + else + { + + + Your Address Book is currently empty. + Add your first contact + + + + } + } + else + { + Loading address book... + + + } + +
+
+ + +@code +{ + private void OpenDialog() + { + var tempWallet = new Wallet() + { + Address = string.Empty, + IsReadOnly = true, + AddedDate = DateTimeOffset.UtcNow, + Source = WalletTypes.Manual + }; + + var parameters = new DialogParameters { { x => x.Wallet, tempWallet } }; + + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true }; + DialogService.Show("Add Contact", parameters, options); + } + + private async void EditWallet(Wallet wallet) + { + var parameters = new DialogParameters { { x => x.Wallet, wallet } }; + + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true }; + DialogService.Show("Edit Contact", parameters, options); + + StateHasChanged(); + } + + private async void DeleteWallet(Wallet wallet) + { + bool? result = await DialogService.ShowMessageBox( + "Warning", + $"Are you sure you want to delete this contact? {wallet.Address}?", + yesText: "Delete!", cancelText: "Cancel"); + + if (result != null) + { + await BindingContext.DeleteWallet(wallet); + + Snackbar.Add($"Contact deleted ({wallet.Address})", Severity.Info); + } + StateHasChanged(); + } + + +} diff --git a/src/aoWebWallet/Pages/AddressBook.razor.cs b/src/aoWebWallet/Pages/AddressBook.razor.cs new file mode 100644 index 0000000..8d519b8 --- /dev/null +++ b/src/aoWebWallet/Pages/AddressBook.razor.cs @@ -0,0 +1,27 @@ +using aoWebWallet.ViewModels; + +namespace aoWebWallet.Pages +{ + public partial class AddressBook : MvvmComponentBase + { + protected override void OnInitialized() + { + //WatchDataLoaderVM(BindingContext.TokenList); + WatchDataLoaderVM(BindingContext.WalletList); + WatchDataLoaderVM(BindingContext.ProcessesDataList); + + base.OnInitialized(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await BindingContext.LoadWalletList(); + } + + await base.OnAfterRenderAsync(firstRender); + } + + } +} diff --git a/src/aoWebWallet/Pages/MvvmComponentBase.cs b/src/aoWebWallet/Pages/MvvmComponentBase.cs index 0687f07..a78d574 100644 --- a/src/aoWebWallet/Pages/MvvmComponentBase.cs +++ b/src/aoWebWallet/Pages/MvvmComponentBase.cs @@ -1,8 +1,6 @@ -using aoWebWallet.ViewModels; -using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; +using System.Collections.Specialized; using System.ComponentModel; -using System.Diagnostics; using webvNext.DataLoader; namespace aoWebWallet.Pages @@ -13,19 +11,23 @@ public abstract class MvvmComponentBase : ComponentBase, IDisposable where T public T BindingContext { get; set; } = default!; public List ObjWatch { get; set; } = new(); + public List CollectionWatch { get; set; } = new(); protected override void OnInitialized() { - BindingContext.PropertyChanged += BindingContext_PropertyChanged; - foreach(var obj in ObjWatch) { obj.PropertyChanged += ObjWatch_PropertyChanged; } + foreach (var obj in CollectionWatch) + { + obj.CollectionChanged += Obj_CollectionChanged; + } + base.OnInitialized(); } - + protected override async Task OnInitializedAsync() { await LoadDataAsync(); @@ -38,24 +40,16 @@ protected override async Task OnInitializedAsync() // await base.OnParametersSetAsync(); //} - internal async void BindingContext_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + internal void ObjWatch_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(MainViewModel.ComputeUnitUrl)) - { - await LoadDataAsync(); - this.StateHasChanged(); - } + this.StateHasChanged(); + //Console.WriteLine("Obj State changed: " + sender?.ToString()); } - - internal async void ObjWatch_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + private void Obj_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { this.StateHasChanged(); - await ChartRenderAsync(); - } + //Console.WriteLine("Obj Collection changed: " + sender?.ToString()); - protected virtual Task ChartRenderAsync() - { - return Task.CompletedTask; } protected virtual Task LoadDataAsync() @@ -63,11 +57,16 @@ protected virtual Task LoadDataAsync() return Task.CompletedTask; } - protected void WatchObject(D obj) where D : ObservableObject + protected void WatchObject(D obj) where D : INotifyPropertyChanged { ObjWatch.Add(obj); } + protected void WatchCollection(D obj) where D : INotifyCollectionChanged + { + CollectionWatch.Add(obj); + } + protected void WatchDataLoaderVM(DataLoaderViewModel vm) where D : class { ObjWatch.Add(vm); @@ -76,12 +75,15 @@ protected void WatchDataLoaderVM(DataLoaderViewModel vm) where D : class public virtual void Dispose() { - BindingContext.PropertyChanged -= BindingContext_PropertyChanged; - foreach (var obj in ObjWatch) { obj.PropertyChanged -= ObjWatch_PropertyChanged; } + + foreach (var obj in CollectionWatch) + { + obj.CollectionChanged -= Obj_CollectionChanged; + } } } } diff --git a/src/aoWebWallet/Pages/Settings.razor b/src/aoWebWallet/Pages/Settings.razor index d3ad79e..c6e7b29 100644 --- a/src/aoWebWallet/Pages/Settings.razor +++ b/src/aoWebWallet/Pages/Settings.razor @@ -24,38 +24,28 @@ } + + - @* - - - + + + + - - - + Save + + + - Save - *@ @code { - private string? newUrl { get; set; } - private string? customUrl { get; set; } - - protected override void OnInitialized() - { - newUrl = MainViewModel.ComputeUnitUrl; - base.OnInitialized(); - } - void Submit() + async void Submit() { - if (!string.IsNullOrEmpty(newUrl)) - MainViewModel.ComputeUnitUrl = newUrl; - if (!string.IsNullOrEmpty(customUrl)) - MainViewModel.ComputeUnitUrl = customUrl; + await BindingContext.SaveUserSettings(); Snackbar.Add("Settings saved, reloading data...", Severity.Info); } diff --git a/src/aoWebWallet/Pages/Start.razor b/src/aoWebWallet/Pages/Start.razor new file mode 100644 index 0000000..620994a --- /dev/null +++ b/src/aoWebWallet/Pages/Start.razor @@ -0,0 +1,9 @@ +@page "/start" +@inherits MvvmComponentBase +@using aoWebWallet.Models +@using aoWebWallet.Shared + +Add a wallet - @Program.PageTitlePostFix + + + diff --git a/src/aoWebWallet/Pages/TokenDetail.razor b/src/aoWebWallet/Pages/TokenDetail.razor index 9adb983..b181293 100644 --- a/src/aoWebWallet/Pages/TokenDetail.razor +++ b/src/aoWebWallet/Pages/TokenDetail.razor @@ -1,9 +1,11 @@ @page "/token/{tokenId}" @using aoWebWallet.Models -@inherits MvvmComponentBase +@inherits MvvmComponentBase @inject IDialogService DialogService @inject ISnackbar Snackbar @inject NavigationManager NavigationManager; +@inject TokenDataService dataService; +@inject GatewayUrlHelper UrlHelper; @Program.PageTitlePostFix @@ -11,25 +13,23 @@ Token Explorer - + - @if (BindingContext.TokenList.Data != null) + @if (BindingContext.Token.Data != null) { - var token = BindingContext.TokenList.Data.Where(x => x.TokenId == TokenId).FirstOrDefault(); - if (token != null) - { - - - - - @token.TokenData?.Name - @token.TokenData?.Ticker - @token.TokenId - + var token = BindingContext.Token.Data; + + + + + + @token.TokenData?.Name + @token.TokenData?.Ticker + @token.TokenId - - } + + } @@ -42,11 +42,18 @@ Transactions - @foreach (var transfer in BindingContext.TokenTransferList.Data) + @foreach (var transfer in BindingContext.TokenTransferList.Data) + { + + } + + + @if (BindingContext.TokenTransferList.DataLoader.LoadingState == LoadingState.Finished && BindingContext.CanLoadMoreTransactions) { - + Load More } - + + } @@ -57,5 +64,9 @@ [Parameter] public string? TokenId { get; set; } + private Task LoadMoreTransactions() + { + return BindingContext.LoadMoreTransactions(); + } } diff --git a/src/aoWebWallet/Pages/TokenDetail.razor.cs b/src/aoWebWallet/Pages/TokenDetail.razor.cs index 406530a..28ff237 100644 --- a/src/aoWebWallet/Pages/TokenDetail.razor.cs +++ b/src/aoWebWallet/Pages/TokenDetail.razor.cs @@ -3,31 +3,33 @@ namespace aoWebWallet.Pages { - public partial class TokenDetail : MvvmComponentBase + public partial class TokenDetail : MvvmComponentBase { protected override void OnInitialized() { - WatchDataLoaderVM(BindingContext.TokenList); + WatchCollection(dataService.TokenList); + WatchDataLoaderVM(BindingContext.Token); WatchDataLoaderVM(BindingContext.TokenTransferList); base.OnInitialized(); } - protected override void OnParametersSet() + protected override async Task OnParametersSetAsync() { - BindingContext.SelectedTokenId = null; - if (TokenId != null && TokenId.Length != 43) + if (TokenId == null || TokenId.Length != 43) { NavigationManager.NavigateTo(""); } - BindingContext.SelectedTokenId = TokenId; - base.OnParametersSet(); + if(TokenId != null) + await BindingContext.Initialize(TokenId); + + base.OnParametersSetAsync(); } protected override async Task LoadDataAsync() { - await BindingContext.LoadTokenList(); + //await dataService.LoadTokenList(); await base.LoadDataAsync(); diff --git a/src/aoWebWallet/Pages/Tokens.razor b/src/aoWebWallet/Pages/Tokens.razor index 5d6511a..fdfe2e6 100644 --- a/src/aoWebWallet/Pages/Tokens.razor +++ b/src/aoWebWallet/Pages/Tokens.razor @@ -2,6 +2,7 @@ @using aoWebWallet.Models @inherits MvvmComponentBase @inject IDialogService DialogService +@inject TokenDataService dataService @inject ISnackbar Snackbar @Program.PageTitlePostFix @@ -10,20 +11,20 @@ Token Explorer - + - @if (BindingContext.TokenList.Data != null) + @if (dataService.TokenList != null) { - var autoAddedTokens = BindingContext.TokenList.Data.Where(x => !x.IsVisible); + var autoAddedTokens = dataService.TokenList.Where(x => !x.IsVisible); - @foreach (var token in BindingContext.TokenList.Data.Where(x => x.IsVisible)) + @foreach (var token in dataService.TokenList.Where(x => x.IsVisible)) { } @@ -35,17 +36,6 @@ } - @* else - { - foreach (var token in BindingContext.TokenList.Data) - { - - } - } *@ - - - - } @@ -61,8 +51,7 @@ private async void ToggleVisibility(Token token) { - - await BindingContext.TokenToggleVisibility(token.TokenId); + await dataService.TokenToggleVisibility(token.TokenId); StateHasChanged(); } @@ -76,7 +65,7 @@ if (result != null) { - await BindingContext.DeleteToken(token.TokenId); + await dataService.DeleteToken(token.TokenId); Snackbar.Add($"Token {token.TokenData?.Name} deleted ({token.TokenId})", Severity.Info); } diff --git a/src/aoWebWallet/Pages/Tokens.razor.cs b/src/aoWebWallet/Pages/Tokens.razor.cs index 1ee069f..ec675b1 100644 --- a/src/aoWebWallet/Pages/Tokens.razor.cs +++ b/src/aoWebWallet/Pages/Tokens.razor.cs @@ -6,17 +6,20 @@ public partial class Tokens : MvvmComponentBase { protected override void OnInitialized() { - WatchDataLoaderVM(BindingContext.TokenList); + WatchCollection(dataService.TokenList); + WatchObject(dataService.TokenDataLoader); base.OnInitialized(); } - protected override async Task LoadDataAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { - await BindingContext.LoadTokenList(); - - await base.LoadDataAsync(); + if (firstRender) + { + await dataService.LoadTokenList(); + } + await base.OnAfterRenderAsync(firstRender); } } diff --git a/src/aoWebWallet/Pages/TransactionDetail.razor b/src/aoWebWallet/Pages/TransactionDetail.razor index c8f5710..d5f677f 100644 --- a/src/aoWebWallet/Pages/TransactionDetail.razor +++ b/src/aoWebWallet/Pages/TransactionDetail.razor @@ -1,7 +1,9 @@ @page "/transaction/{txid}" @using aoWebWallet.Models -@inherits MvvmComponentBase +@inherits MvvmComponentBase @inject NavigationManager NavigationManager; +@inject TokenDataService dataService +@inject GatewayUrlHelper UrlHelper; @TxId - @Program.PageTitlePostFix @@ -9,7 +11,6 @@ - @if (BindingContext.SelectedTransaction.Data != null) @@ -21,7 +22,7 @@ @transfer.Id - var tokenData = BindingContext.TokenList.Data?.Where(x => x.TokenId == transfer.TokenId).Select(x => x.TokenData).FirstOrDefault(); + var tokenData = dataService.TokenList.Where(x => x.TokenId == transfer.TokenId).Select(x => x.TokenData).FirstOrDefault(); diff --git a/src/aoWebWallet/Pages/TransactionDetail.razor.cs b/src/aoWebWallet/Pages/TransactionDetail.razor.cs index b96d054..a68298e 100644 --- a/src/aoWebWallet/Pages/TransactionDetail.razor.cs +++ b/src/aoWebWallet/Pages/TransactionDetail.razor.cs @@ -1,40 +1,41 @@ -using aoWebWallet.ViewModels; +using aoWebWallet.Models; +using aoWebWallet.ViewModels; using Microsoft.AspNetCore.Components; using MudBlazor; using System.Net; namespace aoWebWallet.Pages { - public partial class TransactionDetail : MvvmComponentBase + public partial class TransactionDetail : MvvmComponentBase { [Parameter] public string? TxId { get; set; } protected override void OnInitialized() { + WatchCollection(dataService.TokenList); WatchDataLoaderVM(BindingContext.TokenTransferList); WatchDataLoaderVM(BindingContext.SelectedTransaction); base.OnInitialized(); } - protected override void OnParametersSet() + protected override async Task OnParametersSetAsync() { - BindingContext.SelectedTransactionId = null; - if (TxId != null && TxId.Length != 43) { NavigationManager.NavigateTo(""); } - BindingContext.SelectedTransactionId = this.TxId; + if (TxId != null) + await BindingContext.Initialize(TxId); - base.OnParametersSet(); + base.OnParametersSetAsync(); } protected override async Task LoadDataAsync() { - await BindingContext.LoadTokenList(); + //await dataService.LoadTokenList(); //if (!string.IsNullOrEmpty(Address)) //{ diff --git a/src/aoWebWallet/Pages/WalletDetail.razor b/src/aoWebWallet/Pages/WalletDetail.razor index 8de155d..714219a 100644 --- a/src/aoWebWallet/Pages/WalletDetail.razor +++ b/src/aoWebWallet/Pages/WalletDetail.razor @@ -1,9 +1,13 @@ @page "/wallet/{address}" @using aoWebWallet.Models -@inherits MvvmComponentBase +@inherits MvvmComponentBase @inject IDialogService DialogService @inject ISnackbar Snackbar -@inject NavigationManager NavigationManager; +@inject NavigationManager NavigationManager +@inject TokenDataService dataService +@inject MainViewModel MainViewModel +@inject ClipboardService ClipboardService +@inject GatewayUrlHelper UrlHelper; @Address - @Program.PageTitlePostFix @@ -35,7 +39,7 @@ } - + @if (!string.IsNullOrEmpty(BindingContext.SelectedWallet?.Wallet.Jwk)) { @@ -48,8 +52,14 @@ @if (BindingContext.SelectedWallet?.Wallet.Source == WalletTypes.Explorer) { - - + + + + } + else if(BindingContext.SelectedWallet?.Wallet != null) + { + + } @@ -59,10 +69,10 @@ { - @if (BindingContext.UserSettings != null) + @if (MainViewModel.UserSettings != null) { - @if (BindingContext.UserSettings.Claimed1 && BindingContext.UserSettings.Claimed2 && BindingContext.UserSettings.Claimed3) + @if (MainViewModel.UserSettings.Claimed1 && MainViewModel.UserSettings.Claimed2 && MainViewModel.UserSettings.Claimed3) { All Rewards Claimed (3/3) @@ -70,25 +80,25 @@ else { - if (BindingContext.UserSettings.Claimed1) + if (MainViewModel.UserSettings.Claimed1) { } else { - Claim Reward (1/3) + Claim Reward (1/3) } - if (BindingContext.UserSettings.Claimed2) + if (MainViewModel.UserSettings.Claimed2) { } else { - Claim Reward (2/3) + Claim Reward (2/3) } - Claim Reward (3/3) + Claim Reward (3/3) } } @@ -106,51 +116,14 @@ - - + - @if (BindingContext.BalanceDataList.Data != null) + @if (BindingContext.BalanceDataList != null) { - @foreach (var balance in BindingContext.BalanceDataList.Data) + @foreach (var balance in BindingContext.BalanceDataList) { - if (balance.Data?.Token?.TokenData == null) - continue; - - - -
- - - @balance.Data?.Token?.TokenData?.Name - @balance.Data?.Token?.TokenData?.Ticker - -
- - - - @if (balance.Data?.BalanceData != null) - { - @BalanceHelper.FormatBalance(balance.Data.BalanceData.Balance, balance.Data.Token?.TokenData?.Denomination ?? 0) - } - - - - - - - - - @if ((BindingContext.SelectedWallet?.CanSend ?? false)) - { - var hasBalance = balance.Data?.BalanceData?.Balance ?? 0; - - - - } - -
-
+ } } @@ -174,9 +147,16 @@ @foreach (var transfer in BindingContext.TokenTransferList.Data) { - + } + + @if (BindingContext.TokenTransferList.DataLoader.LoadingState == LoadingState.Finished && BindingContext.CanLoadMoreTransactions) + { + Load More + } + + }
@@ -222,38 +202,29 @@ private void OpenAddTokenDialog() { var options = new DialogOptions { CloseOnEscapeKey = true }; - DialogService.Show("Add Token", options); + DialogService.Show("Add Token", options); } - private void Receive(BalanceDataViewModel? balanceDataVM) + private async void EditWallet(Wallet wallet) { - BindingContext.SelectedBalanceDataVM = balanceDataVM; - var options = new DialogOptions { CloseOnEscapeKey = true }; - DialogService.Show("Receive Token", options); - } + var parameters = new DialogParameters { { x => x.Wallet, wallet } }; - private void Send(BalanceDataViewModel? balanceDataVM) - { - BindingContext.SelectedBalanceDataVM = balanceDataVM; - var options = new DialogOptions { CloseOnEscapeKey = true }; - DialogService.Show("Transfer Token", options); + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true }; + DialogService.Show("Edit Wallet", parameters, options); + + StateHasChanged(); } + + private async Task RefreshBalances() { - if (BindingContext.SelectedAddress != null) - await BindingContext.LoadBalanceDataList(BindingContext.SelectedAddress); + await BindingContext.RefreshBalanceDataList(); } private async Task RefreshTransactions() { - if (BindingContext.SelectedAddress != null) - await BindingContext.LoadTokenTransferList(BindingContext.SelectedAddress); - } - - private async Task AddWalletAsReadonly() - { - await BindingContext.AddWalletAsReadonly(); + await BindingContext.RefreshTokenTransferList(); } private async Task Claim1() @@ -271,4 +242,9 @@ await BindingContext.Claim3(); } + private Task LoadMoreTransactions() + { + return BindingContext.LoadMoreTransactions(); + } + } diff --git a/src/aoWebWallet/Pages/WalletDetail.razor.cs b/src/aoWebWallet/Pages/WalletDetail.razor.cs index 9ddfe91..86a72f1 100644 --- a/src/aoWebWallet/Pages/WalletDetail.razor.cs +++ b/src/aoWebWallet/Pages/WalletDetail.razor.cs @@ -5,51 +5,71 @@ namespace aoWebWallet.Pages { - public partial class WalletDetail : MvvmComponentBase + public partial class WalletDetail : MvvmComponentBase { protected override void OnInitialized() { - WatchDataLoaderVM(BindingContext.TokenList); - WatchDataLoaderVM(BindingContext.WalletList); - WatchDataLoaderVM(BindingContext.BalanceDataList); + //WatchObject(dataService.TokenList); + //WatchObject(BindingContext.BalanceDataList); + WatchObject(dataService.TokenDataLoader); + + WatchCollection(dataService.TokenList); + WatchCollection(BindingContext.BalanceDataList); + WatchDataLoaderVM(MainViewModel.WalletList); WatchDataLoaderVM(BindingContext.TokenTransferList); WatchDataLoaderVM(BindingContext.SelectedProcessData); + dataService.TokenList.CollectionChanged += TokenList_CollectionChanged; + BindingContext.PropertyChanged += BindingContext_PropertyChanged; + base.OnInitialized(); } - protected override void OnParametersSet() + private void BindingContext_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(BindingContext.VisibleTokenList)) + { + BindingContext.TokenAddedRefresh(); + } + } + + private async void TokenList_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { - BindingContext.SelectedWallet = null; - //BindingContext.SelectedAddress = null; + await BindingContext.TokenAddedRefresh(); + } - if(Address != null && Address.Length != 43) + protected override async Task OnParametersSetAsync() + { + if (Address != null && Address.Length != 43) { NavigationManager.NavigateTo(""); } - BindingContext.SelectedAddress = Address; + if (Address != null) + await BindingContext.Initialize(Address); - base.OnParametersSet(); + await base.OnParametersSetAsync(); } protected override async Task LoadDataAsync() { - BindingContext.LoadTokenList(); - - //if (!string.IsNullOrEmpty(Address)) - //{ - // BindingContext.LoadBalanceDataList(Address); - //} + dataService.LoadTokenList(); await base.LoadDataAsync(); } private async void DownloadWallet(Wallet wallet) { - await BindingContext.DownloadWallet(wallet); + await MainViewModel.DownloadWallet(wallet); StateHasChanged(); } + public override void Dispose() + { + dataService.TokenList.CollectionChanged -= TokenList_CollectionChanged; + + base.Dispose(); + } + } } diff --git a/src/aoWebWallet/Pages/Wallets.razor b/src/aoWebWallet/Pages/Wallets.razor index 0f1a344..7f8f2d1 100644 --- a/src/aoWebWallet/Pages/Wallets.razor +++ b/src/aoWebWallet/Pages/Wallets.razor @@ -3,13 +3,16 @@ @inherits MvvmComponentBase @inject IDialogService DialogService @inject ISnackbar Snackbar +@inject TokenDataService dataService +@inject ClipboardService ClipboardService +@inject NavigationManager Navigation @Program.PageTitlePostFix - @if (BindingContext.WalletList.Data == null || BindingContext.WalletList.Data.Count > 1) + @if (BindingContext.WalletList.Data == null || BindingContext.WalletList.Data.Where(x => !x.IsReadOnly).Count() > 1) { Wallets } @@ -18,13 +21,13 @@ Wallet } - + @if (BindingContext.WalletList.Data != null && BindingContext.WalletList.Data.Any()) { - + } @@ -32,14 +35,14 @@ @if (BindingContext.WalletList.Data != null) { - if(BindingContext.WalletList.Data.Any()) + if (BindingContext.WalletList.Data.Where(x => !x.IsReadOnly).Any()) { int logoCount = 1; - foreach (var wallet in BindingContext.WalletList.Data) + foreach (var wallet in BindingContext.WalletList.Data.Where(x => !x.IsReadOnly)) { string logoUrl = $"images/account--{logoCount}.svg"; string detailUrl = $"wallet/{wallet.Address}"; - + @@ -48,13 +51,9 @@ @wallet.Address - +
- @if (wallet.IsReadOnly) - { - read-only   - } @wallet.Name @if (wallet.NeedsBackup) @@ -73,6 +72,7 @@ { } + @@ -84,25 +84,7 @@ } else { - - - - - - - - - - - - - - - - - - - + Navigation.NavigateTo("/start"); } } else @@ -118,10 +100,15 @@ @code { - private void OpenDialog() + + private async void EditWallet(Wallet wallet) { - var options = new DialogOptions { CloseOnEscapeKey = true }; - DialogService.Show("Add Wallet", options); + var parameters = new DialogParameters { { x => x.Wallet, wallet } }; + + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true }; + DialogService.Show("Edit Wallet", parameters, options); + + StateHasChanged(); } private async void DeleteWallet(Wallet wallet) diff --git a/src/aoWebWallet/Pages/Wallets.razor.cs b/src/aoWebWallet/Pages/Wallets.razor.cs index 17980e8..ddce30b 100644 --- a/src/aoWebWallet/Pages/Wallets.razor.cs +++ b/src/aoWebWallet/Pages/Wallets.razor.cs @@ -6,7 +6,7 @@ public partial class Wallets : MvvmComponentBase { protected override void OnInitialized() { - WatchDataLoaderVM(BindingContext.TokenList); + //WatchDataLoaderVM(BindingContext.TokenList); WatchDataLoaderVM(BindingContext.WalletList); WatchDataLoaderVM(BindingContext.ProcessesDataList); @@ -20,18 +20,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) await BindingContext.CheckHasArConnectExtension(); await BindingContext.LoadWalletList(); - await BindingContext.LoadTokenList(); + await dataService.LoadTokenList(); } await base.OnAfterRenderAsync(firstRender); } - //protected override async Task LoadDataAsync() - //{ - - - // //BindingContext.LoadStats(); - //} - } } diff --git a/src/aoWebWallet/Program.cs b/src/aoWebWallet/Program.cs index d4e138a..ee61b05 100644 --- a/src/aoWebWallet/Program.cs +++ b/src/aoWebWallet/Program.cs @@ -12,6 +12,9 @@ using System.Globalization; using ClipLazor.Extention; using aoww.Services; +using aoww.Services.Models; +using aoWebWallet.Models; +using ArweaveAO.Models; namespace aoWebWallet { @@ -89,8 +92,11 @@ private static void ConfigureServices(IServiceCollection services, string baseAd services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }); //Services - services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddSingleton(); @@ -98,14 +104,23 @@ private static void ConfigureServices(IServiceCollection services, string baseAd services.AddScoped(); services.AddArweaveBlazor(); + services.AddScoped(); //Register ViewModels services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddBlazoredLocalStorage(); services.AddClipboard(); + + //Options + services.AddSingleton(new GraphqlConfig()); + services.AddSingleton(new GatewayConfig()); + services.AddSingleton(new ArweaveConfig()); } } } diff --git a/src/aoWebWallet/Services/BalanceHelper.cs b/src/aoWebWallet/Services/BalanceHelper.cs index 57193c7..018dd39 100644 --- a/src/aoWebWallet/Services/BalanceHelper.cs +++ b/src/aoWebWallet/Services/BalanceHelper.cs @@ -1,7 +1,4 @@ -using Microsoft.AspNetCore.Components; -using System.Numerics; - -namespace aoWebWallet.Services +namespace aoWebWallet.Services { public class BalanceHelper { diff --git a/src/aoWebWallet/Services/ClipboardService.cs b/src/aoWebWallet/Services/ClipboardService.cs new file mode 100644 index 0000000..aa8919e --- /dev/null +++ b/src/aoWebWallet/Services/ClipboardService.cs @@ -0,0 +1,26 @@ +using ClipLazor.Components; +using ClipLazor.Enums; +using MudBlazor; + +namespace aoWebWallet.Services +{ + public class ClipboardService(IClipLazor clipboard, ISnackbar snackbar) + { + public async Task CopyToClipboard(string? text) + { + bool isSupported = await clipboard.IsClipboardSupported(); + bool isWritePermitted = await clipboard.IsPermitted(PermissionCommand.Write); + if (isSupported && !string.IsNullOrEmpty(text)) + { + if (isWritePermitted) + { + var isCopied = await clipboard.WriteTextAsync(text.AsMemory()); + if (isCopied) + { + snackbar.Add("Address copied to clipboard", Severity.Success); + } + } + } + } + } +} diff --git a/src/aoWebWallet/Services/DataService.cs b/src/aoWebWallet/Services/DataService.cs deleted file mode 100644 index 8b6ea6e..0000000 --- a/src/aoWebWallet/Services/DataService.cs +++ /dev/null @@ -1,48 +0,0 @@ - -using aoWebWallet.Models; -using ArweaveAO; - -namespace aoWebWallet.Services -{ - public class DataService - { - private readonly StorageService storageService; - private readonly TokenClient tokenClient; - - public DataService(StorageService storageService, TokenClient tokenClient) - { - this.storageService = storageService; - this.tokenClient = tokenClient; - } - - internal void Init(string value) - { - // throw new NotImplementedException(); - } - - public async IAsyncEnumerable LoadTokenDataAsync() - { - var tokens = await storageService.GetTokenIds(); - foreach (var token in tokens) - { - if (token.TokenData == null) - { - //Load metadata - try - { - var data = await tokenClient.GetTokenMetaData(token.TokenId); - token.TokenData = data; - } - catch { } - } - - if (token.TokenData != null) - yield return token; - } - - await storageService.SaveTokenList(tokens); - - } - - } -} diff --git a/src/aoWebWallet/Services/StorageService.cs b/src/aoWebWallet/Services/StorageService.cs index 2f01b0b..95463e8 100644 --- a/src/aoWebWallet/Services/StorageService.cs +++ b/src/aoWebWallet/Services/StorageService.cs @@ -1,8 +1,6 @@ using aoWebWallet.Models; -using aoWebWallet.Pages; using ArweaveAO.Models.Token; using Blazored.LocalStorage; -using System.Reflection.Metadata; namespace aoWebWallet.Services { @@ -23,38 +21,120 @@ public async ValueTask> GetTokenIds() var result = await localStorage.GetItemAsync>(TOKEN_LIST_KEY); result = result ?? new(); - AddSystemToken(result, "Sa0iBLPNyJQrwpTTG-tWLQU-1QeUAJA73DdxGGiKoJc"); //CRED - AddSystemToken(result, "8p7ApPZxC_37M06QHVejCQrKsHbcJEerd3jWNkDUWPQ"); //BARK - AddSystemToken(result, "OT9qTE2467gcozb2g8R6D6N3nQS94ENcaAIJfUzHCww"); //TRUNK - AddSystemToken(result, "BUhZLMwQ6yZHguLtJYA5lLUa9LQzLXMXRfaq9FVcPJc"); //0rbit + AddSystemTokens(result); return result; } - private void AddSystemToken(List list, string tokenId) + public static void AddSystemTokens(List result) + { + AddSystemToken(result, "Sa0iBLPNyJQrwpTTG-tWLQU-1QeUAJA73DdxGGiKoJc", + new TokenData + { + TokenId = "Sa0iBLPNyJQrwpTTG-tWLQU-1QeUAJA73DdxGGiKoJc", + Denomination = 3, + Logo = "eIOOJiqtJucxvB4k8a-sEKcKpKTh9qQgOV3Au7jlGYc", + Name = "AOCRED", + Ticker = "testnet-AOCRED" + }); //CRED + + + AddSystemToken(result, "8p7ApPZxC_37M06QHVejCQrKsHbcJEerd3jWNkDUWPQ", + new TokenData + { + TokenId = "8p7ApPZxC_37M06QHVejCQrKsHbcJEerd3jWNkDUWPQ", + Denomination = 3, + Logo = "AdFxCN1eEPboxNpCNL23WZRNhIhiamOeS-TUwx_Nr3Q", + Name = "Bark", + Ticker = "BRKTST" + }); //BARK + + AddSystemToken(result, "OT9qTE2467gcozb2g8R6D6N3nQS94ENcaAIJfUzHCww", + new TokenData + { + TokenId = "OT9qTE2467gcozb2g8R6D6N3nQS94ENcaAIJfUzHCww", + Denomination = 3, + Logo = "4eTBOaxZSSyGbpKlHyilxNKhXbocuZdiMBYIORjS4f0", + Name = "TRUNK", + Ticker = "TRUNK" + }); //TRUNK + + AddSystemToken(result, "BUhZLMwQ6yZHguLtJYA5lLUa9LQzLXMXRfaq9FVcPJc", + new TokenData + { + TokenId = "BUhZLMwQ6yZHguLtJYA5lLUa9LQzLXMXRfaq9FVcPJc", + Denomination = 12, + Logo = "nvx7DgTR8ws_k6VNCSe8vhwbZLx5jNbfNLJS0IKTTHA", + Name = "0rbit Points", + Ticker = "0RBT" + }); //0rbit + + AddSystemToken(result, "PBg5TSJPQp9xgXGfjN27GA28Mg5bQmNEdXH2TXY4t-A", + new TokenData + { + TokenId = "PBg5TSJPQp9xgXGfjN27GA28Mg5bQmNEdXH2TXY4t-A", + Denomination = 12, + Logo = "VzvP24VxdNt1kf3E-EXxxrihaNBnXpEI-5ymwWddJRk", + Name = "Earth", + Ticker = "EARTH" + }); + + AddSystemToken(result, "KmGmJieqSRJpbW6JJUFQrH3sQPEG9F6DQETlXNt4GpM", + new TokenData + { + TokenId = "KmGmJieqSRJpbW6JJUFQrH3sQPEG9F6DQETlXNt4GpM", + Denomination = 12, + Logo = "jayAVj1wgIcmin0bjG_DIGxq3_qANSp5EV7PcfUAvdQ", + Name = "Fire", + Ticker = "FIRE" + }); + + AddSystemToken(result, "2nfFJb8LIA69gwuLNcFQezSuw4CXPE4--U-j-7cxKOU", + new TokenData + { + TokenId = "2nfFJb8LIA69gwuLNcFQezSuw4CXPE4--U-j-7cxKOU", + Denomination = 12, + Logo = "7WqV5FWdDcbQzQNxNvfpr093yLHDtjeO7qPM9HQskWE", + Name = "Air", + Ticker = "AIR" + }); + + AddSystemToken(result, "NkXX3uZ4oGkQ3DPAWtjLb2sTA-yxmZKdlOlEHqMfWLQ", + new TokenData + { + TokenId = "NkXX3uZ4oGkQ3DPAWtjLb2sTA-yxmZKdlOlEHqMfWLQ", + Denomination = 12, + Logo = "ioI2_z6qkzGBrvZXbojjf6Q5uVZumx4rDDdHm-Jfyt0", + Name = "Lava", + Ticker = "FIRE-EARTH" + }); + } + + private static void AddSystemToken(List list, string tokenId, TokenData tokenData) { var existing = list.Where(x => x.TokenId == tokenId).FirstOrDefault(); if (existing != null) - return; + existing.IsSystemToken = true; else - list.Add(new Token { TokenId = tokenId, IsSystemToken = true }); + list.Add(new Token { TokenId = tokenId, IsSystemToken = true, TokenData = tokenData }); } - public async ValueTask AddToken(string tokenId, TokenData data, bool isUserAdded) + public async ValueTask AddToken(string tokenId, TokenData data, bool isUserAdded, bool? isVisible) { var list = await GetTokenIds(); var existing = list.Where(x => x.TokenId == tokenId).FirstOrDefault(); if (existing != null) { - existing.IsVisible = true; + if(isVisible.HasValue) + existing.IsVisible = isVisible.Value; if(!existing.IsSystemToken) existing.IsUserAdded = true; } else { - existing = new Token { TokenId = tokenId, TokenData = data, IsUserAdded = isUserAdded }; + existing = new Token { TokenId = tokenId, TokenData = data, IsUserAdded = isUserAdded, IsVisible = isVisible ?? true }; list.Add(existing); } @@ -76,7 +156,9 @@ public async ValueTask DeleteToken(string tokenId) public ValueTask SaveTokenList(List list) { - return localStorage.SetItemAsync(TOKEN_LIST_KEY, list); + var uniqueItems = list.GroupBy(i => i.TokenId).Select(g => g.First()); + + return localStorage.SetItemAsync(TOKEN_LIST_KEY, uniqueItems); } public async ValueTask> GetWallets() @@ -85,15 +167,16 @@ public async ValueTask> GetWallets() return result ?? new(); } + public async ValueTask SaveWallet (Wallet wallet) { var list = await GetWallets(); - var existing = list.Where(x => x.Address == wallet.Address).FirstOrDefault(); + var existing = list.Where(x => x.Address == wallet.Address && x.IsReadOnly == wallet.IsReadOnly).FirstOrDefault(); if(existing != null) list.Remove(existing); - list.Add(wallet); + list.Insert(0,wallet); await SaveWalletList(list); } diff --git a/src/aoWebWallet/Services/TokenDataService.cs b/src/aoWebWallet/Services/TokenDataService.cs new file mode 100644 index 0000000..75a36fd --- /dev/null +++ b/src/aoWebWallet/Services/TokenDataService.cs @@ -0,0 +1,160 @@ + +using aoWebWallet.Models; +using aoWebWallet.Pages; +using ArweaveAO; +using ArweaveAO.Models.Token; +using System.Collections.ObjectModel; +using webvNext.DataLoader; + +namespace aoWebWallet.Services +{ + public class TokenDataService + { + public DataLoader TokenDataLoader { get; set; } = new(); + public ObservableCollection TokenList { get; } = new(); + + + private readonly StorageService storageService; + private readonly TokenClient tokenClient; + + public TokenDataService(StorageService storageService, TokenClient tokenClient) + { + this.storageService = storageService; + this.tokenClient = tokenClient; + } + + public async Task TryAddTokenIds(List allTokenIds) + { + allTokenIds = allTokenIds.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + + foreach (var tokenId in allTokenIds) + { + if (string.IsNullOrEmpty(tokenId) || tokenId.Length != 43) + continue; + + var exist = TokenList.Where(x => x.TokenId == tokenId).Any(); + if (exist) + continue; + + var data = await TokenDataLoader.LoadAsync(async () => + { + var data = await tokenClient.GetTokenMetaData(tokenId); + return data; + }, async data => + { + if (data != null) + { + await storageService.AddToken(tokenId, data, isUserAdded: false, null); + + await LoadTokenList(force: true); + } + }); + + + } + } + + public async Task LoadTokenAsync(string tokenId) + { + var token = TokenList.Where(x => x.TokenId.Equals(tokenId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + if(token == null) + { + token = new Token() + { + TokenId = tokenId, + }; + } + + if (token.TokenData == null) + { + var data = await TokenDataLoader.LoadAsync(async () => + { + var data = await tokenClient.GetTokenMetaData(tokenId); + return data; + }); + + if (data != null) + { + + token.TokenData = data; + + var existing = TokenList.Where(x => x.TokenId.Equals(tokenId, StringComparison.OrdinalIgnoreCase)).Any(); + if(!existing) + TokenList.Add(token); + + await storageService.AddToken(tokenId, data, false, null); + } + + } + + return token; + } + + public async Task LoadTokenList(bool force = false) + { + if (!TokenList.Any() || force) + { + TokenList.Clear(); + await foreach (var item in LoadTokenDataAsync()) + { + var existing = TokenList.Where(x => x.TokenId == item.TokenId).Any(); + + if(!existing) + TokenList.Add(item); + } + } + } + + private async IAsyncEnumerable LoadTokenDataAsync() + { + var tokens = await storageService.GetTokenIds(); + foreach (var token in tokens) + { + if (token.TokenData == null) + { + //Load metadata + try + { + var data = await TokenDataLoader.LoadAsync(async () => + { + var data = await tokenClient.GetTokenMetaData(token.TokenId); + return data; + }); + + token.TokenData = data; + } + catch { } + } + + if (token.TokenData != null) + yield return token; + } + + await storageService.SaveTokenList(tokens); + } + + public async Task DeleteToken(string tokenId) + { + await storageService.DeleteToken(tokenId); + await this.LoadTokenList(force: true); + } + + public async Task TokenToggleVisibility(string tokenId) + { + var all = TokenList ?? new(); + var token = all.Where(x => x.TokenId == tokenId).FirstOrDefault(); + if (token != null) + { + token.IsVisible = !token.IsVisible; + await storageService.SaveTokenList(all.ToList()); + await this.LoadTokenList(force: true); + } + } + + public async Task Clear() + { + await storageService.SaveTokenList(new()); + TokenList.Clear(); + } + } +} diff --git a/src/aoWebWallet/Services/TransactionService.cs b/src/aoWebWallet/Services/TransactionService.cs new file mode 100644 index 0000000..73e599e --- /dev/null +++ b/src/aoWebWallet/Services/TransactionService.cs @@ -0,0 +1,182 @@ +using aoWebWallet.Extensions; +using aoWebWallet.Models; +using ArweaveAO.Requests; +using ArweaveAO.Responses; +using ArweaveBlazor; +using CommunityToolkit.Mvvm.ComponentModel; +using webvNext.DataLoader; + +namespace aoWebWallet.Services +{ + public class TransactionService(ArweaveService arweaveService, + ArweaveAO.AODataClient aODataClient, + TokenDataService tokenDataService) : ObservableObject + { + public void Reset() + { + LastTransaction.Data = null; + DryRunResult.Data = null; + } + public DataLoaderViewModel LastTransaction { get; set; } = new(); + public DataLoaderViewModel DryRunResult { get; set; } = new(); + + public async Task GetActiveArConnectAddress() + { + bool hasArConnectExtension = await arweaveService.HasArConnectAsync(); + + if (hasArConnectExtension) + { + var address = await arweaveService.GetActiveAddress(); + + return address; + + } + + return null; + } + + public Task DryRunAction(Wallet wallet, AoAction action) + => DryRunResult.DataLoader.LoadAsync(async () => + { + DryRunResult.Data = null; + + var target = action.Target?.Value ?? string.Empty; + var druRunRequest = new DryRunRequest() + { + Target = target, + Owner = wallet.Address, + Tags = action.ToDryRunTags() + }; + + var result = await aODataClient.DryRun(target, druRunRequest); + + var balanceInputs = action.AllInputs.Where(x => x.ParamType == ActionParamType.Balance); + foreach (var balanceInput in balanceInputs) + { + if (balanceInput.Value == null) + continue; + + var token = tokenDataService.TokenList.Where(x => x.TokenId == balanceInput.Args.FirstOrDefault()).FirstOrDefault(); + + if(token?.TokenData?.Denomination != null) + { + string original1 = $"You received {balanceInput.Value}"; + string original2 = $"You transferred {balanceInput.Value}"; + + long longValue = long.Parse(balanceInput.Value); + var formatValue = BalanceHelper.FormatBalance(longValue, token.TokenData.Denomination.Value); + + string replace1 = $"You received {formatValue} {token.TokenData.Ticker}"; + string replace2 = $"You transferred {formatValue} {token.TokenData.Ticker}"; + + foreach(var msg in result?.Messages ?? new()) + { + msg.Data = RemoveColorCodes(msg.Data); + msg.Data = msg.Data.Replace(original1, replace1); + msg.Data = msg.Data.Replace(original2, replace2); + } + + } + } + + return result; + }, x => DryRunResult.Data = x); + + static string RemoveColorCodes(string? input) + { + if (input == null) + return string.Empty; + + // Define a regular expression pattern to match color codes + string pattern = @"\x1B\[[0-9;]*[mK]"; + + // Replace color codes with an empty string + string output = System.Text.RegularExpressions.Regex.Replace(input, pattern, ""); + return output; + } + + public async Task SendAction(Wallet wallet, Wallet? ownerWallet, AoAction action) + { + if (wallet.Source == WalletTypes.ArConnect) + { + var activeAddress = await GetActiveArConnectAddress(); + if(activeAddress == wallet.Address) + await SendActionWithArConnect(action); + } + + if (ownerWallet?.Source == WalletTypes.ArConnect) + { + var activeAddress = await GetActiveArConnectAddress(); + if (activeAddress == ownerWallet.Address) + await SendActionWithEvalWithArConnect(wallet.Address, action); + } + + if (!string.IsNullOrEmpty(wallet.OwnerAddress) && ownerWallet?.Address == wallet.OwnerAddress + && !string.IsNullOrEmpty(ownerWallet?.Jwk)) + { + await SendActionWithEval(ownerWallet.Jwk, wallet.Address, action); + } + + if (!string.IsNullOrEmpty(wallet.Jwk)) + await SendActionWithJwk(wallet.Jwk, action); + + //Console.WriteLine("No Wallet to send"); + return; + } + + private async Task SendActionWithEvalWithArConnect(string processId, AoAction action) + { + var activeAddress = await GetActiveArConnectAddress(); + if (string.IsNullOrEmpty(activeAddress)) + return; + + await SendActionWithEval(null, processId, action); + } + + private async Task SendActionWithArConnect(AoAction action) + { + var activeAddress = await GetActiveArConnectAddress(); + if (string.IsNullOrEmpty(activeAddress)) + return; + + await SendActionWithJwk(null, action); + } + + private Task SendActionWithEval(string? jwk, string processId, AoAction action) + => LastTransaction.DataLoader.LoadAsync(async () => + { + + var transferTags = action.ToEvalTags(); + + var data = $"Send({transferTags.ToSendCommand()})"; + + var evalTags = new List + { + new ArweaveBlazor.Models.Tag() { Name = "Action", Value = "Eval"}, + new ArweaveBlazor.Models.Tag() { Name = "X-Wallet", Value = "aoww"}, + }; + + var idResult = await arweaveService.SendAsync(jwk, processId, null, data, evalTags); + + return new Transaction { Id = idResult }; + }, x => LastTransaction.Data = x); + + private Task SendActionWithJwk(string? jwk, AoAction action) + => LastTransaction.DataLoader.LoadAsync(async () => + { + if (action.Target?.Value == null) + return null; + + var transferTags = action.ToTags(); + transferTags.Add(new ArweaveBlazor.Models.Tag() { Name = "X-Wallet", Value = "aoww" }); + + var idResult = await arweaveService.SendAsync(jwk, action.Target.Value, null, null, transferTags); + + return new Transaction { Id = idResult }; + }, x => LastTransaction.Data = x); + + + + + } +} diff --git a/src/aoWebWallet/Services/UrlHelper.cs b/src/aoWebWallet/Services/UrlHelper.cs index 2416720..ec3fb17 100644 --- a/src/aoWebWallet/Services/UrlHelper.cs +++ b/src/aoWebWallet/Services/UrlHelper.cs @@ -1,13 +1,24 @@ -namespace aoWebWallet.Services +using aoWebWallet.Models; +using Microsoft.Extensions.Options; + +namespace aoWebWallet.Services { - public static class UrlHelper + public class GatewayUrlHelper { - public static string? GetArweaveUrl(string? id) + private readonly GatewayConfig config; + + public GatewayUrlHelper(IOptions config) + { + this.config = config.Value; + } + + public string? GetArweaveUrl(string? id) { if (string.IsNullOrWhiteSpace(id)) return null; - return $"https://arweave.net/{id}"; + Uri combinedUri = new Uri(new Uri(config.GatewayUrl), id); + return combinedUri.ToString(); } } } diff --git a/src/aoWebWallet/Shared/ActionEditor.razor b/src/aoWebWallet/Shared/ActionEditor.razor new file mode 100644 index 0000000..2a9a111 --- /dev/null +++ b/src/aoWebWallet/Shared/ActionEditor.razor @@ -0,0 +1,52 @@ +@using aoWebWallet.Models + + + Target @AoAction.Target?.Value + + + + @foreach (var ActionParam in AoAction.Filled) + { + + @ActionParam.Key @ActionParam.Value + + } + + + + + @foreach (var param in AoAction.AllInputs) + { + if (param.ParamType == ActionParamType.Input + || param.ParamType == ActionParamType.Integer + || param.ParamType == ActionParamType.Process + ) + { + + } + else if (param.ParamType == ActionParamType.Quantity || param.ParamType == ActionParamType.Balance) + { + var tokenId = param.Args.FirstOrDefault(); + if (tokenId != null) + { + + } + + + + } + } + + + + +@code { + [Parameter] + public required AoAction AoAction { get; set; } + + [Parameter] + public bool ReadOnly { get; set; } + + [Parameter] + public string? Address { get; set; } +} diff --git a/src/aoWebWallet/Shared/AddArConnectComponent.razor b/src/aoWebWallet/Shared/AddArConnectComponent.razor index 3c9b6f1..f6fc840 100644 --- a/src/aoWebWallet/Shared/AddArConnectComponent.razor +++ b/src/aoWebWallet/Shared/AddArConnectComponent.razor @@ -1,13 +1,11 @@ @using aoWebWallet.Models @inherits MvvmComponentBase -@inject TokenClient TokenClient @inject ISnackbar Snackbar @inject ArweaveService ArweaveService +@inject NavigationManager NavigationManager - + - Connect Wallet - @if (!BindingContext.HasArConnectExtension.HasValue) @@ -128,7 +126,14 @@ Snackbar.Add($"Wallet added ({address})", Severity.Info); - MudDialog?.Close(true); + if (MudDialog != null) + { + MudDialog.Close(); + } + else + { + NavigationManager.NavigateTo($"/wallet/{wallet.Address}"); + } } } diff --git a/src/aoWebWallet/Shared/AddGenerateWalletComponent.razor b/src/aoWebWallet/Shared/AddGenerateWalletComponent.razor index 83e00fb..f30882b 100644 --- a/src/aoWebWallet/Shared/AddGenerateWalletComponent.razor +++ b/src/aoWebWallet/Shared/AddGenerateWalletComponent.razor @@ -1,22 +1,30 @@ @using aoWebWallet.Models @inherits MvvmComponentBase -@inject TokenClient TokenClient @inject ArweaveService ArweaveService @inject ISnackbar Snackbar +@inject NavigationManager NavigationManager - + - - @Progress -
- - Create AOWW Wallet + + + + + + @Progress +
+ + Create aoWW Wallet
@code { + + bool Disabled { get; set; } = false; + DefaultFocus DefaultFocus { get; set; } = DefaultFocus.FirstChild; + [Parameter] public bool HideAddButton { get; set; } @@ -58,6 +66,11 @@ { MudDialog.Close(); } + else + { + NavigationManager.NavigateTo($"/wallet/{wallet.Address}"); + } + return true; } } diff --git a/src/aoWebWallet/Shared/AddTokenDialog.razor b/src/aoWebWallet/Shared/AddTokenDialog.razor index 5d5c47d..4b25fb0 100644 --- a/src/aoWebWallet/Shared/AddTokenDialog.razor +++ b/src/aoWebWallet/Shared/AddTokenDialog.razor @@ -1,12 +1,11 @@ -@inherits MvvmComponentBase -@inject TokenClient TokenClient +@inject TokenDataService dataService @inject ISnackbar Snackbar Add a token to view your balance. Provide a process-id that implements the token standard. - + @Progress @@ -37,11 +36,10 @@ Progress = "Checking metadata..."; try { - var data = await TokenClient.GetTokenMetaData(TokenId); + var token = await dataService.LoadTokenAsync(TokenId); + var data = token.TokenData; if (data != null) { - await BindingContext.AddToken(TokenId, data, isUserAdded: true); - Snackbar.Add($"Token added ({data.Name})", Severity.Info); MudDialog.Close(DialogResult.Ok(true)); diff --git a/src/aoWebWallet/Shared/AddTokenToWalletDialog.razor b/src/aoWebWallet/Shared/AddTokenToWalletDialog.razor new file mode 100644 index 0000000..9facebe --- /dev/null +++ b/src/aoWebWallet/Shared/AddTokenToWalletDialog.razor @@ -0,0 +1,64 @@ +@inject TokenDataService dataService +@inherits MvvmComponentBase +@inject ISnackbar Snackbar + + + + Add a token to view your balance. Provide a process-id that implements the token standard. + + + + @Progress + + + Cancel + Ok + + +@code { + [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; + + public string? TokenId { get; set; } + public string? Progress { get; set; } + + public async Task Submit() + { + if (string.IsNullOrWhiteSpace(TokenId)) + { + Progress = "Input the process-id of an ao-process implementing the token standard."; + return; + } + if(TokenId.Length != 43) + { + Progress = "Length must be 43 characters."; + return; + } + + Progress = "Checking metadata..."; + try + { + var token = await dataService.LoadTokenAsync(TokenId); + var data = token.TokenData; + if (data != null) + { + BindingContext.VisibleTokenList.Add(TokenId); + BindingContext.TokenAddedRefresh(); + + Snackbar.Add($"Token added ({data.Name})", Severity.Info); + + MudDialog.Close(DialogResult.Ok(true)); + } + else + { + Progress = "Could not find token metadata."; + } + } + catch + { + Progress = "Could not find token metadata."; + } + } + + //void Submit() => MudDialog.Close(DialogResult.Ok(true)); + void Cancel() => MudDialog.Cancel(); +} \ No newline at end of file diff --git a/src/aoWebWallet/Shared/AddUploadWalletComponent.razor b/src/aoWebWallet/Shared/AddUploadWalletComponent.razor index 3f9ec11..3684acf 100644 --- a/src/aoWebWallet/Shared/AddUploadWalletComponent.razor +++ b/src/aoWebWallet/Shared/AddUploadWalletComponent.razor @@ -1,10 +1,11 @@ @using aoWebWallet.Models @inherits MvvmComponentBase -@inject TokenClient TokenClient @inject ArweaveService ArweaveService @inject ISnackbar Snackbar +@inject NavigationManager NavigationManager - + + @* Load .json wallet *@ @@ -13,20 +14,20 @@ Accept=".json" OnFilesChanged="OnInputFileChanged" Hidden="@false" - InputClass="absolute mud-width-full mud-height-full overflow-hidden z-20" + InputClass="absolute mud-width-full mud-height-full overflow-hidden z-20 trigger-transparency cursor-pointer" InputStyle="opacity:0" @ondragenter="@SetDragClass" @ondragleave="@ClearDragClass" @ondragend="@ClearDragClass"> - Drag and drop wallet file or click here. - Your files won't be uploaded and are only read by the local app. + Your .JSON files won't be uploaded and are only read by the local app. @foreach (var file in _fileNames) { @@ -34,13 +35,14 @@ } - - Open file picker - + Class="relative d-flex justify-center gap-4 z-30"> + + Load .json wallet + @* -@inject TokenClient TokenClient -@inject ISnackbar Snackbar - - - - - - @Progress - -
- - Add Custom Wallet - -
-
-
- - - -@code { - [Parameter] - public bool HideAddButton { get; set; } - - public string? Name { get; set; } - public string? Address { get; set; } - public string? Progress { get; set; } - - [Parameter] - public bool IsExpanded { get; set; } - - private void OnExpandCollapseClick() - { - IsExpanded = !IsExpanded; - } - - public async Task Submit() - { - if(string.IsNullOrWhiteSpace(Address)) - { - Progress = "Input a wallet address."; - StateHasChanged(); - return false; - } - if (Address.Length != 43) - { - Progress = "Length must be 43 characters."; - StateHasChanged(); - return false; - } - - var wallet = new Wallet - { - Address = Address, - Name = Name, - Source = WalletTypes.Manual, - IsReadOnly = true, - AddedDate = DateTimeOffset.UtcNow - }; - - await BindingContext.SaveWallet(wallet); - - Snackbar.Add($"Wallet added ({Address})", Severity.Info); - return true; - } -} + + + + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
diff --git a/src/aoWebWallet/Shared/AddWalletDialog.razor b/src/aoWebWallet/Shared/AddWalletDialog.razor index 357614a..926ef1e 100644 --- a/src/aoWebWallet/Shared/AddWalletDialog.razor +++ b/src/aoWebWallet/Shared/AddWalletDialog.razor @@ -1,48 +1,18 @@ @using aoWebWallet.Models @using aoWebWallet.Shared -@inherits MvvmComponentBase -@inject TokenClient TokenClient @inject ISnackbar Snackbar - - - - - - - - - - - - - - - + @code { [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; - private AddWalletComponent? addWalletRef; - public async Task Submit() { - // Call a function in AddWalletComponent - if (addWalletRef != null) - { - var result = await addWalletRef.Submit(); - if(result) - { - MudDialog.Close(DialogResult.Ok(true)); - } - } - else - { - MudDialog.Close(DialogResult.Ok(true)); - } + MudDialog.Close(DialogResult.Ok(true)); } //void Submit() => MudDialog.Close(DialogResult.Ok(true)); diff --git a/src/aoWebWallet/Shared/BalanceDataComponent.razor b/src/aoWebWallet/Shared/BalanceDataComponent.razor new file mode 100644 index 0000000..b8b7801 --- /dev/null +++ b/src/aoWebWallet/Shared/BalanceDataComponent.razor @@ -0,0 +1,70 @@ +@using aoWebWallet.Models +@inject GatewayUrlHelper UrlHelper +@inject IDialogService DialogService +@inject NavigationManager NavigationManager + +@if (BalanceDataVM?.Token?.TokenData == null) +{ + return; +} + + + +
+ + + @BalanceDataVM.Token.TokenData?.Name + @BalanceDataVM.Token.TokenData?.Ticker + +
+ + + + @if (BalanceDataVM.BalanceDataLoader.Data != null) + { + @BalanceHelper.FormatBalance(BalanceDataVM.BalanceDataLoader.Data?.Balance, BalanceDataVM.Token?.TokenData?.Denomination ?? 0) + } + + + + + + Receive + + + @if (CanSend) + { + var hasBalance = BalanceDataVM.BalanceDataLoader.Data?.Balance ?? 0; + + Send + + } + +
+
+ +@code{ + [Parameter] + public BalanceDataViewModel? BalanceDataVM { get; set; } + + [Parameter] + public bool CanSend { get; set; } + + private void Receive(BalanceDataViewModel? balanceDataVM) + { + var parameters = new DialogParameters { { x => x.SelectedBalanceDataVM, balanceDataVM } }; + var options = new DialogOptions { CloseOnEscapeKey = true }; + DialogService.Show("Receive Token", parameters, options); + } + + private void Send(BalanceDataViewModel? balanceDataVM) + { + if (balanceDataVM?.Token == null) + return; + + var aoAction = AoAction.CreateForTokenTransaction(balanceDataVM.Token.TokenId); + + NavigationManager.NavigateTo($"/action?{aoAction.ToQueryString()}"); + } +} + \ No newline at end of file diff --git a/src/aoWebWallet/Shared/Components/ActionInputComponent.razor b/src/aoWebWallet/Shared/Components/ActionInputComponent.razor new file mode 100644 index 0000000..9c5833c --- /dev/null +++ b/src/aoWebWallet/Shared/Components/ActionInputComponent.razor @@ -0,0 +1,181 @@ +@using aoWebWallet.Models +@inject MainViewModel MainViewModel +@*

@ActionParam.Key = @ActionParam.Value | @ActionParam.ParamType

*@ + + +@if(ReadOnly) +{ + @ActionParam.Key @ActionParam.Value +} +else +{ + if (ActionParam.ParamType == ActionParamType.Input) + { + + } + else if (ActionParam.ParamType == ActionParamType.Process) + { + + + + @e.ToAutocompleteDisplay() + + + + + @e.ToAutocompleteDisplay() + + + + @(ActionParam.Value ?? "Not selected") + + @* *@ + } + else if (ActionParam.ParamType == ActionParamType.Integer) + { + + } +} + + +@code { + + [Parameter] + public required ActionParam ActionParam { get; set; } + + [Parameter] + public bool ReadOnly { get; set; } + + private string? textValue; + + MudTextField? mudTextField; + //MudTextField? mudProcessField; + MudAutocomplete? mudProcessField; + MudTextField? mudIntField; + + // protected override void OnParametersSet() + // { + // mudTextField?.SetText("TESTAAA"); + // Console.WriteLine("Param Set"); + + // base.OnParametersSet(); + // } + + // protected override void OnInitialized() + // { + // mudTextField?.SetText("TESTAAA"); + // Console.WriteLine("Init"); + + // base.OnInitialized(); + // } + + protected override void OnAfterRender(bool firstRender) + { + if (!(mudTextField?.ValidationErrors.Any() ?? false)) + { + mudTextField?.SetText(ActionParam.Value); + } + + if (!(mudProcessField?.ValidationErrors.Any() ?? false)) + { + // if (ActionParam.Value != null && mudProcessField != null) + // mudProcessField.Text = ActionParam.Value; + + //mudProcessField?.ForceUpdate(); + + // if(mudProcessField != null) + // mudProcessField.Value = ActionParam.Value; + } + + if (!(mudIntField?.ValidationErrors.Any() ?? false)) + { + mudIntField?.SetText(ActionParam.Value); + } + + base.OnAfterRender(firstRender); + } + + public async void UpdateStringValue(string? e) + { + if (mudTextField != null) + await mudTextField.Validate(); + if(mudProcessField != null) + await mudProcessField.Validate(); + + if (!(mudTextField?.ValidationErrors.Any() ?? false) + && !(mudProcessField?.ValidationErrors.Any() ?? false)) + { + ActionParam.Value = e; + } + else + ActionParam.Value = null; + + StateHasChanged(); + } + + public async void UpdateWalletValue(Wallet? e) + { + if (mudProcessField != null) + await mudProcessField.Validate(); + + if (!(mudProcessField?.ValidationErrors.Any() ?? false)) + { + ActionParam.Value = e?.Address; + } + else + ActionParam.Value = null; + + StateHasChanged(); + } + + public IEnumerable ValidateProcess(Wallet? input) + { + if (input == null || input.Address.Length != 43) + { + yield return "Address must have length of 43 characters."; + } + } + + public async void UpdateIntValue(int e) + { + if (mudIntField != null) + await mudIntField.Validate(); + + if (!(mudIntField?.ValidationErrors.Any() ?? false)) + ActionParam.Value = e.ToString(); + else + ActionParam.Value = null; + + StateHasChanged(); + } + + private async Task> WalletSearch(string value) + { + // if text is null or empty, don't return values (drop-down will not open) + if (string.IsNullOrEmpty(value)) + return new Wallet[0]; + + var contacts = MainViewModel.WalletList.Data?.Where(x => + x.Address.Contains(value, StringComparison.InvariantCultureIgnoreCase) + || x.ToAutocompleteDisplay().Equals(value, StringComparison.InvariantCultureIgnoreCase) + || (x.Name?.Contains(value, StringComparison.InvariantCultureIgnoreCase) ?? false) + ).Select(x => x).ToList() ?? new(); + + if (contacts.Any()) + return contacts; + else if(value.Length == 43) + return new Wallet[1] { new Wallet() { Address = value } }; + else + return new Wallet[0]; + } + +} diff --git a/src/aoWebWallet/Shared/Components/ActionQuantityComponent.razor b/src/aoWebWallet/Shared/Components/ActionQuantityComponent.razor new file mode 100644 index 0000000..8706f56 --- /dev/null +++ b/src/aoWebWallet/Shared/Components/ActionQuantityComponent.razor @@ -0,0 +1,152 @@ +@using ArweaveAO.Models.Token +@using aoWebWallet.Models +@inject TokenDataService tokenDataService +@inject TokenClient tokenClient +@*

@ActionParam.Key = @ActionParam.Value | @ActionParam.ParamType

*@ + + +@if(Token == null) +{ + Loading token data... + + return; +} +@if (ActionParam.ParamType == ActionParamType.Balance && string.IsNullOrEmpty(Address)) +{ + Please select a wallet... + return; +} +@if (ActionParam.ParamType == ActionParamType.Balance && BalanceData == null && !ReadOnly) +{ + Loading balance... + + return; +} + +@if (ReadOnly) +{ + @ActionParam.Key @BalanceHelper.FormatBalance(long.Parse(ActionParam.Value ?? "0"), Token?.TokenData?.Denomination ?? 0) @Token?.TokenData?.Ticker + } + else + { + if (ActionParam.ParamType == ActionParamType.Quantity + || ActionParam.ParamType == ActionParamType.Balance) + { + var label = $"{ActionParam.Key} ({Token?.TokenData?.Ticker})"; + + + + @*@Token?.TokenData?.Ticker*@ + + + if (ActionParam.ParamType == ActionParamType.Balance) + { + Balance available: @BalanceHelper.FormatBalance(BalanceData?.Balance, Token?.TokenData?.Denomination ?? 1) @Token?.TokenData?.Ticker + } + } +} + + +@code { + + [Parameter] + public required ActionParam ActionParam { get; set; } + + [Parameter] + public string? Address { get; set; } + + [Parameter] + public bool ReadOnly { get; set; } + + [Parameter] + public required string TokenId { get; set; } + + public Token? Token { get; set; } + + public BalanceData? BalanceData { get; set; } + + public string DenominationFormat => "F" + (Token?.TokenData?.Denomination ?? 1).ToString(); + + MudTextField? mudTextField; + + protected override void OnAfterRender(bool firstRender) + { + if (!(mudTextField?.ValidationErrors.Any() ?? false) && Token?.TokenData?.Denomination != null) + { + mudTextField?.SetText(@BalanceHelper.FormatBalance(long.Parse(ActionParam.Value ?? "0"), Token.TokenData.Denomination.Value)); + } + + base.OnAfterRender(firstRender); + } + + protected override async Task OnParametersSetAsync() + { + BalanceData = null; + var token = await tokenDataService.LoadTokenAsync(TokenId); + if (token.TokenData?.Denomination != null) + Token = token; + + if (ActionParam.ParamType == ActionParamType.Balance + && !string.IsNullOrEmpty(Address) + && !ReadOnly) + { + BalanceData = await tokenClient.GetBalance(token.TokenId, Address); + } + + base.OnParametersSetAsync(); + } + + public IEnumerable ValidateBalance(decimal e) + { + if (e < 0) + { + yield return "Must be greater or equal than 0."; + } + + if(e > 0) + { + + if (ActionParam.ParamType == ActionParamType.Balance) + { + if (Token?.TokenData?.Denomination.HasValue ?? false) + { + long amountLong = BalanceHelper.DecimalToTokenAmount(e, Token.TokenData.Denomination.Value); + + if (BalanceData?.Balance < amountLong) + { + yield return "Not enough balance available."; + } + } + else + { + yield return "Token data is not available."; + } + } + } + } + + public async void UpdateDecimalValue(decimal e) + { + if (mudTextField != null) + await mudTextField.Validate(); + + if (Token?.TokenData?.Denomination == null) + { + ActionParam.Value = null; + return; + } + + + if (!(mudTextField?.ValidationErrors.Any() ?? false)) + { + long amountLong = BalanceHelper.DecimalToTokenAmount(e, Token.TokenData.Denomination.Value); + + ActionParam.Value = amountLong.ToString(); + } + else + ActionParam.Value = null; + + StateHasChanged(); + } + +} diff --git a/src/aoWebWallet/Shared/Components/ApiConnectionDisplay.razor b/src/aoWebWallet/Shared/Components/ApiConnectionDisplay.razor deleted file mode 100644 index 1fc93db..0000000 --- a/src/aoWebWallet/Shared/Components/ApiConnectionDisplay.razor +++ /dev/null @@ -1,14 +0,0 @@ -@inherits MvvmComponentBase -@inject IDialogService DialogService - -@BindingContext.ComputeUnitUrl - - - -@code { - private void OpenDialog() - { - var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true }; - DialogService.Show("Change Compute Unit Url", options); - } -} diff --git a/src/aoWebWallet/Shared/Components/ChangeAPIDialog.razor b/src/aoWebWallet/Shared/Components/ChangeAPIDialog.razor deleted file mode 100644 index 229ba97..0000000 --- a/src/aoWebWallet/Shared/Components/ChangeAPIDialog.razor +++ /dev/null @@ -1,44 +0,0 @@ -@inject MainViewModel MainViewModel - - - - - - - - - - - - - - - - Cancel - Ok - - -@code { - [CascadingParameter] - MudDialogInstance? MudDialog { get; set; } - - private string? newUrl { get; set; } - private string? customUrl { get; set; } - - protected override void OnInitialized() - { - newUrl = MainViewModel.ComputeUnitUrl; - base.OnInitialized(); - } - - void Submit() { - if(!string.IsNullOrEmpty(newUrl)) - MainViewModel.ComputeUnitUrl = newUrl; - if (!string.IsNullOrEmpty(customUrl)) - MainViewModel.ComputeUnitUrl = customUrl; - - MudDialog?.Close(DialogResult.Ok(true)); - } - - void Cancel() => MudDialog?.Cancel(); -} \ No newline at end of file diff --git a/src/aoWebWallet/Shared/Components/TokenListComponent.razor b/src/aoWebWallet/Shared/Components/TokenListComponent.razor index 4aee823..3d7a1f0 100644 --- a/src/aoWebWallet/Shared/Components/TokenListComponent.razor +++ b/src/aoWebWallet/Shared/Components/TokenListComponent.razor @@ -1,5 +1,6 @@ @using aoWebWallet.Models @inherits MvvmComponentBase +@inject GatewayUrlHelper UrlHelper; @if (token != null) { diff --git a/src/aoWebWallet/Shared/Components/TransactionComponent.razor b/src/aoWebWallet/Shared/Components/TransactionComponent.razor index fff8d94..5fb7cfe 100644 --- a/src/aoWebWallet/Shared/Components/TransactionComponent.razor +++ b/src/aoWebWallet/Shared/Components/TransactionComponent.razor @@ -1,10 +1,11 @@ @using aoWebWallet.Models - @inherits MvvmComponentBase +@inject TokenDataService dataService +@inject GatewayUrlHelper UrlHelper; @if (transfer != null) { - var tokenData = BindingContext.TokenList.Data?.Where(x => x.TokenId == transfer.TokenId).Select(x => x.TokenData).FirstOrDefault(); + var tokenData = dataService.TokenList.Where(x => x.TokenId == transfer.TokenId).Select(x => x.TokenData).FirstOrDefault(); var isSend = SelectedAddress == transfer.From; var isReceive = SelectedAddress == transfer.To; string txUrl = $"transaction/{transfer.Id}"; @@ -47,7 +48,7 @@ else if(isReceive) { + @BalanceHelper.FormatBalance(transfer.Quantity, tokenData?.Denomination ?? 0) - + @if(transfer.TokenTransferType == aoww.Services.Enums.TokenTransferType.Mint) { MINT @@ -65,9 +66,7 @@ @transfer.From -
-
@transfer.To diff --git a/src/aoWebWallet/Shared/EditWalletComponent.razor b/src/aoWebWallet/Shared/EditWalletComponent.razor new file mode 100644 index 0000000..c8c070f --- /dev/null +++ b/src/aoWebWallet/Shared/EditWalletComponent.razor @@ -0,0 +1,77 @@ +@using aoWebWallet.Models +@inherits MvvmComponentBase +@inject ISnackbar Snackbar + + + + + @Progress + + +
+ + Save + +
+
+
+ + + +@code { + [Parameter] + public Wallet Wallet { get; set; } = new() { Address = string.Empty }; + + [CascadingParameter] MudDialogInstance? MudDialog { get; set; } + + public string? Progress { get; set; } + public string Address { get; set; } = string.Empty; + public string? Name { get; set; } + + public bool IsReadOnly => !string.IsNullOrEmpty(Wallet.Address); + + + protected override void OnParametersSet() + { + Address = Wallet.Address; + Name = Wallet.Name; + + base.OnParametersSet(); + } + + protected override void OnInitialized() + { + base.OnInitialized(); + } + + + public async Task Submit() + { + // if(string.IsNullOrWhiteSpace(Address)) + // { + // Progress = "Input a wallet address."; + // StateHasChanged(); + // return false; + // } + + if (string.IsNullOrWhiteSpace(Address) || Address.Length != 43) + { + Progress = "Length must be 43 characters."; + StateHasChanged(); + return false; + } + + Wallet.Name = Name; + Wallet.Address = Address; + + await BindingContext.SaveWallet(Wallet); + + StateHasChanged(); + + Snackbar.Add($"Address saved {Wallet.Name} ({Wallet.Address})", Severity.Info); + + MudDialog?.Close(true); + + return true; + } +} diff --git a/src/aoWebWallet/Shared/NavMenu.razor b/src/aoWebWallet/Shared/NavMenu.razor index 2f506d6..2e95140 100644 --- a/src/aoWebWallet/Shared/NavMenu.razor +++ b/src/aoWebWallet/Shared/NavMenu.razor @@ -2,14 +2,14 @@ - @if ((BindingContext.WalletList.Data ?? new()).Any()) + @if (BindingContext.WalletList.Data?.Where(x => !x.IsReadOnly).Any() ?? false) { Home @{ int logoCount = 1; } - @foreach (var wallet in BindingContext.WalletList.Data ?? new()) + @foreach (var wallet in BindingContext.WalletList.Data?.Where(x => !x.IsReadOnly).ToList() ?? new()) { string logoUrl = $"images/account--{logoCount}.svg"; string detailUrl = $"wallet/{wallet.Address}"; @@ -28,32 +28,41 @@ Wallets } + Address Book + Token Explorer -
+
Settings About
-
- @if (BindingContext.UserSettings?.IsDarkMode ?? true) - { - - } - else - { - - } - Theme +
+ + + + + + + + Twitter + + + + + + + + + + + Discord + +
-
- - - Copyright @DateTimeOffset.UtcNow.Year -
+ @code { @@ -64,26 +73,7 @@ { WatchDataLoaderVM(BindingContext.WalletList); - BindingContext.PropertyChanged += BindingContext_PropertyChanged; - base.OnInitialized(); } - - private void BindingContext_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(MainViewModel.IsDarkMode)) - { - this.StateHasChanged(); - } - } - - public virtual void Dispose() - { - BindingContext.PropertyChanged -= BindingContext_PropertyChanged; - } - - private Task ToggleTheme() - { - return BindingContext.SetIsDarkMode(!BindingContext.IsDarkMode); - } + } diff --git a/src/aoWebWallet/Shared/ReceiveTokenDialog.razor b/src/aoWebWallet/Shared/ReceiveTokenDialog.razor index 7e473aa..6f744df 100644 --- a/src/aoWebWallet/Shared/ReceiveTokenDialog.razor +++ b/src/aoWebWallet/Shared/ReceiveTokenDialog.razor @@ -1,16 +1,14 @@ @using aoWebWallet.Models @using aoWebWallet.Shared -@inherits MvvmComponentBase -@inject TokenClient TokenClient -@inject ISnackbar Snackbar +@inject GatewayUrlHelper UrlHelper; - + - @BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Name - @BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Ticker + @SelectedBalanceDataVM?.Token?.TokenData?.Name + @SelectedBalanceDataVM?.Token?.TokenData?.Ticker
@@ -18,7 +16,7 @@ How to receive tokens? Send tokens to this address: - @BindingContext.SelectedBalanceDataVM?.BalanceData?.Account + @SelectedBalanceDataVM?.BalanceDataLoader.Data?.Account @@ -27,7 +25,7 @@ From aos: Command: - Send({ Target = "@BindingContext.SelectedBalanceDataVM?.Token?.TokenId", Action = "Transfer", Recipient = "@BindingContext.SelectedBalanceDataVM?.BalanceData?.Account", Quantity = "TOKEN_AMOUNT"}) + Send({ Target = "@SelectedBalanceDataVM?.Token?.TokenId", Action = "Transfer", Recipient = "@SelectedBalanceDataVM?.BalanceDataLoader.Data?.Account", Quantity = "TOKEN_AMOUNT"})
@@ -39,6 +37,9 @@ @code { [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; + [Parameter] + public BalanceDataViewModel? SelectedBalanceDataVM { get; set; } + public async Task Submit() { MudDialog.Close(DialogResult.Ok(true)); diff --git a/src/aoWebWallet/Shared/SendTokenDialog.razor b/src/aoWebWallet/Shared/SendTokenDialog.razor deleted file mode 100644 index 02b1812..0000000 --- a/src/aoWebWallet/Shared/SendTokenDialog.razor +++ /dev/null @@ -1,200 +0,0 @@ -@using aoWebWallet.Models -@using aoWebWallet.Shared -@inherits MvvmComponentBase -@inject TokenClient TokenClient -@inject ISnackbar Snackbar - - - - - - - @BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Name - @BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Ticker - - - Available balance
@BalanceHelper.FormatBalance(BindingContext.SelectedBalanceDataVM?.BalanceData?.Balance, BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Denomination ?? 0)
- - @if (!isConfirm) - { - - - - - @Progress - } - - @if (isConfirm) - { - if (string.IsNullOrEmpty(TransactionId)) - { - - Are you sure? - You are about to transfer: - - } - - Amount: @Amount @BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Ticker - Receiver: - - @Address - - - - - - if (!string.IsNullOrEmpty(TransactionId)) - { - - Transfer success! - TransactionId - - @TransactionId - - - } - else if (!string.IsNullOrEmpty(Error)) - { - - Transfer Error! - TransactionId - - @Error - - - } - } -
- - Cancel - @if (!isConfirm) - { - Next - } - else - { - if (string.IsNullOrEmpty(TransactionId)) - { - if (!BindingContext.LastTransactionId.DataLoader.IsLoading) - { - Confirm - } - } - else - { - Close - } - } - -
-@code { - [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; - - public string? Progress { get; set; } - public string? Address { get; set; } - public decimal Amount { get; set; } - public string? TransactionId { get; set; } - public string? Error { get; set; } - - public bool isConfirm = false; - public bool showLoader = false; - - public MudButton? confButtonRef; - - public int Denomination => BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Denomination ?? 0; - public string DenominationFormat => "F" + (BindingContext.SelectedBalanceDataVM?.Token?.TokenData?.Denomination ?? 1).ToString(); - - public async Task Submit() - { - if (string.IsNullOrWhiteSpace(Address)) - { - Progress = "Input a wallet address."; - StateHasChanged(); - return; - } - if (Address.Length != 43) - { - Progress = "Address length must be 43 characters."; - StateHasChanged(); - return; - } - - if (string.IsNullOrEmpty(BindingContext.SelectedBalanceDataVM?.BalanceData?.TokenId) - || BindingContext.SelectedBalanceDataVM.Token?.TokenData?.Denomination == null - || !BindingContext.SelectedBalanceDataVM.Token.TokenData.Denomination.HasValue) - return; - - long amountLong = BalanceHelper.DecimalToTokenAmount(Amount, BindingContext.SelectedBalanceDataVM.Token.TokenData.Denomination!.Value); - if (amountLong <= 0) - { - Progress = "Amount has to be higher than 0."; - StateHasChanged(); - return; - } - if (amountLong > BindingContext.SelectedBalanceDataVM?.BalanceData?.Balance) - { - Progress = "Not enough balance available."; - StateHasChanged(); - return; - } - - isConfirm = true; - } - - public async Task Confirm() - { - Error = null; - - if (string.IsNullOrEmpty(Address)) - return; - if (Address.Length != 43) - { - Progress = "Address length must be 43 characters."; - StateHasChanged(); - return; - } - - if (string.IsNullOrEmpty(BindingContext.SelectedBalanceDataVM?.BalanceData?.TokenId) - || BindingContext.SelectedBalanceDataVM.Token?.TokenData?.Denomination == null - || !BindingContext.SelectedBalanceDataVM.Token.TokenData.Denomination.HasValue) - return; - - if (confButtonRef != null) - confButtonRef.Disabled = true; - - this.StateHasChanged(); - - if (BindingContext.SelectedWallet == null) - { - throw new Exception("SelectedWallet is null"); - } - - long amountLong = BalanceHelper.DecimalToTokenAmount(Amount, BindingContext.SelectedBalanceDataVM.Token.TokenData.Denomination!.Value); - var result = await BindingContext.SendToken(BindingContext.SelectedWallet.Wallet, BindingContext.SelectedBalanceDataVM.Token.TokenId, Address, amountLong); - TransactionId = result?.Id; - - if(string.IsNullOrEmpty(TransactionId)) - { - Error = "Error sending transaction."; - } - - if (!string.IsNullOrEmpty(BindingContext.SelectedAddress)) - { - await BindingContext.LoadBalanceDataList(BindingContext.SelectedAddress); - await BindingContext.LoadTokenTransferList(BindingContext.SelectedAddress); - } - - if (TransactionId != null) - { - await BindingContext.AddToLog(ActivityLogType.SendTransaction, TransactionId); - } - } - - public void Close() - { - MudDialog.Close(DialogResult.Ok(true)); - - } - - void Cancel() => MudDialog.Cancel(); -} diff --git a/src/aoWebWallet/ViewModels/BalanceDataViewModel.cs b/src/aoWebWallet/ViewModels/BalanceDataViewModel.cs index d33c121..7d129ac 100644 --- a/src/aoWebWallet/ViewModels/BalanceDataViewModel.cs +++ b/src/aoWebWallet/ViewModels/BalanceDataViewModel.cs @@ -1,11 +1,25 @@ using aoWebWallet.Models; using ArweaveAO.Models.Token; +using webvNext.DataLoader; namespace aoWebWallet.ViewModels { public class BalanceDataViewModel { - public BalanceData? BalanceData { get; set; } + public DataLoaderViewModel BalanceDataLoader { get; set; } = new DataLoaderViewModel(); public Token? Token { get; set; } + + //public void Load() + //{ + // BalanceDataLoader.DataLoader.LoadAsync(async () => + // { + // var balanceData = await tokenClient.GetBalance(token.TokenId, address); + // return balanceData; + // }, (x) => + // { + // balanceData.BalanceDataLoader.Data = x; + // TokenTransferList.ForcePropertyChanged(); + // }); + //} } } diff --git a/src/aoWebWallet/ViewModels/MainViewModel.cs b/src/aoWebWallet/ViewModels/MainViewModel.cs index c66dcc0..9d974fe 100644 --- a/src/aoWebWallet/ViewModels/MainViewModel.cs +++ b/src/aoWebWallet/ViewModels/MainViewModel.cs @@ -5,12 +5,14 @@ using aoww.Services; using aoww.Services.Models; using ArweaveAO; +using ArweaveAO.Models; using ArweaveAO.Models.Token; using ArweaveBlazor; using ClipLazor.Components; using ClipLazor.Enums; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Microsoft.Extensions.Options; using MudBlazor; using System.Text.Json; using webvNext.DataLoader; @@ -22,17 +24,14 @@ public partial class MainViewModel : ObservableRecipient { private const string CLAIM_PROCESS_ID = "5Mv1TBYZvKjNlWUpH78hWORIhqj1uqn_wdkJrA7emfU"; - private readonly DataService dataService; - private readonly TokenClient tokenClient; + private readonly TokenDataService dataService; private readonly StorageService storageService; private readonly ArweaveService arweaveService; private readonly GraphqlClient graphqlClient; - private readonly ISnackbar snackbar; - private readonly IClipLazor clipboard; private readonly MemoryDataCache memoryDataCache; - - [ObservableProperty] - private bool isDarkMode = true; + private readonly ArweaveConfig arweaveConfig; + private readonly GatewayConfig gatewayConfig; + private readonly GraphqlConfig graphqlConfig; [ObservableProperty] public bool? hasArConnectExtension; @@ -41,182 +40,42 @@ public partial class MainViewModel : ObservableRecipient public string? activeArConnectAddress; [ObservableProperty] - [NotifyPropertyChangedRecipients] - private string? computeUnitUrl; - - [ObservableProperty] - private string? selectedAddress; - - [ObservableProperty] - private WalletDetailsViewModel? selectedWallet; - - [ObservableProperty] - private int? selectedWalletIndex; + private UserSettings userSettings = new(); - [ObservableProperty] - private BalanceDataViewModel? selectedBalanceDataVM; - - [ObservableProperty] - private string? selectedTransactionId; - - [ObservableProperty] - private string? selectedTokenId; - - [ObservableProperty] - private UserSettings? userSettings; - - [ObservableProperty] - private bool canClaim1; - [ObservableProperty] - private bool canClaim2; - [ObservableProperty] - private bool canClaim3; public DataLoaderViewModel LastTransactionId { get; set; } = new(); - public DataLoaderViewModel> TokenList { get; set; } = new(); - public DataLoaderViewModel>> BalanceDataList { get; set; } = new(); public DataLoaderViewModel> WalletList { get; set; } = new(); - public DataLoaderViewModel> TokenTransferList { get; set; } = new(); - public DataLoaderViewModel SelectedTransaction { get; set; } = new(); public DataLoaderViewModel>> ProcessesDataList { get; set; } = new(); - public DataLoaderViewModel SelectedProcessData { get; set; } = new(); - - - //TODO: - //Actions List (optional? address) /// /// Gets the responsible for loading the source markdown docs. /// - public MainViewModel(DataService dataService, - TokenClient tokenClient, + public MainViewModel(TokenDataService dataService, StorageService storageService, ArweaveService arweaveService, GraphqlClient graphqlClient, - ISnackbar snackbar, - IClipLazor clipboard, - MemoryDataCache memoryDataCache) : base() + MemoryDataCache memoryDataCache, + IOptions graphqlConfig, + IOptions gatewayConfig, + IOptions arweaveConfig) : base() { this.dataService = dataService; - this.tokenClient = tokenClient; this.storageService = storageService; this.arweaveService = arweaveService; this.graphqlClient = graphqlClient; - this.snackbar = snackbar; - this.clipboard = clipboard; this.memoryDataCache = memoryDataCache; + this.arweaveConfig = arweaveConfig.Value; + this.gatewayConfig = gatewayConfig.Value; + this.graphqlConfig = graphqlConfig.Value; } public async Task AddToLog(ActivityLogType type, string id) { await storageService.AddToLog(type, id); - await SetClaims(); } - public Task LoadTokenTransferList(string address) => TokenTransferList.DataLoader.LoadAsync(async () => - { - TokenTransferList.Data = new(); - - var incoming = await graphqlClient.GetTransactionsIn(address); - var outgoing = await graphqlClient.GetTransactionsOut(address); - var outgoingProcess = await graphqlClient.GetTransactionsOutFromProcess(address); - - var all = incoming.Concat(outgoing).Concat(outgoingProcess); - - TokenTransferList.Data = all.OrderByDescending(x => x.Timestamp).ToList(); - - var allTokenIds = all.Select(x => x.TokenId).Distinct().ToList(); - TryAddTokenIds(allTokenIds); - - return TokenTransferList.Data; - }); - - - public Task LoadTokenTransferListForToken(string? tokenId) => TokenTransferList.DataLoader.LoadAsync(async () => - { - TokenTransferList.Data = new(); - - if (!string.IsNullOrWhiteSpace(tokenId)) - { - var all = await graphqlClient.GetTransactionsForToken(tokenId); - - TokenTransferList.Data = all.ToList(); - - var allTokenIds = all.Select(x => x.TokenId).Distinct().ToList(); - TryAddTokenIds(allTokenIds); - - return TokenTransferList.Data; - } - else - { - return null; - } - }); - - public Task LoadTokenList(bool force = false) => TokenList.DataLoader.LoadAsync(async () => - { - if (TokenList.Data == null || force) - { - TokenList.Data = new(); - await foreach (var item in dataService.LoadTokenDataAsync()) - { - TokenList.Data.Add(item); - TokenList.ForcePropertyChanged(); - } - } - TokenList.ForcePropertyChanged(); - return TokenList.Data; - }); - - public Task LoadSelectedTokenTransfer() => SelectedTransaction.DataLoader.LoadAsync(async () => - { - SelectedTransaction.Data = null; - if (!string.IsNullOrEmpty(SelectedTransactionId)) - { - var result = await graphqlClient.GetTransactionsById(SelectedTransactionId); - - SelectedTransaction.Data = result; - - if (result != null) - TryAddTokenIds(new List() { result.TokenId }); - - return result; - } - else - { - return null; - } - }); - - - public Task LoadBalanceDataList(string address) => BalanceDataList.DataLoader.LoadAsync(async () => - { - //First clear - BalanceDataList.Data = null; - var tokens = TokenList.Data ?? new(); - - var result = new List>(); - - foreach (var token in tokens.Where(x => x.IsVisible)) - { - var balanceData = new DataLoaderViewModel(); - balanceData.Data = new BalanceDataViewModel { Token = token }; - - balanceData.DataLoader.LoadAsync(async () => - { - var balanceData = await tokenClient.GetBalance(token.TokenId, address); - return new BalanceDataViewModel() { BalanceData = balanceData, Token = token }; - }, (x) => { balanceData.Data = x; BalanceDataList.ForcePropertyChanged(); }); - result.Add(balanceData); - } - - BalanceDataList.Data = result; - - return result; - - }); - + public Task LoadProcessesDataList() => ProcessesDataList.DataLoader.LoadAsync(async () => { ProcessesDataList.Data = null; @@ -248,67 +107,7 @@ public Task LoadProcessesDataList() => ProcessesDataList.DataLoader.LoadAsync(as }); - public async Task LoadSelectedWalletProcessData() - { - if (string.IsNullOrEmpty(SelectedAddress)) - return; - - SelectedProcessData.Data = null; - - var address = SelectedAddress; - - SelectedProcessData.Data = new WalletProcessDataViewModel { Address = address }; - - SelectedProcessData.DataLoader.LoadAsync(() => - { - return memoryDataCache!.GetAsync($"{nameof(LoadProcessesDataList)}-{address}", async () => - { - var data = await graphqlClient.GetAoProcessesForAddress(address); - return new WalletProcessDataViewModel() { Address = address, Processes = data }; - }, TimeSpan.FromMinutes(1)); - - - }, (x) => { SelectedProcessData.Data = x; }); - } - - public async Task LoadSelectedWalletOwnerData() - { - if (string.IsNullOrEmpty(SelectedAddress)) - return; - - if (SelectedWallet?.Wallet.OwnerAddress != null) - { - CheckCanOwnerOfSelectedWalletSend(); - return; - } - - var address = SelectedAddress; - - var ownerAddress = await memoryDataCache!.GetAsync($"{nameof(LoadSelectedWalletOwnerData)}-{address}", async () => - { - var data = await graphqlClient.GetOwnerForAoProcessAddress(address); - return data?.Owner; - }, TimeSpan.FromMinutes(1)); - - if (SelectedWallet != null) - SelectedWallet.Wallet.OwnerAddress = ownerAddress; - - CheckCanOwnerOfSelectedWalletSend(); - } - - private void CheckCanOwnerOfSelectedWalletSend() - { - if (!string.IsNullOrEmpty(SelectedWallet?.Wallet.OwnerAddress)) - { - var owner = WalletList.Data?.Where(x => x.Address == SelectedWallet.Wallet.OwnerAddress).FirstOrDefault(); - if (owner != null) - { - var details = new WalletDetailsViewModel(owner); - var canOwnerSend = details?.CanSend ?? false; - SelectedWallet.OwnerCanSend = canOwnerSend; - } - } - } + public async Task LoadWalletList(bool force = false) { @@ -328,66 +127,14 @@ public async Task LoadWalletList(bool force = false) WalletList.Data = list; - - await LoadProcessesDataList(); } } - public async Task AddToken(string tokenId, TokenData data, bool isUserAdded = false) - { - BalanceDataList.Data = null; - var newToken = await storageService.AddToken(tokenId, data, isUserAdded); - var existing = TokenList.Data?.Where(x => x.TokenId == newToken.TokenId).FirstOrDefault(); - if (existing == null) - { - if (TokenList.Data == null) - TokenList.Data = new(); - - TokenList.Data.Add(newToken); - TokenList.ForcePropertyChanged(); - } - else - { - existing = newToken; - } - - if (!string.IsNullOrEmpty(SelectedAddress)) - { - await LoadBalanceDataList(SelectedAddress); - } - - if (!string.IsNullOrEmpty(SelectedTransactionId)) - { - await this.LoadSelectedTokenTransfer(); - } - - await this.SetClaims(); - } - public async Task DeleteToken(string tokenId) - { - BalanceDataList.Data = null; - await storageService.DeleteToken(tokenId); - await this.LoadTokenList(force: true); - } - - public async Task TokenToggleVisibility(string tokenId) - { - var all = TokenList.Data ?? new(); - var token = all.Where(x => x.TokenId == tokenId).FirstOrDefault(); - if (token != null) - { - token.IsVisible = !token.IsVisible; - await storageService.SaveTokenList(all); - await this.LoadTokenList(force: true); - } - } - public async Task SaveWallet(Wallet wallet) { await storageService.SaveWallet(wallet); await LoadWalletList(force: true); - await SetClaims(); } public async Task DeleteWallet(Wallet wallet) @@ -421,133 +168,22 @@ public async Task ClearUserData() { memoryDataCache.Clear(); - await storageService.SaveTokenList(new()); + await dataService.Clear(); await storageService.SaveWalletList(new()); await storageService.SaveUserSettings(new()); await storageService.ClearActivityLog(); //Clear all data - TokenList.Data = null; WalletList = new(); - BalanceDataList.Data = null; - } - - private async Task TryAddTokenIds(List allTokenIds) - { - allTokenIds = allTokenIds.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - - foreach (var tokenId in allTokenIds) - { - Console.WriteLine("TryAddTokenIds: " + tokenId); - if (string.IsNullOrEmpty(tokenId)) - continue; - - var exist = TokenList.Data?.Where(x => x.TokenId == tokenId).Any() ?? false; - if (exist) - continue; - - var data = await tokenClient.GetTokenMetaData(tokenId); - if (data != null) - { - await AddToken(tokenId, data, isUserAdded: false); - } - } + //BalanceDataList.Data = null; } - - - partial void OnSelectedAddressChanged(string? value) - { - SelectWallet(value); - LoadSelectedWalletProcessData(); - LoadSelectedWalletOwnerData(); - - if (value != null) - this.AddToLog(ActivityLogType.ViewAddress, value); - } - - partial void OnSelectedWalletChanged(WalletDetailsViewModel? value) - { - CheckHasArConnectExtension(); - } - - private async Task SelectWallet(string? address) - { - if (!string.IsNullOrEmpty(address)) - { - if (this.WalletList.Data == null) - { - await LoadWalletList(); - } - - var all = this.WalletList.Data ?? new(); - var current = all.Where(x => x.Address == address).FirstOrDefault(); - if (current != null) - { - SelectedWallet = new WalletDetailsViewModel(current); - var indexOf = all.IndexOf(current); - SelectedWalletIndex = (indexOf % 5) + 1; - - } - else - { - var tempWallet = new Wallet - { - Address = address, - AddedDate = DateTimeOffset.Now, - LastUsedDate = DateTimeOffset.UtcNow, - Name = null, - Source = WalletTypes.Explorer - }; - SelectedWallet = new WalletDetailsViewModel(tempWallet); - SelectedWalletIndex = 5; - } - - this.LoadBalanceDataList(address); - this.LoadTokenTransferList(address); - - } - else - { - SelectedWallet = null; - SelectedWalletIndex = null; - } - } - - partial void OnSelectedTransactionIdChanged(string? value) - { - this.LoadSelectedTokenTransfer(); - - if (value != null) - this.AddToLog(ActivityLogType.ViewTransaction, value); - } - - partial void OnSelectedTokenIdChanged(string? value) - { - this.LoadTokenTransferListForToken(value); - - if (value != null) - this.AddToLog(ActivityLogType.ViewToken, value); - } - - partial void OnComputeUnitUrlChanged(string? value) - { - //ClearUserData(); - - if (!string.IsNullOrEmpty(value)) - { - //storageService.SetApiUrl(value); - - dataService.Init(value); - } - - } - + public async Task LoadUserSettings() { UserSettings = await storageService.GetUserSettings(); if (UserSettings != null) { - IsDarkMode = UserSettings.IsDarkMode ?? true; + UpdateUserSettings(UserSettings); } } @@ -556,106 +192,18 @@ public async Task SaveUserSettings() if (UserSettings != null) { await storageService.SaveUserSettings(UserSettings); - IsDarkMode = UserSettings.IsDarkMode ?? true; - } - } - public async Task AddWalletAsReadonly() - { - if (SelectedWallet != null) - { - SelectedWallet.Wallet.Source = WalletTypes.Manual; - if(SelectedWallet.Wallet.OwnerAddress != null) - SelectedWallet.Wallet.Source = WalletTypes.AoProcess; - - await storageService.SaveWallet(SelectedWallet.Wallet); - await LoadWalletList(force: true); - - snackbar.Add("Wallet added to list.", Severity.Info); - - } - } - - public async Task SetClaims() - { - var viewTokenActivity = await storageService.GetLog(ActivityLogType.ViewToken); - var viewTransactionctivity = await storageService.GetLog(ActivityLogType.ViewTransaction); - var viewAddressActivity = await storageService.GetLog(ActivityLogType.ViewAddress); - var sendTransactionActivity = await storageService.GetLog(ActivityLogType.SendTransaction); - - CanClaim1 = sendTransactionActivity.Count > 0; - CanClaim2 = CanClaim1 && sendTransactionActivity.Count > 1 && (WalletList.Data?.Count() > 1 || TokenList.Data?.Count() > 6); - CanClaim3 = CanClaim2 && sendTransactionActivity.Count > 1 && WalletList.Data?.Count() > 2 && viewTokenActivity.Count > 0 && viewAddressActivity.Count > 2 && viewTransactionctivity.Count > 1; - - Console.WriteLine("1:" + CanClaim1); - } - - public async Task Claim1() - { - if (UserSettings != null) - { - var tx = await Claim(1); - if (tx != null && !string.IsNullOrEmpty(tx.Id)) - { - UserSettings.Claimed1 = true; - await storageService.SaveUserSettings(UserSettings); - - snackbar.Add("Claim 1 successful. You received 10 AOWW!", Severity.Info); - - if (SelectedAddress != null) - await LoadBalanceDataList(this.SelectedAddress); - - } - else - { - snackbar.Add("Claim was not successful.", Severity.Error); - } + UpdateUserSettings(UserSettings); } - } - public async Task Claim2() - { - if (UserSettings != null) - { - var tx = await Claim(2); - if (tx != null && !string.IsNullOrEmpty(tx.Id)) - { - UserSettings.Claimed2 = true; - await storageService.SaveUserSettings(UserSettings); - - snackbar.Add("Claim 2 successful. You received 20 AOWW!", Severity.Info); - if (SelectedAddress != null) - await LoadBalanceDataList(this.SelectedAddress); - - } - else - { - snackbar.Add("Claim was not successful.", Severity.Error); - } - } - } - public async Task Claim3() + private void UpdateUserSettings(UserSettings userSettings) { - if (UserSettings != null) - { - var tx = await Claim(3); - if (tx != null && !string.IsNullOrEmpty(tx.Id)) - { - UserSettings.Claimed3 = true; - await storageService.SaveUserSettings(UserSettings); - - snackbar.Add("Claim 3 successful. You received 30 AOWW!", Severity.Info); - - if (SelectedAddress != null) - await LoadBalanceDataList(this.SelectedAddress); + graphqlConfig.ApiUrl = userSettings.GraphqlUrl; + gatewayConfig.GatewayUrl = userSettings.GatewayUrl; + arweaveConfig.ComputeUnitUrl = userSettings.ComputeUnitUrl; - } - else - { - snackbar.Add("Claim was not successful.", Severity.Error); - } - } + arweaveService.SetConnection(userSettings.GatewayUrl, userSettings.GraphqlUrl, userSettings.MessengerUnitUrl, userSettings.ComputeUnitUrl); } public async Task DisconnectArWallet() @@ -673,42 +221,14 @@ public async Task CheckHasArConnectExtension() public async Task GetActiveArConnectAddress() { - //if (this.WalletList.Data != null) - //{ - // var wallets = this.WalletList.Data.Where(x => x.IsConnected && x.Source == WalletTypes.ArConnect); - // foreach (var wallet in wallets) - // { - // wallet.IsConnected = false; - // } - // this.WalletList.ForcePropertyChanged(); - // await storageService.SaveWalletList(this.WalletList.Data); - //} - if (HasArConnectExtension.HasValue && HasArConnectExtension.Value) { ActiveArConnectAddress = await arweaveService.GetActiveAddress(); - if (this.SelectedWallet != null) - { - this.SelectedWallet.IsConnected = SelectedWallet.Wallet.Address == ActiveArConnectAddress; - } - } - } - - public async Task CopyToClipboard(string? text) - { - bool isSupported = await clipboard.IsClipboardSupported(); - bool isWritePermitted = await clipboard.IsPermitted(PermissionCommand.Write); - if (isSupported && !string.IsNullOrEmpty(text)) - { - if (isWritePermitted) - { - var isCopied = await clipboard.WriteTextAsync(text.AsMemory()); - if (isCopied) - { - snackbar.Add("Address copied to clipboard", Severity.Success); - } - } + //if (this.SelectedWallet != null) + //{ + // this.SelectedWallet.IsConnected = SelectedWallet.Wallet.Address == ActiveArConnectAddress; + //} } } @@ -799,13 +319,5 @@ public async Task CopyToClipboard(string? text) return new Transaction { Id = idResult }; }); - public async Task SetIsDarkMode(bool isDarkMode) - { - if (UserSettings != null) - { - UserSettings.IsDarkMode = isDarkMode; - await SaveUserSettings(); - } - } } } diff --git a/src/aoWebWallet/ViewModels/TokenDetailViewModel.cs b/src/aoWebWallet/ViewModels/TokenDetailViewModel.cs new file mode 100644 index 0000000..12f1cb0 --- /dev/null +++ b/src/aoWebWallet/ViewModels/TokenDetailViewModel.cs @@ -0,0 +1,85 @@ +using aoWebWallet.Models; +using aoWebWallet.Services; +using aoww.Services; +using aoww.Services.Models; +using CommunityToolkit.Mvvm.ComponentModel; +using webvNext.DataLoader; +using static MudBlazor.Colors; + +namespace aoWebWallet.ViewModels +{ + public class TokenDetailViewModel : ObservableObject + { + private readonly MainViewModel mainViewModel; + private readonly GraphqlClient graphqlClient; + private readonly TokenDataService dataService; + private string? _tokenId; + + private List tokenTransactions = new(); + + public bool CanLoadMoreTransactions { get; set; } = true; + + public DataLoaderViewModel Token { get; set; } = new(); + + public DataLoaderViewModel> TokenTransferList { get; set; } = new(); + + public TokenDetailViewModel(MainViewModel mainViewModel, GraphqlClient graphqlClient, TokenDataService dataService) + { + this.mainViewModel = mainViewModel; + this.graphqlClient = graphqlClient; + this.dataService = dataService; + } + + + public async Task Initialize(string tokenId) + { + _tokenId = tokenId; + TokenTransferList.Data = new(); + + await this.LoadTokenData(tokenId); + + this.LoadTokenTransferListForToken(tokenId); + + mainViewModel.AddToLog(ActivityLogType.ViewToken, tokenId); + } + + public Task LoadTokenData(string tokenId) => Token.DataLoader.LoadAsync(async () => + { + Token.Data = null; + + var result = await dataService.LoadTokenAsync(tokenId); + + return result; + + }, (x) => Token.Data = x); + + public Task LoadTokenTransferListForToken(string tokenId) => TokenTransferList.DataLoader.LoadAsync(async () => + { + tokenTransactions = await graphqlClient.GetTransactionsForToken(tokenId, GetCursor(tokenTransactions)); + CanLoadMoreTransactions = tokenTransactions.Any(); + + var existing = TokenTransferList.Data ?? new(); + + TokenTransferList.Data = existing.Concat(tokenTransactions).OrderByDescending(x => x.Timestamp).ToList(); + + var allTokenIds = tokenTransactions.Where(x => x.TokenId != null).Select(x => x.TokenId!).Distinct().ToList(); + dataService.TryAddTokenIds(allTokenIds); + + return TokenTransferList.Data; + + }); + + public async Task LoadMoreTransactions() + { + if (_tokenId != null) + { + await LoadTokenTransferListForToken(_tokenId); + } + } + + private static string? GetCursor(List transactions) + { + return transactions.Select(x => x.Cursor).LastOrDefault(); + } + } +} diff --git a/src/aoWebWallet/ViewModels/TransactionDetailViewModel.cs b/src/aoWebWallet/ViewModels/TransactionDetailViewModel.cs new file mode 100644 index 0000000..f9e07c5 --- /dev/null +++ b/src/aoWebWallet/ViewModels/TransactionDetailViewModel.cs @@ -0,0 +1,51 @@ +using aoWebWallet.Models; +using aoWebWallet.Services; +using aoww.Services; +using aoww.Services.Models; +using CommunityToolkit.Mvvm.ComponentModel; +using webvNext.DataLoader; + +namespace aoWebWallet.ViewModels +{ + public class TransactionDetailViewModel : ObservableObject + { + private readonly MainViewModel mainViewModel; + private readonly GraphqlClient graphqlClient; + private readonly TokenDataService dataService; + + public DataLoaderViewModel SelectedTransaction { get; set; } = new(); + public DataLoaderViewModel> TokenTransferList { get; set; } = new(); + + public TransactionDetailViewModel(MainViewModel mainViewModel, GraphqlClient graphqlClient, TokenDataService dataService) + { + this.mainViewModel = mainViewModel; + this.graphqlClient = graphqlClient; + this.dataService = dataService; + } + + + public async Task Initialize(string txId) + { + this.LoadSelectedTokenTransfer(txId); + + if (txId != null) + mainViewModel.AddToLog(ActivityLogType.ViewTransaction, txId); + } + + + + public Task LoadSelectedTokenTransfer(string txId) => SelectedTransaction.DataLoader.LoadAsync(async () => + { + SelectedTransaction.Data = null; + var result = await graphqlClient.GetTransactionsById(txId); + + SelectedTransaction.Data = result; + + if (result?.TokenId != null) + dataService.TryAddTokenIds(new List() { result.TokenId }); + + return result; + }, (x) => SelectedTransaction.Data = x); + + } +} diff --git a/src/aoWebWallet/ViewModels/WalletDetailViewModel.cs b/src/aoWebWallet/ViewModels/WalletDetailViewModel.cs new file mode 100644 index 0000000..64b386c --- /dev/null +++ b/src/aoWebWallet/ViewModels/WalletDetailViewModel.cs @@ -0,0 +1,464 @@ +using aoWebWallet.Models; +using aoWebWallet.Services; +using aoww.Services; +using aoww.Services.Models; +using ArweaveAO; +using ArweaveBlazor; +using CommunityToolkit.Mvvm.ComponentModel; +using MudBlazor; +using System.Collections.ObjectModel; +using webvNext.DataLoader; +using webvNext.DataLoader.Cache; + +namespace aoWebWallet.ViewModels +{ + public partial class WalletDetailViewModel : ObservableObject + { + private readonly MainViewModel mainViewModel; + private readonly GraphqlClient graphqlClient; + private readonly TokenDataService dataService; + private readonly TokenClient tokenClient; + private readonly StorageService storageService; + private readonly ArweaveService arweaveService; + private readonly ISnackbar snackbar; + private readonly MemoryDataCache memoryDataCache; + + private List incoming = new(); + private List outgoing = new(); + private List outgoingProcess = new(); + + + [ObservableProperty] + public List visibleTokenList = new(); + + + + private string? selectedAddress = null; + + public bool CanLoadMoreTransactions { get; set; } = true; + + [ObservableProperty] + private bool canClaim1; + [ObservableProperty] + private bool canClaim2; + [ObservableProperty] + private bool canClaim3; + + [ObservableProperty] + public string? activeArConnectAddress; + + [ObservableProperty] + public bool? hasArConnectExtension; + + public int? SelectedWalletIndex { get; set; } + + + public WalletDetailsViewModel? SelectedWallet { get; set; } + + + public DataLoaderViewModel SelectedProcessData { get; set; } = new(); + + public ObservableCollection BalanceDataList { get; } = new(); + + public DataLoaderViewModel> TokenTransferList { get; set; } = new(); + + + public WalletDetailViewModel(MainViewModel mainViewModel, + GraphqlClient graphqlClient, + TokenDataService dataService, + TokenClient tokenClient, + StorageService storageService, + ArweaveService arweaveService, + ISnackbar snackbar, + MemoryDataCache memoryDataCache) + { + this.mainViewModel = mainViewModel; + this.graphqlClient = graphqlClient; + this.dataService = dataService; + this.tokenClient = tokenClient; + this.storageService = storageService; + this.arweaveService = arweaveService; + this.snackbar = snackbar; + this.memoryDataCache = memoryDataCache; + } + + + public async Task Initialize(string address) + { + VisibleTokenList = new(); + VisibleTokenList.Add("Sa0iBLPNyJQrwpTTG-tWLQU-1QeUAJA73DdxGGiKoJc"); + + ResetTokenTransferlist(); + + selectedAddress = address; + + await SelectWallet(address); + + await LoadSelectedWalletProcessData(address); + await LoadSelectedWalletOwnerData(address); + + CheckHasArConnectExtension(); + + SetClaims(); + + mainViewModel.AddToLog(ActivityLogType.ViewAddress, address); + } + + public async Task CheckHasArConnectExtension() + { + HasArConnectExtension = await arweaveService.HasArConnectAsync(); + await GetActiveArConnectAddress(); + } + + public async Task GetActiveArConnectAddress() + { + if (HasArConnectExtension.HasValue && HasArConnectExtension.Value) + { + ActiveArConnectAddress = await arweaveService.GetActiveAddress(); + + if (this.SelectedWallet != null) + { + this.SelectedWallet.IsConnected = SelectedWallet.Wallet.Address == ActiveArConnectAddress; + } + } + } + + public async Task RefreshTokenTransferList() + { + if (selectedAddress != null) + { + ResetTokenTransferlist(); + + await LoadTokenTransferList(selectedAddress); + } + } + + private void ResetTokenTransferlist() + { + incoming = new(); + outgoing = new(); + outgoingProcess = new(); + TokenTransferList.Data = new(); + } + + public async Task LoadMoreTransactions() + { + if (selectedAddress != null) + { + await LoadTokenTransferList(selectedAddress); + } + } + + public async Task RefreshBalanceDataList() + { + if (selectedAddress != null) + { + await LoadBalanceDataList(selectedAddress); + } + } + + public async Task RefreshBalance() + { + if (selectedAddress != null) + { + await RefreshTokenTransferList(); + await LoadBalanceDataList(selectedAddress); + } + } + + public async Task TokenAddedRefresh() + { + if (selectedAddress != null) + { + await LoadBalanceDataList(selectedAddress, onlyNew: true); + } + } + + //public async Task Refresh() + //{ + // if (selectedAddress != null) + // await Initialize(selectedAddress); + //} + + private async Task SelectWallet(string? address) + { + if (!string.IsNullOrEmpty(address)) + { + if (mainViewModel.WalletList.Data == null) + { + await mainViewModel.LoadWalletList(); + } + + var all = mainViewModel.WalletList.Data ?? new(); + var current = all.Where(x => x.Address == address).FirstOrDefault(); + if (current != null) + { + SelectedWallet = new WalletDetailsViewModel(current); + var indexOf = all.IndexOf(current); + SelectedWalletIndex = (indexOf % 5) + 1; + + } + else + { + var tempWallet = new Wallet + { + Address = address, + AddedDate = DateTimeOffset.Now, + LastUsedDate = DateTimeOffset.UtcNow, + Name = null, + Source = WalletTypes.Explorer + }; + SelectedWallet = new WalletDetailsViewModel(tempWallet); + SelectedWalletIndex = 5; + } + + this.LoadBalanceDataList(address); + this.LoadTokenTransferList(address); + + if (this.SelectedWallet != null) + { + this.SelectedWallet.IsConnected = SelectedWallet.Wallet.Address == ActiveArConnectAddress; + } + + } + else + { + SelectedWallet = null; + SelectedWalletIndex = null; + } + } + + public Task LoadTokenTransferList(string address) => TokenTransferList.DataLoader.LoadAsync(async () => + { + incoming = await graphqlClient.GetTransactionsIn(address, GetCursor(incoming)); + outgoing = await graphqlClient.GetTransactionsOut(address, GetCursor(outgoing)); + outgoingProcess = await graphqlClient.GetTransactionsOutFromProcess(address, GetCursor(outgoingProcess)); + + var allNew = incoming.Concat(outgoing).Concat(outgoingProcess).OrderByDescending(x => x.Timestamp).ToList(); + CanLoadMoreTransactions = allNew.Any(); + + var existing = TokenTransferList.Data ?? new(); + + TokenTransferList.Data = existing.Concat(allNew).OrderByDescending(x => x.Timestamp).ToList(); + + List allTokenIds = allNew.Where(x => x.TokenId != null).Select(x => x.TokenId!).Distinct().ToList(); + dataService.TryAddTokenIds(allTokenIds); + + bool hasNew = false; + foreach(var token in allTokenIds) + { + var exist = VisibleTokenList.Where(x => x == token).Any(); + if (!exist) + { + VisibleTokenList.Add(token); + hasNew = true; + } + } + if (hasNew) + OnPropertyChanged(nameof(VisibleTokenList)); + + + return TokenTransferList.Data; + }); + + private static string? GetCursor(List transactions) + { + return transactions.Select(x => x.Cursor).LastOrDefault(); + } + + private async Task LoadBalanceDataList(string address, bool onlyNew = false) + { + //First clear + if (!onlyNew) + BalanceDataList.Clear(); + + foreach (var token in dataService.TokenList.Where(x => VisibleTokenList.Contains(x.TokenId) && x.IsVisible)) + { + if (onlyNew) + { + if (BalanceDataList.Where(x => x.Token?.TokenId == token.TokenId).Any()) + continue; + + } + var balanceData = new BalanceDataViewModel { Token = token }; + BalanceDataList.Add(balanceData); + + await Task.Delay(50); + + balanceData.BalanceDataLoader.DataLoader.LoadAsync(async () => + { + var balanceData = await tokenClient.GetBalance(token.TokenId, address); + return balanceData; + }, (x) => + { + balanceData.BalanceDataLoader.Data = x; + TokenTransferList.ForcePropertyChanged(); + }); + + + + } + } + + public async Task LoadSelectedWalletProcessData(string address) + { + SelectedProcessData.Data = new WalletProcessDataViewModel { Address = address }; + + SelectedProcessData.DataLoader.LoadAsync(() => + { + return memoryDataCache!.GetAsync($"{nameof(MainViewModel.LoadProcessesDataList)}-{address}", async () => + { + var data = await graphqlClient.GetAoProcessesForAddress(address); + return new WalletProcessDataViewModel() { Address = address, Processes = data }; + }, TimeSpan.FromMinutes(1)); + + + }, (x) => { SelectedProcessData.Data = x; }); + } + + public async Task LoadSelectedWalletOwnerData(string address) + { + var ownerAddress = await memoryDataCache!.GetAsync($"{nameof(LoadSelectedWalletOwnerData)}-{address}", async () => + { + var data = await graphqlClient.GetOwnerForAoProcessAddress(address); + return data?.Owner; + }, TimeSpan.FromMinutes(1)); + + if (SelectedWallet != null) + SelectedWallet.Wallet.OwnerAddress = ownerAddress; + + CheckCanOwnerOfSelectedWalletSend(); + } + + private void CheckCanOwnerOfSelectedWalletSend() + { + if (!string.IsNullOrEmpty(SelectedWallet?.Wallet.OwnerAddress)) + { + var owner = mainViewModel.WalletList.Data?.Where(x => x.Address == SelectedWallet.Wallet.OwnerAddress).FirstOrDefault(); + if (owner != null) + { + var details = new WalletDetailsViewModel(owner); + var canOwnerSend = details?.CanSend ?? false; + SelectedWallet.OwnerCanSend = canOwnerSend; + } + } + } + + public async Task SaveExplorerWallet() + { + if (SelectedWallet?.Wallet != null) + { + var existing = mainViewModel.WalletList.Data?.Where(x => x.Address == SelectedWallet.Wallet.Address).Any() ?? false; + if (existing) + return; + + SelectedWallet.Wallet.Source = WalletTypes.Manual; + SelectedWallet.Wallet.IsReadOnly = true; + + var ownerAddress = SelectedWallet.Wallet.OwnerAddress; + if (ownerAddress != null) + { + var ownerWallet = mainViewModel.WalletList.Data?.Where(x => !x.IsReadOnly && x.Address == ownerAddress).FirstOrDefault(); + + if (ownerWallet != null) + { + SelectedWallet.Wallet.Source = WalletTypes.AoProcess; + SelectedWallet.Wallet.IsReadOnly = false; + } + } + + await storageService.SaveWallet(SelectedWallet.Wallet); + await mainViewModel.LoadWalletList(force: true); + + snackbar.Add("Wallet added to list.", Severity.Info); + + } + } + + + + + public async Task SetClaims() + { + var viewTokenActivity = await storageService.GetLog(ActivityLogType.ViewToken); + var viewTransactionctivity = await storageService.GetLog(ActivityLogType.ViewTransaction); + var viewAddressActivity = await storageService.GetLog(ActivityLogType.ViewAddress); + var sendTransactionActivity = await storageService.GetLog(ActivityLogType.SendTransaction); + + CanClaim1 = sendTransactionActivity.Count > 0; + CanClaim2 = CanClaim1 && sendTransactionActivity.Count > 1 && (mainViewModel.WalletList.Data?.Count() > 1 || dataService.TokenList.Count() > 6); + CanClaim3 = CanClaim2 && sendTransactionActivity.Count > 1 && mainViewModel.WalletList.Data?.Count() > 2 && viewTokenActivity.Count > 0 && viewAddressActivity.Count > 2 && viewTransactionctivity.Count > 1; + + } + + public async Task Claim1() + { + if (mainViewModel.UserSettings != null) + { + var tx = await mainViewModel.Claim(1); + if (tx != null && !string.IsNullOrEmpty(tx.Id)) + { + mainViewModel.UserSettings.Claimed1 = true; + await storageService.SaveUserSettings(mainViewModel.UserSettings); + + snackbar.Add("Claim 1 successful. You received 10 AOWW!", Severity.Info); + + if (SelectedWallet != null) + await LoadBalanceDataList(this.SelectedWallet.Wallet.Address); + + } + else + { + snackbar.Add("Claim was not successful.", Severity.Error); + } + } + + } + public async Task Claim2() + { + if (mainViewModel.UserSettings != null) + { + var tx = await mainViewModel.Claim(2); + if (tx != null && !string.IsNullOrEmpty(tx.Id)) + { + mainViewModel.UserSettings.Claimed2 = true; + await storageService.SaveUserSettings(mainViewModel.UserSettings); + + snackbar.Add("Claim 2 successful. You received 20 AOWW!", Severity.Info); + + if (SelectedWallet != null) + await LoadBalanceDataList(this.SelectedWallet.Wallet.Address); + + } + else + { + snackbar.Add("Claim was not successful.", Severity.Error); + } + } + } + public async Task Claim3() + { + if (mainViewModel.UserSettings != null) + { + var tx = await mainViewModel.Claim(3); + if (tx != null && !string.IsNullOrEmpty(tx.Id)) + { + mainViewModel.UserSettings.Claimed3 = true; + await storageService.SaveUserSettings(mainViewModel.UserSettings); + + snackbar.Add("Claim 3 successful. You received 30 AOWW!", Severity.Info); + + if (SelectedWallet != null) + await LoadBalanceDataList(this.SelectedWallet.Wallet.Address); + + } + else + { + snackbar.Add("Claim was not successful.", Severity.Error); + } + } + } + + + } +} diff --git a/src/aoWebWallet/ViewModels/WalletDetailsViewModel.cs b/src/aoWebWallet/ViewModels/WalletDetailsViewModel.cs index 94082e1..e849a3c 100644 --- a/src/aoWebWallet/ViewModels/WalletDetailsViewModel.cs +++ b/src/aoWebWallet/ViewModels/WalletDetailsViewModel.cs @@ -29,7 +29,6 @@ partial void OnIsConnectedChanged(bool value) partial void OnOwnerCanSendChanged(bool value) { - Console.WriteLine("Owner can send: " + value); SetCanSend(); } diff --git a/src/aoWebWallet/aoWebWallet.csproj b/src/aoWebWallet/aoWebWallet.csproj index b3de095..5a32973 100644 --- a/src/aoWebWallet/aoWebWallet.csproj +++ b/src/aoWebWallet/aoWebWallet.csproj @@ -5,22 +5,22 @@ enable nullable enable - 0.2.0 + 0.3.0 false false true - - + + - - + + @@ -28,4 +28,10 @@ + + + true + + + diff --git a/src/aoWebWallet/wwwroot/css/app.css b/src/aoWebWallet/wwwroot/css/app.css index c058ba6..9de43bb 100644 --- a/src/aoWebWallet/wwwroot/css/app.css +++ b/src/aoWebWallet/wwwroot/css/app.css @@ -111,9 +111,10 @@ a, .btn-link { justify-content: center; } .loading-progress-text { - position: absolute; + position: relative; text-align: center; font-weight: bold; + color: white; } @@ -268,3 +269,152 @@ button.mud-button-root.mud-icon-button.mud-ripple.mud-ripple-icon.copy-clipboard display: flex; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.09); } + +.border-radius-25 { + border-radius: 25px; +} + +.mud-paper.border-radius-25 { + border-radius: 25px; +} + +.mud-paper.first-wallet { + background: rgb(48,20,82); + background: linear-gradient(180deg, rgba(48,20,82,1) 0%, rgba(69,20,84,1) 100%); + height: auto; + min-height: 333px; + +} + +.mud-paper.first-wallet-upload { + background: rgb(48,20,82); + background: linear-gradient(180deg, rgba(48,20,82,1) 0%, rgba(69,20,84,1) 100%); + height: auto; + +} + +.cursor-pointer { + cursor: pointer; +} + +.mud-navmenu { + background: rgb(48,20,82); + background: linear-gradient(180deg, rgba(48,20,82,1) 0%, rgba(69,20,84,1) 100%); +} + +.mud-appbar { + background-color: #101043 !important; +} + +body { + background: rgb(56,18,74); + background: linear-gradient(90deg, rgba(56,18,74,1) 0%, rgba(24,17,69,1) 100%, rgba(0,212,255,1) 100%); +} + + + +.wallet-list.mud-paper { + background-color: rgba(222,222,222,0); +} + +.trigger-transparency.mud-paper { + background-color: rgba(222,222,222,0) !important; +} + +.trigger-transparency { + background-color: rgba(222,222,222,0) !important; +} + +.trigger-transparency .mud-input-control-input-container .mud-paper{ + background-color: rgba(222,222,222,0) !important; +} + + + + +.background-xs { + background-image: url("../images/origin-icon-base.png"); + width: 333px; + background-size: 333px; + animation:spin 111s linear infinite; +} + +.ww-image-start-xs { + width: 333px; + border-radius: 333px !important; + animation:spin-clockwise 111s linear infinite; +} + +.background-x { + background-image: url("../images/origin-icon-base.png"); + width: 100px; + background-size: 100px; + animation:spin 111s linear infinite; +} + +.ww-image-start { + width: 100px; + border-radius: 333px !important; + animation:spin-clockwise 111s linear infinite; + +} + +.ar-image-start { + width: 33px; + border-radius: 333px !important; + opacity: 0.55; + padding: 1.72rem 0; + animation:spin-clockwise 111s linear infinite; +} + +.ar-logo-setup { + padding: 2.4321rem; + opacity: 0.67; + +} + +@keyframes spin{ + from{transform:rotate(0deg)} + to{transform:rotate(360deg)} +} + +@keyframes spin-clockwise{ + from{transform:rotate(360deg)} + to{transform:rotate(0deg)} +} + +.text-transform-none { + text-transform: none !important; +} + +.twitter-image { + margin-top: 10px; + margin-left: 10px; + object-fit: fill !important; +} + +.discord-image { + padding: 5px; + object-fit: fill !important; +} + +.mud-chip.mud-chip-size-medium .mud-avatar.custom-avatar-size { + width: 24px !important; + height: 24px !important; +} + +.mud-paper, .mud-tabs-toolbar { + background-color: rgba(16,16,67,0.33) !important; +} + +.wallet-item-background { + background-color: rgba(16,16,67,1) !important; +} + +.mud-primary-hover.wallet-item-background { + background-color: rgba(16,16,67,1) !important; +} + +.mud-list.mud-list-padding, .mud-dialog { + background-color: rgba(16,16,67,1) !important; +} diff --git a/src/aoWebWallet/wwwroot/images/aoww.svg b/src/aoWebWallet/wwwroot/images/aoww.svg new file mode 100644 index 0000000..dc710cf --- /dev/null +++ b/src/aoWebWallet/wwwroot/images/aoww.svg @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/aoWebWallet/wwwroot/images/arconnect-logo.svg b/src/aoWebWallet/wwwroot/images/arconnect-logo.svg new file mode 100644 index 0000000..eae3d78 --- /dev/null +++ b/src/aoWebWallet/wwwroot/images/arconnect-logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/aoWebWallet/wwwroot/images/discord.svg b/src/aoWebWallet/wwwroot/images/discord.svg new file mode 100644 index 0000000..cded9c1 --- /dev/null +++ b/src/aoWebWallet/wwwroot/images/discord.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/src/aoWebWallet/wwwroot/images/json-logo.svg b/src/aoWebWallet/wwwroot/images/json-logo.svg new file mode 100644 index 0000000..d3e8231 --- /dev/null +++ b/src/aoWebWallet/wwwroot/images/json-logo.svg @@ -0,0 +1,35 @@ + + + + + + + diff --git a/src/aoWebWallet/wwwroot/images/ths.svg b/src/aoWebWallet/wwwroot/images/ths.svg new file mode 100644 index 0000000..052fbfd --- /dev/null +++ b/src/aoWebWallet/wwwroot/images/ths.svg @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/aoWebWallet/wwwroot/images/twitter.svg b/src/aoWebWallet/wwwroot/images/twitter.svg new file mode 100644 index 0000000..5905b05 --- /dev/null +++ b/src/aoWebWallet/wwwroot/images/twitter.svg @@ -0,0 +1,34 @@ + + + + + + diff --git a/src/aoWebWallet/wwwroot/index.html b/src/aoWebWallet/wwwroot/index.html index 8ff86ed..b061bb3 100644 --- a/src/aoWebWallet/wwwroot/index.html +++ b/src/aoWebWallet/wwwroot/index.html @@ -59,15 +59,20 @@
-
-
- - - - -
+
+
+
+ AoWW +
+

loading...

+
-
diff --git a/src/aoww.Services.Tests/GraphqlTests.cs b/src/aoww.Services.Tests/GraphqlTests.cs index cb6482a..59abfa0 100644 --- a/src/aoww.Services.Tests/GraphqlTests.cs +++ b/src/aoww.Services.Tests/GraphqlTests.cs @@ -1,3 +1,6 @@ +using aoww.Services.Models; +using Microsoft.Extensions.Options; + namespace aoww.Services.Tests { [TestClass] @@ -6,7 +9,7 @@ public class GraphqlTests [TestMethod] public async Task GetTransactionsTest() { - var graph = new GraphqlClient(new HttpClient()); + var graph = new GraphqlClient(new HttpClient(), Options.Create(new())); var result = await graph.GetTransactionsIn("4NdFkWsgFQIEmJnzFSYrO88UmRPf0ABfVh_fRc2u130"); @@ -16,7 +19,7 @@ public async Task GetTransactionsTest() [TestMethod] public async Task GetMintTest() { - var graph = new GraphqlClient(new HttpClient()); + var graph = new GraphqlClient(new HttpClient(), Options.Create(new())); var result = await graph.GetTransactionsIn("CeiYr2VjUVAFXmPJvfj-Pfk6zmprBzeqNeRWAbImbOo"); diff --git a/src/aoww.Services.Tests/aoww.Services.Tests.csproj b/src/aoww.Services.Tests/aoww.Services.Tests.csproj index 45f184c..efa497d 100644 --- a/src/aoww.Services.Tests/aoww.Services.Tests.csproj +++ b/src/aoww.Services.Tests/aoww.Services.Tests.csproj @@ -10,10 +10,13 @@ - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/aoww.Services/GraphqlClient.cs b/src/aoww.Services/GraphqlClient.cs index e2368b0..cac74c2 100644 --- a/src/aoww.Services/GraphqlClient.cs +++ b/src/aoww.Services/GraphqlClient.cs @@ -1,4 +1,5 @@ using aoww.Services.Models; +using Microsoft.Extensions.Options; using System.Net.Http.Json; namespace aoww.Services @@ -9,18 +10,21 @@ namespace aoww.Services public class GraphqlClient { private readonly HttpClient httpClient; + private readonly GraphqlConfig config; - public GraphqlClient(HttpClient httpClient) + public GraphqlClient(HttpClient httpClient, IOptions config) { this.httpClient = httpClient; + this.config = config.Value; } - public async Task> GetTransactionsIn(string adddress, string? fromTxId = null) + public async Task> GetTransactionsIn(string adddress, string? cursor = null) { string query = $$""" query { transactions( - first: 100 + first: 50 + after: "{{cursor}}" sort: HEIGHT_DESC tags: [ { name: "Data-Protocol", values: ["ao"] } @@ -29,6 +33,7 @@ public async Task> GetTransactionsIn(string adddress, string ] ) { edges { + cursor node { id recipient @@ -77,6 +82,7 @@ public async Task> GetTransactionsIn(string adddress, string var transaction = new TokenTransfer() { Id = edge.Node.Id, + Cursor = edge.Cursor, From = edge.Node.Owner?.Address ?? string.Empty, TokenTransferType = Enums.TokenTransferType.Transfer }; @@ -132,12 +138,13 @@ public async Task> GetTransactionsIn(string adddress, string return processInfo; } - public async Task> GetTransactionsOut(string address, string? fromTxId = null) + public async Task> GetTransactionsOut(string address, string? cursor = null) { string query = $$""" query { transactions( - first: 100 + first: 50 + after: "{{cursor}}" sort: HEIGHT_DESC owners: ["{{address}}"] tags: [ @@ -146,6 +153,7 @@ public async Task> GetTransactionsOut(string address, string ] ) { edges { + cursor node { id recipient @@ -180,12 +188,13 @@ public async Task> GetTransactionsOut(string address, string return result; } - public async Task> GetTransactionsOutFromProcess(string address, string? fromTxId = null) + public async Task> GetTransactionsOutFromProcess(string address, string? cursor = null) { string query = $$""" query { transactions( - first: 100 + first: 50 + after: "{{cursor}}" sort: HEIGHT_DESC tags: [ { name: "From-Process", values: ["{{address}}"] } @@ -194,6 +203,7 @@ public async Task> GetTransactionsOutFromProcess(string addr ] ) { edges { + cursor node { id recipient @@ -277,12 +287,13 @@ public async Task> GetTransactionsOutFromProcess(string addr return result.FirstOrDefault(); } - public async Task> GetTransactionsForToken(string tokenId, string? fromTxId = null) + public async Task> GetTransactionsForToken(string tokenId, string? cursor = null) { string query = $$""" query { transactions( first: 50 + after: "{{cursor}}" sort: HEIGHT_DESC recipients: ["{{tokenId}}"] tags: [ @@ -291,6 +302,7 @@ public async Task> GetTransactionsForToken(string tokenId, s ] ) { edges { + cursor node { id recipient @@ -354,8 +366,6 @@ public async Task> GetAoProcessesForAddress(string address) var result = new List(); - Console.WriteLine("AOresult:" + (queryResult?.Data?.Transactions?.Edges.Count ?? 0).ToString()); - foreach (var edge in queryResult?.Data?.Transactions?.Edges ?? new()) { AoProcessInfo? processInfo = GetAoProcessInfo(edge); @@ -414,7 +424,7 @@ public async Task> GetAoProcessesForAddress(string address) { var request = new GraphqlRequest { Query = query }; - HttpResponseMessage res = await httpClient.PostAsJsonAsync("https://arweave.net/graphql", request); + HttpResponseMessage res = await httpClient.PostAsJsonAsync(config.ApiUrl, request); if (res.IsSuccessStatusCode) { return await res.Content.ReadFromJsonAsync(); diff --git a/src/aoww.Services/Models/GraphqlConfig.cs b/src/aoww.Services/Models/GraphqlConfig.cs new file mode 100644 index 0000000..3adb6ed --- /dev/null +++ b/src/aoww.Services/Models/GraphqlConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace aoww.Services.Models +{ + public class GraphqlConfig + { + public string ApiUrl { get; set; } = "https://arweave.net/graphql"; + } +} diff --git a/src/aoww.Services/Models/GraphqlResponse.cs b/src/aoww.Services/Models/GraphqlResponse.cs index 315901a..4e8282f 100644 --- a/src/aoww.Services/Models/GraphqlResponse.cs +++ b/src/aoww.Services/Models/GraphqlResponse.cs @@ -26,6 +26,9 @@ public class Data public class Edge { + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + [JsonPropertyName("node")] public Node? Node { get; set; } } diff --git a/src/aoww.Services/Models/TokenTransfer.cs b/src/aoww.Services/Models/TokenTransfer.cs index 31a2800..6e3cb7b 100644 --- a/src/aoww.Services/Models/TokenTransfer.cs +++ b/src/aoww.Services/Models/TokenTransfer.cs @@ -18,5 +18,6 @@ public class TokenTransfer public long Quantity { get; set; } public TokenTransferType TokenTransferType { get; set; } + public string? Cursor { get; set; } } } diff --git a/src/aoww.Services/aoww.Services.csproj b/src/aoww.Services/aoww.Services.csproj index fa71b7a..cc6a561 100644 --- a/src/aoww.Services/aoww.Services.csproj +++ b/src/aoww.Services/aoww.Services.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/src/webvNext.DataLoader/DataLoader.cs b/src/webvNext.DataLoader/DataLoader.cs index 0d58d22..06ad6c7 100644 --- a/src/webvNext.DataLoader/DataLoader.cs +++ b/src/webvNext.DataLoader/DataLoader.cs @@ -36,9 +36,6 @@ public DataLoader(bool swallowExceptions = true) private bool _swallowExceptions; - [ObservableProperty] - private bool isLoading; - [ObservableProperty] private LoadingState loadingState; @@ -57,36 +54,38 @@ public DataLoader(bool swallowExceptions = true) //await semaphoreSlim.WaitAsync(); //try //{ - //Set loading state - LoadingState = LoadingState.Loading; + //Set loading state + LoadingState = LoadingState.Loading; - T? result = default; + T? result = default; - try - { - result = await loadingMethod(); + try + { + result = await loadingMethod(); - //Set finished state - LoadingState = LoadingState.Finished; - LoadedDateTime = DateTimeOffset.UtcNow; - - if (resultCallback != null) - resultCallback(result); + //Set finished state + LoadingState = LoadingState.Finished; + LoadedDateTime = DateTimeOffset.UtcNow; - } - catch (Exception e) + if (resultCallback != null) { - //Set error state - LoadingState = LoadingState.Error; + resultCallback(result); + } - if (errorCallback != null) - errorCallback(e); - else if (!_swallowExceptions) //swallow exception if _swallowExceptions is true - throw; //throw error if no callback is defined + } + catch (Exception e) + { + //Set error state + LoadingState = LoadingState.Error; - } + if (errorCallback != null) + errorCallback(e); + else if (!_swallowExceptions) //swallow exception if _swallowExceptions is true + throw; //throw error if no callback is defined + + } - return result; + return result; //} //finally //{