diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index dbd80eb..9e3b2f9 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -1,34 +1,42 @@ name: Build Linux on: + workflow_dispatch: push: - branches: [main] + tags: + - 'v*' jobs: - build-linux: + build: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '9.0.x' + dotnet-version: '8.0.x' + + - name: Restore dependencies + run: dotnet restore Clario/Clario.csproj + + - name: Build + run: dotnet build Clario/Clario.csproj --configuration Release --no-restore - name: Publish - run: | - dotnet publish Clario.Desktop/Clario.Desktop.csproj \ - -r linux-x64 \ - -c Release \ - --self-contained true \ - -p:PublishSingleFile=true \ - -o ./publish/linux-x64 - - - name: Package as tar.gz - run: tar -czf Clario-linux-x64.tar.gz -C ./publish/linux-x64 . + run: dotnet publish Clario/Clario.csproj \ + --configuration Release \ + --runtime linux-x64 \ + --self-contained true \ + --output ./publish/linux \ + -p:PublishSingleFile=true \ + -p:IncludeNativeLibrariesForSelfExtract=true - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: clario-linux-x64 - path: Clario-linux-x64.tar.gz \ No newline at end of file + name: Clario-linux-x64 + path: ./publish/linux + retention-days: 7 \ No newline at end of file diff --git a/Clario/Assets/Icons/chart-bar.svg b/Clario/Assets/Icons/chart-bar.svg new file mode 100644 index 0000000..f085b03 --- /dev/null +++ b/Clario/Assets/Icons/chart-bar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Clario/Assets/Icons/chart-column.svg b/Clario/Assets/Icons/chart-column.svg new file mode 100644 index 0000000..e750861 --- /dev/null +++ b/Clario/Assets/Icons/chart-column.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Clario/Assets/Icons/chevron-down.svg b/Clario/Assets/Icons/chevron-down.svg new file mode 100644 index 0000000..e576b4f --- /dev/null +++ b/Clario/Assets/Icons/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Clario/Assets/Icons/receipt.svg b/Clario/Assets/Icons/receipt.svg new file mode 100644 index 0000000..65ed153 --- /dev/null +++ b/Clario/Assets/Icons/receipt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Clario/Assets/Logo textmark.png b/Clario/Assets/Logo textmark.png deleted file mode 100644 index e69de29..0000000 diff --git a/Clario/Assets/logo-no-bg.ico b/Clario/Assets/logo-no-bg.ico new file mode 100644 index 0000000..76cb10f Binary files /dev/null and b/Clario/Assets/logo-no-bg.ico differ diff --git a/Clario/Assets/logo-textmark.svg b/Clario/Assets/logo-textmark.svg index 57837f2..5d1c95b 100644 --- a/Clario/Assets/logo-textmark.svg +++ b/Clario/Assets/logo-textmark.svg @@ -1,28 +1,47 @@ - - - - - - + + + + + + + + + - + - + - + - + - + + + + + + + + + + + + + + + + + diff --git a/Clario/Behaviors/NumericInputBehavior.cs b/Clario/Behaviors/NumericInputBehavior.cs new file mode 100644 index 0000000..e107618 --- /dev/null +++ b/Clario/Behaviors/NumericInputBehavior.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Xaml.Interactivity; + +namespace Clario.Behaviors; + +public class NumericInputBehavior : Behavior +{ + protected override void OnAttached() + { + base.OnAttached(); + AssociatedObject!.AddHandler(TextBox.TextInputEvent, OnTextInput, RoutingStrategies.Tunnel); + AssociatedObject.TextChanged += OnTextChanged; + } + + protected override void OnDetaching() + { + base.OnDetaching(); + AssociatedObject!.RemoveHandler(TextBox.TextInputEvent, OnTextInput); + AssociatedObject.TextChanged -= OnTextChanged; + } + + private void OnTextInput(object? sender, TextInputEventArgs e) + { + if (e.Text is null) return; + foreach (var c in e.Text) + { + if (!char.IsDigit(c) && c != '.') + { + e.Handled = true; + return; + } + } + + var current = (sender as TextBox)?.Text ?? ""; + if (e.Text.Contains('.') && current.Contains('.')) + { + e.Handled = true; + } + } + + private void OnTextChanged(object? sender, TextChangedEventArgs e) + { + if (sender is not TextBox tb) return; + var text = tb.Text ?? ""; + + var clean = new string(text.Where(c => char.IsDigit(c) || c == '.').ToArray()); + + var dotIndex = clean.IndexOf('.'); + if (dotIndex >= 0) + { + clean = clean[..(dotIndex + 1)] + clean[(dotIndex + 1)..].Replace(".", ""); + } + + if (clean != text) + { + var caret = tb.CaretIndex; + tb.Text = clean; + tb.CaretIndex = Math.Min(caret, clean.Length); + } + } +} \ No newline at end of file diff --git a/Clario/Clario.csproj b/Clario/Clario.csproj index de6fc5e..ee64c82 100644 --- a/Clario/Clario.csproj +++ b/Clario/Clario.csproj @@ -27,6 +27,8 @@ + + diff --git a/Clario/Clario.parcel b/Clario/Clario.parcel deleted file mode 100644 index f024ba8..0000000 --- a/Clario/Clario.parcel +++ /dev/null @@ -1,30 +0,0 @@ -{ - "GeneralSettings": { - "NetProjectPath": "Clario.csproj", - "ApplicationName": "Clario", - "Version": "1.0.0", - "PackageName": { - "$type": "msbuild", - "property": "AssemblyName" - }, - "AssemblyName": { - "$type": "msbuild", - "property": "AssemblyName" - } - }, - "LinuxSettings": { - "CreateBinSymlink": "True" - }, - "Win32Settings": { - "IncludeUninstaller": "True" - }, - "MacOsSettings": { - "CreateBundle": true, - "BundleIdentifier": "com.CompanyName.Clario", - "SigningCredentialsType": "AdHoc" - }, - "PublishSettings": { - "PublishSingleFile": "True", - "ExtraBuildProperties": {} - } -} \ No newline at end of file diff --git a/Clario/Converters/AccountMaskToStringConverter.cs b/Clario/Converters/AccountMaskToStringConverter.cs index 1975fa7..69911d2 100644 --- a/Clario/Converters/AccountMaskToStringConverter.cs +++ b/Clario/Converters/AccountMaskToStringConverter.cs @@ -8,7 +8,7 @@ public class AccountMaskToStringConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (value is not string mask) return string.Empty; + if (value is not string mask || string.IsNullOrWhiteSpace(mask)) return string.Empty; return $"•••• {mask}"; } diff --git a/Clario/CustomControls/DateRangePicker.axaml b/Clario/CustomControls/DateRangePicker.axaml index 9c715a2..160c99d 100644 --- a/Clario/CustomControls/DateRangePicker.axaml +++ b/Clario/CustomControls/DateRangePicker.axaml @@ -9,7 +9,7 @@ - + @@ -37,7 +37,8 @@ Text="{TemplateBinding DisplayText}" FontSize="{TemplateBinding FontSize}" Foreground="{TemplateBinding Foreground}" - VerticalAlignment="Center" /> + VerticalAlignment="Center" + TextTrimming="CharacterEllipsis"/> ("PART_Button"); _popup = e.NameScope.Find("PART_Popup"); @@ -81,7 +87,7 @@ public class DateRangePicker : TemplatedControl _calendar.SelectedDatesChanged += OnCalendarDatesChanged; - // _calendar.PointerPressed + _calendar.AddHandler(PointerReleasedEvent, OnCalendarPointerReleased, RoutingStrategies.Tunnel); SyncToCalendar(); @@ -90,8 +96,45 @@ public class DateRangePicker : TemplatedControl UpdateDisplayText(); } + private void OnCalendarPointerReleased(object? sender, PointerReleasedEventArgs e) + { + if (_calendar!.SelectionMode != CalendarSelectionMode.SingleDate) return; - private void OnButtonClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + if (_isSyncing) return; + + if (_popup is null || !_popup.IsOpen) return; + + var newDates = _calendar!.SelectedDates.OrderBy(d => d).ToList(); + + _isSyncing = true; + try + { + SelectedDates = newDates; + + + SelectedDate = newDates.Count > 0 ? newDates[0] : null; + + UpdateDisplayText(); + + + bool shouldClose = SelectionMode switch + { + CalendarSelectionMode.SingleDate => newDates.Count >= 1, + CalendarSelectionMode.SingleRange => newDates.Count >= 2, + _ => false + }; + + if (shouldClose) + _popup.IsOpen = false; + } + finally + { + _isSyncing = false; + } + } + + + private void OnButtonClick(object? sender, RoutedEventArgs e) { if (_popup is null) return; @@ -102,10 +145,12 @@ public class DateRangePicker : TemplatedControl private void OnCalendarDatesChanged(object? sender, SelectionChangedEventArgs e) { + if (_calendar!.SelectionMode == CalendarSelectionMode.SingleDate) return; + if (_isSyncing) return; - + if (_popup is null || !_popup.IsOpen) return; - Console.WriteLine("test"); + var newDates = _calendar!.SelectedDates.OrderBy(d => d).ToList(); _isSyncing = true; diff --git a/Clario/MobileViews/MainViewMobile.axaml b/Clario/MobileViews/MainViewMobile.axaml index f27b386..1221ea6 100644 --- a/Clario/MobileViews/MainViewMobile.axaml +++ b/Clario/MobileViews/MainViewMobile.axaml @@ -3,13 +3,22 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:Clario.ViewModels" + xmlns:views="clr-namespace:Clario.Views" + xmlns:mobileViews="clr-namespace:Clario.MobileViews" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Clario.MobileViews.MainViewMobile" - x:CompileBindings="False" - Classes="mobile"> + x:DataType="vm:MainViewModel" + Classes="mobile" + x:Name="MainControl"> + + + - + + @@ -65,7 +74,7 @@ VerticalAlignment="Center" Width="52" Height="52" CornerRadius="26" - Padding="0"> + Padding="0" Command="{Binding OpenAddTransactionCommand}"> diff --git a/Clario/MobileViews/TransactionFormViewMobile.axaml b/Clario/MobileViews/TransactionFormViewMobile.axaml new file mode 100644 index 0000000..d4e59f8 --- /dev/null +++ b/Clario/MobileViews/TransactionFormViewMobile.axaml @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Clario/MobileViews/TransactionFormViewMobile.axaml.cs b/Clario/MobileViews/TransactionFormViewMobile.axaml.cs new file mode 100644 index 0000000..6322d35 --- /dev/null +++ b/Clario/MobileViews/TransactionFormViewMobile.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Clario.MobileViews; + +public partial class TransactionFormViewMobile : UserControl +{ + public TransactionFormViewMobile() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Clario/Theme/AppTheme.axaml b/Clario/Theme/AppTheme.axaml index f72f39e..68d6d9b 100644 --- a/Clario/Theme/AppTheme.axaml +++ b/Clario/Theme/AppTheme.axaml @@ -306,21 +306,21 @@ - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -168,49 +184,63 @@ Foreground="{DynamicResource TextPrimary}" /> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -231,50 +261,64 @@ FontSize="{StaticResource FontSizeBody}" Cursor="Hand" Content="View all →" VerticalAlignment="Center" Command="{Binding ViewAllTransactionsCommand}" /> - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/Clario/Views/MainView.axaml b/Clario/Views/MainView.axaml index 7367057..b3168f1 100644 --- a/Clario/Views/MainView.axaml +++ b/Clario/Views/MainView.axaml @@ -91,20 +91,12 @@ + BorderThickness="0,0,1,0" Padding="16,28,16,24" IsEnabled="{Binding !IsTransactionFormVisible}"> - - - - - - - - - + + + + diff --git a/Clario/Views/MainWindow.axaml b/Clario/Views/MainWindow.axaml index 20bd1e5..6f59576 100644 --- a/Clario/Views/MainWindow.axaml +++ b/Clario/Views/MainWindow.axaml @@ -7,6 +7,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" MinWidth="1000" MinHeight="600" x:Class="Clario.Views.MainWindow" + Icon="../Assets/logo-no-bg.ico" Title="Clario" x:CompileBindings="False"> diff --git a/Clario/Views/TransactionFormView.axaml b/Clario/Views/TransactionFormView.axaml index 2b1cbf0..a9984c2 100644 --- a/Clario/Views/TransactionFormView.axaml +++ b/Clario/Views/TransactionFormView.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:Clario.ViewModels" xmlns:cc="clr-namespace:Clario.CustomControls" + xmlns:behaviors="clr-namespace:Clario.Behaviors" mc:Ignorable="d" x:Class="Clario.Views.TransactionFormView" x:DataType="vm:TransactionFormViewModel"> @@ -87,7 +88,7 @@ CommandParameter="expense"> + Width="13" Height="13" /> + Width="13" Height="13" /> @@ -141,7 +142,11 @@ Foreground="{DynamicResource TextPrimary}" Height="54" Padding="0" - VerticalContentAlignment="Center" /> + VerticalContentAlignment="Center"> + + + + - + + + + + Padding="12,10" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 4166dc0..9c1c6cc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,5 +22,7 @@ + + \ No newline at end of file