mirror of
https://github.com/ivuorinen/hiha-arvio.git
synced 2026-03-22 21:04:19 +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
|
- **Coverage:** 51.28% line (low due to MAUI template), 87.5% branch
|
||||||
- **Build:** 0 warnings, 0 errors across all platforms
|
- **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
|
### 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)
|
**Milestone 5: Platform-Specific Implementations** (Not Started)
|
||||||
- IAccelerometerService interface
|
- IAccelerometerService interface
|
||||||
- iOS implementation (real accelerometer)
|
- iOS implementation (real accelerometer)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:local="clr-namespace:HihaArvio"
|
xmlns:local="clr-namespace:HihaArvio"
|
||||||
|
xmlns:converters="clr-namespace:HihaArvio.Converters"
|
||||||
x:Class="HihaArvio.App">
|
x:Class="HihaArvio.App">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
@@ -9,6 +10,13 @@
|
|||||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</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>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -4,12 +4,26 @@
|
|||||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:local="clr-namespace:HihaArvio"
|
xmlns:local="clr-namespace:HihaArvio"
|
||||||
Shell.FlyoutBehavior="Disabled"
|
Title="Hiha-Arvio">
|
||||||
Title="HihaArvio">
|
|
||||||
|
|
||||||
<ShellContent
|
<TabBar>
|
||||||
Title="Home"
|
<ShellContent
|
||||||
ContentTemplate="{DataTemplate local:MainPage}"
|
Title="Estimate"
|
||||||
Route="MainPage" />
|
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>
|
</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"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
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>
|
<ScrollView>
|
||||||
<VerticalStackLayout
|
<VerticalStackLayout Padding="20" Spacing="20">
|
||||||
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" />
|
|
||||||
|
|
||||||
<Label
|
<!-- Mode Selector -->
|
||||||
Text="Hello, World!"
|
<Frame BorderColor="{StaticResource Primary}" Padding="10">
|
||||||
Style="{StaticResource Headline}"
|
<VerticalStackLayout Spacing="10">
|
||||||
SemanticProperties.HeadingLevel="Level1" />
|
<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
|
<!-- Shake Instruction / Current Estimate Display -->
|
||||||
Text="Welcome to .NET Multi-platform App UI"
|
<Frame BorderColor="{StaticResource Primary}"
|
||||||
Style="{StaticResource SubHeadline}"
|
Padding="30"
|
||||||
SemanticProperties.HeadingLevel="Level2"
|
HasShadow="True"
|
||||||
SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
|
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>
|
</VerticalStackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,12 @@
|
|||||||
namespace HihaArvio;
|
using HihaArvio.ViewModels;
|
||||||
|
|
||||||
|
namespace HihaArvio;
|
||||||
|
|
||||||
public partial class MainPage : ContentPage
|
public partial class MainPage : ContentPage
|
||||||
{
|
{
|
||||||
int count = 0;
|
public MainPage(MainViewModel viewModel)
|
||||||
|
{
|
||||||
public MainPage()
|
InitializeComponent();
|
||||||
{
|
BindingContext = viewModel;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using HihaArvio.Services;
|
||||||
|
using HihaArvio.Services.Interfaces;
|
||||||
|
using HihaArvio.ViewModels;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace HihaArvio;
|
namespace HihaArvio;
|
||||||
|
|
||||||
@@ -19,6 +22,24 @@ public static class MauiProgram
|
|||||||
builder.Logging.AddDebug();
|
builder.Logging.AddDebug();
|
||||||
#endif
|
#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();
|
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