Add initial project structure with basic view models, views, and resources

This commit is contained in:
2026-09-14 19:46:46 +03:00
commit 45401645c6
68 changed files with 2538 additions and 0 deletions
@@ -0,0 +1,118 @@
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
xmlns:vm="using:CryptoPortfolio_Tracker.ViewModels"
xmlns:u="https://irihi.tech/ursa"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DesignWidth="900" d:DesignHeight="600"
x:Class="CryptoPortfolio_Tracker.Views.CryptoChartView"
x:DataType="vm:CryptoChartViewModel">
<Border Background="#232429"
CornerRadius="14"
Padding="20">
<Grid RowDefinitions="Auto,Auto,*,Auto"
RowSpacing="15">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Spacing="3">
<TextBlock Text="{Binding CryptocurrencyName}"
FontSize="24"
FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Spacing="8">
<TextBlock Text="{Binding CurrentPrice, StringFormat='{}{0:N2} $'}"
FontSize="20"
FontWeight="SemiBold" />
<TextBlock Text="{Binding PriceChangePercentage24H, StringFormat='{}{0:+0.00;-0.00;0.00} %'}"
Classes.Success="{Binding IsPositive}"
Classes.Danger="{Binding IsNegative}"
FontSize="16"
VerticalAlignment="Center" />
</StackPanel>
</StackPanel>
</Grid>
<Grid Grid.Row="1"
ColumnDefinitions="Auto,Auto,Auto"
ColumnSpacing="25">
<StackPanel Spacing="2">
<TextBlock Text="Изменение"
FontSize="12"
Opacity="0.6" />
<TextBlock Text="{Binding PriceChange24H, StringFormat='{}{0:+0.00;-0.00;0.00} $'}"
Classes.Success="{Binding IsPositive}"
Classes.Danger="{Binding IsNegative}"
FontWeight="SemiBold" />
</StackPanel>
<StackPanel Grid.Column="1"
Spacing="2">
<TextBlock Text="Рыночная капитализация"
FontSize="12"
Opacity="0.6" />
<TextBlock Text="{Binding MarketCap, StringFormat='{}{0:N0} $'}"
FontWeight="SemiBold" />
</StackPanel>
<StackPanel Grid.Column="2"
Spacing="2">
<TextBlock Text="Объём за 24ч"
FontSize="12"
Opacity="0.6" />
<TextBlock Text="{Binding Volume24H, StringFormat='{}{0:N0} $'}"
FontWeight="SemiBold" />
</StackPanel>
</Grid>
<lvc:CartesianChart
Grid.Row="2"
MinHeight="400"
LegendPosition="Hidden"
TooltipPosition="Top"
FindingStrategy="CompareOnlyX"
ZoomMode="X"
AnimationsSpeed="0"
DrawMarginFrame="{x:Null}"
Series="{Binding Series}">
<lvc:CartesianChart.XAxes>
<lvc:XamlDateTimeAxis
UnitWidth="{Binding UnitWidth}"
Interval="01:00:00"
DateFormatter="{Binding DateFormatter}"
SeparatorsPaint="{Binding GridPaint}" />
</lvc:CartesianChart.XAxes>
<lvc:CartesianChart.YAxes>
<lvc:XamlAxis
Labeler="{Binding PriceFormatter}"
SeparatorsPaint="{Binding GridPaint}" />
</lvc:CartesianChart.YAxes>
</lvc:CartesianChart>
<TextBlock
Grid.Row="3"
Text="Колесо мыши — масштабирование • Перетаскивание — перемещение"
FontSize="12"
Opacity="0.5"
HorizontalAlignment="Center" />
</Grid>
</Border>
</UserControl>
@@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace CryptoPortfolio_Tracker.Views;
public partial class CryptoChartView : UserControl
{
public CryptoChartView()
{
InitializeComponent();
}
}
@@ -0,0 +1,209 @@
<ContentPage xmlns="https://github.com/avaloniaui"
xmlns:avalonia="using:Material.Icons.Avalonia"
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:vm="using:CryptoPortfolio_Tracker.ViewModels"
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
xmlns:models="clr-namespace:CryptoPortfolio_Tracker.Models"
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="720"
Header="CryptoProfile Tracker"
x:DataType="vm:HomeViewModel"
x:Class="CryptoPortfolio_Tracker.Views.HomeView">
<Design.DataContext>
<vm:HomeViewModel />
</Design.DataContext>
<ContentPage.Styles>
<Style Selector="Border.Card">
<Setter Property="Height" Value="100" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Padding" Value="10" />
<Setter Property="BorderBrush" Value="{StaticResource PrimaryColor}" />
<Setter Property="Background" Value="#232429" />
</Style>
</ContentPage.Styles>
<Grid RowDefinitions="Auto, Auto, Auto, *" RowSpacing="15" ShowGridLines="False">
<Grid ColumnDefinitions="*, Auto" Margin="15 15 15 0">
<TextBlock Text="{Binding UserName}" VerticalAlignment="Center"
FontWeight="Bold" FontSize="18" />
<Button Grid.Column="1" Classes="Danger" Theme="{StaticResource OutlineButton}"
Command="{Binding LogoutCommand}">
<avalonia:MaterialIcon Kind="Logout" />
</Button>
</Grid>
<Border ClipToBounds="True" Grid.Row="1" Margin="15 0">
<Grid ColumnDefinitions="*,*,*,*" ColumnSpacing="15">
<Border Classes="Card">
<Grid RowDefinitions="*,*" RowSpacing="10" VerticalAlignment="Center">
<TextBlock Text="Портфель" FontWeight="Bold" HorizontalAlignment="Center" TextWrapping="Wrap"
TextAlignment="Center" />
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Grid.Row="1" Text="{Binding DollarBalance, StringFormat='{}{0:F2}'}"
FontWeight="Bold" HorizontalAlignment="Center" TextWrapping="Wrap" />
<avalonia:MaterialIcon Kind="CurrencyUsd" Height="18" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</Border>
<Border Classes="Card" Grid.Column="1">
<Grid RowDefinitions="*,*" RowSpacing="10" VerticalAlignment="Center">
<TextBlock Text="Портфель" FontWeight="Bold" HorizontalAlignment="Center" TextWrapping="Wrap"
TextAlignment="Center" />
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="{Binding BitcoinBalance, StringFormat='{}{0:F2}'}" FontWeight="Bold"
HorizontalAlignment="Center" TextWrapping="Wrap" />
<avalonia:MaterialIcon Kind="CurrencyBtc" Height="18" VerticalAlignment="Center" />
</StackPanel>
</Grid>
</Border>
<Border Classes="Card" Grid.Column="2">
<Grid RowDefinitions="*,*" RowSpacing="10" VerticalAlignment="Center">
<TextBlock Text="Прибыль/Убытки" FontWeight="Bold" HorizontalAlignment="Center"
TextWrapping="Wrap" TextAlignment="Center" />
<TextBlock Grid.Row="1" Text="{Binding ProfitLoss, StringFormat='{}{0:F2} $'}"
FontWeight="Bold" HorizontalAlignment="Center" TextWrapping="Wrap" />
</Grid>
</Border>
<Border Classes="Card" Grid.Column="3">
<Grid RowDefinitions="Auto,*" RowSpacing="10" VerticalAlignment="Center">
<TextBlock Text="Валюты" FontWeight="Bold" HorizontalAlignment="Center" TextWrapping="Wrap"
TextAlignment="Center" />
<TextBlock Grid.Row="1" Text="{Binding CoinNames}" FontWeight="Bold"
HorizontalAlignment="Center" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" />
</Grid>
</Border>
</Grid>
</Border>
<Border ClipToBounds="True" Grid.Row="2" Margin="20 0">
<Grid ColumnDefinitions="*, Auto, Auto, Auto, Auto, Auto" ColumnSpacing="10">
<TextBox PlaceholderText="Поиск" />
<DropDownButton Grid.Column="1" Foreground="{DynamicResource PrimaryColor}" CornerRadius="10"
Padding="10">
<DropDownButton.Flyout>
<MenuFlyout>
<ComboBox HorizontalAlignment="Stretch" Width="200" />
</MenuFlyout>
</DropDownButton.Flyout>
<avalonia:MaterialIcon Kind="Filter" />
</DropDownButton>
<Button Grid.Column="2" Classes="WithIcon" VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryColor}"
Command="{Binding LoadCryptocurrenciesCommand}">
<avalonia:MaterialIcon Kind="Refresh" />
</Button>
<Button Grid.Column="3" Classes="WithIcon" Theme="{DynamicResource SolidButton}"
Background="{DynamicResource PrimaryColor}"
BorderBrush="{StaticResource PrimaryColor}" CornerRadius="10" Padding="10">
<avalonia:MaterialIcon Kind="Plus" />
</Button>
<Button Grid.Column="4" Classes="WithIcon"
Foreground="{StaticResource PrimaryColor}" CornerRadius="10" Padding="10">
<avalonia:MaterialIcon Kind="Edit" />
</Button>
<Button Grid.Column="5" Classes="WithIcon Danger" Theme="{DynamicResource SolidButton}"
Background="{DynamicResource PrimaryColor}"
BorderBrush="{StaticResource PrimaryColor}" CornerRadius="10" Padding="10">
<avalonia:MaterialIcon Kind="Trash" />
</Button>
</Grid>
</Border>
<Grid Grid.Row="3" Margin="20 0 20 20">
<ProgressBar IsIndeterminate="True" VerticalAlignment="Top" Margin="0 -5 0 0" Height="1"
Foreground="{DynamicResource PrimaryColor}"
IsVisible="{Binding IsCryptoLoading}" />
<TableView x:Name="CryptoTable"
CornerRadius="10"
ItemsSource="{Binding Cryptocurrencies}"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
SelectionMode="Single"
SelectionChanged="CryptoTable_OnSelectionChanged">
<TableView.Columns>
<TableViewColumn Binding="{Binding Image}" CanUserResize="True" Width="50">
<TableViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Image}" Margin="0" Height="17" />
</DataTemplate>
</TableViewColumn.CellTemplate>
</TableViewColumn>
<TableViewColumn Header="Символ"
Binding="{Binding Symbol}" Width="100" />
<TableViewColumn Header="Название"
Binding="{Binding Name}" Width="125" />
<TableViewColumn Header="Курс"
Binding="{Binding CurrentPriceUsd, StringFormat='{}{0:N2} $'}"
Width="135" />
<TableViewColumn Header="Изменения за 24ч"
Binding="{Binding PriceChange24H, StringFormat='{}{0:N2} $'}">
<TableViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Spacing="5" Orientation="Horizontal">
<TextBlock
Text="{Binding PriceChangePercentage24H, StringFormat='{}{0:N2} %'}"
Classes.Success="{Binding IsPositive}"
Classes.Danger="{Binding IsNegative}" />
<TextBlock
Text="{Binding PriceChange24H, StringFormat='{}({0:N2} $)'}"
Classes.Success="{Binding IsPositive}"
Classes.Danger="{Binding IsNegative}" />
</StackPanel>
</DataTemplate>
</TableViewColumn.CellTemplate>
</TableViewColumn>
<TableViewColumn Header="График за 24ч"
Width="160">
<TableViewColumn.CellTemplate>
<DataTemplate>
<Border Name="ChartBorder">
<lvc:CartesianChart
Width="140"
Height="17"
LegendPosition="Hidden"
TooltipPosition="Hidden"
ZoomMode="None"
Padding="0"
AnimationsSpeed="0:00:00">
<lvc:CartesianChart.Series>
<lvc:XamlLineSeries
Values="{Binding PriceHistory24H}"
GeometrySize="0"
Fill="{x:Null}"
GeometryFill="{x:Null}"
GeometryStroke="{x:Null}"
LineSmoothness="0.5"
Classes.Success="{Binding IsPositive}"
Classes.Danger="{Binding IsNegative}"/>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.XAxes>
<lvc:XamlAxis IsVisible="False" />
</lvc:CartesianChart.XAxes>
<lvc:CartesianChart.YAxes>
<lvc:XamlAxis IsVisible="False" />
</lvc:CartesianChart.YAxes>
</lvc:CartesianChart>
</Border>
</DataTemplate>
</TableViewColumn.CellTemplate>
</TableViewColumn>
<TableViewColumn Header="Рыночная капитализация"
Binding="{Binding MarketCapUsd, StringFormat='{}{0:N2} $'}" />
<TableViewColumn Header="Объём торгов" Binding="{Binding Volume24HUsd, StringFormat='{}{0:N2} $'}" />
</TableView.Columns>
</TableView>
</Grid>
</Grid>
</ContentPage>
@@ -0,0 +1,55 @@
using Avalonia.Controls;
using CryptoPortfolio_Tracker.Models;
using CryptoPortfolio_Tracker.ViewModels;
using Ursa.Controls;
namespace CryptoPortfolio_Tracker.Views;
public partial class HomeView : ContentPage
{
public HomeView()
{
InitializeComponent();
}
private void CryptoTable_OnSelectionChanged(
object? sender,
SelectionChangedEventArgs e)
{
if (sender is not TableView tableView)
{
return;
}
if (tableView.SelectedItem is not Cryptocurrency cryptocurrency)
{
return;
}
CryptoChartViewModel viewModel =
new(cryptocurrency);
tableView.SelectedIndex = -1;
_ = OverlayDialog.ShowCustomAsync<
CryptoChartView,
CryptoChartViewModel,
object>(
viewModel,
null,
new OverlayDialogOptions
{
Title = cryptocurrency.Name,
CanDragMove = true,
CanResize = true,
CanLightDismiss = true,
IsCloseButtonVisible = true,
HorizontalScrollBarVisibility =
Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled,
VerticalScrollBarVisibility =
Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled,
Buttons = DialogButton.None,
StyleClass = "CryptoChartDialog"
});
}
}
@@ -0,0 +1,36 @@
<ContentPage xmlns="https://github.com/avaloniaui"
xmlns:avalonia="using:Material.Icons.Avalonia"
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:u="https://irihi.tech/ursa"
xmlns:viewmodels="using:CryptoPortfolio_Tracker.ViewModels"
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="720"
x:Class="CryptoPortfolio_Tracker.Views.LoginView"
x:DataType="viewmodels:LoginViewModel"
Header="Вход"
NavigationPage.HasNavigationBar="False">
<Design.DataContext>
<viewmodels:LoginViewModel />
</Design.DataContext>
<Grid ColumnDefinitions="1.4*, *">
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{DynamicResource PrimaryColor}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="0">
<avalonia:MaterialIcon Kind="Bitcoin" Width="400" HorizontalAlignment="Center" />
<TextBlock Text="CryptoPortfolio Tracker" FontSize="44" FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" TextAlignment="Center" />
</StackPanel>
</Border>
<u:Form Grid.Column="1" Margin="40" VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<TextBlock FontSize="34" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center"
TextAlignment="Center" Text="Добро пожаловать" />
<TextBox Text="{Binding Login}" u:FormItem.Label="Логин" />
<TextBox Text="{Binding Password}" u:FormItem.Label="Пароль" Classes="revealPasswordButton"/>
<Button Content="Войти" HorizontalAlignment="Stretch" Theme="{StaticResource SolidButton}"
Classes="ForSolidTheme" Command="{Binding LoginCommand}" />
</u:Form>
</Grid>
</ContentPage>
@@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace CryptoPortfolio_Tracker.Views
{
public partial class LoginView : ContentPage
{
public LoginView()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,17 @@
<NavigationPage xmlns="https://github.com/avaloniaui"
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:vm="using:CryptoPortfolio_Tracker.ViewModels"
xmlns:behaviors="clr-namespace:CryptoPortfolio_Tracker.Behaviors"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CryptoPortfolio_Tracker.Views.MainView"
x:DataType="vm:MainViewModel"
behaviors:WindowDragBehavior.EnableWindowDrag="True">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainViewModel />
</Design.DataContext>
</NavigationPage>
@@ -0,0 +1,45 @@
using Avalonia;
using Avalonia.Controls;
using CryptoPortfolio_Tracker.ViewModels;
using System;
using Avalonia.Controls.Notifications;
using Ursa.Controls;
namespace CryptoPortfolio_Tracker.Views;
public partial class MainView : NavigationPage
{
public static MainView Current { get; set; } = null!;
private WindowToastManager _toastManager = null!;
public MainView()
{
InitializeComponent();
Current = this;
}
protected override async void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_toastManager = new WindowToastManager(TopLevel.GetTopLevel(this));
if (CurrentPage == null)
await PushAsync(new LoginView()
{
DataContext = new LoginViewModel()
});
}
public void ShowToast(string message, NotificationType type)
{
_toastManager.Show(
content: message,
showIcon: true,
showClose: true,
type: type,
expiration: TimeSpan.FromSeconds(3),
classes: ["Light"]);
}
}
@@ -0,0 +1,18 @@
<u:UrsaWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:CryptoPortfolio_Tracker.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="using:CryptoPortfolio_Tracker.Views"
xmlns:u="https://irihi.tech/ursa"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CryptoPortfolio_Tracker.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="CryptoPortfolio Tracker"
MinWidth="1280"
MinHeight="720"
WindowStartupLocation="CenterScreen"
IsTitleBarVisible="False"
IsFullScreenButtonVisible="False">
<views:MainView />
</u:UrsaWindow>
@@ -0,0 +1,12 @@
using Avalonia.Controls;
using Ursa.Controls;
namespace CryptoPortfolio_Tracker.Views;
public partial class MainWindow : UrsaWindow
{
public MainWindow()
{
InitializeComponent();
}
}