From 2374b739c792abfa63d9cbd852c7ff5b95044c04 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 21 Apr 2022 20:46:31 +0800 Subject: [PATCH 01/10] [wip] add gui --- gui/.gitignore | 454 ++++++++++++++++++++ gui/App.axaml | 15 + gui/App.axaml.cs | 63 +++ gui/Common/Consts.cs | 23 + gui/Common/Extension/RxRealmExtension.cs | 25 ++ gui/Data/Model/DbPersonaModel.cs | 39 ++ gui/Data/Repository/PersonaRepository.cs | 53 +++ gui/Data/Repository/WalletRepository.cs | 11 + gui/Directory.Build.props | 5 + gui/FodyWeavers.xml | 3 + gui/Lifecycle/Controls/Dialog.cs | 26 ++ gui/Lifecycle/Controls/Page.cs | 80 ++++ gui/Lifecycle/ViewModel/ViewModel.cs | 15 + gui/Lifecycle/ViewModel/ViewModelScope.cs | 27 ++ gui/MaskCore.csproj | 49 +++ gui/Program.cs | 27 ++ gui/UI/Model/UiPersonaModel.cs | 8 + gui/UI/Pages/Persona/PersonaPage.axaml | 11 + gui/UI/Pages/Persona/PersonaPage.axaml.cs | 14 + gui/UI/Pages/Persona/PersonaViewModel.cs | 8 + gui/UI/Pages/Settings/SettingsPage.axaml | 10 + gui/UI/Pages/Settings/SettingsPage.axaml.cs | 14 + gui/UI/Pages/Settings/SettingsViewModel.cs | 8 + gui/UI/Pages/Wallet/WalletPage.axaml | 11 + gui/UI/Pages/Wallet/WalletPage.axaml.cs | 14 + gui/UI/Pages/Wallet/WalletViewModel.cs | 8 + gui/UI/Shell/RootShell.axaml | 138 ++++++ gui/UI/Shell/RootShell.axaml.cs | 11 + gui/UI/Shell/RootWindow.axaml | 14 + gui/UI/Shell/RootWindow.axaml.cs | 15 + 30 files changed, 1199 insertions(+) create mode 100644 gui/.gitignore create mode 100644 gui/App.axaml create mode 100644 gui/App.axaml.cs create mode 100644 gui/Common/Consts.cs create mode 100644 gui/Common/Extension/RxRealmExtension.cs create mode 100644 gui/Data/Model/DbPersonaModel.cs create mode 100644 gui/Data/Repository/PersonaRepository.cs create mode 100644 gui/Data/Repository/WalletRepository.cs create mode 100644 gui/Directory.Build.props create mode 100644 gui/FodyWeavers.xml create mode 100644 gui/Lifecycle/Controls/Dialog.cs create mode 100644 gui/Lifecycle/Controls/Page.cs create mode 100644 gui/Lifecycle/ViewModel/ViewModel.cs create mode 100644 gui/Lifecycle/ViewModel/ViewModelScope.cs create mode 100644 gui/MaskCore.csproj create mode 100644 gui/Program.cs create mode 100644 gui/UI/Model/UiPersonaModel.cs create mode 100644 gui/UI/Pages/Persona/PersonaPage.axaml create mode 100644 gui/UI/Pages/Persona/PersonaPage.axaml.cs create mode 100644 gui/UI/Pages/Persona/PersonaViewModel.cs create mode 100644 gui/UI/Pages/Settings/SettingsPage.axaml create mode 100644 gui/UI/Pages/Settings/SettingsPage.axaml.cs create mode 100644 gui/UI/Pages/Settings/SettingsViewModel.cs create mode 100644 gui/UI/Pages/Wallet/WalletPage.axaml create mode 100644 gui/UI/Pages/Wallet/WalletPage.axaml.cs create mode 100644 gui/UI/Pages/Wallet/WalletViewModel.cs create mode 100644 gui/UI/Shell/RootShell.axaml create mode 100644 gui/UI/Shell/RootShell.axaml.cs create mode 100644 gui/UI/Shell/RootWindow.axaml create mode 100644 gui/UI/Shell/RootWindow.axaml.cs diff --git a/gui/.gitignore b/gui/.gitignore new file mode 100644 index 0000000..8afdcb6 --- /dev/null +++ b/gui/.gitignore @@ -0,0 +1,454 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/gui/App.axaml b/gui/App.axaml new file mode 100644 index 0000000..93f1a15 --- /dev/null +++ b/gui/App.axaml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/gui/App.axaml.cs b/gui/App.axaml.cs new file mode 100644 index 0000000..0dc4bd8 --- /dev/null +++ b/gui/App.axaml.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using CommunityToolkit.Mvvm.DependencyInjection; +using Dimension.MaskCore.Common; +using Dimension.MaskCore.Data.Repository; +using Dimension.MaskCore.UI; +using Microsoft.Extensions.DependencyInjection; +using Realms; +using RootShell = Dimension.MaskCore.UI.Shell.RootShell; +using RootWindow = Dimension.MaskCore.UI.Shell.RootWindow; + +namespace Dimension.MaskCore; + +internal partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + EnsureWorkingDirectory(); + Ioc.Default.ConfigureServices(ConfigureServices()); + } + + private void EnsureWorkingDirectory() + { + if (!Directory.Exists(Consts.ConfigDirectory)) + { + Directory.CreateDirectory(Consts.ConfigDirectory); + } + } + + private static IServiceProvider ConfigureServices() + { + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(_ => + Realm.GetInstance(new RealmConfiguration(Path.Combine(Consts.ConfigDirectory, ".realm")) + { ShouldDeleteIfMigrationNeeded = true })); + return services.BuildServiceProvider(); + } + + public override void OnFrameworkInitializationCompleted() + { + switch (ApplicationLifetime) + { + case IClassicDesktopStyleApplicationLifetime classicDesktopStyleApplicationLifetime: + classicDesktopStyleApplicationLifetime.MainWindow = new RootWindow(); + classicDesktopStyleApplicationLifetime.Exit += (_, _) => + { + Ioc.Default.GetRequiredService().Dispose(); + }; + break; + case ISingleViewApplicationLifetime singleViewApplicationLifetime: + singleViewApplicationLifetime.MainView = new RootShell(); + break; + } + + base.OnFrameworkInitializationCompleted(); + } +} \ No newline at end of file diff --git a/gui/Common/Consts.cs b/gui/Common/Consts.cs new file mode 100644 index 0000000..746eea9 --- /dev/null +++ b/gui/Common/Consts.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Dimension.MaskCore.Common; + +internal class Consts +{ + public static string ConfigDirectory + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return System.IO.Path.Combine( + System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments), + "Mask"); + } + return System.IO.Path.Combine( + System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), + ".mask"); + } + } + +} \ No newline at end of file diff --git a/gui/Common/Extension/RxRealmExtension.cs b/gui/Common/Extension/RxRealmExtension.cs new file mode 100644 index 0000000..a088596 --- /dev/null +++ b/gui/Common/Extension/RxRealmExtension.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using Realms; + +namespace Dimension.MaskCore.Common.Extension; + +public static class RxRealmExtension +{ + public static IObservable> AsObservable(this IQueryable query) where T : RealmObjectBase + { + return Observable.Create>(emitter => query.SubscribeForNotifications((sender, _, error) => + { + if (error != null) + { + emitter.OnError(error); + } + else + { + emitter.OnNext(sender); + } + })); + } +} \ No newline at end of file diff --git a/gui/Data/Model/DbPersonaModel.cs b/gui/Data/Model/DbPersonaModel.cs new file mode 100644 index 0000000..e1c8fcf --- /dev/null +++ b/gui/Data/Model/DbPersonaModel.cs @@ -0,0 +1,39 @@ +using System; +using System.Text.Json; +using Dimension.MaskWalletCore; +using MongoDB.Bson; +using Realms; + +namespace Dimension.MaskCore.Data.Model; + +internal class DbPersonaModel : RealmObject +{ + [PrimaryKey] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + [Required] public string Name { get; set; } = string.Empty; + [Required] public string Identifier { get; set; } = string.Empty; + [Required] public string Mnemonic { get; set; } = string.Empty; + [Required] public string Path { get; set; } = string.Empty; + public bool WithPassword { get; set; } + [Required] public string Password { get; set; } = string.Empty; + [Required] public string PrivateKey { get; set; } = string.Empty; + [Required] public string PublicKey { get; set; } = string.Empty; + public string? LocalKey { get; set; } + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; + + public static DbPersonaModel FromPersona(PersonaKey persona, string mnemonic, string path, string password, bool withPassword, string name) + { + return new DbPersonaModel + { + Name = name, + Identifier = persona.Identifier, + Mnemonic = mnemonic, + Path = path, + WithPassword = withPassword, + Password = password, + PrivateKey = JsonSerializer.Serialize(persona.PrivateKey), + PublicKey = JsonSerializer.Serialize(persona.PrivateKey with { d = null }), + LocalKey = persona.LocalKey == null ? null : JsonSerializer.Serialize(persona.LocalKey), + }; + } +} \ No newline at end of file diff --git a/gui/Data/Repository/PersonaRepository.cs b/gui/Data/Repository/PersonaRepository.cs new file mode 100644 index 0000000..c23d16e --- /dev/null +++ b/gui/Data/Repository/PersonaRepository.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.DependencyInjection; +using Dimension.MaskCore.Common.Extension; +using Dimension.MaskCore.Data.Model; +using Dimension.MaskCore.UI.Model; +using Dimension.MaskWalletCore; +using Realms; + +namespace Dimension.MaskCore.Data.Repository; + +internal class PersonaRepository +{ + const string _path = "m/44'/60'/0'/0/0"; + const string _password = ""; + + private readonly Realm _realm = Ioc.Default.GetRequiredService(); + public IObservable> Personas; + + public PersonaRepository() + { + Personas = _realm.All().AsObservable() + .Select(it => it.Select(UiPersonaModel.FromDb).ToImmutableList()); + } + + public async Task CreatePersonaFromMnemonic(string mnemonic, string name) + { + if (string.IsNullOrEmpty(mnemonic) || string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(mnemonic)); + } + if (_realm.All().Any(it => it.Mnemonic == mnemonic)) + { + throw new ArgumentException($"Persona with name {name} already exists"); + } + + var persona = await Task.Run(() => PersonaKey.Create( + mnemonic, + _password, + _path, + CurveType.Secp256k1, + new EncryptionOption(EncryptionOption.EncVersion.V38) + )); + _realm.Write(() => + { + _realm.Add(DbPersonaModel.FromPersona(persona, mnemonic, _path, _password, false, name)); + }); + } +} \ No newline at end of file diff --git a/gui/Data/Repository/WalletRepository.cs b/gui/Data/Repository/WalletRepository.cs new file mode 100644 index 0000000..ed13f3b --- /dev/null +++ b/gui/Data/Repository/WalletRepository.cs @@ -0,0 +1,11 @@ +using Dimension.MaskWalletCore; + +namespace Dimension.MaskCore.Data.Repository; + +internal class WalletRepository +{ + public string GenerateMnemonic() + { + return WalletKey.GenerateMnemonic(); + } +} \ No newline at end of file diff --git a/gui/Directory.Build.props b/gui/Directory.Build.props new file mode 100644 index 0000000..7093843 --- /dev/null +++ b/gui/Directory.Build.props @@ -0,0 +1,5 @@ + + + 0.10.13 + + \ No newline at end of file diff --git a/gui/FodyWeavers.xml b/gui/FodyWeavers.xml new file mode 100644 index 0000000..cc07b89 --- /dev/null +++ b/gui/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/gui/Lifecycle/Controls/Dialog.cs b/gui/Lifecycle/Controls/Dialog.cs new file mode 100644 index 0000000..42ccfaf --- /dev/null +++ b/gui/Lifecycle/Controls/Dialog.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.LogicalTree; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; + +namespace Dimension.MaskCore.Lifecycle.Controls; + +public class Dialog : Dialog where T : ViewModel.ViewModel +{ + public T ViewModel => (DataContext as T)!; +} + +public class Dialog : ContentDialog, IStyleable +{ + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnDetachedFromLogicalTree(e); + if (DataContext is IDisposable disposable) + { + disposable.Dispose(); + } + } + + + Type IStyleable.StyleKey => typeof(ContentDialog); +} \ No newline at end of file diff --git a/gui/Lifecycle/Controls/Page.cs b/gui/Lifecycle/Controls/Page.cs new file mode 100644 index 0000000..2fa313b --- /dev/null +++ b/gui/Lifecycle/Controls/Page.cs @@ -0,0 +1,80 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.LogicalTree; +using Avalonia.VisualTree; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Media.Animation; +using FluentAvalonia.UI.Navigation; + +namespace Dimension.MaskCore.Lifecycle.Controls; + +public class Page : Page where T : ViewModel.ViewModel, new() +{ + public T ViewModel => (DataContext as T)!; +} + +public class Page : UserControl +{ + protected Frame? Frame { get; private set; } + + protected Window Window => this.FindAncestorOfType(); + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + Frame = this.FindAncestorOfType(); + Frame.Navigated += OnNavigated; + } + + private void OnNavigated(object sender, NavigationEventArgs e) + { + if (ReferenceEquals(e.Content, this)) + { + switch (e.NavigationMode) + { + case NavigationMode.New: + OnCreated(e.Parameter); + break; + case NavigationMode.Back: + break; + case NavigationMode.Forward: + break; + case NavigationMode.Refresh: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + protected virtual void OnCreated(object parameter) + { + } + + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnDetachedFromLogicalTree(e); + if (DataContext is IDisposable disposable) + { + disposable.Dispose(); + } + + if (Frame != null) + { + Frame.Navigated -= OnNavigated; + Frame = null; + } + } + + protected void GoBack() + { + this.FindAncestorOfType().GoBack(new SlideNavigationTransitionInfo + { Effect = SlideNavigationTransitionEffect.FromLeft }); + } + + protected void Navigate(object? parameter = null) + { + Frame?.Navigate(typeof(T), parameter, new SlideNavigationTransitionInfo()); + } +} \ No newline at end of file diff --git a/gui/Lifecycle/ViewModel/ViewModel.cs b/gui/Lifecycle/ViewModel/ViewModel.cs new file mode 100644 index 0000000..4aa7ae4 --- /dev/null +++ b/gui/Lifecycle/ViewModel/ViewModel.cs @@ -0,0 +1,15 @@ +using System; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Dimension.MaskCore.Lifecycle.ViewModel; + +[ObservableObject] +public abstract partial class ViewModel : IDisposable +{ + protected internal ViewModelScope Scope { get; } = new(); + + public void Dispose() + { + Scope.Dispose(); + } +} \ No newline at end of file diff --git a/gui/Lifecycle/ViewModel/ViewModelScope.cs b/gui/Lifecycle/ViewModel/ViewModelScope.cs new file mode 100644 index 0000000..701467e --- /dev/null +++ b/gui/Lifecycle/ViewModel/ViewModelScope.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace Dimension.MaskCore.Lifecycle.ViewModel; + +public sealed class ViewModelScope : IDisposable +{ + private readonly List _disposables = new(); + + public void Dispose() + { + _disposables.ForEach(x => x.Dispose()); + } + + internal void Add(IDisposable disposable) + { + _disposables.Add(disposable); + } +} + +public static class RxExtensions +{ + public static void SubscribeIn(this IObservable source, ViewModel viewModel, Action onNext) + { + viewModel.Scope.Add(source.Subscribe(onNext)); + } +} \ No newline at end of file diff --git a/gui/MaskCore.csproj b/gui/MaskCore.csproj new file mode 100644 index 0000000..7e48579 --- /dev/null +++ b/gui/MaskCore.csproj @@ -0,0 +1,49 @@ + + + WinExe + net6.0 + enable + Dimension.MaskCore + + true + true + true + win-x64 + linux-x64 + osx-arm64 + true + true + true + true + true + + copyused + true + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/Program.cs b/gui/Program.cs new file mode 100644 index 0000000..560a247 --- /dev/null +++ b/gui/Program.cs @@ -0,0 +1,27 @@ +using System; +using Avalonia; +using Avalonia.ReactiveUI; + +namespace Dimension.MaskCore; + +internal class Program +{ + [STAThread] + public static void Main(string[] args) + { + BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } + + public static AppBuilder BuildAvaloniaApp() + { + return AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace() + .UseReactiveUI() + .With(new Win32PlatformOptions + { + UseWindowsUIComposition = true + }); + } +} \ No newline at end of file diff --git a/gui/UI/Model/UiPersonaModel.cs b/gui/UI/Model/UiPersonaModel.cs new file mode 100644 index 0000000..0920be1 --- /dev/null +++ b/gui/UI/Model/UiPersonaModel.cs @@ -0,0 +1,8 @@ +using Dimension.MaskCore.Data.Model; + +namespace Dimension.MaskCore.UI.Model; + +internal record UiPersonaModel(string Name, string Identifier) +{ + public static UiPersonaModel FromDb(DbPersonaModel model) => new(model.Name, model.Identifier); +} \ No newline at end of file diff --git a/gui/UI/Pages/Persona/PersonaPage.axaml b/gui/UI/Pages/Persona/PersonaPage.axaml new file mode 100644 index 0000000..a94d2af --- /dev/null +++ b/gui/UI/Pages/Persona/PersonaPage.axaml @@ -0,0 +1,11 @@ + + Welcome to Avalonia! + diff --git a/gui/UI/Pages/Persona/PersonaPage.axaml.cs b/gui/UI/Pages/Persona/PersonaPage.axaml.cs new file mode 100644 index 0000000..fe38a28 --- /dev/null +++ b/gui/UI/Pages/Persona/PersonaPage.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Dimension.MaskCore.Lifecycle.Controls; + +namespace Dimension.MaskCore.UI.Pages.Persona; + +internal partial class PersonaPage : Page +{ + public PersonaPage() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Persona/PersonaViewModel.cs b/gui/UI/Pages/Persona/PersonaViewModel.cs new file mode 100644 index 0000000..4427aa2 --- /dev/null +++ b/gui/UI/Pages/Persona/PersonaViewModel.cs @@ -0,0 +1,8 @@ +using Dimension.MaskCore.Lifecycle.ViewModel; + +namespace Dimension.MaskCore.UI.Pages.Persona; + +internal partial class PersonaViewModel : ViewModel +{ + +} \ No newline at end of file diff --git a/gui/UI/Pages/Settings/SettingsPage.axaml b/gui/UI/Pages/Settings/SettingsPage.axaml new file mode 100644 index 0000000..9e7b336 --- /dev/null +++ b/gui/UI/Pages/Settings/SettingsPage.axaml @@ -0,0 +1,10 @@ + + diff --git a/gui/UI/Pages/Settings/SettingsPage.axaml.cs b/gui/UI/Pages/Settings/SettingsPage.axaml.cs new file mode 100644 index 0000000..7786c3f --- /dev/null +++ b/gui/UI/Pages/Settings/SettingsPage.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Dimension.MaskCore.Lifecycle.Controls; + +namespace Dimension.MaskCore.UI.Pages.Settings; + +internal partial class SettingsPage : Page +{ + public SettingsPage() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Settings/SettingsViewModel.cs b/gui/UI/Pages/Settings/SettingsViewModel.cs new file mode 100644 index 0000000..605c35d --- /dev/null +++ b/gui/UI/Pages/Settings/SettingsViewModel.cs @@ -0,0 +1,8 @@ +using Dimension.MaskCore.Lifecycle.ViewModel; + +namespace Dimension.MaskCore.UI.Pages.Settings; + +internal partial class SettingsViewModel : ViewModel +{ + +} \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/WalletPage.axaml b/gui/UI/Pages/Wallet/WalletPage.axaml new file mode 100644 index 0000000..605ed17 --- /dev/null +++ b/gui/UI/Pages/Wallet/WalletPage.axaml @@ -0,0 +1,11 @@ + + Welcome to Avalonia! + diff --git a/gui/UI/Pages/Wallet/WalletPage.axaml.cs b/gui/UI/Pages/Wallet/WalletPage.axaml.cs new file mode 100644 index 0000000..5a749aa --- /dev/null +++ b/gui/UI/Pages/Wallet/WalletPage.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Dimension.MaskCore.Lifecycle.Controls; + +namespace Dimension.MaskCore.UI.Pages.Wallet; + +internal partial class WalletPage : Page +{ + public WalletPage() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/WalletViewModel.cs b/gui/UI/Pages/Wallet/WalletViewModel.cs new file mode 100644 index 0000000..1ad0b4f --- /dev/null +++ b/gui/UI/Pages/Wallet/WalletViewModel.cs @@ -0,0 +1,8 @@ +using Dimension.MaskCore.Lifecycle.ViewModel; + +namespace Dimension.MaskCore.UI.Pages.Wallet; + +internal partial class WalletViewModel: ViewModel +{ + +} \ No newline at end of file diff --git a/gui/UI/Shell/RootShell.axaml b/gui/UI/Shell/RootShell.axaml new file mode 100644 index 0000000..d15d4a9 --- /dev/null +++ b/gui/UI/Shell/RootShell.axaml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/UI/Shell/RootShell.axaml.cs b/gui/UI/Shell/RootShell.axaml.cs new file mode 100644 index 0000000..7be303b --- /dev/null +++ b/gui/UI/Shell/RootShell.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Dimension.MaskCore.UI.Shell; + +internal partial class RootShell : UserControl +{ + public RootShell() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/gui/UI/Shell/RootWindow.axaml b/gui/UI/Shell/RootWindow.axaml new file mode 100644 index 0000000..c917a29 --- /dev/null +++ b/gui/UI/Shell/RootWindow.axaml @@ -0,0 +1,14 @@ + + + diff --git a/gui/UI/Shell/RootWindow.axaml.cs b/gui/UI/Shell/RootWindow.axaml.cs new file mode 100644 index 0000000..c161f45 --- /dev/null +++ b/gui/UI/Shell/RootWindow.axaml.cs @@ -0,0 +1,15 @@ +using Avalonia; +using FluentAvalonia.UI.Controls; + +namespace Dimension.MaskCore.UI.Shell; + +internal partial class RootWindow : CoreWindow +{ + public RootWindow() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } +} \ No newline at end of file From 6dd43f24904618bb41a0c8d1d8a3f7a708b41ad7 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 22 Apr 2022 19:03:56 +0800 Subject: [PATCH 02/10] add persona create page --- gui/Common/Helpers/MnemonicHelper.cs | 8 ++ gui/Data/Repository/PersonaRepository.cs | 47 +++++--- gui/Data/Repository/WalletRepository.cs | 4 - .../CreatePersona/CreatePersonaPage.axaml | 65 +++++++++++ .../CreatePersona/CreatePersonaPage.axaml.cs | 20 ++++ .../CreatePersona/CreatePersonaViewModel.cs | 12 ++ gui/UI/Pages/Persona/PersonaPage.axaml | 104 ++++++++++++++++-- gui/UI/Pages/Persona/PersonaPage.axaml.cs | 7 ++ gui/UI/Pages/Persona/PersonaViewModel.cs | 17 +++ 9 files changed, 256 insertions(+), 28 deletions(-) create mode 100644 gui/Common/Helpers/MnemonicHelper.cs create mode 100644 gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml create mode 100644 gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml.cs create mode 100644 gui/UI/Pages/Persona/CreatePersona/CreatePersonaViewModel.cs diff --git a/gui/Common/Helpers/MnemonicHelper.cs b/gui/Common/Helpers/MnemonicHelper.cs new file mode 100644 index 0000000..1f48eec --- /dev/null +++ b/gui/Common/Helpers/MnemonicHelper.cs @@ -0,0 +1,8 @@ +using Dimension.MaskWalletCore; + +namespace Dimension.MaskCore.Common.Helpers; + +internal static class MnemonicHelper +{ + public static string GenerateMnemonic() => WalletKey.GenerateMnemonic(); +} \ No newline at end of file diff --git a/gui/Data/Repository/PersonaRepository.cs b/gui/Data/Repository/PersonaRepository.cs index c23d16e..c4d72f7 100644 --- a/gui/Data/Repository/PersonaRepository.cs +++ b/gui/Data/Repository/PersonaRepository.cs @@ -15,17 +15,12 @@ namespace Dimension.MaskCore.Data.Repository; internal class PersonaRepository { - const string _path = "m/44'/60'/0'/0/0"; - const string _password = ""; + private const string Path = "m/44'/60'/0'/0/0"; + private const string Password = ""; - private readonly Realm _realm = Ioc.Default.GetRequiredService(); - public IObservable> Personas; - - public PersonaRepository() - { - Personas = _realm.All().AsObservable() - .Select(it => it.Select(UiPersonaModel.FromDb).ToImmutableList()); - } + private static readonly Realm _realm = Ioc.Default.GetRequiredService(); + public IObservable> Personas { get; } = _realm.All().AsObservable() + .Select(it => it.Select(UiPersonaModel.FromDb).ToImmutableList()); public async Task CreatePersonaFromMnemonic(string mnemonic, string name) { @@ -40,14 +35,40 @@ public async Task CreatePersonaFromMnemonic(string mnemonic, string name) var persona = await Task.Run(() => PersonaKey.Create( mnemonic, - _password, - _path, + Password, + Path, CurveType.Secp256k1, new EncryptionOption(EncryptionOption.EncVersion.V38) )); _realm.Write(() => { - _realm.Add(DbPersonaModel.FromPersona(persona, mnemonic, _path, _password, false, name)); + _realm.Add(DbPersonaModel.FromPersona(persona, mnemonic, Path, Password, false, name)); + }); + } + + public void UpdatePersonaName(UiPersonaModel item, string name) + { + var dbPersona = _realm.All().FirstOrDefault(it => it.Identifier == item.Identifier); + if (dbPersona == null) + { + return; + } + _realm.Write(() => + { + dbPersona.Name = name; + }); + } + + public void DeletePersona(UiPersonaModel persona) + { + var item = _realm.All().FirstOrDefault(it => it.Identifier == persona.Identifier); + if (item == null) + { + return; + } + _realm.Write(() => + { + _realm.Remove(item); }); } } \ No newline at end of file diff --git a/gui/Data/Repository/WalletRepository.cs b/gui/Data/Repository/WalletRepository.cs index ed13f3b..1c8a9f2 100644 --- a/gui/Data/Repository/WalletRepository.cs +++ b/gui/Data/Repository/WalletRepository.cs @@ -4,8 +4,4 @@ namespace Dimension.MaskCore.Data.Repository; internal class WalletRepository { - public string GenerateMnemonic() - { - return WalletKey.GenerateMnemonic(); - } } \ No newline at end of file diff --git a/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml b/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml new file mode 100644 index 0000000..55b24da --- /dev/null +++ b/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/UI/Pages/Persona/PersonaPage.axaml.cs b/gui/UI/Pages/Persona/PersonaPage.axaml.cs index fe38a28..0b84cd6 100644 --- a/gui/UI/Pages/Persona/PersonaPage.axaml.cs +++ b/gui/UI/Pages/Persona/PersonaPage.axaml.cs @@ -1,7 +1,9 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Dimension.MaskCore.Lifecycle.Controls; +using Dimension.MaskCore.UI.Pages.Persona.CreatePersona; namespace Dimension.MaskCore.UI.Pages.Persona; @@ -11,4 +13,9 @@ public PersonaPage() { InitializeComponent(); } + + private void New_OnClicked(object? sender, RoutedEventArgs e) + { + Navigate(); + } } \ No newline at end of file diff --git a/gui/UI/Pages/Persona/PersonaViewModel.cs b/gui/UI/Pages/Persona/PersonaViewModel.cs index 4427aa2..29f7ea2 100644 --- a/gui/UI/Pages/Persona/PersonaViewModel.cs +++ b/gui/UI/Pages/Persona/PersonaViewModel.cs @@ -1,8 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Subjects; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using Dimension.MaskCore.Data.Repository; using Dimension.MaskCore.Lifecycle.ViewModel; +using Dimension.MaskCore.UI.Model; namespace Dimension.MaskCore.UI.Pages.Persona; internal partial class PersonaViewModel : ViewModel { + private static readonly PersonaRepository _personaRepository = Ioc.Default.GetRequiredService(); + private readonly BehaviorSubject _searchTextSubject = new(string.Empty); + [ObservableProperty] private string _searchText = string.Empty; + partial void OnSearchTextChanged(string value) + { + _searchTextSubject.OnNext(value); + } + + public IObservable> Personas => _personaRepository.Personas; + } \ No newline at end of file From eac8211bc4e837322c7060f9c76d794b498bf0a1 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sat, 23 Apr 2022 04:33:16 +0800 Subject: [PATCH 03/10] [wip] add mnemonic validate page --- gui/Common/Extension/EnumerabsleExtension.cs | 15 ++++ gui/Lifecycle/Controls/Page.cs | 16 ++++ gui/UI/Model/UiPersonaModel.cs | 4 +- .../CreatePersona/CreatePersonaPage.axaml | 2 +- .../CreatePersona/CreatePersonaPage.axaml.cs | 6 ++ .../CreatePersona/CreatePersonaViewModel.cs | 2 +- .../MnemonicValidatePage.axaml | 89 +++++++++++++++++++ .../MnemonicValidatePage.axaml.cs | 23 +++++ .../MnemonicValidateViewModel.cs | 53 +++++++++++ 9 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 gui/Common/Extension/EnumerabsleExtension.cs create mode 100644 gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml create mode 100644 gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs create mode 100644 gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs diff --git a/gui/Common/Extension/EnumerabsleExtension.cs b/gui/Common/Extension/EnumerabsleExtension.cs new file mode 100644 index 0000000..7b9d99c --- /dev/null +++ b/gui/Common/Extension/EnumerabsleExtension.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Dimension.MaskCore.Common.Extension; + +internal static class EnumerabsleExtension +{ + public static IEnumerable Randomize(this IEnumerable source) + { + Random rnd = new Random(); + return source.OrderBy((item) => rnd.Next()); + } + +} \ No newline at end of file diff --git a/gui/Lifecycle/Controls/Page.cs b/gui/Lifecycle/Controls/Page.cs index 2fa313b..5496bcc 100644 --- a/gui/Lifecycle/Controls/Page.cs +++ b/gui/Lifecycle/Controls/Page.cs @@ -9,6 +9,22 @@ namespace Dimension.MaskCore.Lifecycle.Controls; +public class Page : Page where T : ViewModel.ViewModel, new() where R : class +{ + protected override void OnCreated(object parameter) + { + base.OnCreated(parameter); + if (parameter is R r) + { + OnCreated(r); + } + } + + protected virtual void OnCreated(R parameter) + { + } +} + public class Page : Page where T : ViewModel.ViewModel, new() { public T ViewModel => (DataContext as T)!; diff --git a/gui/UI/Model/UiPersonaModel.cs b/gui/UI/Model/UiPersonaModel.cs index 0920be1..f173b47 100644 --- a/gui/UI/Model/UiPersonaModel.cs +++ b/gui/UI/Model/UiPersonaModel.cs @@ -5,4 +5,6 @@ namespace Dimension.MaskCore.UI.Model; internal record UiPersonaModel(string Name, string Identifier) { public static UiPersonaModel FromDb(DbPersonaModel model) => new(model.Name, model.Identifier); -} \ No newline at end of file +} + +internal record MnemonicModel(string Word, int Index); diff --git a/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml b/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml index 55b24da..411539f 100644 --- a/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml +++ b/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml @@ -58,7 +58,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs b/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs new file mode 100644 index 0000000..72d87ed --- /dev/null +++ b/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs @@ -0,0 +1,23 @@ +using Avalonia.Interactivity; +using Dimension.MaskCore.Lifecycle.Controls; + +namespace Dimension.MaskCore.UI.Pages.Persona.MnemonicValidate; + +internal partial class MnemonicValidatePage : Page +{ + public MnemonicValidatePage() + { + InitializeComponent(); + } + + protected override void OnCreated(MnemonicValidateParameter parameter) + { + base.OnCreated(parameter); + ViewModel.Init(parameter); + } + + private void BackClicked(object? sender, RoutedEventArgs e) + { + GoBack(); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs b/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs new file mode 100644 index 0000000..d7ea4cb --- /dev/null +++ b/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Dimension.MaskCore.Common.Extension; +using Dimension.MaskCore.Lifecycle.ViewModel; +using Dimension.MaskCore.UI.Model; + +namespace Dimension.MaskCore.UI.Pages.Persona.MnemonicValidate; + +internal partial class MnemonicValidateViewModel : ViewModel +{ + private readonly List _mnemonic = new(); + [ObservableProperty] + private string _name = string.Empty; + [ObservableProperty] + [AlsoNotifyCanExecuteFor(nameof(ComfirmCommand))] + private IReadOnlyCollection _selectedMnemonic = new List(); + [ObservableProperty] + [AlsoNotifyCanExecuteFor(nameof(ComfirmCommand))] + private IReadOnlyCollection _randomMnemonic = new List(); + public void Init(MnemonicValidateParameter parameter) + { + _mnemonic.Clear(); + _mnemonic.AddRange(parameter.Mnemonic.Split(" ")); + Name = parameter.Name; + RandomMnemonic = _mnemonic.Randomize().Select((word, index) => new MnemonicModel(word, index)).ToList(); + } + + [ICommand] + private void SelectWord(MnemonicModel item) + { + if (SelectedMnemonic.Contains(item)) + { + SelectedMnemonic = SelectedMnemonic.Where(x => x.Index != item.Index).ToList(); + RandomMnemonic = RandomMnemonic.Concat(new[] { item }).OrderBy(it => it.Index).ToList(); + } + else + { + SelectedMnemonic = SelectedMnemonic.Concat(new[] { item }).ToList(); + RandomMnemonic = RandomMnemonic.Where(x => x.Index != item.Index).OrderBy(it => it.Index).ToList(); + } + } + + public bool IsValid => SelectedMnemonic.Select(x => x.Word).SequenceEqual(_mnemonic); + + [ICommand(CanExecute = nameof(IsValid))] + private void Comfirm() + { + } +} + +internal record MnemonicValidateParameter(string Mnemonic, string Name); \ No newline at end of file From 9e4302e556a7998814fbfa0b6bf26178bef12557 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 24 Apr 2022 16:21:38 +0800 Subject: [PATCH 04/10] add wallet support --- .editorconfig | 5 + gui/App.axaml.cs | 6 +- gui/Common/Consts.cs | 12 +- gui/Common/Extension/EnumerabsleExtension.cs | 5 +- gui/Common/Helpers/MnemonicHelper.cs | 5 +- gui/Data/Model/DbPersonaModel.cs | 5 +- gui/Data/Model/DbWalletModel.cs | 25 ++++ gui/Data/Repository/PersonaRepository.cs | 38 +++--- gui/Data/Repository/WalletRepository.cs | 62 +++++++++ gui/Directory.Build.props | 5 - gui/FodyWeavers.xml | 2 +- gui/Lifecycle/Controls/Dialog.cs | 5 +- gui/Lifecycle/Controls/Page.cs | 33 +++-- gui/Lifecycle/ViewModel/ViewModel.cs | 15 +++ gui/MaskCore.csproj | 33 ++--- gui/Model/PlatformType.cs | 6 + gui/UI/Controls/MnemonicValidateControl.axaml | 86 +++++++++++++ .../Controls/MnemonicValidateControl.axaml.cs | 111 ++++++++++++++++ gui/UI/Model/UiPersonaModel.cs | 7 +- gui/UI/Model/UiWalletModel.cs | 11 ++ .../CreatePersona/CreatePersonaPage.axaml | 52 ++++---- .../CreatePersona/CreatePersonaPage.axaml.cs | 9 +- .../CreatePersona/CreatePersonaViewModel.cs | 4 +- .../MnemonicValidatePage.axaml | 89 ------------- .../MnemonicValidatePage.axaml.cs | 23 ---- .../MnemonicValidateViewModel.cs | 53 -------- .../PersonaMnemonicValidatePage.axaml | 41 ++++++ .../PersonaMnemonicValidatePage.axaml.cs | 29 +++++ .../PersonaMnemonicValidateViewModel.cs | 40 ++++++ gui/UI/Pages/Persona/PersonaPage.axaml | 31 +++-- gui/UI/Pages/Persona/PersonaPage.axaml.cs | 10 +- gui/UI/Pages/Persona/PersonaViewModel.cs | 37 +++++- .../RenamePersona/RenamePersonaDialog.axaml | 19 +++ .../RenamePersonaDialog.axaml.cs | 18 +++ .../RenamePersona/RenamePersonaViewModel.cs | 32 +++++ gui/UI/Pages/Settings/SettingsPage.axaml | 38 ++++-- gui/UI/Pages/Settings/SettingsPage.axaml.cs | 17 ++- gui/UI/Pages/Settings/SettingsViewModel.cs | 3 +- .../CreateWallet/CreateWalletPage.axaml | 69 ++++++++++ .../CreateWallet/CreateWalletPage.axaml.cs | 27 ++++ .../CreateWallet/CreateWalletViewModel.cs | 12 ++ .../WalletMnemonicValidatePage.axaml | 40 ++++++ .../WalletMnemonicValidatePage.axaml.cs | 29 +++++ .../WalletMnemonicValidateViewModel.cs | 40 ++++++ .../RenameWallet/RenameWalletDialog.axaml | 18 +++ .../RenameWallet/RenameWalletDialog.axaml.cs | 18 +++ .../RenameWallet/RenameWalletViewModel.cs | 32 +++++ gui/UI/Pages/Wallet/WalletPage.axaml | 118 ++++++++++++++++-- gui/UI/Pages/Wallet/WalletPage.axaml.cs | 17 ++- gui/UI/Pages/Wallet/WalletViewModel.cs | 54 +++++++- gui/UI/Shell/RootShell.axaml | 2 +- gui/UI/Shell/RootWindow.axaml | 5 +- 52 files changed, 1184 insertions(+), 319 deletions(-) create mode 100644 gui/Data/Model/DbWalletModel.cs delete mode 100644 gui/Directory.Build.props create mode 100644 gui/Model/PlatformType.cs create mode 100644 gui/UI/Controls/MnemonicValidateControl.axaml create mode 100644 gui/UI/Controls/MnemonicValidateControl.axaml.cs create mode 100644 gui/UI/Model/UiWalletModel.cs delete mode 100644 gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml delete mode 100644 gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs delete mode 100644 gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs create mode 100644 gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml create mode 100644 gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml.cs create mode 100644 gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidateViewModel.cs create mode 100644 gui/UI/Pages/Persona/RenamePersona/RenamePersonaDialog.axaml create mode 100644 gui/UI/Pages/Persona/RenamePersona/RenamePersonaDialog.axaml.cs create mode 100644 gui/UI/Pages/Persona/RenamePersona/RenamePersonaViewModel.cs create mode 100644 gui/UI/Pages/Wallet/CreateWallet/CreateWalletPage.axaml create mode 100644 gui/UI/Pages/Wallet/CreateWallet/CreateWalletPage.axaml.cs create mode 100644 gui/UI/Pages/Wallet/CreateWallet/CreateWalletViewModel.cs create mode 100644 gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidatePage.axaml create mode 100644 gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidatePage.axaml.cs create mode 100644 gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidateViewModel.cs create mode 100644 gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml create mode 100644 gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml.cs create mode 100644 gui/UI/Pages/Wallet/RenameWallet/RenameWalletViewModel.cs diff --git a/.editorconfig b/.editorconfig index 7c20842..a347e01 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,13 @@ root = true + charset = utf-8 + end_of_line = lf + insert_final_newline = true + trim_trailing_whitespace = true + indent_style = space [*.rs] diff --git a/gui/App.axaml.cs b/gui/App.axaml.cs index 0dc4bd8..d938542 100644 --- a/gui/App.axaml.cs +++ b/gui/App.axaml.cs @@ -6,15 +6,13 @@ using CommunityToolkit.Mvvm.DependencyInjection; using Dimension.MaskCore.Common; using Dimension.MaskCore.Data.Repository; -using Dimension.MaskCore.UI; +using Dimension.MaskCore.UI.Shell; using Microsoft.Extensions.DependencyInjection; using Realms; -using RootShell = Dimension.MaskCore.UI.Shell.RootShell; -using RootWindow = Dimension.MaskCore.UI.Shell.RootWindow; namespace Dimension.MaskCore; -internal partial class App : Application +internal class App : Application { public override void Initialize() { diff --git a/gui/Common/Consts.cs b/gui/Common/Consts.cs index 746eea9..0b59e54 100644 --- a/gui/Common/Consts.cs +++ b/gui/Common/Consts.cs @@ -1,3 +1,5 @@ +using System; +using System.IO; using System.Runtime.InteropServices; namespace Dimension.MaskCore.Common; @@ -10,14 +12,14 @@ public static string ConfigDirectory { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return System.IO.Path.Combine( - System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments), + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Mask"); } - return System.IO.Path.Combine( - System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), + + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".mask"); } } - } \ No newline at end of file diff --git a/gui/Common/Extension/EnumerabsleExtension.cs b/gui/Common/Extension/EnumerabsleExtension.cs index 7b9d99c..a6af59e 100644 --- a/gui/Common/Extension/EnumerabsleExtension.cs +++ b/gui/Common/Extension/EnumerabsleExtension.cs @@ -8,8 +8,7 @@ internal static class EnumerabsleExtension { public static IEnumerable Randomize(this IEnumerable source) { - Random rnd = new Random(); - return source.OrderBy((item) => rnd.Next()); + var rnd = new Random(); + return source.OrderBy(item => rnd.Next()); } - } \ No newline at end of file diff --git a/gui/Common/Helpers/MnemonicHelper.cs b/gui/Common/Helpers/MnemonicHelper.cs index 1f48eec..49cc583 100644 --- a/gui/Common/Helpers/MnemonicHelper.cs +++ b/gui/Common/Helpers/MnemonicHelper.cs @@ -4,5 +4,8 @@ namespace Dimension.MaskCore.Common.Helpers; internal static class MnemonicHelper { - public static string GenerateMnemonic() => WalletKey.GenerateMnemonic(); + public static string GenerateMnemonic() + { + return WalletKey.GenerateMnemonic(); + } } \ No newline at end of file diff --git a/gui/Data/Model/DbPersonaModel.cs b/gui/Data/Model/DbPersonaModel.cs index e1c8fcf..1f74096 100644 --- a/gui/Data/Model/DbPersonaModel.cs +++ b/gui/Data/Model/DbPersonaModel.cs @@ -21,7 +21,8 @@ internal class DbPersonaModel : RealmObject public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; - public static DbPersonaModel FromPersona(PersonaKey persona, string mnemonic, string path, string password, bool withPassword, string name) + public static DbPersonaModel FromPersona(PersonaKey persona, string mnemonic, string path, string password, + bool withPassword, string name) { return new DbPersonaModel { @@ -33,7 +34,7 @@ public static DbPersonaModel FromPersona(PersonaKey persona, string mnemonic, st Password = password, PrivateKey = JsonSerializer.Serialize(persona.PrivateKey), PublicKey = JsonSerializer.Serialize(persona.PrivateKey with { d = null }), - LocalKey = persona.LocalKey == null ? null : JsonSerializer.Serialize(persona.LocalKey), + LocalKey = persona.LocalKey == null ? null : JsonSerializer.Serialize(persona.LocalKey) }; } } \ No newline at end of file diff --git a/gui/Data/Model/DbWalletModel.cs b/gui/Data/Model/DbWalletModel.cs new file mode 100644 index 0000000..caa8d25 --- /dev/null +++ b/gui/Data/Model/DbWalletModel.cs @@ -0,0 +1,25 @@ +using System; +using Dimension.MaskCore.Model; +using MongoDB.Bson; +using Realms; + +namespace Dimension.MaskCore.Data.Model; + +internal class DbWalletModel : RealmObject +{ + [PrimaryKey] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + [Required] public string Name { get; set; } = string.Empty; + [Required] public string Address { get; set; } = string.Empty; + public string DerivationPath { get; set; } = string.Empty; + public byte[] Data { get; set; } = Array.Empty(); + private string PlatformTypeRaw { get; set; } = string.Empty; + + public PlatformType PlatformType + { + get => Enum.TryParse(PlatformTypeRaw, out PlatformType result) ? result : PlatformType.Ethereum; + set => PlatformTypeRaw = value.ToString(); + } + + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; +} \ No newline at end of file diff --git a/gui/Data/Repository/PersonaRepository.cs b/gui/Data/Repository/PersonaRepository.cs index c4d72f7..38eaade 100644 --- a/gui/Data/Repository/PersonaRepository.cs +++ b/gui/Data/Repository/PersonaRepository.cs @@ -17,17 +17,20 @@ internal class PersonaRepository { private const string Path = "m/44'/60'/0'/0/0"; private const string Password = ""; - + private static readonly Realm _realm = Ioc.Default.GetRequiredService(); - public IObservable> Personas { get; } = _realm.All().AsObservable() + + public IObservable> Personas { get; } = _realm.All() + .AsObservable() .Select(it => it.Select(UiPersonaModel.FromDb).ToImmutableList()); - public async Task CreatePersonaFromMnemonic(string mnemonic, string name) + public async Task CreatePersona(string name, string mnemonic) { if (string.IsNullOrEmpty(mnemonic) || string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(mnemonic)); } + if (_realm.All().Any(it => it.Mnemonic == mnemonic)) { throw new ArgumentException($"Persona with name {name} already exists"); @@ -40,35 +43,28 @@ public async Task CreatePersonaFromMnemonic(string mnemonic, string name) CurveType.Secp256k1, new EncryptionOption(EncryptionOption.EncVersion.V38) )); - _realm.Write(() => - { - _realm.Add(DbPersonaModel.FromPersona(persona, mnemonic, Path, Password, false, name)); - }); + _realm.Write(() => { _realm.Add(DbPersonaModel.FromPersona(persona, mnemonic, Path, Password, false, name)); }); } - - public void UpdatePersonaName(UiPersonaModel item, string name) + + public void UpdatePersonaName(string identifier, string name) { - var dbPersona = _realm.All().FirstOrDefault(it => it.Identifier == item.Identifier); + var dbPersona = _realm.All().FirstOrDefault(it => it.Identifier == identifier); if (dbPersona == null) { return; } - _realm.Write(() => - { - dbPersona.Name = name; - }); + + _realm.Write(() => { dbPersona.Name = name; }); } - - public void DeletePersona(UiPersonaModel persona) + + public void DeletePersona(string identifier) { - var item = _realm.All().FirstOrDefault(it => it.Identifier == persona.Identifier); + var item = _realm.All().FirstOrDefault(it => it.Identifier == identifier); if (item == null) { return; } - _realm.Write(() => - { - _realm.Remove(item); - }); + + _realm.Write(() => _realm.Remove(item)); } } \ No newline at end of file diff --git a/gui/Data/Repository/WalletRepository.cs b/gui/Data/Repository/WalletRepository.cs index 1c8a9f2..fc06b8c 100644 --- a/gui/Data/Repository/WalletRepository.cs +++ b/gui/Data/Repository/WalletRepository.cs @@ -1,7 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.DependencyInjection; +using Dimension.MaskCore.Common.Extension; +using Dimension.MaskCore.Data.Model; +using Dimension.MaskCore.Model; +using Dimension.MaskCore.UI.Model; using Dimension.MaskWalletCore; +using Realms; namespace Dimension.MaskCore.Data.Repository; internal class WalletRepository { + private const string Path = "m/44'/60'/0'/0/0"; + private const string Password = ""; + + private static readonly Realm _realm = Ioc.Default.GetRequiredService(); + + public IObservable> Wallets { get; } = _realm.All() + .AsObservable() + .Select(list => list.Select(UiWalletModel.From).ToImmutableList()); + + public async Task CreateWallet(string name, string mnemonic, string password = Password) + { + var wallet = await Task.Run(() => WalletKey.FromMnemonic(mnemonic, password)); + var account = await Task.Run(() => wallet.AddNewAccountAtPath(CoinType.Ethereum, Path, name, password)); + if (_realm.All().Any(it => it.Address == account.Address)) + { + return; + } + + var item = new DbWalletModel + { + Name = name, + Address = account.Address, + DerivationPath = account.DerivationPath, + Data = wallet.Data, + PlatformType = PlatformType.Ethereum + }; + _realm.Write(() => _realm.Add(item)); + } + + public void RenameWallet(string address, string name) + { + var item = _realm.All().FirstOrDefault(it => it.Address == address); + if (item == null) + { + return; + } + + _realm.Write(() => item.Name = name); + } + + public void DeleteWallet(string address) + { + var item = _realm.All().FirstOrDefault(it => it.Address == address); + if (item == null) + { + return; + } + + _realm.Write(() => _realm.Remove(item)); + } } \ No newline at end of file diff --git a/gui/Directory.Build.props b/gui/Directory.Build.props deleted file mode 100644 index 7093843..0000000 --- a/gui/Directory.Build.props +++ /dev/null @@ -1,5 +0,0 @@ - - - 0.10.13 - - \ No newline at end of file diff --git a/gui/FodyWeavers.xml b/gui/FodyWeavers.xml index cc07b89..ef11585 100644 --- a/gui/FodyWeavers.xml +++ b/gui/FodyWeavers.xml @@ -1,3 +1,3 @@  - + \ No newline at end of file diff --git a/gui/Lifecycle/Controls/Dialog.cs b/gui/Lifecycle/Controls/Dialog.cs index 42ccfaf..bbd9eb3 100644 --- a/gui/Lifecycle/Controls/Dialog.cs +++ b/gui/Lifecycle/Controls/Dialog.cs @@ -12,6 +12,8 @@ public class Dialog : Dialog where T : ViewModel.ViewModel public class Dialog : ContentDialog, IStyleable { + Type IStyleable.StyleKey => typeof(ContentDialog); + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); @@ -20,7 +22,4 @@ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs disposable.Dispose(); } } - - - Type IStyleable.StyleKey => typeof(ContentDialog); } \ No newline at end of file diff --git a/gui/Lifecycle/Controls/Page.cs b/gui/Lifecycle/Controls/Page.cs index 5496bcc..3e6484a 100644 --- a/gui/Lifecycle/Controls/Page.cs +++ b/gui/Lifecycle/Controls/Page.cs @@ -3,25 +3,31 @@ using Avalonia.Controls; using Avalonia.LogicalTree; using Avalonia.VisualTree; +using Dimension.MaskCore.Lifecycle.ViewModel; using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Media.Animation; using FluentAvalonia.UI.Navigation; namespace Dimension.MaskCore.Lifecycle.Controls; -public class Page : Page where T : ViewModel.ViewModel, new() where R : class +public class Page : Page + where TViewModel : ViewModel.ViewModel, new() where TParameter : class { protected override void OnCreated(object parameter) { base.OnCreated(parameter); - if (parameter is R r) + if (parameter is TParameter r) { OnCreated(r); } } - - protected virtual void OnCreated(R parameter) + + protected virtual void OnCreated(TParameter parameter) { + if (ViewModel is IParameterizedViewModel parameterizedViewModel) + { + parameterizedViewModel.Initialize(parameter); + } } } @@ -63,11 +69,11 @@ private void OnNavigated(object sender, NavigationEventArgs e) } } } - + protected virtual void OnCreated(object parameter) { } - + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); @@ -85,7 +91,7 @@ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs protected void GoBack() { - this.FindAncestorOfType().GoBack(new SlideNavigationTransitionInfo + Frame?.GoBack(new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft }); } @@ -93,4 +99,17 @@ protected void Navigate(object? parameter = null) { Frame?.Navigate(typeof(T), parameter, new SlideNavigationTransitionInfo()); } + + protected void Navigate(Type page, object? parameter = null) + { + Frame?.NavigateToType(page, parameter, new FrameNavigationOptions + { + TransitionInfoOverride = new SlideNavigationTransitionInfo() + }); + } + + protected void ClearBackStack() + { + Frame?.BackStack?.Clear(); + } } \ No newline at end of file diff --git a/gui/Lifecycle/ViewModel/ViewModel.cs b/gui/Lifecycle/ViewModel/ViewModel.cs index 4aa7ae4..ffb3bcf 100644 --- a/gui/Lifecycle/ViewModel/ViewModel.cs +++ b/gui/Lifecycle/ViewModel/ViewModel.cs @@ -12,4 +12,19 @@ public void Dispose() { Scope.Dispose(); } +} + +public interface IParameterizedViewModel +{ + void Initialize(T parameter); +} + +public abstract class ParameterizedViewModel : ViewModel, IParameterizedViewModel +{ + public void Initialize(T parameter) + { + InitializeCore(parameter); + } + + protected abstract void InitializeCore(T parameter); } \ No newline at end of file diff --git a/gui/MaskCore.csproj b/gui/MaskCore.csproj index 7e48579..63fa149 100644 --- a/gui/MaskCore.csproj +++ b/gui/MaskCore.csproj @@ -4,7 +4,6 @@ net6.0 enable Dimension.MaskCore - true true true @@ -21,29 +20,33 @@ true - + - - + + + + 0.10.13 + - - + + - - - - - - - - + + + + + + + + + - + diff --git a/gui/Model/PlatformType.cs b/gui/Model/PlatformType.cs new file mode 100644 index 0000000..7010b93 --- /dev/null +++ b/gui/Model/PlatformType.cs @@ -0,0 +1,6 @@ +namespace Dimension.MaskCore.Model; + +internal enum PlatformType +{ + Ethereum +} \ No newline at end of file diff --git a/gui/UI/Controls/MnemonicValidateControl.axaml b/gui/UI/Controls/MnemonicValidateControl.axaml new file mode 100644 index 0000000..4238ee3 --- /dev/null +++ b/gui/UI/Controls/MnemonicValidateControl.axaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/UI/Controls/MnemonicValidateControl.axaml.cs b/gui/UI/Controls/MnemonicValidateControl.axaml.cs new file mode 100644 index 0000000..af6ff42 --- /dev/null +++ b/gui/UI/Controls/MnemonicValidateControl.axaml.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Dimension.MaskCore.Common.Extension; +using Dimension.MaskCore.UI.Model; +using DynamicData; + +namespace Dimension.MaskCore.UI.Controls; + +internal partial class MnemonicValidateControl : UserControl +{ + public static readonly DirectProperty ConfirmCommandProperty = + AvaloniaProperty.RegisterDirect( + nameof(ConfirmCommand), o => o.ConfirmCommand, (o, v) => o.ConfirmCommand = v); + + public static readonly DirectProperty> MnemonicWordsProperty = + AvaloniaProperty.RegisterDirect>( + nameof(MnemonicWords), o => o.MnemonicWords, (o, v) => o.MnemonicWords = v); + + public static readonly DirectProperty IsValidProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsValid), + o => o.IsValid); + + private ICommand? _confirmCommand; + private bool _isValid; + + private IReadOnlyCollection _mnemonicWords = new List(); + + public MnemonicValidateControl() + { + InitializeComponent(); + } + + public ICommand? ConfirmCommand + { + get => _confirmCommand; + set => SetAndRaise(ConfirmCommandProperty, ref _confirmCommand, value); + } + + public IReadOnlyCollection MnemonicWords + { + get => _mnemonicWords; + set + { + SetAndRaise(MnemonicWordsProperty, ref _mnemonicWords, value); + SelectedMnemonic.Clear(); + RandomMnemonic.Clear(); + RandomMnemonic.AddRange(value + .Randomize() + .Select((word, index) => new MnemonicModel(word, index))); + } + } + + public ObservableCollection SelectedMnemonic { get; set; } = new(); + public ObservableCollection RandomMnemonic { get; set; } = new(); + + public bool IsValid + { + get => _isValid; + private set => SetAndRaise(IsValidProperty, ref _isValid, value); + } + + private void CheckValid() + { + IsValid = SelectedMnemonic.Select(x => x.Word).SequenceEqual(MnemonicWords); + } + + private void ClearClicked(object? sender, RoutedEventArgs e) + { + SelectedMnemonic.Clear(); + RandomMnemonic.AddRange(MnemonicWords.Select((word, index) => new MnemonicModel(word, index))); + CheckValid(); + } + + private void AddWord(MnemonicModel item) + { + if (!RandomMnemonic.Contains(item)) + { + return; + } + + RandomMnemonic.Remove(item); + SelectedMnemonic.Add(item); + CheckValid(); + } + + private void RemoveWord(MnemonicModel item) + { + if (!SelectedMnemonic.Contains(item)) + { + return; + } + + SelectedMnemonic.Remove(item); + RandomMnemonic.Add(item); + CheckValid(); + } + + public event EventHandler? Confirm; + + private void OnConfirmClicked(object? sender, RoutedEventArgs e) + { + Confirm?.Invoke(this, EventArgs.Empty); + } +} \ No newline at end of file diff --git a/gui/UI/Model/UiPersonaModel.cs b/gui/UI/Model/UiPersonaModel.cs index f173b47..ade66d7 100644 --- a/gui/UI/Model/UiPersonaModel.cs +++ b/gui/UI/Model/UiPersonaModel.cs @@ -4,7 +4,10 @@ namespace Dimension.MaskCore.UI.Model; internal record UiPersonaModel(string Name, string Identifier) { - public static UiPersonaModel FromDb(DbPersonaModel model) => new(model.Name, model.Identifier); + public static UiPersonaModel FromDb(DbPersonaModel model) + { + return new(model.Name, model.Identifier); + } } -internal record MnemonicModel(string Word, int Index); +internal record MnemonicModel(string Word, int Index); \ No newline at end of file diff --git a/gui/UI/Model/UiWalletModel.cs b/gui/UI/Model/UiWalletModel.cs new file mode 100644 index 0000000..d171cd9 --- /dev/null +++ b/gui/UI/Model/UiWalletModel.cs @@ -0,0 +1,11 @@ +using Dimension.MaskCore.Data.Model; + +namespace Dimension.MaskCore.UI.Model; + +internal record UiWalletModel(string Address, string Name) +{ + public static UiWalletModel From(DbWalletModel item) + { + return new UiWalletModel(item.Address, item.Name); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml b/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml index 411539f..6096094 100644 --- a/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml +++ b/gui/UI/Pages/Persona/CreatePersona/CreatePersonaPage.axaml @@ -11,7 +11,7 @@ x:DataType="createPersona:CreatePersonaViewModel" x:Class="Dimension.MaskCore.UI.Pages.Persona.CreatePersona.CreatePersonaPage"> - + @@ -31,34 +31,38 @@ VerticalAlignment="Center" /> - - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs b/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs deleted file mode 100644 index 72d87ed..0000000 --- a/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidatePage.axaml.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Avalonia.Interactivity; -using Dimension.MaskCore.Lifecycle.Controls; - -namespace Dimension.MaskCore.UI.Pages.Persona.MnemonicValidate; - -internal partial class MnemonicValidatePage : Page -{ - public MnemonicValidatePage() - { - InitializeComponent(); - } - - protected override void OnCreated(MnemonicValidateParameter parameter) - { - base.OnCreated(parameter); - ViewModel.Init(parameter); - } - - private void BackClicked(object? sender, RoutedEventArgs e) - { - GoBack(); - } -} \ No newline at end of file diff --git a/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs b/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs deleted file mode 100644 index d7ea4cb..0000000 --- a/gui/UI/Pages/Persona/MnemonicValidate/MnemonicValidateViewModel.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Dimension.MaskCore.Common.Extension; -using Dimension.MaskCore.Lifecycle.ViewModel; -using Dimension.MaskCore.UI.Model; - -namespace Dimension.MaskCore.UI.Pages.Persona.MnemonicValidate; - -internal partial class MnemonicValidateViewModel : ViewModel -{ - private readonly List _mnemonic = new(); - [ObservableProperty] - private string _name = string.Empty; - [ObservableProperty] - [AlsoNotifyCanExecuteFor(nameof(ComfirmCommand))] - private IReadOnlyCollection _selectedMnemonic = new List(); - [ObservableProperty] - [AlsoNotifyCanExecuteFor(nameof(ComfirmCommand))] - private IReadOnlyCollection _randomMnemonic = new List(); - public void Init(MnemonicValidateParameter parameter) - { - _mnemonic.Clear(); - _mnemonic.AddRange(parameter.Mnemonic.Split(" ")); - Name = parameter.Name; - RandomMnemonic = _mnemonic.Randomize().Select((word, index) => new MnemonicModel(word, index)).ToList(); - } - - [ICommand] - private void SelectWord(MnemonicModel item) - { - if (SelectedMnemonic.Contains(item)) - { - SelectedMnemonic = SelectedMnemonic.Where(x => x.Index != item.Index).ToList(); - RandomMnemonic = RandomMnemonic.Concat(new[] { item }).OrderBy(it => it.Index).ToList(); - } - else - { - SelectedMnemonic = SelectedMnemonic.Concat(new[] { item }).ToList(); - RandomMnemonic = RandomMnemonic.Where(x => x.Index != item.Index).OrderBy(it => it.Index).ToList(); - } - } - - public bool IsValid => SelectedMnemonic.Select(x => x.Word).SequenceEqual(_mnemonic); - - [ICommand(CanExecute = nameof(IsValid))] - private void Comfirm() - { - } -} - -internal record MnemonicValidateParameter(string Mnemonic, string Name); \ No newline at end of file diff --git a/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml b/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml new file mode 100644 index 0000000..7c12351 --- /dev/null +++ b/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml.cs b/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml.cs new file mode 100644 index 0000000..31f4873 --- /dev/null +++ b/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidatePage.axaml.cs @@ -0,0 +1,29 @@ +using Avalonia.Interactivity; +using Dimension.MaskCore.Lifecycle.Controls; + +namespace Dimension.MaskCore.UI.Pages.Persona.MnemonicValidate; + +internal partial class + PersonaMnemonicValidatePage : Page +{ + public PersonaMnemonicValidatePage() + { + InitializeComponent(); + ViewModel.OnComplete = OnComplete; + } + + private async void OnComplete() + { + await new Dialog + { + Title = "Persona Created", + CloseButtonText = "OK" + }.ShowAsync(); + Navigate(); + } + + private void BackClicked(object? sender, RoutedEventArgs e) + { + GoBack(); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidateViewModel.cs b/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidateViewModel.cs new file mode 100644 index 0000000..e5e648d --- /dev/null +++ b/gui/UI/Pages/Persona/MnemonicValidate/PersonaMnemonicValidateViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; +using Dimension.MaskCore.Data.Repository; +using Dimension.MaskCore.Lifecycle.ViewModel; + +namespace Dimension.MaskCore.UI.Pages.Persona.MnemonicValidate; + +internal partial class PersonaMnemonicValidateViewModel : ParameterizedViewModel +{ + private readonly PersonaRepository _personaRepository = Ioc.Default.GetRequiredService(); + [ObservableProperty] private IReadOnlyCollection _words = new List(); + public Action? OnComplete { get; set; } + public PersonaMnemonicValidateParameter? Parameter { get; private set; } + + protected override void InitializeCore(PersonaMnemonicValidateParameter parameter) + { + Parameter = parameter; + Words = Parameter.WordList.ToImmutableList(); + } + + [ICommand] + private async Task Confirm() + { + if (Parameter != null) + { + await _personaRepository.CreatePersona(Parameter.Name, Parameter.Mnemonic); + OnComplete?.Invoke(); + } + } +} + +internal record PersonaMnemonicValidateParameter(string Mnemonic, string Name) +{ + public string[] WordList => Mnemonic.Split(' '); +} \ No newline at end of file diff --git a/gui/UI/Pages/Persona/PersonaPage.axaml b/gui/UI/Pages/Persona/PersonaPage.axaml index 7a76e61..8cdc8b1 100644 --- a/gui/UI/Pages/Persona/PersonaPage.axaml +++ b/gui/UI/Pages/Persona/PersonaPage.axaml @@ -22,17 +22,17 @@ - + - + - + @@ -55,28 +55,33 @@ IsVisible="{CompiledBinding !!Personas^.Count}"> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidatePage.axaml.cs b/gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidatePage.axaml.cs new file mode 100644 index 0000000..d332f72 --- /dev/null +++ b/gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidatePage.axaml.cs @@ -0,0 +1,29 @@ +using Avalonia.Interactivity; +using Dimension.MaskCore.Lifecycle.Controls; + +namespace Dimension.MaskCore.UI.Pages.Wallet.MnemonicValidate; + +internal partial class + WalletMnemonicValidatePage : Page +{ + public WalletMnemonicValidatePage() + { + InitializeComponent(); + ViewModel.OnComplete = OnComplete; + } + + private async void OnComplete() + { + await new Dialog + { + Title = "Wallet Created", + CloseButtonText = "OK" + }.ShowAsync(); + Navigate(); + } + + private void BackClicked(object? sender, RoutedEventArgs e) + { + GoBack(); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidateViewModel.cs b/gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidateViewModel.cs new file mode 100644 index 0000000..4968204 --- /dev/null +++ b/gui/UI/Pages/Wallet/MnemonicValidate/WalletMnemonicValidateViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; +using Dimension.MaskCore.Data.Repository; +using Dimension.MaskCore.Lifecycle.ViewModel; + +namespace Dimension.MaskCore.UI.Pages.Wallet.MnemonicValidate; + +internal partial class WalletMnemonicValidateViewModel : ParameterizedViewModel +{ + private readonly WalletRepository _repository = Ioc.Default.GetRequiredService(); + [ObservableProperty] private IReadOnlyCollection _words = new List(); + public Action? OnComplete { get; set; } + public WalletMnemonicValidateParameter? Parameter { get; private set; } + + protected override void InitializeCore(WalletMnemonicValidateParameter parameter) + { + Parameter = parameter; + Words = Parameter.WordList.ToImmutableList(); + } + + [ICommand] + private async Task Confirm() + { + if (Parameter != null) + { + await _repository.CreateWallet(Parameter.Name, Parameter.Mnemonic); + OnComplete?.Invoke(); + } + } +} + +internal record WalletMnemonicValidateParameter(string Mnemonic, string Name) +{ + public string[] WordList => Mnemonic.Split(' '); +} \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml b/gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml new file mode 100644 index 0000000..7e1be54 --- /dev/null +++ b/gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml.cs b/gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml.cs new file mode 100644 index 0000000..7382913 --- /dev/null +++ b/gui/UI/Pages/Wallet/RenameWallet/RenameWalletDialog.axaml.cs @@ -0,0 +1,18 @@ +using Dimension.MaskCore.Lifecycle.Controls; +using Dimension.MaskCore.UI.Model; + +namespace Dimension.MaskCore.UI.Pages.Wallet.RenameWallet; + +internal partial class RenameWalletDialog : Dialog +{ + public RenameWalletDialog() + { + InitializeComponent(); + } + + public RenameWalletDialog(UiWalletModel item) + { + InitializeComponent(); + ViewModel.Initialize(item); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/RenameWallet/RenameWalletViewModel.cs b/gui/UI/Pages/Wallet/RenameWallet/RenameWalletViewModel.cs new file mode 100644 index 0000000..4ecebeb --- /dev/null +++ b/gui/UI/Pages/Wallet/RenameWallet/RenameWalletViewModel.cs @@ -0,0 +1,32 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; +using Dimension.MaskCore.Data.Repository; +using Dimension.MaskCore.Lifecycle.ViewModel; +using Dimension.MaskCore.UI.Model; + +namespace Dimension.MaskCore.UI.Pages.Wallet.RenameWallet; + +internal partial class RenameWalletViewModel : ViewModel +{ + private readonly WalletRepository _repository = Ioc.Default.GetRequiredService(); + [ObservableProperty] private string _name = string.Empty; + private UiWalletModel? _wallet; + + public void Initialize(UiWalletModel item) + { + Name = item.Name; + _wallet = item; + } + + [ICommand] + private void Rename() + { + if (_wallet == null) + { + return; + } + + _repository.RenameWallet(_wallet.Address, Name); + } +} \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/WalletPage.axaml b/gui/UI/Pages/Wallet/WalletPage.axaml index 605ed17..4f8c75b 100644 --- a/gui/UI/Pages/Wallet/WalletPage.axaml +++ b/gui/UI/Pages/Wallet/WalletPage.axaml @@ -1,11 +1,107 @@ - - Welcome to Avalonia! - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/WalletPage.axaml.cs b/gui/UI/Pages/Wallet/WalletPage.axaml.cs index 5a749aa..2f88eaa 100644 --- a/gui/UI/Pages/Wallet/WalletPage.axaml.cs +++ b/gui/UI/Pages/Wallet/WalletPage.axaml.cs @@ -1,7 +1,8 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using Avalonia.Interactivity; using Dimension.MaskCore.Lifecycle.Controls; +using Dimension.MaskCore.UI.Model; +using Dimension.MaskCore.UI.Pages.Wallet.CreateWallet; +using Dimension.MaskCore.UI.Pages.Wallet.RenameWallet; namespace Dimension.MaskCore.UI.Pages.Wallet; @@ -11,4 +12,14 @@ public WalletPage() { InitializeComponent(); } + + private void New_OnClicked(object? sender, RoutedEventArgs e) + { + Navigate(); + } + + private void RenameWallet(UiWalletModel item) + { + new RenameWalletDialog(item).ShowAsync(); + } } \ No newline at end of file diff --git a/gui/UI/Pages/Wallet/WalletViewModel.cs b/gui/UI/Pages/Wallet/WalletViewModel.cs index 1ad0b4f..65e9f64 100644 --- a/gui/UI/Pages/Wallet/WalletViewModel.cs +++ b/gui/UI/Pages/Wallet/WalletViewModel.cs @@ -1,8 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; +using Dimension.MaskCore.Data.Repository; using Dimension.MaskCore.Lifecycle.ViewModel; +using Dimension.MaskCore.UI.Model; +using TextCopy; namespace Dimension.MaskCore.UI.Pages.Wallet; -internal partial class WalletViewModel: ViewModel +internal partial class WalletViewModel : ViewModel { - + private readonly WalletRepository _repository = Ioc.Default.GetRequiredService(); + private readonly BehaviorSubject _searchTextSubject = new(string.Empty); + [ObservableProperty] private string _searchText = string.Empty; + + public WalletViewModel() + { + Wallets = _repository.Wallets.CombineLatest(_searchTextSubject).Throttle(TimeSpan.FromSeconds(0.3)).Select( + it => + { + var (wallets, searchText) = it; + if (string.IsNullOrWhiteSpace(searchText)) + { + return wallets; + } + + return wallets.Where(wallet => + wallet.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase) || + wallet.Address.Contains(searchText, StringComparison.OrdinalIgnoreCase)).ToImmutableList(); + }); + } + + public IObservable> Wallets { get; } + + [ICommand] + private void DeleteWallet(UiWalletModel model) + { + _repository.DeleteWallet(model.Address); + } + + [ICommand] + private void CopyAddress(UiWalletModel model) + { + ClipboardService.SetText(model.Address); + } + + partial void OnSearchTextChanged(string value) + { + _searchTextSubject.OnNext(value); + } } \ No newline at end of file diff --git a/gui/UI/Shell/RootShell.axaml b/gui/UI/Shell/RootShell.axaml index d15d4a9..1ea0abb 100644 --- a/gui/UI/Shell/RootShell.axaml +++ b/gui/UI/Shell/RootShell.axaml @@ -135,4 +135,4 @@ - + \ No newline at end of file diff --git a/gui/UI/Shell/RootWindow.axaml b/gui/UI/Shell/RootWindow.axaml index c917a29..de78272 100644 --- a/gui/UI/Shell/RootWindow.axaml +++ b/gui/UI/Shell/RootWindow.axaml @@ -2,7 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="clr-namespace:Dimension.MaskCore.UI" xmlns:shell="clr-namespace:Dimension.MaskCore.UI.Shell" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Dimension.MaskCore.UI.Shell.RootWindow" @@ -10,5 +9,5 @@ Height="576" WindowStartupLocation="CenterScreen" Title="MaskCore"> - - + + \ No newline at end of file From 8d7172e75c63724eb4fcc1b5212709aee1ce8ca6 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 24 Apr 2022 16:30:27 +0800 Subject: [PATCH 05/10] add github actions --- .github/workflows/gui.yaml | 75 ++++++++++++++++++++++++++++++++++++++ gui/MaskCore.csproj | 15 +------- 2 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/gui.yaml diff --git a/.github/workflows/gui.yaml b/.github/workflows/gui.yaml new file mode 100644 index 0000000..7eeec34 --- /dev/null +++ b/.github/workflows/gui.yaml @@ -0,0 +1,75 @@ +name: gui + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + + linux: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + - name: Install dependencies + working-directory: ./gui + run: dotnet restore + - name: Publish + working-directory: ./gui + run: dotnet publish -c Release -r linux-x64 --self-contained true /p:PublishSingleFile=true /p:EnableCompressionInSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:PublishTrimmed=true + - name: Archive publish artifacts + uses: actions/upload-artifact@v3 + with: + name: maskcore_linux-x64 + path: gui/bin/Release/net6.0/linux-x64/publish + + win: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v1 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + - name: Install dependencies + working-directory: ./gui + run: dotnet restore + - name: Publish + working-directory: ./gui + run: dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true /p:EnableCompressionInSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:PublishTrimmed=true + - name: Archive publish artifacts + uses: actions/upload-artifact@v3 + with: + name: maskcore_win-x64 + path: gui/bin/Release/net6.0/win-x64/publish + + osx: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v1 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + - name: Install dependencies + working-directory: ./gui + run: dotnet restore + - name: Publish + working-directory: ./gui + run: dotnet publish -c Release -r osx-x64 --self-contained true /p:PublishSingleFile=true /p:EnableCompressionInSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:PublishTrimmed=true + - name: Archive publish artifacts + uses: actions/upload-artifact@v3 + with: + name: maskcore_osx-x64 + path: gui/bin/Release/net6.0/osx-x64/publish diff --git a/gui/MaskCore.csproj b/gui/MaskCore.csproj index 63fa149..46519db 100644 --- a/gui/MaskCore.csproj +++ b/gui/MaskCore.csproj @@ -4,20 +4,9 @@ net6.0 enable Dimension.MaskCore - true - true - true - win-x64 - linux-x64 - osx-arm64 - true - true - true - true - true - copyused - true + copyused + true From 3c6ff8271cf9e50d593537ebbce07c8626c16560 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 24 Apr 2022 16:33:19 +0800 Subject: [PATCH 06/10] roll back editor config --- .editorconfig | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.editorconfig b/.editorconfig index a347e01..7c20842 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,8 @@ root = true - charset = utf-8 - end_of_line = lf - insert_final_newline = true - trim_trailing_whitespace = true - indent_style = space [*.rs] From 37b3e4c4fc06f490b86133426596f266fac98944 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 24 Apr 2022 16:36:01 +0800 Subject: [PATCH 07/10] update github actions --- .github/workflows/gui.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/gui.yaml b/.github/workflows/gui.yaml index 7eeec34..e3eca21 100644 --- a/.github/workflows/gui.yaml +++ b/.github/workflows/gui.yaml @@ -1,12 +1,6 @@ name: gui -on: - push: - branches: - - master - pull_request: - branches: - - master +on: [push, pull_request] jobs: From 2c132dd64057a63f048289e4073700c4e9f8d3d1 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 24 Apr 2022 16:50:09 +0800 Subject: [PATCH 08/10] add icon --- gui/Assets/favicon.ico | Bin 0 -> 15406 bytes gui/MaskCore.csproj | 4 ++++ gui/UI/Shell/RootWindow.axaml | 1 + 3 files changed, 5 insertions(+) create mode 100644 gui/Assets/favicon.ico diff --git a/gui/Assets/favicon.ico b/gui/Assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6c099524def9e2acfb8c0e8a9fba69ad1783648d GIT binary patch literal 15406 zcmeHN|BoG26(1vi0GjwyU&{gp8xg2rXfP6MG*Kgo22rD_wggJ|^~(Y&yFh7`)^1Bm zlWv1wXq8G`O{7p2LKM1C+J?4j(OMQzi~RzwTiaUL?R5&pDra?!D)px%XXnIul3^vQatiUDZcqiI-w5h zhBCq@c`K7Qa$1Uq1{8TCXDW-7o{%qmQm;y0NeWN)igDj;u^+n=)TQ1FJJyMyjEQwa zzVI3Hlh^vuZQ@(H_?{P#c}}b!%$LHBht&V#n+Ie0iCJHKndo<4E&4szR@2>AiJtEW z^7tO@YrY`nhFcmU){VB`dHG2xop`Mw zV%;cz@yMT4-!}5Yu@>cTb^KP&fvC)S|Hm^2L*7`Y&_pKvOb2KVi2f-l$%EQOc7ED& zE1&x}+U3`4TL$Gmbx~)i+sNm<4CL1IeVR<_BnC5RcT>D+a+@hD>rSxwbYRMoV%L!v z-P6VR-e;xo%tk3ZwNAKF&WK+srIPo~Nnp59@i@EstLipuf7)HY=i`u-Q+;bZ z_<3oFy023_de=v)>xcP~80oyvtR8>v!RH|75)j8%7DRtqXZ}~rKfhqB7>#jS7Gyuu z2mNUns@v~dCgQx|vOf$4oNpsuWS%}UHUjys@=z8g6<@L*WQ3{N{x2TdCHfs5Vy^xY z=%ulG&5nQ0J#)j{z-ElUmwZ^g8!P9C zZ;Xz~RWhm_zJ^MY%pR zKYdhRnDepElrZln4rh}66gFTaR$^uw*cOF7HGZFb?9nz6GqKy~586Fie!>3-W@3k* zHZJ5blVnF?CibNA>U^$sS$t&qXFmK5DGt7*zH@!okqIU2H5LbVD-5l)fo*qz9S2C!0C3{|L$`}IsD6mE zabh0!?9hL~e(S`+jnhO7#8NLluO4GF)OhE3X)5vCn)_(jH}FmmNonY)BEBnIpiF&N zHzAZ5%#Cx){v&3N#`Xd5W3D!gAGmpjSRC9d_O|6>;=Ikc?^B}p%vQvAv-gW)_peZA ziQ?D|w?p9vmcajIjGws8QE?BwDCR>q!&h7iJ@gkz-xerabZOuhUfrY4M$wSe-e@O& z@8mJ;?evI#=SR!-0dBOxxF7a@_!hB#c!!vz^*5tGK3VpK<+k1RG4#g+;+=YHQt*4j zC&c_dY`!Si6XRJm{jGc8X?5<)vu%!fk~eZnO2hvYci*qkPhSO3*{6fo?EO00FqGJa z%N94q-5*n_@KoSoZ%72oHa!%3+;-U`3lg7XUQMYAuk&a7n{*blY5S>vZ~ z;eMd9dmej{Yj2X`>wA>niSF4Y{)Znb9)3ZL)jT`HoW#DS{fiaBd|>-4C{z59>;7dx z^sbpouhDZ2e9kWTsRH&>u@Bh{IR)$i??gW!MqnE-AKX{>*2;iiW9*WJ|KT~9vF>OFW)Z*9{znf7cird{mR=#&mL*bnb`#c~g zn8&iQ_>?@VJ!M|Vq-^S_Oqq?JP$*(|=Y?%zuK2WCGxeodi@UB=_y263E$Z)vHEW(M zv$x}}B<=^2NAY6aH6Y6wcwE$XL4+%F{FKjed-Axre|TD)UB49P<)<+B2E|8AP%p6# z>yn4OltEeQ3^ay$-1wt@u=0PJzpShm_ue3HYxwc~zV^%F{_TLcM-EOd0RyoF^DDYe zrTpjoT5|b?LCQa*V2zFK7+2BXT?kYe<2BX}qx)L;=5Dcma_{64Fc3@A@iV>|xJHKe z9wzOX;rC07duw}EYyKx8{%kwiS_LiR=lF@liM7((pQG?My?;2>_iu6E0{iFOf1m9A zbDlpmzkd!NJ9d7tJJ?6#9tp-%dniWsIL|MXzpXkynmaLPM?9m#cpvxqQPrBv^^bA- zL{ejG?o>WM<$Z#wettdG?+@?}#8kb%!Fw~jgA(53m`e9&LhQunet6.0 enable Dimension.MaskCore + Assets/favicon.ico copyused true @@ -38,4 +39,7 @@ + + + diff --git a/gui/UI/Shell/RootWindow.axaml b/gui/UI/Shell/RootWindow.axaml index de78272..abd574d 100644 --- a/gui/UI/Shell/RootWindow.axaml +++ b/gui/UI/Shell/RootWindow.axaml @@ -8,6 +8,7 @@ Width="1024" Height="576" WindowStartupLocation="CenterScreen" + Icon="/Assets/favicon.ico" Title="MaskCore"> \ No newline at end of file From 0a8dac4f1b11ceed998f416ebf9d37d42db8c2ab Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 24 Apr 2022 17:23:13 +0800 Subject: [PATCH 09/10] add osx app bundle --- .github/workflows/gui.yaml | 4 ++-- gui/MaskCore.csproj | 5 +++++ gui/icon.icns | Bin 0 -> 50321 bytes 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 gui/icon.icns diff --git a/.github/workflows/gui.yaml b/.github/workflows/gui.yaml index e3eca21..0cec6a9 100644 --- a/.github/workflows/gui.yaml +++ b/.github/workflows/gui.yaml @@ -58,10 +58,10 @@ jobs: dotnet-version: 6.0.x - name: Install dependencies working-directory: ./gui - run: dotnet restore + run: dotnet restore -r osx-x64 - name: Publish working-directory: ./gui - run: dotnet publish -c Release -r osx-x64 --self-contained true /p:PublishSingleFile=true /p:EnableCompressionInSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:PublishTrimmed=true + run: dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -property:Configuration=Release -p:UseAppHost=true -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=true -p:SelfContained=true - name: Archive publish artifacts uses: actions/upload-artifact@v3 with: diff --git a/gui/MaskCore.csproj b/gui/MaskCore.csproj index 487f88e..87d4f4c 100644 --- a/gui/MaskCore.csproj +++ b/gui/MaskCore.csproj @@ -5,6 +5,10 @@ enable Dimension.MaskCore Assets/favicon.ico + 0.1.0 + 0.1.0 + com.dimension + icon.icns copyused true @@ -35,6 +39,7 @@ + diff --git a/gui/icon.icns b/gui/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..e91067f1861d73c051bd21aa25f55d0ac74d3bc1 GIT binary patch literal 50321 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 Y!^>uDJA=fmiLFjHrd}6;_{rn{*#V2UkpKVy literal 0 HcmV?d00001 From 26b5dd309aaae0660aedbd3b24838c3ac55ff86c Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 24 Apr 2022 19:03:36 +0800 Subject: [PATCH 10/10] remove mac icon --- gui/MaskCore.csproj | 1 - gui/icon.icns | Bin 50321 -> 0 bytes 2 files changed, 1 deletion(-) delete mode 100644 gui/icon.icns diff --git a/gui/MaskCore.csproj b/gui/MaskCore.csproj index 87d4f4c..82b12fc 100644 --- a/gui/MaskCore.csproj +++ b/gui/MaskCore.csproj @@ -8,7 +8,6 @@ 0.1.0 0.1.0 com.dimension - icon.icns copyused true diff --git a/gui/icon.icns b/gui/icon.icns deleted file mode 100644 index e91067f1861d73c051bd21aa25f55d0ac74d3bc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50321 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 Y!^>uDJA=fmiLFjHrd}6;_{rn{*#V2UkpKVy