mirror of
https://github.com/ivuorinen/hiha-arvio.git
synced 2026-01-26 03:14:00 +00:00
feat: Complete Milestone 4 - Views/UI Layer
Implemented complete MVVM UI layer with data binding: **Dependency Injection (MauiProgram.cs):** - Registered all services as Singleton (shared state) - Registered all ViewModels and Pages as Transient - Configured SQLite database path **Pages:** - MainPage: Mode selector, estimate display, shake status - HistoryPage: CollectionView with auto-load on appearing - SettingsPage: Mode picker, MaxHistorySize stepper **Value Converters:** - IsNullConverter / IsNotNullConverter (visibility) - BoolToShakingConverter (status text) - BoolToColorConverter (status colors) - InvertedBoolConverter (boolean inversion) **Navigation:** - AppShell with TabBar (Estimate, History, Settings) - ContentTemplate for lazy loading **Technical details:** - All XAML uses x:DataType for compile-time checking - All code-behind uses constructor injection - Zero warnings, zero errors - 165 tests passing (no UI tests yet) Build verified across all platforms (net8.0, iOS, macOS Catalyst) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
65
CLAUDE.md
65
CLAUDE.md
@@ -228,19 +228,62 @@ HihaArvio.sln
|
||||
- **Coverage:** 51.28% line (low due to MAUI template), 87.5% branch
|
||||
- **Build:** 0 warnings, 0 errors across all platforms
|
||||
|
||||
**Milestone 3: ViewModels Layer (✅ Complete)**
|
||||
- MainViewModel (18 tests)
|
||||
- Coordinates shake detection and estimate generation
|
||||
- Subscribes to ShakeDetectionService.ShakeDataChanged event
|
||||
- Detects shake stop (transition from shaking → not shaking)
|
||||
- Mode selection and current estimate display
|
||||
- Implements IDisposable for cleanup
|
||||
- HistoryViewModel (15 tests)
|
||||
- Manages estimate history display with ObservableCollection
|
||||
- LoadHistoryCommand for async history retrieval
|
||||
- ClearHistoryCommand for pruning
|
||||
- IsEmpty property for empty state handling
|
||||
- SettingsViewModel (13 tests)
|
||||
- Settings management (SelectedMode, MaxHistorySize)
|
||||
- SaveSettingsCommand for persistence
|
||||
- Auto-loads settings on initialization
|
||||
- All ViewModels use CommunityToolkit.Mvvm source generators
|
||||
- [ObservableProperty] for property change notifications
|
||||
- [RelayCommand] for commands
|
||||
- **Total: 165 tests, all passing (119 services + 46 ViewModels)**
|
||||
- **Build:** 0 warnings, 0 errors across all platforms
|
||||
|
||||
**Milestone 4: Views/UI Layer (✅ Complete)**
|
||||
- Dependency Injection configuration in MauiProgram.cs
|
||||
- All services registered as Singleton
|
||||
- All ViewModels and Pages registered as Transient
|
||||
- SQLite database path configured with FileSystem.AppDataDirectory
|
||||
- MainPage.xaml with data binding
|
||||
- Mode selector (Work/Generic)
|
||||
- Current estimate display with conditional visibility
|
||||
- Shake status indicator
|
||||
- Uses x:DataType for compile-time binding verification
|
||||
- HistoryPage.xaml with data binding
|
||||
- CollectionView with ItemTemplate
|
||||
- Empty state when no history
|
||||
- Refresh and Clear All buttons
|
||||
- Auto-loads history on OnAppearing()
|
||||
- SettingsPage.xaml with data binding
|
||||
- Picker for mode selection (Work/Generic only - Humorous is easter egg)
|
||||
- Stepper for MaxHistorySize (5-100, increment by 5)
|
||||
- Save Settings button
|
||||
- About section with easter egg hint
|
||||
- Value Converters for UI logic
|
||||
- IsNullConverter / IsNotNullConverter (conditional visibility)
|
||||
- BoolToShakingConverter (status text)
|
||||
- BoolToColorConverter (status colors)
|
||||
- InvertedBoolConverter (boolean inversion)
|
||||
- All registered in App.xaml resources
|
||||
- AppShell.xaml navigation
|
||||
- TabBar with 3 tabs: Estimate, History, Settings
|
||||
- Each tab uses ContentTemplate for lazy loading
|
||||
- **Total: 165 tests still passing (no UI tests yet)**
|
||||
- **Build:** 0 warnings, 0 errors across all platforms (net8.0, iOS, macOS Catalyst)
|
||||
|
||||
### Remaining Work
|
||||
|
||||
**Milestone 3: ViewModels Layer** (Not Started)
|
||||
- MainViewModel (estimate display, settings)
|
||||
- HistoryViewModel (estimate history)
|
||||
- SettingsViewModel (mode selection, history management)
|
||||
|
||||
**Milestone 4: Views/UI Layer** (Not Started)
|
||||
- MainPage (shake screen, estimate display)
|
||||
- HistoryPage (estimate list)
|
||||
- SettingsPage (mode toggle, history clear)
|
||||
- Navigation infrastructure
|
||||
|
||||
**Milestone 5: Platform-Specific Implementations** (Not Started)
|
||||
- IAccelerometerService interface
|
||||
- iOS implementation (real accelerometer)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:HihaArvio"
|
||||
xmlns:converters="clr-namespace:HihaArvio.Converters"
|
||||
x:Class="HihaArvio.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -9,6 +10,13 @@
|
||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Value Converters -->
|
||||
<converters:IsNullConverter x:Key="IsNullConverter"/>
|
||||
<converters:IsNotNullConverter x:Key="IsNotNullConverter"/>
|
||||
<converters:BoolToShakingConverter x:Key="BoolToShakingConverter"/>
|
||||
<converters:BoolToColorConverter x:Key="BoolToColorConverter"/>
|
||||
<converters:InvertedBoolConverter x:Key="InvertedBoolConverter"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -4,12 +4,26 @@
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:HihaArvio"
|
||||
Shell.FlyoutBehavior="Disabled"
|
||||
Title="HihaArvio">
|
||||
Title="Hiha-Arvio">
|
||||
|
||||
<ShellContent
|
||||
Title="Home"
|
||||
ContentTemplate="{DataTemplate local:MainPage}"
|
||||
Route="MainPage" />
|
||||
<TabBar>
|
||||
<ShellContent
|
||||
Title="Estimate"
|
||||
Icon="home.png"
|
||||
ContentTemplate="{DataTemplate local:MainPage}"
|
||||
Route="MainPage" />
|
||||
|
||||
<ShellContent
|
||||
Title="History"
|
||||
Icon="history.png"
|
||||
ContentTemplate="{DataTemplate local:HistoryPage}"
|
||||
Route="HistoryPage" />
|
||||
|
||||
<ShellContent
|
||||
Title="Settings"
|
||||
Icon="settings.png"
|
||||
ContentTemplate="{DataTemplate local:SettingsPage}"
|
||||
Route="SettingsPage" />
|
||||
</TabBar>
|
||||
|
||||
</Shell>
|
||||
|
||||
20
src/HihaArvio/Converters/BoolToColorConverter.cs
Normal file
20
src/HihaArvio/Converters/BoolToColorConverter.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace HihaArvio.Converters;
|
||||
|
||||
public class BoolToColorConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isShaking)
|
||||
{
|
||||
return isShaking ? Colors.Green : Colors.Gray;
|
||||
}
|
||||
return Colors.Gray;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
20
src/HihaArvio/Converters/BoolToShakingConverter.cs
Normal file
20
src/HihaArvio/Converters/BoolToShakingConverter.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace HihaArvio.Converters;
|
||||
|
||||
public class BoolToShakingConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isShaking)
|
||||
{
|
||||
return isShaking ? "Shaking" : "Idle";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
16
src/HihaArvio/Converters/InvertedBoolConverter.cs
Normal file
16
src/HihaArvio/Converters/InvertedBoolConverter.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace HihaArvio.Converters;
|
||||
|
||||
public class InvertedBoolConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is bool boolValue && !boolValue;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is bool boolValue && !boolValue;
|
||||
}
|
||||
}
|
||||
29
src/HihaArvio/Converters/IsNullConverter.cs
Normal file
29
src/HihaArvio/Converters/IsNullConverter.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace HihaArvio.Converters;
|
||||
|
||||
public class IsNullConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class IsNotNullConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is not null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
96
src/HihaArvio/HistoryPage.xaml
Normal file
96
src/HihaArvio/HistoryPage.xaml
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:HihaArvio.ViewModels"
|
||||
xmlns:models="clr-namespace:HihaArvio.Models"
|
||||
x:Class="HihaArvio.HistoryPage"
|
||||
x:DataType="viewmodels:HistoryViewModel"
|
||||
Title="History">
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
|
||||
<!-- Toolbar -->
|
||||
<HorizontalStackLayout Grid.Row="0" Padding="15" Spacing="10">
|
||||
<Button Text="Refresh"
|
||||
Command="{Binding LoadHistoryCommand}"
|
||||
HorizontalOptions="FillAndExpand"/>
|
||||
<Button Text="Clear All"
|
||||
Command="{Binding ClearHistoryCommand}"
|
||||
BackgroundColor="{StaticResource Danger}"
|
||||
HorizontalOptions="FillAndExpand"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<!-- Empty State -->
|
||||
<VerticalStackLayout Grid.Row="1"
|
||||
IsVisible="{Binding IsEmpty}"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center"
|
||||
Padding="20">
|
||||
<Label Text="No history yet"
|
||||
FontSize="24"
|
||||
FontAttributes="Bold"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextColor="{StaticResource Gray600}"/>
|
||||
<Label Text="Shake your device to generate estimates"
|
||||
FontSize="16"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextColor="{StaticResource Gray500}"
|
||||
Margin="0,10,0,0"/>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- History List -->
|
||||
<CollectionView Grid.Row="1"
|
||||
ItemsSource="{Binding History}"
|
||||
IsVisible="{Binding IsEmpty, Converter={StaticResource InvertedBoolConverter}}"
|
||||
Margin="10">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:EstimateResult">
|
||||
<Frame Padding="15" Margin="5" HasShadow="True">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="*,Auto" RowSpacing="8">
|
||||
|
||||
<!-- Estimate Text -->
|
||||
<Label Grid.Row="0" Grid.Column="0"
|
||||
Text="{Binding EstimateText}"
|
||||
FontSize="24"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Primary}"/>
|
||||
|
||||
<!-- Mode Badge -->
|
||||
<Frame Grid.Row="0" Grid.Column="1"
|
||||
Padding="8,4"
|
||||
BackgroundColor="{StaticResource Secondary}"
|
||||
CornerRadius="12"
|
||||
HasShadow="False">
|
||||
<Label Text="{Binding Mode}"
|
||||
FontSize="12"
|
||||
TextColor="White"/>
|
||||
</Frame>
|
||||
|
||||
<!-- Timestamp -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
Text="{Binding Timestamp, StringFormat='{0:MMM d, yyyy h:mm tt}'}"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource Gray600}"/>
|
||||
|
||||
<!-- Shake Details -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Gray500}">
|
||||
<Label.FormattedText>
|
||||
<FormattedString>
|
||||
<Span Text="Intensity: "/>
|
||||
<Span Text="{Binding ShakeIntensity, StringFormat='{0:P0}'}" FontAttributes="Bold"/>
|
||||
<Span Text=" | Duration: "/>
|
||||
<Span Text="{Binding ShakeDuration, StringFormat='{0:0.0}s'}" FontAttributes="Bold"/>
|
||||
</FormattedString>
|
||||
</Label.FormattedText>
|
||||
</Label>
|
||||
</Grid>
|
||||
</Frame>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ContentPage>
|
||||
23
src/HihaArvio/HistoryPage.xaml.cs
Normal file
23
src/HihaArvio/HistoryPage.xaml.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using HihaArvio.ViewModels;
|
||||
|
||||
namespace HihaArvio;
|
||||
|
||||
public partial class HistoryPage : ContentPage
|
||||
{
|
||||
public HistoryPage(HistoryViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
// Load history when page appears
|
||||
if (BindingContext is HistoryViewModel viewModel)
|
||||
{
|
||||
await viewModel.LoadHistoryCommand.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,107 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="HihaArvio.MainPage">
|
||||
xmlns:viewmodels="clr-namespace:HihaArvio.ViewModels"
|
||||
xmlns:models="clr-namespace:HihaArvio.Models"
|
||||
x:Class="HihaArvio.MainPage"
|
||||
x:DataType="viewmodels:MainViewModel"
|
||||
Title="Hiha-Arvio">
|
||||
|
||||
<ScrollView>
|
||||
<VerticalStackLayout
|
||||
Padding="30,0"
|
||||
Spacing="25">
|
||||
<Image
|
||||
Source="dotnet_bot.png"
|
||||
HeightRequest="185"
|
||||
Aspect="AspectFit"
|
||||
SemanticProperties.Description="dot net bot in a race car number eight" />
|
||||
<VerticalStackLayout Padding="20" Spacing="20">
|
||||
|
||||
<Label
|
||||
Text="Hello, World!"
|
||||
Style="{StaticResource Headline}"
|
||||
SemanticProperties.HeadingLevel="Level1" />
|
||||
<!-- Mode Selector -->
|
||||
<Frame BorderColor="{StaticResource Primary}" Padding="10">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="Estimation Mode"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"/>
|
||||
<Picker SelectedItem="{Binding SelectedMode}">
|
||||
<Picker.ItemsSource>
|
||||
<x:Array Type="{x:Type models:EstimateMode}">
|
||||
<models:EstimateMode>Work</models:EstimateMode>
|
||||
<models:EstimateMode>Generic</models:EstimateMode>
|
||||
</x:Array>
|
||||
</Picker.ItemsSource>
|
||||
</Picker>
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<Label
|
||||
Text="Welcome to .NET Multi-platform App UI"
|
||||
Style="{StaticResource SubHeadline}"
|
||||
SemanticProperties.HeadingLevel="Level2"
|
||||
SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
|
||||
<!-- Shake Instruction / Current Estimate Display -->
|
||||
<Frame BorderColor="{StaticResource Primary}"
|
||||
Padding="30"
|
||||
HasShadow="True"
|
||||
MinimumHeightRequest="200">
|
||||
<VerticalStackLayout Spacing="15"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center">
|
||||
|
||||
<!-- Show instruction when no estimate -->
|
||||
<Label Text="Shake your device to generate an estimate"
|
||||
FontSize="20"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextColor="{StaticResource Gray600}"
|
||||
IsVisible="{Binding CurrentEstimate, Converter={StaticResource IsNullConverter}}"/>
|
||||
|
||||
<!-- Show estimate when available -->
|
||||
<VerticalStackLayout Spacing="10"
|
||||
IsVisible="{Binding CurrentEstimate, Converter={StaticResource IsNotNullConverter}}">
|
||||
<Label Text="Estimated Time:"
|
||||
FontSize="16"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextColor="{StaticResource Gray600}"/>
|
||||
<Label Text="{Binding CurrentEstimate.EstimateText}"
|
||||
FontSize="32"
|
||||
FontAttributes="Bold"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextColor="{StaticResource Primary}"/>
|
||||
<Label FontSize="14"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextColor="{StaticResource Gray500}">
|
||||
<Label.FormattedText>
|
||||
<FormattedString>
|
||||
<Span Text="Mode: "/>
|
||||
<Span Text="{Binding CurrentEstimate.Mode}" FontAttributes="Bold"/>
|
||||
<Span Text=" | Intensity: "/>
|
||||
<Span Text="{Binding CurrentEstimate.ShakeIntensity, StringFormat='{0:P0}'}" FontAttributes="Bold"/>
|
||||
</FormattedString>
|
||||
</Label.FormattedText>
|
||||
</Label>
|
||||
</VerticalStackLayout>
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<!-- Current Shake Data (Debug Info) -->
|
||||
<Frame BorderColor="{StaticResource Secondary}" Padding="15">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="Shake Status"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"/>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="10" RowSpacing="5">
|
||||
<Label Grid.Row="0" Grid.Column="0" Text="Status:" FontAttributes="Bold"/>
|
||||
<Label Grid.Row="0" Grid.Column="1"
|
||||
Text="{Binding CurrentShakeData.IsShaking, Converter={StaticResource BoolToShakingConverter}}"
|
||||
TextColor="{Binding CurrentShakeData.IsShaking, Converter={StaticResource BoolToColorConverter}}"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Text="Intensity:" FontAttributes="Bold"/>
|
||||
<Label Grid.Row="1" Grid.Column="1"
|
||||
Text="{Binding CurrentShakeData.Intensity, StringFormat='{0:P0}'}"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Text="Duration:" FontAttributes="Bold"/>
|
||||
<Label Grid.Row="2" Grid.Column="1"
|
||||
Text="{Binding CurrentShakeData.Duration, StringFormat='{0:0.0} seconds'}"/>
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<!-- Info Text -->
|
||||
<Label Text="Note: Shake detection requires accelerometer support. Desktop users: feature coming soon!"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Gray500}"
|
||||
HorizontalTextAlignment="Center"
|
||||
Margin="0,10,0,0"/>
|
||||
|
||||
<Button
|
||||
x:Name="CounterBtn"
|
||||
Text="Click me"
|
||||
SemanticProperties.Hint="Counts the number of times you click"
|
||||
Clicked="OnCounterClicked"
|
||||
HorizontalOptions="Fill" />
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
namespace HihaArvio;
|
||||
using HihaArvio.ViewModels;
|
||||
|
||||
namespace HihaArvio;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnCounterClicked(object sender, EventArgs e)
|
||||
{
|
||||
count++;
|
||||
|
||||
if (count == 1)
|
||||
CounterBtn.Text = $"Clicked {count} time";
|
||||
else
|
||||
CounterBtn.Text = $"Clicked {count} times";
|
||||
|
||||
SemanticScreenReader.Announce(CounterBtn.Text);
|
||||
}
|
||||
public MainPage(MainViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using HihaArvio.Services;
|
||||
using HihaArvio.Services.Interfaces;
|
||||
using HihaArvio.ViewModels;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HihaArvio;
|
||||
|
||||
@@ -19,6 +22,24 @@ public static class MauiProgram
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
// Register Services
|
||||
builder.Services.AddSingleton<IEstimateService, EstimateService>();
|
||||
builder.Services.AddSingleton<IShakeDetectionService, ShakeDetectionService>();
|
||||
|
||||
// Storage service with app data path
|
||||
var dbPath = Path.Combine(FileSystem.AppDataDirectory, "hihaarvio.db");
|
||||
builder.Services.AddSingleton<IStorageService>(sp => new StorageService(dbPath));
|
||||
|
||||
// Register ViewModels
|
||||
builder.Services.AddTransient<MainViewModel>();
|
||||
builder.Services.AddTransient<HistoryViewModel>();
|
||||
builder.Services.AddTransient<SettingsViewModel>();
|
||||
|
||||
// Register Pages
|
||||
builder.Services.AddTransient<MainPage>();
|
||||
builder.Services.AddTransient<HistoryPage>();
|
||||
builder.Services.AddTransient<SettingsPage>();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
||||
113
src/HihaArvio/SettingsPage.xaml
Normal file
113
src/HihaArvio/SettingsPage.xaml
Normal file
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:viewmodels="clr-namespace:HihaArvio.ViewModels"
|
||||
xmlns:models="clr-namespace:HihaArvio.Models"
|
||||
x:Class="HihaArvio.SettingsPage"
|
||||
x:DataType="viewmodels:SettingsViewModel"
|
||||
Title="Settings">
|
||||
|
||||
<ScrollView>
|
||||
<VerticalStackLayout Padding="20" Spacing="20">
|
||||
|
||||
<!-- Estimation Mode Section -->
|
||||
<Frame BorderColor="{StaticResource Primary}" Padding="15">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="Estimation Mode"
|
||||
FontSize="20"
|
||||
FontAttributes="Bold"/>
|
||||
<Label Text="Choose the type of estimates you want to generate"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource Gray600}"
|
||||
Margin="0,0,0,10"/>
|
||||
|
||||
<Picker SelectedItem="{Binding SelectedMode}">
|
||||
<Picker.ItemsSource>
|
||||
<x:Array Type="{x:Type models:EstimateMode}">
|
||||
<models:EstimateMode>Work</models:EstimateMode>
|
||||
<models:EstimateMode>Generic</models:EstimateMode>
|
||||
</x:Array>
|
||||
</Picker.ItemsSource>
|
||||
</Picker>
|
||||
|
||||
<Label FontSize="12" TextColor="{StaticResource Gray500}" Margin="0,5,0,0">
|
||||
<Label.FormattedText>
|
||||
<FormattedString>
|
||||
<Span Text="Work" FontAttributes="Bold"/>
|
||||
<Span Text=": Project and task estimates (hours, days, weeks)"/><Span Text=" "/>
|
||||
<Span Text="Generic" FontAttributes="Bold"/>
|
||||
<Span Text=": General time durations (minutes, hours)"/>
|
||||
</FormattedString>
|
||||
</Label.FormattedText>
|
||||
</Label>
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<!-- History Settings Section -->
|
||||
<Frame BorderColor="{StaticResource Primary}" Padding="15">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="History Settings"
|
||||
FontSize="20"
|
||||
FontAttributes="Bold"/>
|
||||
<Label Text="Maximum number of estimates to keep in history"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource Gray600}"
|
||||
Margin="0,0,0,10"/>
|
||||
|
||||
<HorizontalStackLayout Spacing="15">
|
||||
<Label Text="Max History Size:"
|
||||
VerticalOptions="Center"
|
||||
FontSize="16"/>
|
||||
<Stepper Value="{Binding MaxHistorySize}"
|
||||
Minimum="5"
|
||||
Maximum="100"
|
||||
Increment="5"
|
||||
VerticalOptions="Center"/>
|
||||
<Label Text="{Binding MaxHistorySize}"
|
||||
VerticalOptions="Center"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource Primary}"/>
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<Label Text="Older estimates will be automatically removed when this limit is reached"
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Gray500}"
|
||||
Margin="0,10,0,0"/>
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<!-- Save Button -->
|
||||
<Button Text="Save Settings"
|
||||
Command="{Binding SaveSettingsCommand}"
|
||||
FontSize="18"
|
||||
Padding="15"
|
||||
Margin="0,10,0,0"/>
|
||||
|
||||
<!-- Info Section -->
|
||||
<Frame BorderColor="{StaticResource Secondary}" Padding="15" Margin="0,20,0,0">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="About Hiha-Arvio"
|
||||
FontSize="18"
|
||||
FontAttributes="Bold"/>
|
||||
<Label FontSize="14" TextColor="{StaticResource Gray600}">
|
||||
<Label.FormattedText>
|
||||
<FormattedString>
|
||||
<Span Text="Hiha-Arvio ("/>
|
||||
<Span Text="Sleeve Estimate" FontAttributes="Italic"/>
|
||||
<Span Text=") generates semi-random time estimates based on shake intensity. Perfect for those moments when you need to pull an estimate from your sleeve!"/>
|
||||
</FormattedString>
|
||||
</Label.FormattedText>
|
||||
</Label>
|
||||
<Label Text="Easter Egg: Try shaking for more than 15 seconds..."
|
||||
FontSize="12"
|
||||
TextColor="{StaticResource Gray500}"
|
||||
FontAttributes="Italic"
|
||||
Margin="0,10,0,0"/>
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
</ContentPage>
|
||||
12
src/HihaArvio/SettingsPage.xaml.cs
Normal file
12
src/HihaArvio/SettingsPage.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using HihaArvio.ViewModels;
|
||||
|
||||
namespace HihaArvio;
|
||||
|
||||
public partial class SettingsPage : ContentPage
|
||||
{
|
||||
public SettingsPage(SettingsViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user