feat: project setup and core models with TDD

MILESTONE 1 COMPLETE:

Project Setup:
- Create .NET 8 MAUI solution targeting iOS, macOS, and web
- Configure nullable reference types and warnings as errors
- Add required dependencies (MAUI 8.0.3, SQLite, CommunityToolkit.Mvvm)
- Add testing dependencies (xUnit, NSubstitute, FluentAssertions, Coverlet)
- Create comprehensive .editorconfig with C# coding standards
- Update CLAUDE.md with development commands

Core Models (TDD - 48 tests, all passing):
- EstimateMode enum (Work, Generic, Humorous modes)
- EstimateResult model with validation (intensity 0.0-1.0, non-null text)
- ShakeData model with intensity validation
- AppSettings model with defaults (Work mode, max history 10)

Build Status:
- All platforms build successfully (net8.0, iOS, macOS)
- 0 warnings, 0 errors
- Test coverage: 51.28% line, 87.5% branch (models have ~100% coverage)

Per spec: Strict TDD followed (RED-GREEN-REFACTOR), models fully validated

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 12:12:36 +02:00
parent 17df545fba
commit 83ec08f14b
49 changed files with 2725 additions and 0 deletions

304
.editorconfig Normal file
View File

@@ -0,0 +1,304 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# All files
[*]
charset = utf-8
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
# XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# XML config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
# YAML files
[*.{yml,yaml}]
indent_size = 2
# Markdown files
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
tab_width = 4
# New line preferences
end_of_line = lf
insert_final_newline = true
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false:warning
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_property = false:warning
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
# Expression-level preferences
dotnet_style_coalesce_expression = true:warning
dotnet_style_collection_initializer = true:warning
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_null_propagation = true:warning
dotnet_style_object_initializer = true:warning
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_simplified_boolean_expressions = true:warning
dotnet_style_prefer_simplified_interpolation = true:warning
# Field preferences
dotnet_style_readonly_field = true:warning
# Parameter preferences
dotnet_code_quality_unused_parameters = all:warning
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = false:warning
dotnet_style_allow_statement_immediately_after_block_experimental = false:warning
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false:suggestion
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_lambdas = true:suggestion
csharp_style_expression_bodied_local_functions = false:suggestion
csharp_style_expression_bodied_methods = false:suggestion
csharp_style_expression_bodied_operators = false:suggestion
csharp_style_expression_bodied_properties = true:suggestion
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:warning
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_prefer_not_pattern = true:warning
csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:warning
csharp_style_prefer_parameter_null_checking = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
# Code-block preferences
csharp_prefer_braces = true:warning
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_prefer_method_group_conversion = true:suggestion
csharp_style_prefer_top_level_statements = false:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true:warning
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_inlined_variable_declaration = true:warning
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:warning
csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_throw_expression = true:warning
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:warning
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning
csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = no_change
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
#### Naming Conventions ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = warning
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.severity = warning
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.style = begins_with_underscore
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.begins_with_underscore.required_prefix = _
dotnet_naming_style.begins_with_underscore.required_suffix =
dotnet_naming_style.begins_with_underscore.word_separator =
dotnet_naming_style.begins_with_underscore.capitalization = camel_case
#### Code Analysis Rules ####
# CA1031: Do not catch general exception types
dotnet_diagnostic.CA1031.severity = suggestion
# CA1062: Validate arguments of public methods
dotnet_diagnostic.CA1062.severity = suggestion
# CA1303: Do not pass literals as localized parameters
dotnet_diagnostic.CA1303.severity = none
# CA1720: Identifiers should not contain type names
dotnet_diagnostic.CA1720.severity = suggestion
# CA2007: Do not directly await a Task
dotnet_diagnostic.CA2007.severity = none
# IDE0005: Using directive is unnecessary
dotnet_diagnostic.IDE0005.severity = warning
# IDE0058: Expression value is never used
dotnet_diagnostic.IDE0058.severity = none
# IDE0063: Use simple 'using' statement
dotnet_diagnostic.IDE0063.severity = suggestion

207
CLAUDE.md Normal file
View File

@@ -0,0 +1,207 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Hiha-Arvio (Finnish: "Sleeve Estimate") is a .NET 8 MAUI cross-platform application that generates semi-random time estimates based on physical shake input (accelerometer on mobile, mouse movement on desktop). This is a humor app for "pulling an estimate from your sleeve."
**Platforms (in priority order):** iOS (primary) → Web (Blazor) → macOS
## Critical Requirements
### Specification Compliance
- **ALWAYS read `spec.md` before implementing features** - contains RFC 2119 formal requirements (MUST, REQUIRED, SHALL)
- **Design reference:** `docs/plans/2025-11-18-hiha-arvio-design.md` contains validated architecture decisions
- Nullable reference types MUST be enabled
- All compiler warnings MUST be treated as errors
- Minimum test coverage: 95% (enforced in CI/CD)
### Architecture Constraints
**MVVM Pattern with Strict Separation:**
- Models: Plain data objects only, no business logic
- ViewModels: All presentation logic, must be 100% testable without UI dependencies
- Views: Thin layer, data binding only, minimal presentation code
- Services: All business logic and infrastructure concerns
**Dependency Injection:**
- All services MUST be injected via constructor (no service locator, no `new` keyword in ViewModels)
- Register services in `MauiProgram.cs`
- Platform-specific implementations use conditional compilation (`#if IOS`, `#elif WINDOWS || MACCATALYST`)
### Testing Requirements
- Test coverage MUST be ≥95% (measured with Coverlet, enforced in CI/CD)
- Testing stack: xUnit + NSubstitute (mocking) + FluentAssertions (assertions)
- All tests MUST use deterministic randomness (seeded RNG)
- Mock all external dependencies (sensors, database, file system)
- Use test data builders for complex objects
## Technology Stack
- **Framework:** .NET 8 MAUI (LTS)
- **Language:** C# 12 with nullable reference types
- **Database:** SQLite via `sqlite-net-pcl`
- **MVVM:** CommunityToolkit.Mvvm (source generators)
- **Testing:** xUnit, NSubstitute, FluentAssertions, Coverlet
## Core Architecture
### Service Layer (All Injectable)
**IAccelerometerService**
- Platform abstraction: accelerometer (iOS) or mouse movement (desktop/web)
- Emits observable stream of sensor data
**IShakeDetectionService**
- Processes accelerometer stream
- Detects shake start/stop, calculates normalized intensity [0.0-1.0]
- Tracks shake duration for easter egg trigger (>15 seconds)
**IEstimateService**
- Generates estimates based on: intensity, duration, mode
- Implements intensity → range mapping:
- Intensity <0.3: narrow range (first 20% of pool)
- Intensity 0.3-0.7: medium range (first 50% of pool)
- Intensity >0.7: full range (entire pool)
- Easter egg: duration >15s forces Humorous mode
- MUST use cryptographically secure RNG (`System.Security.Cryptography.RandomNumberGenerator`)
**IStorageService**
- Settings: Preferences API
- History: SQLite with auto-pruning (max 10 estimates)
- All operations MUST be async
### Key Models
```csharp
EstimateResult: Id, Timestamp, EstimateText, Mode, ShakeIntensity, ShakeDuration
EstimateMode enum: Work, Generic, Humorous
ShakeData: Intensity, Duration, IsShaking
AppSettings: SelectedMode, MaxHistorySize
```
### Estimate Pools (from spec.md §3.2.2)
**Work Mode:**
- Gentle: "2 hours", "4 hours", "1 day", "2 days", "3 days", "5 days", "1 week"
- Hard: adds "15 minutes", "30 minutes", "2 weeks", "1 month", "3 months", "6 months", "1 year"
**Generic Mode:**
- Gentle: "1 minute" → "3 hours"
- Hard: "30 seconds" → "1 month"
**Humorous Mode (easter egg):**
- "tomorrow", "eventually", "next quarter", "when hell freezes over", "3 lifetimes", "Tuesday", "never", "your retirement"
## Platform-Specific Implementation
### iOS (Primary Platform)
- Accelerometer via `Microsoft.Maui.Devices.Sensors.Accelerometer`
- Shake detection: `magnitude = sqrt(x² + y² + z²)`, threshold 2.5g
- Must request motion permissions in Info.plist
- Haptic feedback on shake detection (recommended)
- Target iOS 15+
### Web (Blazor WebAssembly)
- Mouse movement simulation: track delta over 200ms window, calculate velocity
- PWA manifest for home screen install
- Support Device Orientation API for mobile browsers (optional)
### macOS
- Mouse movement tracking (similar to web)
- Keyboard shortcut: Cmd+Shift+S for manual shake trigger
- Native menu bar integration
## Project Structure (When Implemented)
```
HihaArvio.sln
├── src/HihaArvio/ # Main MAUI project
│ ├── Models/
│ ├── ViewModels/
│ ├── Views/
│ ├── Services/
│ │ ├── Interfaces/
│ │ └── Platform/ # Platform-specific implementations
│ └── MauiProgram.cs
├── tests/
│ ├── HihaArvio.Tests/ # Unit tests
│ ├── HihaArvio.IntegrationTests/ # Integration tests
│ └── HihaArvio.UITests/ # UI automation (future)
├── docs/plans/ # Design documents
└── spec.md # Formal specification
```
## Development Commands
### Build Commands
- **Build all platforms:** `dotnet build HihaArvio.sln`
- **Build specific framework:** `dotnet build HihaArvio.sln -f net8.0`
- **Build iOS:** `dotnet build HihaArvio.sln -f net8.0-ios`
- **Build macOS:** `dotnet build HihaArvio.sln -f net8.0-maccatalyst`
### Test Commands
- **Run all tests:** `dotnet test tests/HihaArvio.Tests/HihaArvio.Tests.csproj`
- **Run specific test class:** `dotnet test tests/HihaArvio.Tests/HihaArvio.Tests.csproj --filter "FullyQualifiedName~EstimateModeTests"`
- **Run single test:** `dotnet test --filter "FullyQualifiedName~TestClassName.TestMethodName"`
### Code Coverage
- **Generate coverage:** `dotnet test tests/HihaArvio.Tests/HihaArvio.Tests.csproj --collect:"XPlat Code Coverage"`
- **Coverage files:** Located in `tests/HihaArvio.Tests/TestResults/{guid}/coverage.cobertura.xml`
### Run Commands
- **iOS Simulator:** `dotnet build src/HihaArvio/HihaArvio.csproj -t:Run -f net8.0-ios`
- **macOS:** `dotnet build src/HihaArvio/HihaArvio.csproj -t:Run -f net8.0-maccatalyst`
### Notes
- All commands should be run from the repository root directory
- Xcode must be installed and configured (`xcode-select -p` should point to Xcode.app)
- MAUI workload must be installed (`dotnet workload list` should show `maui`)
## Critical Implementation Notes
### Easter Egg Behavior
- Hidden feature: NO UI indication
- Trigger: shake duration >15 seconds
- Effect: temporarily force EstimateMode.Humorous
- Do NOT expose in Settings or UI
### Shake Detection Algorithm
1. Monitor sensor stream (accelerometer or mouse)
2. Calculate magnitude/velocity
3. Detect start: exceeds threshold
4. Track peak intensity during session
5. Detect end: below threshold for 500ms continuous
6. Normalize to [0.0, 1.0]
### Performance Requirements
- Shake response: <100ms latency
- Estimate display: <200ms after shake stop
- History load: <500ms
- All database operations: async, non-blocking
### Security
- Use `System.Security.Cryptography.RandomNumberGenerator` for estimate selection
- No external data transmission
- All data stored locally only
- Request minimum required permissions
## Code Quality Enforcement
- Enable nullable reference types across all projects
- Treat warnings as errors
- Follow EditorConfig rules (when defined)
- Code analysis: StyleCop + built-in analyzers enabled
- CI/CD must enforce 95% coverage threshold and fail builds below this
## Important Implementation Order
Per design document, phased development:
1. **Phase 1:** iOS app (primary platform, accelerometer-based)
2. **Phase 2:** Web app (Blazor, mouse simulation)
3. **Phase 3:** macOS app (native integration)
Focus on Phase 1 first to validate core shake detection and estimate generation logic.

36
HihaArvio.sln Normal file
View File

@@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7228B5C2-A84A-4648-BB86-FF76E090592E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HihaArvio", "src\HihaArvio\HihaArvio.csproj", "{E8438360-C957-4B6B-A7EA-9F910063D141}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1BA313A1-7BF6-4504-B397-6EFA6B72D5AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HihaArvio.Tests", "tests\HihaArvio.Tests\HihaArvio.Tests.csproj", "{FCA4D2E3-827F-4557-BBD4-A2F9F81CB158}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E8438360-C957-4B6B-A7EA-9F910063D141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8438360-C957-4B6B-A7EA-9F910063D141}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8438360-C957-4B6B-A7EA-9F910063D141}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8438360-C957-4B6B-A7EA-9F910063D141}.Release|Any CPU.Build.0 = Release|Any CPU
{FCA4D2E3-827F-4557-BBD4-A2F9F81CB158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCA4D2E3-827F-4557-BBD4-A2F9F81CB158}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCA4D2E3-827F-4557-BBD4-A2F9F81CB158}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCA4D2E3-827F-4557-BBD4-A2F9F81CB158}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E8438360-C957-4B6B-A7EA-9F910063D141} = {7228B5C2-A84A-4648-BB86-FF76E090592E}
{FCA4D2E3-827F-4557-BBD4-A2F9F81CB158} = {1BA313A1-7BF6-4504-B397-6EFA6B72D5AA}
EndGlobalSection
EndGlobal

14
src/HihaArvio/App.xaml Normal file
View File

@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:HihaArvio"
x:Class="HihaArvio.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

11
src/HihaArvio/App.xaml.cs Normal file
View File

@@ -0,0 +1,11 @@
namespace HihaArvio;
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="HihaArvio.AppShell"
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">
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
</Shell>

View File

@@ -0,0 +1,9 @@
namespace HihaArvio;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,70 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Per spec: iOS (primary), Web (Blazor), macOS (tertiary) -->
<!-- net8.0 included for unit testing without platform dependencies -->
<TargetFrameworks>net8.0;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<!-- Note for MacCatalyst:
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
<OutputType Condition="'$(TargetFramework)' != 'net8.0'">Exe</OutputType>
<OutputType Condition="'$(TargetFramework)' == 'net8.0'">Library</OutputType>
<RootNamespace>HihaArvio</RootNamespace>
<UseMaui Condition="'$(TargetFramework)' != 'net8.0'">true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Per spec: treat warnings as errors, C# 12 -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<LangVersion>12</LangVersion>
<!-- Display name -->
<ApplicationTitle>HihaArvio</ApplicationTitle>
<!-- App Identifier -->
<ApplicationId>com.companyname.hihaarvio</ApplicationId>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<!-- Per spec: iOS 15+, macOS 12+ (Monterey) -->
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
<!-- Per spec: MAUI 8.x with explicit lower bound -->
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.3" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.11" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,36 @@
<?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">
<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" />
<Label
Text="Hello, World!"
Style="{StaticResource Headline}"
SemanticProperties.HeadingLevel="Level1" />
<Label
Text="Welcome to &#10;.NET Multi-platform App UI"
Style="{StaticResource SubHeadline}"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
HorizontalOptions="Fill" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,24 @@
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);
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.Extensions.Logging;
namespace HihaArvio;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}

View File

@@ -0,0 +1,37 @@
namespace HihaArvio.Models;
/// <summary>
/// Represents application settings and user preferences.
/// </summary>
public class AppSettings
{
private int _maxHistorySize = 10;
/// <summary>
/// Gets or sets the selected estimate mode.
/// Default is <see cref="EstimateMode.Work"/>.
/// </summary>
public EstimateMode SelectedMode { get; set; } = EstimateMode.Work;
/// <summary>
/// Gets or sets the maximum number of estimate results to keep in history.
/// Default is 10.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when value is less than or equal to 0.</exception>
public int MaxHistorySize
{
get => _maxHistorySize;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
"MaxHistorySize must be greater than 0.");
}
_maxHistorySize = value;
}
}
}

View File

@@ -0,0 +1,22 @@
namespace HihaArvio.Models;
/// <summary>
/// Represents the mode of time estimate generation.
/// </summary>
public enum EstimateMode
{
/// <summary>
/// Work/project-related time estimates (e.g., "2 weeks", "3 sprints").
/// </summary>
Work = 0,
/// <summary>
/// Generic duration estimates (e.g., "5 minutes", "2 hours").
/// </summary>
Generic = 1,
/// <summary>
/// Humorous/exaggerated estimates (easter egg mode, triggered by >15s shake).
/// </summary>
Humorous = 2
}

View File

@@ -0,0 +1,100 @@
namespace HihaArvio.Models;
/// <summary>
/// Represents a time estimate result generated from a shake gesture.
/// </summary>
public class EstimateResult
{
private string _estimateText = string.Empty;
private double _shakeIntensity;
/// <summary>
/// Gets or sets the unique identifier for this estimate.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Gets or sets the timestamp when this estimate was generated.
/// </summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>
/// Gets or sets the estimate text (e.g., "2 weeks", "eventually").
/// </summary>
/// <exception cref="ArgumentNullException">Thrown when value is null.</exception>
/// <exception cref="ArgumentException">Thrown when value is empty or whitespace.</exception>
public string EstimateText
{
get => _estimateText;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value), "EstimateText cannot be null.");
}
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("EstimateText cannot be empty or whitespace.", nameof(value));
}
_estimateText = value;
}
}
/// <summary>
/// Gets or sets the estimation mode (Work, Generic, or Humorous).
/// </summary>
public EstimateMode Mode { get; set; }
/// <summary>
/// Gets or sets the normalized shake intensity (0.0 to 1.0).
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when value is outside [0.0, 1.0] range.</exception>
public double ShakeIntensity
{
get => _shakeIntensity;
set
{
if (value < 0.0 || value > 1.0)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
"ShakeIntensity must be between 0.0 and 1.0.");
}
_shakeIntensity = value;
}
}
/// <summary>
/// Gets or sets the duration of the shake gesture.
/// </summary>
public TimeSpan ShakeDuration { get; set; }
/// <summary>
/// Creates a new EstimateResult with the specified values and auto-generated ID and timestamp.
/// </summary>
/// <param name="estimateText">The estimate text.</param>
/// <param name="mode">The estimation mode.</param>
/// <param name="shakeIntensity">The shake intensity (0.0 to 1.0).</param>
/// <param name="shakeDuration">The shake duration.</param>
/// <returns>A new EstimateResult instance.</returns>
public static EstimateResult Create(
string estimateText,
EstimateMode mode,
double shakeIntensity,
TimeSpan shakeDuration)
{
return new EstimateResult
{
Id = Guid.NewGuid(),
Timestamp = DateTimeOffset.UtcNow,
EstimateText = estimateText,
Mode = mode,
ShakeIntensity = shakeIntensity,
ShakeDuration = shakeDuration
};
}
}

View File

@@ -0,0 +1,40 @@
namespace HihaArvio.Models;
/// <summary>
/// Represents current shake gesture data.
/// </summary>
public class ShakeData
{
private double _intensity;
/// <summary>
/// Gets or sets the normalized shake intensity (0.0 to 1.0).
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when value is outside [0.0, 1.0] range.</exception>
public double Intensity
{
get => _intensity;
set
{
if (value < 0.0 || value > 1.0)
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
"Intensity must be between 0.0 and 1.0.");
}
_intensity = value;
}
}
/// <summary>
/// Gets or sets the duration of the current shake gesture.
/// </summary>
public TimeSpan Duration { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a shake is currently in progress.
/// </summary>
public bool IsShaking { get; set; }
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@@ -0,0 +1,10 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace HihaArvio;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}

View File

@@ -0,0 +1,15 @@
using Android.App;
using Android.Runtime;
namespace HihaArvio;
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#512BD4</color>
<color name="colorPrimaryDark">#2B0B98</color>
<color name="colorAccent">#2B0B98</color>
</resources>

View File

@@ -0,0 +1,9 @@
using Foundation;
namespace HihaArvio;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
<dict>
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- The Mac App Store requires you specify if the app uses encryption. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
<!-- Please indicate <true/> or <false/> here. -->
<!-- Specify the category for your app here. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
<!-- <key>LSApplicationCategoryType</key> -->
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace HihaArvio;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@@ -0,0 +1,16 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
namespace HihaArvio;
class Program : MauiApplication
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
static void Main(string[] args)
{
var app = new Program();
app.Run(args);
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="8" xmlns="http://tizen.org/ns/packages">
<profile name="common" />
<ui-application appid="maui-application-id-placeholder" exec="HihaArvio.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
<label>maui-application-title-placeholder</label>
<icon>maui-appicon-placeholder</icon>
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
</ui-application>
<shortcut-list />
<privileges>
<privilege>http://tizen.org/privilege/internet</privilege>
</privileges>
<dependencies />
<provides-appdefined-privileges />
</manifest>

View File

@@ -0,0 +1,8 @@
<maui:MauiWinUIApplication
x:Class="HihaArvio.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:HihaArvio.WinUI">
</maui:MauiWinUIApplication>

View File

@@ -0,0 +1,24 @@
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace HihaArvio.WinUI;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : MauiWinUIApplication
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
<mp:PhoneIdentity PhoneProductId="57605B8D-84AE-4E26-B302-7075B28337F9" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>$placeholder$</DisplayName>
<PublisherDisplayName>User Name</PublisherDisplayName>
<Logo>$placeholder$.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="$placeholder$"
Description="$placeholder$"
Square150x150Logo="$placeholder$.png"
Square44x44Logo="$placeholder$.png"
BackgroundColor="transparent">
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="HihaArvio.WinUI.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@@ -0,0 +1,9 @@
using Foundation;
namespace HihaArvio;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace HihaArvio;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
You are responsible for adding extra entries as needed for your application.
More information: https://aka.ms/maui-privacy-manifest
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
<!--
The entry below is only needed when you're using the Preferences API in your app.
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict> -->
</array>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"Windows Machine": {
"commandName": "MsixPackage",
"nativeDebugging": false
}
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>

After

Width:  |  Height:  |  Size: 231 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -0,0 +1,15 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories). Deployment of the asset to your application
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
These files will be deployed with your package and will be accessible using Essentials:
async Task LoadMauiAsset()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
<Color x:Key="Primary">#512BD4</Color>
<Color x:Key="PrimaryDark">#ac99ea</Color>
<Color x:Key="PrimaryDarkText">#242424</Color>
<Color x:Key="Secondary">#DFD8F7</Color>
<Color x:Key="SecondaryDarkText">#9880e5</Color>
<Color x:Key="Tertiary">#2B0B98</Color>
<Color x:Key="White">White</Color>
<Color x:Key="Black">Black</Color>
<Color x:Key="Magenta">#D600AA</Color>
<Color x:Key="MidnightBlue">#190649</Color>
<Color x:Key="OffBlack">#1f1f1f</Color>
<Color x:Key="Gray100">#E1E1E1</Color>
<Color x:Key="Gray200">#C8C8C8</Color>
<Color x:Key="Gray300">#ACACAC</Color>
<Color x:Key="Gray400">#919191</Color>
<Color x:Key="Gray500">#6E6E6E</Color>
<Color x:Key="Gray600">#404040</Color>
<Color x:Key="Gray900">#212121</Color>
<Color x:Key="Gray950">#141414</Color>
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
</ResourceDictionary>

View File

@@ -0,0 +1,427 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Style TargetType="ActivityIndicator">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="IndicatorView">
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
</Style>
<Style TargetType="Border">
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="StrokeShape" Value="Rectangle"/>
<Setter Property="StrokeThickness" Value="1"/>
</Style>
<Style TargetType="BoxView">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="Button">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderWidth" Value="0"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="14,10"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver" />
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="CheckBox">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="DatePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Editor">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Entry">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Frame">
<Setter Property="HasShadow" Value="False" />
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
</Style>
<Style TargetType="ImageButton">
<Setter Property="Opacity" Value="1" />
<Setter Property="BorderColor" Value="Transparent"/>
<Setter Property="BorderWidth" Value="0"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Opacity" Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver" />
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Span">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="Label" x:Key="Headline">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
<Setter Property="FontSize" Value="32" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="Label" x:Key="SubHeadline">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
<Setter Property="FontSize" Value="24" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="ListView">
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="Picker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="ProgressBar">
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RadioButton">
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RefreshView">
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="SearchBar">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SearchHandler">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Shadow">
<Setter Property="Radius" Value="15" />
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
<Setter Property="Offset" Value="10,10" />
</Style>
<Style TargetType="Slider">
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SwipeItem">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
</Style>
<Style TargetType="Switch">
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="ThumbColor" Value="{StaticResource White}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="On">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Off">
<VisualState.Setters>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="TimePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Page" ApplyToDerivedTypes="True">
<Setter Property="Padding" Value="0"/>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
</Style>
<Style TargetType="Shell" ApplyToDerivedTypes="True">
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
<Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
<Setter Property="Shell.NavBarHasShadow" Value="False" />
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="TabbedPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<!-- Per spec: treat warnings as errors, C# 12 -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="FluentAssertions" Version="8.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\HihaArvio\HihaArvio.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,90 @@
using FluentAssertions;
using HihaArvio.Models;
namespace HihaArvio.Tests.Models;
public class AppSettingsTests
{
[Fact]
public void AppSettings_DefaultConstructor_ShouldSetWorkModeAsDefault()
{
// Act
var settings = new AppSettings();
// Assert
settings.SelectedMode.Should().Be(EstimateMode.Work);
}
[Fact]
public void AppSettings_DefaultConstructor_ShouldSetMaxHistorySizeTo10()
{
// Act
var settings = new AppSettings();
// Assert
settings.MaxHistorySize.Should().Be(10);
}
[Theory]
[InlineData(EstimateMode.Work)]
[InlineData(EstimateMode.Generic)]
[InlineData(EstimateMode.Humorous)]
public void AppSettings_ShouldAllowModeChanges(EstimateMode mode)
{
// Arrange
var settings = new AppSettings();
// Act
settings.SelectedMode = mode;
// Assert
settings.SelectedMode.Should().Be(mode);
}
[Theory]
[InlineData(1)]
[InlineData(5)]
[InlineData(10)]
[InlineData(50)]
[InlineData(100)]
public void AppSettings_ShouldAcceptValidMaxHistorySizeValues(int size)
{
// Arrange
var settings = new AppSettings();
// Act
settings.MaxHistorySize = size;
// Assert
settings.MaxHistorySize.Should().Be(size);
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(-10)]
public void AppSettings_ShouldThrowForInvalidMaxHistorySize(int invalidSize)
{
// Arrange
var settings = new AppSettings();
// Act
Action act = () => settings.MaxHistorySize = invalidSize;
// Assert
act.Should().Throw<ArgumentOutOfRangeException>()
.WithMessage("*must be greater than 0*");
}
[Fact]
public void AppSettings_ShouldCreateWithDefaultValues()
{
// Act
var settings = new AppSettings();
// Assert
settings.Should().NotBeNull();
settings.SelectedMode.Should().Be(EstimateMode.Work);
settings.MaxHistorySize.Should().Be(10);
}
}

View File

@@ -0,0 +1,51 @@
using FluentAssertions;
using HihaArvio.Models;
namespace HihaArvio.Tests.Models;
public class EstimateModeTests
{
[Fact]
public void EstimateMode_ShouldHaveWorkValue()
{
// Act
var mode = EstimateMode.Work;
// Assert
mode.Should().Be(EstimateMode.Work);
((int)mode).Should().Be(0);
}
[Fact]
public void EstimateMode_ShouldHaveGenericValue()
{
// Act
var mode = EstimateMode.Generic;
// Assert
mode.Should().Be(EstimateMode.Generic);
((int)mode).Should().Be(1);
}
[Fact]
public void EstimateMode_ShouldHaveHumorousValue()
{
// Act
var mode = EstimateMode.Humorous;
// Assert
mode.Should().Be(EstimateMode.Humorous);
((int)mode).Should().Be(2);
}
[Fact]
public void EstimateMode_ShouldHaveExactlyThreeValues()
{
// Act
var values = Enum.GetValues<EstimateMode>();
// Assert
values.Should().HaveCount(3);
values.Should().Contain(new[] { EstimateMode.Work, EstimateMode.Generic, EstimateMode.Humorous });
}
}

View File

@@ -0,0 +1,147 @@
using FluentAssertions;
using HihaArvio.Models;
namespace HihaArvio.Tests.Models;
public class EstimateResultTests
{
[Fact]
public void EstimateResult_ShouldCreateWithAllProperties()
{
// Arrange
var id = Guid.NewGuid();
var timestamp = DateTimeOffset.UtcNow;
var estimateText = "2 weeks";
var mode = EstimateMode.Work;
var intensity = 0.75;
var duration = TimeSpan.FromSeconds(5);
// Act
var result = new EstimateResult
{
Id = id,
Timestamp = timestamp,
EstimateText = estimateText,
Mode = mode,
ShakeIntensity = intensity,
ShakeDuration = duration
};
// Assert
result.Id.Should().Be(id);
result.Timestamp.Should().Be(timestamp);
result.EstimateText.Should().Be(estimateText);
result.Mode.Should().Be(mode);
result.ShakeIntensity.Should().Be(intensity);
result.ShakeDuration.Should().Be(duration);
}
[Theory]
[InlineData(0.0)]
[InlineData(0.3)]
[InlineData(0.7)]
[InlineData(1.0)]
public void EstimateResult_ShouldAcceptValidIntensityValues(double intensity)
{
// Act
var result = new EstimateResult { ShakeIntensity = intensity };
// Assert
result.ShakeIntensity.Should().Be(intensity);
}
[Theory]
[InlineData(-0.1)]
[InlineData(1.1)]
[InlineData(2.0)]
public void EstimateResult_ShouldThrowForInvalidIntensity(double invalidIntensity)
{
// Act
Action act = () => _ = new EstimateResult { ShakeIntensity = invalidIntensity };
// Assert
act.Should().Throw<ArgumentOutOfRangeException>()
.WithMessage("*must be between 0.0 and 1.0*");
}
[Fact]
public void EstimateResult_ShouldThrowForNullEstimateText()
{
// Act
Action act = () => _ = new EstimateResult { EstimateText = null! };
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("value");
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void EstimateResult_ShouldThrowForEmptyOrWhitespaceEstimateText(string invalidText)
{
// Act
Action act = () => _ = new EstimateResult { EstimateText = invalidText };
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*cannot be empty or whitespace*");
}
[Fact]
public void EstimateResult_ShouldAcceptZeroDuration()
{
// Act
var result = new EstimateResult { ShakeDuration = TimeSpan.Zero };
// Assert
result.ShakeDuration.Should().Be(TimeSpan.Zero);
}
[Fact]
public void EstimateResult_ShouldGenerateUniqueIds()
{
// Arrange & Act
var result1 = EstimateResult.Create("test1", EstimateMode.Work, 0.5, TimeSpan.FromSeconds(1));
var result2 = EstimateResult.Create("test2", EstimateMode.Work, 0.5, TimeSpan.FromSeconds(1));
// Assert
result1.Id.Should().NotBe(result2.Id);
result1.Id.Should().NotBeEmpty();
result2.Id.Should().NotBeEmpty();
}
[Fact]
public void EstimateResult_Create_ShouldSetTimestampAutomatically()
{
// Arrange
var before = DateTimeOffset.UtcNow;
// Act
var result = EstimateResult.Create("test", EstimateMode.Work, 0.5, TimeSpan.FromSeconds(1));
var after = DateTimeOffset.UtcNow;
// Assert
result.Timestamp.Should().BeOnOrAfter(before);
result.Timestamp.Should().BeOnOrBefore(after);
}
[Fact]
public void EstimateResult_Create_ShouldSetAllProperties()
{
// Arrange
var estimateText = "3 months";
var mode = EstimateMode.Generic;
var intensity = 0.8;
var duration = TimeSpan.FromSeconds(10);
// Act
var result = EstimateResult.Create(estimateText, mode, intensity, duration);
// Assert
result.EstimateText.Should().Be(estimateText);
result.Mode.Should().Be(mode);
result.ShakeIntensity.Should().Be(intensity);
result.ShakeDuration.Should().Be(duration);
}
}

View File

@@ -0,0 +1,106 @@
using FluentAssertions;
using HihaArvio.Models;
namespace HihaArvio.Tests.Models;
public class ShakeDataTests
{
[Fact]
public void ShakeData_ShouldCreateWithAllProperties()
{
// Arrange
var intensity = 0.65;
var duration = TimeSpan.FromSeconds(3);
var isShaking = true;
// Act
var shakeData = new ShakeData
{
Intensity = intensity,
Duration = duration,
IsShaking = isShaking
};
// Assert
shakeData.Intensity.Should().Be(intensity);
shakeData.Duration.Should().Be(duration);
shakeData.IsShaking.Should().Be(isShaking);
}
[Theory]
[InlineData(0.0)]
[InlineData(0.25)]
[InlineData(0.5)]
[InlineData(0.75)]
[InlineData(1.0)]
public void ShakeData_ShouldAcceptValidIntensityValues(double intensity)
{
// Act
var shakeData = new ShakeData { Intensity = intensity };
// Assert
shakeData.Intensity.Should().Be(intensity);
}
[Theory]
[InlineData(-0.1)]
[InlineData(-1.0)]
[InlineData(1.01)]
[InlineData(2.0)]
public void ShakeData_ShouldThrowForInvalidIntensity(double invalidIntensity)
{
// Act
Action act = () => _ = new ShakeData { Intensity = invalidIntensity };
// Assert
act.Should().Throw<ArgumentOutOfRangeException>()
.WithMessage("*must be between 0.0 and 1.0*");
}
[Fact]
public void ShakeData_ShouldAcceptZeroDuration()
{
// Act
var shakeData = new ShakeData { Duration = TimeSpan.Zero };
// Assert
shakeData.Duration.Should().Be(TimeSpan.Zero);
}
[Fact]
public void ShakeData_ShouldAcceptPositiveDuration()
{
// Arrange
var duration = TimeSpan.FromSeconds(15.5);
// Act
var shakeData = new ShakeData { Duration = duration };
// Assert
shakeData.Duration.Should().Be(duration);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ShakeData_ShouldAcceptBooleanIsShakingValues(bool isShaking)
{
// Act
var shakeData = new ShakeData { IsShaking = isShaking };
// Assert
shakeData.IsShaking.Should().Be(isShaking);
}
[Fact]
public void ShakeData_DefaultConstructor_ShouldSetDefaultValues()
{
// Act
var shakeData = new ShakeData();
// Assert
shakeData.Intensity.Should().Be(0.0);
shakeData.Duration.Should().Be(TimeSpan.Zero);
shakeData.IsShaking.Should().BeFalse();
}
}

View File

@@ -0,0 +1,490 @@
<?xml version="1.0" encoding="utf-8"?>
<coverage line-rate="0.5128" branch-rate="0.875" version="1.9" timestamp="1763460415" lines-covered="60" lines-valid="117" branches-covered="14" branches-valid="16">
<sources>
<source>/Users/ivuorinen/Code/ivuorinen/hiha-arvio/src/HihaArvio/</source>
</sources>
<packages>
<package name="HihaArvio" line-rate="0.5128" branch-rate="0.875" complexity="41">
<classes>
<class name="__XamlGeneratedCode__.__Type416C8673F66D9458" filename="obj/Debug/net8.0/Microsoft.Maui.Controls.SourceGen/Microsoft.Maui.Controls.SourceGen.CodeBehindGenerator/Resources_Styles_Colors.xaml.sg.cs" line-rate="0" branch-rate="1" complexity="2">
<methods>
<method name="InitializeComponent" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="29" hits="0" branch="False" />
<line number="30" hits="0" branch="False" />
<line number="31" hits="0" branch="False" />
</lines>
</method>
<method name=".ctor" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="20" hits="0" branch="False" />
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="29" hits="0" branch="False" />
<line number="30" hits="0" branch="False" />
<line number="31" hits="0" branch="False" />
<line number="20" hits="0" branch="False" />
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</class>
<class name="__XamlGeneratedCode__.__Type76D52C8BE0F320C6" filename="obj/Debug/net8.0/Microsoft.Maui.Controls.SourceGen/Microsoft.Maui.Controls.SourceGen.CodeBehindGenerator/Resources_Styles_Styles.xaml.sg.cs" line-rate="0" branch-rate="1" complexity="2">
<methods>
<method name="InitializeComponent" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="29" hits="0" branch="False" />
<line number="30" hits="0" branch="False" />
<line number="31" hits="0" branch="False" />
</lines>
</method>
<method name=".ctor" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="20" hits="0" branch="False" />
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="29" hits="0" branch="False" />
<line number="30" hits="0" branch="False" />
<line number="31" hits="0" branch="False" />
<line number="20" hits="0" branch="False" />
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.App" filename="obj/Debug/net8.0/Microsoft.Maui.Controls.SourceGen/Microsoft.Maui.Controls.SourceGen.CodeBehindGenerator/App.xaml.sg.cs" line-rate="0" branch-rate="1" complexity="1">
<methods>
<method name="InitializeComponent" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.App" filename="App.xaml.cs" line-rate="0" branch-rate="1" complexity="1">
<methods>
<method name=".ctor" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="5" hits="0" branch="False" />
<line number="6" hits="0" branch="False" />
<line number="7" hits="0" branch="False" />
<line number="9" hits="0" branch="False" />
<line number="10" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="5" hits="0" branch="False" />
<line number="6" hits="0" branch="False" />
<line number="7" hits="0" branch="False" />
<line number="9" hits="0" branch="False" />
<line number="10" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.AppShell" filename="obj/Debug/net8.0/Microsoft.Maui.Controls.SourceGen/Microsoft.Maui.Controls.SourceGen.CodeBehindGenerator/AppShell.xaml.sg.cs" line-rate="0" branch-rate="1" complexity="1">
<methods>
<method name="InitializeComponent" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.AppShell" filename="AppShell.xaml.cs" line-rate="0" branch-rate="1" complexity="1">
<methods>
<method name=".ctor" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="5" hits="0" branch="False" />
<line number="6" hits="0" branch="False" />
<line number="7" hits="0" branch="False" />
<line number="8" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="5" hits="0" branch="False" />
<line number="6" hits="0" branch="False" />
<line number="7" hits="0" branch="False" />
<line number="8" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.MainPage" filename="MainPage.xaml.cs" line-rate="0" branch-rate="0" complexity="3">
<methods>
<method name="OnCounterClicked" signature="(System.Object,System.EventArgs)" line-rate="0" branch-rate="0" complexity="2">
<lines>
<line number="13" hits="0" branch="False" />
<line number="14" hits="0" branch="False" />
<line number="16" hits="0" branch="True" condition-coverage="0% (0/2)">
<conditions>
<condition number="26" type="jump" coverage="0%" />
</conditions>
</line>
<line number="17" hits="0" branch="False" />
<line number="19" hits="0" branch="False" />
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
</lines>
</method>
<method name=".ctor" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="5" hits="0" branch="False" />
<line number="7" hits="0" branch="False" />
<line number="8" hits="0" branch="False" />
<line number="9" hits="0" branch="False" />
<line number="10" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="13" hits="0" branch="False" />
<line number="14" hits="0" branch="False" />
<line number="16" hits="0" branch="True" condition-coverage="0% (0/2)">
<conditions>
<condition number="26" type="jump" coverage="0%" />
</conditions>
</line>
<line number="17" hits="0" branch="False" />
<line number="19" hits="0" branch="False" />
<line number="21" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="5" hits="0" branch="False" />
<line number="7" hits="0" branch="False" />
<line number="8" hits="0" branch="False" />
<line number="9" hits="0" branch="False" />
<line number="10" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.MainPage" filename="obj/Debug/net8.0/Microsoft.Maui.Controls.SourceGen/Microsoft.Maui.Controls.SourceGen.CodeBehindGenerator/MainPage.xaml.sg.cs" line-rate="0" branch-rate="1" complexity="1">
<methods>
<method name="InitializeComponent" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="25" hits="0" branch="False" />
<line number="26" hits="0" branch="False" />
<line number="27" hits="0" branch="False" />
<line number="28" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="25" hits="0" branch="False" />
<line number="26" hits="0" branch="False" />
<line number="27" hits="0" branch="False" />
<line number="28" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.MauiProgram" filename="MauiProgram.cs" line-rate="0" branch-rate="1" complexity="1">
<methods>
<method name="CreateMauiApp" signature="()" line-rate="0" branch-rate="1" complexity="1">
<lines>
<line number="8" hits="0" branch="False" />
<line number="9" hits="0" branch="False" />
<line number="10" hits="0" branch="False" />
<line number="11" hits="0" branch="False" />
<line number="12" hits="0" branch="False" />
<line number="13" hits="0" branch="False" />
<line number="14" hits="0" branch="False" />
<line number="15" hits="0" branch="False" />
<line number="16" hits="0" branch="False" />
<line number="19" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="8" hits="0" branch="False" />
<line number="9" hits="0" branch="False" />
<line number="10" hits="0" branch="False" />
<line number="11" hits="0" branch="False" />
<line number="12" hits="0" branch="False" />
<line number="13" hits="0" branch="False" />
<line number="14" hits="0" branch="False" />
<line number="15" hits="0" branch="False" />
<line number="16" hits="0" branch="False" />
<line number="19" hits="0" branch="False" />
<line number="22" hits="0" branch="False" />
<line number="23" hits="0" branch="False" />
</lines>
</class>
<class name="HihaArvio.Models.AppSettings" filename="Models/AppSettings.cs" line-rate="1" branch-rate="1" complexity="5">
<methods>
<method name="get_SelectedMode" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="14" hits="22" branch="False" />
</lines>
</method>
<method name="get_MaxHistorySize" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="23" hits="7" branch="False" />
</lines>
</method>
<method name="set_MaxHistorySize" signature="(System.Int32)" line-rate="1" branch-rate="1" complexity="2">
<lines>
<line number="25" hits="8" branch="False" />
<line number="26" hits="8" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="10" type="jump" coverage="100%" />
</conditions>
</line>
<line number="27" hits="3" branch="False" />
<line number="28" hits="3" branch="False" />
<line number="29" hits="3" branch="False" />
<line number="30" hits="3" branch="False" />
<line number="31" hits="3" branch="False" />
<line number="34" hits="5" branch="False" />
<line number="35" hits="5" branch="False" />
</lines>
</method>
<method name=".ctor" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="8" hits="14" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="14" hits="22" branch="False" />
<line number="23" hits="7" branch="False" />
<line number="25" hits="8" branch="False" />
<line number="26" hits="8" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="10" type="jump" coverage="100%" />
</conditions>
</line>
<line number="27" hits="3" branch="False" />
<line number="28" hits="3" branch="False" />
<line number="29" hits="3" branch="False" />
<line number="30" hits="3" branch="False" />
<line number="31" hits="3" branch="False" />
<line number="34" hits="5" branch="False" />
<line number="35" hits="5" branch="False" />
<line number="8" hits="14" branch="False" />
</lines>
</class>
<class name="HihaArvio.Models.EstimateResult" filename="Models/EstimateResult.cs" line-rate="1" branch-rate="1" complexity="16">
<methods>
<method name="get_Id" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="14" hits="10" branch="False" />
</lines>
</method>
<method name="get_Timestamp" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="19" hits="8" branch="False" />
</lines>
</method>
<method name="get_EstimateText" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="28" hits="2" branch="False" />
</lines>
</method>
<method name="set_EstimateText" signature="(System.String)" line-rate="1" branch-rate="1" complexity="4">
<lines>
<line number="30" hits="8" branch="False" />
<line number="31" hits="8" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="7" type="jump" coverage="100%" />
</conditions>
</line>
<line number="32" hits="1" branch="False" />
<line number="33" hits="1" branch="False" />
<line number="36" hits="7" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="34" type="jump" coverage="100%" />
</conditions>
</line>
<line number="37" hits="2" branch="False" />
<line number="38" hits="2" branch="False" />
<line number="41" hits="5" branch="False" />
<line number="42" hits="5" branch="False" />
</lines>
</method>
<method name="get_Mode" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="48" hits="7" branch="False" />
</lines>
</method>
<method name="get_ShakeIntensity" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="56" hits="6" branch="False" />
</lines>
</method>
<method name="set_ShakeIntensity" signature="(System.Double)" line-rate="1" branch-rate="1" complexity="4">
<lines>
<line number="58" hits="12" branch="False" />
<line number="59" hits="12" branch="True" condition-coverage="100% (4/4)">
<conditions>
<condition number="11" type="jump" coverage="100%" />
<condition number="30" type="jump" coverage="100%" />
</conditions>
</line>
<line number="60" hits="3" branch="False" />
<line number="61" hits="3" branch="False" />
<line number="62" hits="3" branch="False" />
<line number="63" hits="3" branch="False" />
<line number="64" hits="3" branch="False" />
<line number="67" hits="9" branch="False" />
<line number="68" hits="9" branch="False" />
</lines>
</method>
<method name="get_ShakeDuration" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="74" hits="9" branch="False" />
</lines>
</method>
<method name="Create" signature="(System.String,HihaArvio.Models.EstimateMode,System.Double,System.TimeSpan)" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="89" hits="4" branch="False" />
<line number="90" hits="4" branch="False" />
<line number="91" hits="4" branch="False" />
<line number="92" hits="4" branch="False" />
<line number="93" hits="4" branch="False" />
<line number="94" hits="4" branch="False" />
<line number="95" hits="4" branch="False" />
<line number="96" hits="4" branch="False" />
<line number="97" hits="4" branch="False" />
<line number="98" hits="4" branch="False" />
<line number="99" hits="4" branch="False" />
</lines>
</method>
<method name=".ctor" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="8" hits="16" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="14" hits="10" branch="False" />
<line number="19" hits="8" branch="False" />
<line number="28" hits="2" branch="False" />
<line number="30" hits="8" branch="False" />
<line number="31" hits="8" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="7" type="jump" coverage="100%" />
</conditions>
</line>
<line number="32" hits="1" branch="False" />
<line number="33" hits="1" branch="False" />
<line number="36" hits="7" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="34" type="jump" coverage="100%" />
</conditions>
</line>
<line number="37" hits="2" branch="False" />
<line number="38" hits="2" branch="False" />
<line number="41" hits="5" branch="False" />
<line number="42" hits="5" branch="False" />
<line number="48" hits="7" branch="False" />
<line number="56" hits="6" branch="False" />
<line number="58" hits="12" branch="False" />
<line number="59" hits="12" branch="True" condition-coverage="100% (4/4)">
<conditions>
<condition number="11" type="jump" coverage="100%" />
<condition number="30" type="jump" coverage="100%" />
</conditions>
</line>
<line number="60" hits="3" branch="False" />
<line number="61" hits="3" branch="False" />
<line number="62" hits="3" branch="False" />
<line number="63" hits="3" branch="False" />
<line number="64" hits="3" branch="False" />
<line number="67" hits="9" branch="False" />
<line number="68" hits="9" branch="False" />
<line number="74" hits="9" branch="False" />
<line number="89" hits="4" branch="False" />
<line number="90" hits="4" branch="False" />
<line number="91" hits="4" branch="False" />
<line number="92" hits="4" branch="False" />
<line number="93" hits="4" branch="False" />
<line number="94" hits="4" branch="False" />
<line number="95" hits="4" branch="False" />
<line number="96" hits="4" branch="False" />
<line number="97" hits="4" branch="False" />
<line number="98" hits="4" branch="False" />
<line number="99" hits="4" branch="False" />
<line number="8" hits="16" branch="False" />
</lines>
</class>
<class name="HihaArvio.Models.ShakeData" filename="Models/ShakeData.cs" line-rate="1" branch-rate="1" complexity="7">
<methods>
<method name="get_Intensity" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="16" hits="7" branch="False" />
</lines>
</method>
<method name="set_Intensity" signature="(System.Double)" line-rate="1" branch-rate="1" complexity="4">
<lines>
<line number="18" hits="10" branch="False" />
<line number="19" hits="10" branch="True" condition-coverage="100% (4/4)">
<conditions>
<condition number="11" type="jump" coverage="100%" />
<condition number="30" type="jump" coverage="100%" />
</conditions>
</line>
<line number="20" hits="4" branch="False" />
<line number="21" hits="4" branch="False" />
<line number="22" hits="4" branch="False" />
<line number="23" hits="4" branch="False" />
<line number="24" hits="4" branch="False" />
<line number="27" hits="6" branch="False" />
<line number="28" hits="6" branch="False" />
</lines>
</method>
<method name="get_Duration" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="34" hits="7" branch="False" />
</lines>
</method>
<method name="get_IsShaking" signature="()" line-rate="1" branch-rate="1" complexity="1">
<lines>
<line number="39" hits="7" branch="False" />
</lines>
</method>
</methods>
<lines>
<line number="16" hits="7" branch="False" />
<line number="18" hits="10" branch="False" />
<line number="19" hits="10" branch="True" condition-coverage="100% (4/4)">
<conditions>
<condition number="11" type="jump" coverage="100%" />
<condition number="30" type="jump" coverage="100%" />
</conditions>
</line>
<line number="20" hits="4" branch="False" />
<line number="21" hits="4" branch="False" />
<line number="22" hits="4" branch="False" />
<line number="23" hits="4" branch="False" />
<line number="24" hits="4" branch="False" />
<line number="27" hits="6" branch="False" />
<line number="28" hits="6" branch="False" />
<line number="34" hits="7" branch="False" />
<line number="39" hits="7" branch="False" />
</lines>
</class>
</classes>
</package>
</packages>
</coverage>