diff --git a/CLAUDE.md b/CLAUDE.md
index 310153f..90d99b2 100644
--- a/CLAUDE.md
+++ b/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)
diff --git a/src/HihaArvio/App.xaml b/src/HihaArvio/App.xaml
index 22179c7..1404f7f 100644
--- a/src/HihaArvio/App.xaml
+++ b/src/HihaArvio/App.xaml
@@ -2,6 +2,7 @@
@@ -9,6 +10,13 @@
+
+
+
+
+
+
+
diff --git a/src/HihaArvio/AppShell.xaml b/src/HihaArvio/AppShell.xaml
index 574c217..a8de905 100644
--- a/src/HihaArvio/AppShell.xaml
+++ b/src/HihaArvio/AppShell.xaml
@@ -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">
-
+
+
+
+
+
+
+
diff --git a/src/HihaArvio/Converters/BoolToColorConverter.cs b/src/HihaArvio/Converters/BoolToColorConverter.cs
new file mode 100644
index 0000000..6478110
--- /dev/null
+++ b/src/HihaArvio/Converters/BoolToColorConverter.cs
@@ -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();
+ }
+}
diff --git a/src/HihaArvio/Converters/BoolToShakingConverter.cs b/src/HihaArvio/Converters/BoolToShakingConverter.cs
new file mode 100644
index 0000000..a5eea65
--- /dev/null
+++ b/src/HihaArvio/Converters/BoolToShakingConverter.cs
@@ -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();
+ }
+}
diff --git a/src/HihaArvio/Converters/InvertedBoolConverter.cs b/src/HihaArvio/Converters/InvertedBoolConverter.cs
new file mode 100644
index 0000000..1eada0b
--- /dev/null
+++ b/src/HihaArvio/Converters/InvertedBoolConverter.cs
@@ -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;
+ }
+}
diff --git a/src/HihaArvio/Converters/IsNullConverter.cs b/src/HihaArvio/Converters/IsNullConverter.cs
new file mode 100644
index 0000000..0d69e39
--- /dev/null
+++ b/src/HihaArvio/Converters/IsNullConverter.cs
@@ -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();
+ }
+}
diff --git a/src/HihaArvio/HistoryPage.xaml b/src/HihaArvio/HistoryPage.xaml
new file mode 100644
index 0000000..ea2407f
--- /dev/null
+++ b/src/HihaArvio/HistoryPage.xaml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/HihaArvio/HistoryPage.xaml.cs b/src/HihaArvio/HistoryPage.xaml.cs
new file mode 100644
index 0000000..53a50cf
--- /dev/null
+++ b/src/HihaArvio/HistoryPage.xaml.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/HihaArvio/MainPage.xaml b/src/HihaArvio/MainPage.xaml
index 06ce07c..2ea19ec 100644
--- a/src/HihaArvio/MainPage.xaml
+++ b/src/HihaArvio/MainPage.xaml
@@ -1,35 +1,107 @@
-
+
+ xmlns:viewmodels="clr-namespace:HihaArvio.ViewModels"
+ xmlns:models="clr-namespace:HihaArvio.Models"
+ x:Class="HihaArvio.MainPage"
+ x:DataType="viewmodels:MainViewModel"
+ Title="Hiha-Arvio">
-
-
+
-
+
+
+
+
+
+
+
+ Work
+ Generic
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/src/HihaArvio/MainPage.xaml.cs b/src/HihaArvio/MainPage.xaml.cs
index 518469e..786920c 100644
--- a/src/HihaArvio/MainPage.xaml.cs
+++ b/src/HihaArvio/MainPage.xaml.cs
@@ -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;
+ }
}
-
diff --git a/src/HihaArvio/MauiProgram.cs b/src/HihaArvio/MauiProgram.cs
index a836421..d66fbdb 100644
--- a/src/HihaArvio/MauiProgram.cs
+++ b/src/HihaArvio/MauiProgram.cs
@@ -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();
+ builder.Services.AddSingleton();
+
+ // Storage service with app data path
+ var dbPath = Path.Combine(FileSystem.AppDataDirectory, "hihaarvio.db");
+ builder.Services.AddSingleton(sp => new StorageService(dbPath));
+
+ // Register ViewModels
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
+ // Register Pages
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
return builder.Build();
}
}
diff --git a/src/HihaArvio/SettingsPage.xaml b/src/HihaArvio/SettingsPage.xaml
new file mode 100644
index 0000000..133bb21
--- /dev/null
+++ b/src/HihaArvio/SettingsPage.xaml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Work
+ Generic
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/HihaArvio/SettingsPage.xaml.cs b/src/HihaArvio/SettingsPage.xaml.cs
new file mode 100644
index 0000000..d1430ca
--- /dev/null
+++ b/src/HihaArvio/SettingsPage.xaml.cs
@@ -0,0 +1,12 @@
+using HihaArvio.ViewModels;
+
+namespace HihaArvio;
+
+public partial class SettingsPage : ContentPage
+{
+ public SettingsPage(SettingsViewModel viewModel)
+ {
+ InitializeComponent();
+ BindingContext = viewModel;
+ }
+}