Skip to content

Commit 529dce1

Browse files
committed
initial
0 parents  commit 529dce1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+5300
-0
lines changed

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Build output
2+
bin/
3+
obj/
4+
5+
## Publish output
6+
publish/
7+
8+
## IDE
9+
.vs/
10+
.vscode/
11+
.idea/
12+
*.user
13+
*.suo
14+
15+
## OS
16+
.DS_Store
17+
Thumbs.db
18+
19+
## MAUI build artifacts
20+
*.apk
21+
*.aab
22+
*.ipa
23+
*.app

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "deps/Qubic.Net"]
2+
path = deps/Qubic.Net
3+
url = https://github.com/qubic/Qubic.Net.git

App.xaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
x:Class="Qubic.Net.Wallet.Mobile.App">
5+
<Application.Resources>
6+
<ResourceDictionary>
7+
</ResourceDictionary>
8+
</Application.Resources>
9+
</Application>

App.xaml.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Qubic.Net.Wallet.Mobile;
2+
3+
public partial class App : Application
4+
{
5+
public App()
6+
{
7+
InitializeComponent();
8+
}
9+
10+
protected override Window CreateWindow(IActivationState? activationState)
11+
{
12+
return new Window(new MainPage());
13+
}
14+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
@inherits LayoutComponentBase
2+
@inject SeedSessionService Seed
3+
@inject TickMonitorService TickMonitor
4+
@inject VaultService Vault
5+
@inject WalletStorageService WalletStorage
6+
@inject TransactionTrackerService TxTracker
7+
@implements IDisposable
8+
9+
<div class="qb-app">
10+
<!-- Top Bar -->
11+
<header class="qb-topbar">
12+
<img src="qubic-icon.svg" alt="Qubic" class="qb-topbar-logo" />
13+
14+
<div class="qb-topbar-status">
15+
@if (Seed.HasSeed)
16+
{
17+
<span class="badge-qb-cyan qb-wallet-badge" @onclick="ToggleWalletMenu" title="Switch wallet">
18+
@WalletDisplayName
19+
@if (Vault.IsUnlocked && Vault.Entries.Count > 1)
20+
{
21+
<i class="bi bi-chevron-down" style="font-size:0.55rem;margin-left:0.15rem"></i>
22+
}
23+
</span>
24+
25+
@if (_showWalletMenu && Vault.IsUnlocked && Vault.Entries.Count > 1)
26+
{
27+
<div class="qb-wallet-dropdown">
28+
@foreach (var entry in Vault.Entries)
29+
{
30+
var isActive = entry.Identity == Seed.Identity?.ToString();
31+
<div class="qb-wallet-dropdown-item @(isActive ? "active" : "")"
32+
@onclick="() => SwitchToEntry(entry)">
33+
<div class="fw-bold small">@entry.Label</div>
34+
<div class="mono" style="font-size:0.6rem;color:var(--qb-text-muted)">@FormatId(entry.Identity)</div>
35+
</div>
36+
}
37+
</div>
38+
}
39+
}
40+
41+
@if (Vault.IsUnlocked)
42+
{
43+
<span class="badge-qb-success" title="Vault unlocked"><i class="bi bi-unlock-fill"></i></span>
44+
}
45+
else if (Vault.VaultExists)
46+
{
47+
<span class="badge-qb-warning" title="Vault locked"><i class="bi bi-lock-fill"></i></span>
48+
}
49+
50+
@if (TickMonitor.IsConnected)
51+
{
52+
<span class="badge-qb-success mono">E@(TickMonitor.Epoch) T@(TickMonitor.Tick)</span>
53+
}
54+
else
55+
{
56+
<span class="badge-qb-warning">Offline</span>
57+
}
58+
</div>
59+
</header>
60+
61+
<!-- Page Content -->
62+
<main class="qb-content" @onclick="CloseWalletMenu">
63+
@Body
64+
</main>
65+
66+
<!-- Bottom Tab Bar -->
67+
<TabBar />
68+
</div>
69+
70+
@code {
71+
bool _showWalletMenu;
72+
73+
string WalletDisplayName
74+
{
75+
get
76+
{
77+
var id = Seed.Identity?.ToString();
78+
var idSnippet = id != null ? $"({id[..4]}...{id[^4..]})" : "";
79+
if (Seed.ActiveLabel != null)
80+
return $"{Seed.ActiveLabel} {idSnippet}";
81+
return idSnippet;
82+
}
83+
}
84+
85+
static string FormatId(string id) => id.Length >= 8 ? $"{id[..4]}...{id[^4..]}" : id;
86+
87+
void ToggleWalletMenu()
88+
{
89+
if (Vault.IsUnlocked && Vault.Entries.Count > 1)
90+
_showWalletMenu = !_showWalletMenu;
91+
}
92+
93+
void CloseWalletMenu()
94+
{
95+
if (_showWalletMenu) _showWalletMenu = false;
96+
}
97+
98+
void SwitchToEntry(VaultEntry entry)
99+
{
100+
_showWalletMenu = false;
101+
if (entry.Identity == Seed.Identity?.ToString()) return;
102+
103+
WalletStorage.ClearSeed();
104+
TxTracker.ClearEncryptionKey();
105+
Seed.SetSeed(entry.Seed, entry.Label);
106+
WalletStorage.SetSeed(entry.Seed, entry.Identity, TickMonitor.Epoch);
107+
TxTracker.SetEncryptionKey(entry.Seed);
108+
}
109+
110+
protected override void OnInitialized()
111+
{
112+
TickMonitor.OnTickChanged += OnStateChanged;
113+
Seed.OnSeedChanged += OnStateChanged;
114+
Vault.OnVaultChanged += OnStateChanged;
115+
}
116+
117+
void OnStateChanged()
118+
{
119+
InvokeAsync(StateHasChanged);
120+
}
121+
122+
public void Dispose()
123+
{
124+
TickMonitor.OnTickChanged -= OnStateChanged;
125+
Seed.OnSeedChanged -= OnStateChanged;
126+
Vault.OnVaultChanged -= OnStateChanged;
127+
}
128+
}

Components/Layout/TabBar.razor

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
@inject NavigationManager Nav
2+
@implements IDisposable
3+
4+
<nav class="qb-tabbar">
5+
@foreach (var tab in _tabs)
6+
{
7+
<a href="@tab.Href" class="qb-tab @(IsActive(tab.Href) ? "active" : "")" @onclick:preventDefault="false">
8+
<i class="bi @tab.Icon"></i>
9+
<span>@tab.Label</span>
10+
</a>
11+
}
12+
</nav>
13+
14+
@code {
15+
record Tab(string Href, string Icon, string Label);
16+
17+
static readonly Tab[] _tabs =
18+
[
19+
new("/", "bi-house-fill", "Home"),
20+
new("/send", "bi-send-fill", "Send"),
21+
new("/assets", "bi-coin", "Assets"),
22+
new("/defi", "bi-graph-up", "DeFi"),
23+
new("/more", "bi-three-dots", "More"),
24+
];
25+
26+
protected override void OnInitialized()
27+
{
28+
Nav.LocationChanged += OnLocationChanged;
29+
}
30+
31+
bool IsActive(string href)
32+
{
33+
var uri = Nav.ToAbsoluteUri(Nav.Uri);
34+
var path = uri.AbsolutePath.TrimEnd('/');
35+
var target = href.TrimEnd('/');
36+
37+
if (string.IsNullOrEmpty(target))
38+
return string.IsNullOrEmpty(path) || path == "/";
39+
40+
return path.Equals(target, StringComparison.OrdinalIgnoreCase)
41+
|| path.StartsWith(target + "/", StringComparison.OrdinalIgnoreCase);
42+
}
43+
44+
void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
45+
{
46+
InvokeAsync(StateHasChanged);
47+
}
48+
49+
public void Dispose()
50+
{
51+
Nav.LocationChanged -= OnLocationChanged;
52+
}
53+
}

0 commit comments

Comments
 (0)