diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c516004
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,527 @@
+# editorconfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 4 spaces as indentation
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+dotnet_diagnostic.CA1027.severity=error
+dotnet_diagnostic.CA1062.severity=error
+dotnet_diagnostic.CA1064.severity=error
+dotnet_diagnostic.CA1066.severity=error
+dotnet_diagnostic.CA1067.severity=error
+dotnet_diagnostic.CA1068.severity=error
+dotnet_diagnostic.CA1069.severity=warning
+dotnet_diagnostic.CA2013.severity=error
+dotnet_diagnostic.CA1802.severity=error
+dotnet_diagnostic.CA1813.severity=error
+dotnet_diagnostic.CA1814.severity=error
+dotnet_diagnostic.CA1815.severity=error
+dotnet_diagnostic.CA1822.severity=error
+dotnet_diagnostic.CA1827.severity=error
+dotnet_diagnostic.CA1828.severity=error
+dotnet_diagnostic.CA1826.severity=error
+dotnet_diagnostic.CA1829.severity=error
+dotnet_diagnostic.CA1830.severity=error
+dotnet_diagnostic.CA1831.severity=error
+dotnet_diagnostic.CA1832.severity=error
+dotnet_diagnostic.CA1833.severity=error
+dotnet_diagnostic.CA1834.severity=error
+dotnet_diagnostic.CA1835.severity=error
+dotnet_diagnostic.CA1836.severity=error
+dotnet_diagnostic.CA1837.severity=error
+dotnet_diagnostic.CA1838.severity=error
+dotnet_diagnostic.CA2015.severity=error
+dotnet_diagnostic.CA2012.severity=error
+dotnet_diagnostic.CA2011.severity=error
+dotnet_diagnostic.CA2009.severity=error
+dotnet_diagnostic.CA2008.severity=error
+dotnet_diagnostic.CA2007.severity=warning
+dotnet_diagnostic.CA2000.severity=suggestion
+
+[project.json]
+indent_size = 2
+
+# C# files
+[*.cs]
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+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 = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+
+# avoid this. unless absolutely necessary
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+# only use var when it's obvious what the variable type is
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+
+# prefer C# premade types.
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# name all constant fields using PascalCase
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# static fields should have s_ prefix
+dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
+dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+dotnet_naming_symbols.static_fields.applicable_kinds = field
+dotnet_naming_symbols.static_fields.required_modifiers = static
+dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
+dotnet_naming_style.static_prefix_style.required_prefix = s_
+dotnet_naming_style.static_prefix_style.capitalization = camel_case
+
+# internal and private fields should be _camelCase
+dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
+dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
+dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
+dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
+dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
+dotnet_naming_style.camel_case_underscore_style.required_prefix = _
+dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
+
+# Code style defaults
+csharp_using_directive_placement = outside_namespace:suggestion
+dotnet_sort_system_directives_first = true
+csharp_prefer_braces = true:silent
+csharp_preserve_single_line_blocks = true:none
+csharp_preserve_single_line_statements = false:none
+csharp_prefer_static_local_function = true:suggestion
+csharp_prefer_simple_using_statement = false:none
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Code quality
+dotnet_style_readonly_field = true:suggestion
+dotnet_code_quality_unused_parameters = non_public:suggestion
+
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+csharp_prefer_simple_default_expression = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_constructors = true:suggestion
+csharp_style_expression_bodied_operators = true:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = true:suggestion
+
+# Pattern matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+
+# Null checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Other features
+csharp_style_prefer_index_operator = false:none
+csharp_style_prefer_range_operator = false:none
+csharp_style_pattern_local_over_anonymous_function = false:none
+
+# 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 = do_not_ignore
+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
+
+# analyzers
+dotnet_diagnostic.AvoidAsyncVoid.severity = suggestion
+
+dotnet_diagnostic.CA1000.severity = none
+dotnet_diagnostic.CA1001.severity = error
+dotnet_diagnostic.CA1009.severity = error
+dotnet_diagnostic.CA1016.severity = error
+dotnet_diagnostic.CA1030.severity = none
+dotnet_diagnostic.CA1031.severity = none
+dotnet_diagnostic.CA1033.severity = none
+dotnet_diagnostic.CA1036.severity = none
+dotnet_diagnostic.CA1049.severity = error
+dotnet_diagnostic.CA1056.severity = suggestion
+dotnet_diagnostic.CA1060.severity = error
+dotnet_diagnostic.CA1061.severity = error
+dotnet_diagnostic.CA1063.severity = error
+dotnet_diagnostic.CA1065.severity = error
+dotnet_diagnostic.CA1301.severity = error
+dotnet_diagnostic.CA1303.severity = none
+dotnet_diagnostic.CA1308.severity = none
+dotnet_diagnostic.CA1400.severity = error
+dotnet_diagnostic.CA1401.severity = error
+dotnet_diagnostic.CA1403.severity = error
+dotnet_diagnostic.CA1404.severity = error
+dotnet_diagnostic.CA1405.severity = error
+dotnet_diagnostic.CA1410.severity = error
+dotnet_diagnostic.CA1415.severity = error
+dotnet_diagnostic.CA1507.severity = error
+dotnet_diagnostic.CA1710.severity = suggestion
+dotnet_diagnostic.CA1724.severity = none
+dotnet_diagnostic.CA1810.severity = none
+dotnet_diagnostic.CA1821.severity = error
+dotnet_diagnostic.CA1900.severity = error
+dotnet_diagnostic.CA1901.severity = error
+dotnet_diagnostic.CA2000.severity = none
+dotnet_diagnostic.CA2002.severity = error
+dotnet_diagnostic.CA2007.severity = none
+dotnet_diagnostic.CA2100.severity = error
+dotnet_diagnostic.CA2101.severity = error
+dotnet_diagnostic.CA2108.severity = error
+dotnet_diagnostic.CA2111.severity = error
+dotnet_diagnostic.CA2112.severity = error
+dotnet_diagnostic.CA2114.severity = error
+dotnet_diagnostic.CA2116.severity = error
+dotnet_diagnostic.CA2117.severity = error
+dotnet_diagnostic.CA2122.severity = error
+dotnet_diagnostic.CA2123.severity = error
+dotnet_diagnostic.CA2124.severity = error
+dotnet_diagnostic.CA2126.severity = error
+dotnet_diagnostic.CA2131.severity = error
+dotnet_diagnostic.CA2132.severity = error
+dotnet_diagnostic.CA2133.severity = error
+dotnet_diagnostic.CA2134.severity = error
+dotnet_diagnostic.CA2137.severity = error
+dotnet_diagnostic.CA2138.severity = error
+dotnet_diagnostic.CA2140.severity = error
+dotnet_diagnostic.CA2141.severity = error
+dotnet_diagnostic.CA2146.severity = error
+dotnet_diagnostic.CA2147.severity = error
+dotnet_diagnostic.CA2149.severity = error
+dotnet_diagnostic.CA2200.severity = error
+dotnet_diagnostic.CA2202.severity = error
+dotnet_diagnostic.CA2207.severity = error
+dotnet_diagnostic.CA2212.severity = error
+dotnet_diagnostic.CA2213.severity = error
+dotnet_diagnostic.CA2214.severity = error
+dotnet_diagnostic.CA2216.severity = error
+dotnet_diagnostic.CA2220.severity = error
+dotnet_diagnostic.CA2229.severity = error
+dotnet_diagnostic.CA2231.severity = error
+dotnet_diagnostic.CA2232.severity = error
+dotnet_diagnostic.CA2235.severity = error
+dotnet_diagnostic.CA2236.severity = error
+dotnet_diagnostic.CA2237.severity = error
+dotnet_diagnostic.CA2238.severity = error
+dotnet_diagnostic.CA2240.severity = error
+dotnet_diagnostic.CA2241.severity = error
+dotnet_diagnostic.CA2242.severity = error
+
+dotnet_diagnostic.RCS1001.severity = error
+dotnet_diagnostic.RCS1018.severity = error
+dotnet_diagnostic.RCS1037.severity = error
+dotnet_diagnostic.RCS1055.severity = error
+dotnet_diagnostic.RCS1062.severity = error
+dotnet_diagnostic.RCS1066.severity = error
+dotnet_diagnostic.RCS1069.severity = error
+dotnet_diagnostic.RCS1071.severity = error
+dotnet_diagnostic.RCS1074.severity = error
+dotnet_diagnostic.RCS1090.severity = error
+dotnet_diagnostic.RCS1138.severity = error
+dotnet_diagnostic.RCS1139.severity = error
+dotnet_diagnostic.RCS1163.severity = suggestion
+dotnet_diagnostic.RCS1168.severity = suggestion
+dotnet_diagnostic.RCS1188.severity = error
+dotnet_diagnostic.RCS1201.severity = error
+dotnet_diagnostic.RCS1207.severity = error
+dotnet_diagnostic.RCS1211.severity = error
+dotnet_diagnostic.RCS1507.severity = error
+
+dotnet_diagnostic.SA1000.severity = error
+dotnet_diagnostic.SA1001.severity = error
+dotnet_diagnostic.SA1002.severity = error
+dotnet_diagnostic.SA1003.severity = error
+dotnet_diagnostic.SA1004.severity = error
+dotnet_diagnostic.SA1005.severity = error
+dotnet_diagnostic.SA1006.severity = error
+dotnet_diagnostic.SA1007.severity = error
+dotnet_diagnostic.SA1008.severity = error
+dotnet_diagnostic.SA1009.severity = error
+dotnet_diagnostic.SA1010.severity = error
+dotnet_diagnostic.SA1011.severity = error
+dotnet_diagnostic.SA1012.severity = error
+dotnet_diagnostic.SA1013.severity = error
+dotnet_diagnostic.SA1014.severity = error
+dotnet_diagnostic.SA1015.severity = error
+dotnet_diagnostic.SA1016.severity = error
+dotnet_diagnostic.SA1017.severity = error
+dotnet_diagnostic.SA1018.severity = error
+dotnet_diagnostic.SA1019.severity = error
+dotnet_diagnostic.SA1020.severity = error
+dotnet_diagnostic.SA1021.severity = error
+dotnet_diagnostic.SA1022.severity = error
+dotnet_diagnostic.SA1023.severity = error
+dotnet_diagnostic.SA1024.severity = error
+dotnet_diagnostic.SA1025.severity = error
+dotnet_diagnostic.SA1026.severity = error
+dotnet_diagnostic.SA1027.severity = error
+dotnet_diagnostic.SA1028.severity = error
+dotnet_diagnostic.SA1100.severity = error
+dotnet_diagnostic.SA1101.severity = none
+dotnet_diagnostic.SA1102.severity = error
+dotnet_diagnostic.SA1103.severity = error
+dotnet_diagnostic.SA1104.severity = error
+dotnet_diagnostic.SA1105.severity = error
+dotnet_diagnostic.SA1106.severity = error
+dotnet_diagnostic.SA1107.severity = error
+dotnet_diagnostic.SA1108.severity = error
+dotnet_diagnostic.SA1110.severity = error
+dotnet_diagnostic.SA1111.severity = error
+dotnet_diagnostic.SA1112.severity = error
+dotnet_diagnostic.SA1113.severity = error
+dotnet_diagnostic.SA1114.severity = error
+dotnet_diagnostic.SA1115.severity = error
+dotnet_diagnostic.SA1116.severity = error
+dotnet_diagnostic.SA1117.severity = error
+dotnet_diagnostic.SA1118.severity = error
+dotnet_diagnostic.SA1119.severity = error
+dotnet_diagnostic.SA1120.severity = error
+dotnet_diagnostic.SA1121.severity = error
+dotnet_diagnostic.SA1122.severity = error
+dotnet_diagnostic.SA1123.severity = error
+dotnet_diagnostic.SA1124.severity = error
+dotnet_diagnostic.SA1125.severity = error
+dotnet_diagnostic.SA1127.severity = error
+dotnet_diagnostic.SA1128.severity = error
+dotnet_diagnostic.SA1129.severity = error
+dotnet_diagnostic.SA1130.severity = error
+dotnet_diagnostic.SA1131.severity = error
+dotnet_diagnostic.SA1132.severity = error
+dotnet_diagnostic.SA1133.severity = error
+dotnet_diagnostic.SA1134.severity = error
+dotnet_diagnostic.SA1135.severity = error
+dotnet_diagnostic.SA1136.severity = error
+dotnet_diagnostic.SA1137.severity = error
+dotnet_diagnostic.SA1139.severity = error
+dotnet_diagnostic.SA1200.severity = none
+dotnet_diagnostic.SA1201.severity = error
+dotnet_diagnostic.SA1202.severity = error
+dotnet_diagnostic.SA1203.severity = error
+dotnet_diagnostic.SA1204.severity = error
+dotnet_diagnostic.SA1205.severity = error
+dotnet_diagnostic.SA1206.severity = error
+dotnet_diagnostic.SA1207.severity = error
+dotnet_diagnostic.SA1208.severity = error
+dotnet_diagnostic.SA1209.severity = error
+dotnet_diagnostic.SA1210.severity = error
+dotnet_diagnostic.SA1211.severity = error
+dotnet_diagnostic.SA1212.severity = error
+dotnet_diagnostic.SA1213.severity = error
+dotnet_diagnostic.SA1214.severity = error
+dotnet_diagnostic.SA1216.severity = error
+dotnet_diagnostic.SA1217.severity = error
+dotnet_diagnostic.SA1300.severity = error
+dotnet_diagnostic.SA1302.severity = error
+dotnet_diagnostic.SA1303.severity = error
+dotnet_diagnostic.SA1304.severity = error
+dotnet_diagnostic.SA1306.severity = none
+dotnet_diagnostic.SA1307.severity = error
+dotnet_diagnostic.SA1308.severity = error
+dotnet_diagnostic.SA1309.severity = none
+dotnet_diagnostic.SA1310.severity = error
+dotnet_diagnostic.SA1311.severity = none
+dotnet_diagnostic.SA1312.severity = error
+dotnet_diagnostic.SA1313.severity = error
+dotnet_diagnostic.SA1314.severity = error
+dotnet_diagnostic.SA1316.severity = none
+dotnet_diagnostic.SA1400.severity = error
+dotnet_diagnostic.SA1401.severity = error
+dotnet_diagnostic.SA1402.severity = error
+dotnet_diagnostic.SA1403.severity = error
+dotnet_diagnostic.SA1404.severity = error
+dotnet_diagnostic.SA1405.severity = error
+dotnet_diagnostic.SA1406.severity = error
+dotnet_diagnostic.SA1407.severity = error
+dotnet_diagnostic.SA1408.severity = error
+dotnet_diagnostic.SA1410.severity = error
+dotnet_diagnostic.SA1411.severity = error
+dotnet_diagnostic.SA1413.severity = none
+dotnet_diagnostic.SA1500.severity = error
+dotnet_diagnostic.SA1501.severity = error
+dotnet_diagnostic.SA1502.severity = error
+dotnet_diagnostic.SA1503.severity = error
+dotnet_diagnostic.SA1504.severity = error
+dotnet_diagnostic.SA1505.severity = none
+dotnet_diagnostic.SA1506.severity = error
+dotnet_diagnostic.SA1507.severity = error
+dotnet_diagnostic.SA1508.severity = error
+dotnet_diagnostic.SA1509.severity = error
+dotnet_diagnostic.SA1510.severity = error
+dotnet_diagnostic.SA1511.severity = error
+dotnet_diagnostic.SA1512.severity = error
+dotnet_diagnostic.SA1513.severity = error
+dotnet_diagnostic.SA1514.severity = none
+dotnet_diagnostic.SA1515.severity = error
+dotnet_diagnostic.SA1516.severity = error
+dotnet_diagnostic.SA1517.severity = error
+dotnet_diagnostic.SA1518.severity = error
+dotnet_diagnostic.SA1519.severity = error
+dotnet_diagnostic.SA1520.severity = error
+dotnet_diagnostic.SA1600.severity = error
+dotnet_diagnostic.SA1601.severity = error
+dotnet_diagnostic.SA1602.severity = error
+dotnet_diagnostic.SA1604.severity = error
+dotnet_diagnostic.SA1605.severity = error
+dotnet_diagnostic.SA1606.severity = error
+dotnet_diagnostic.SA1607.severity = error
+dotnet_diagnostic.SA1608.severity = error
+dotnet_diagnostic.SA1610.severity = error
+dotnet_diagnostic.SA1611.severity = error
+dotnet_diagnostic.SA1612.severity = error
+dotnet_diagnostic.SA1613.severity = error
+dotnet_diagnostic.SA1614.severity = error
+dotnet_diagnostic.SA1615.severity = error
+dotnet_diagnostic.SA1616.severity = error
+dotnet_diagnostic.SA1617.severity = error
+dotnet_diagnostic.SA1618.severity = error
+dotnet_diagnostic.SA1619.severity = error
+dotnet_diagnostic.SA1620.severity = error
+dotnet_diagnostic.SA1621.severity = error
+dotnet_diagnostic.SA1622.severity = error
+dotnet_diagnostic.SA1623.severity = error
+dotnet_diagnostic.SA1624.severity = error
+dotnet_diagnostic.SA1625.severity = error
+dotnet_diagnostic.SA1626.severity = error
+dotnet_diagnostic.SA1627.severity = error
+dotnet_diagnostic.SA1629.severity = error
+dotnet_diagnostic.SA1633.severity = error
+dotnet_diagnostic.SA1634.severity = error
+dotnet_diagnostic.SA1635.severity = error
+dotnet_diagnostic.SA1636.severity = error
+dotnet_diagnostic.SA1637.severity = none
+dotnet_diagnostic.SA1638.severity = none
+dotnet_diagnostic.SA1640.severity = error
+dotnet_diagnostic.SA1641.severity = error
+dotnet_diagnostic.SA1642.severity = error
+dotnet_diagnostic.SA1643.severity = error
+dotnet_diagnostic.SA1649.severity = error
+dotnet_diagnostic.SA1651.severity = error
+
+dotnet_diagnostic.SX1101.severity = error
+dotnet_diagnostic.SX1309.severity = error
+dotnet_diagnostic.SX1623.severity = none
+dotnet_diagnostic.RCS1102.severity=error
+dotnet_diagnostic.RCS1166.severity=error
+dotnet_diagnostic.RCS1078i.severity=error
+dotnet_diagnostic.RCS1248.severity=suggestion
+dotnet_diagnostic.RCS1080.severity=error
+dotnet_diagnostic.RCS1077.severity=error
+dotnet_diagnostic.CA1825.severity=error
+dotnet_diagnostic.CA1812.severity=error
+dotnet_diagnostic.CA1805.severity=error
+dotnet_diagnostic.RCS1197.severity=error
+dotnet_diagnostic.RCS1198.severity=error
+dotnet_diagnostic.RCS1231.severity=none
+dotnet_diagnostic.RCS1235.severity=error
+dotnet_diagnostic.RCS1242.severity=error
+dotnet_diagnostic.CA2016.severity=warning
+dotnet_diagnostic.CA2014.severity=error
+dotnet_diagnostic.RCS1010.severity=error
+dotnet_diagnostic.RCS1006.severity=error
+dotnet_diagnostic.RCS1005.severity=error
+dotnet_diagnostic.RCS1020.severity=error
+dotnet_diagnostic.RCS1049.severity=warning
+dotnet_diagnostic.RCS1058.severity=warning
+dotnet_diagnostic.RCS1068.severity=warning
+dotnet_diagnostic.RCS1073.severity=warning
+dotnet_diagnostic.RCS1084.severity=error
+dotnet_diagnostic.RCS1085.severity=error
+dotnet_diagnostic.RCS1105.severity=error
+dotnet_diagnostic.RCS1112.severity=error
+dotnet_diagnostic.RCS1128.severity=error
+dotnet_diagnostic.RCS1143.severity=error
+dotnet_diagnostic.RCS1171.severity=error
+dotnet_diagnostic.RCS1173.severity=error
+dotnet_diagnostic.RCS1176.severity=error
+dotnet_diagnostic.RCS1177.severity=error
+dotnet_diagnostic.RCS1179.severity=error
+dotnet_diagnostic.RCS1180.severity=warning
+dotnet_diagnostic.RCS1190.severity=error
+dotnet_diagnostic.RCS1195.severity=error
+dotnet_diagnostic.RCS1214.severity=error
+
+# C++ Files
+[*.{cpp,h,in}]
+curly_bracket_next_line = true
+indent_brace_style = Allman
+
+# Xml project files
+[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
+indent_size = 2
+
+# Xml build files
+[*.builds]
+indent_size = 2
+
+# Xml files
+[*.{xml,stylecop,resx,ruleset}]
+indent_size = 2
+
+# Xml config files
+[*.{props,targets,config,nuspec}]
+indent_size = 2
+
+# Shell scripts
+[*.sh]
+end_of_line = lf
+[*.{cmd, bat}]
+end_of_line = crlf
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..5044446
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,289 @@
+# Catch all for anything we forgot. Add rules if you get CRLF to LF warnings.
+* text=auto
+
+# Text files that should be normalized to LF in odb.
+*.cs text eol=lf diff=csharp
+*.xaml text
+*.config text
+*.c text
+*.h text
+*.cpp text
+*.hpp text
+*.sln text
+*.csproj text
+*.vcxproj text
+*.md text
+*.tt text
+*.sh text
+*.ps1 text
+*.cmd text
+*.bat text
+*.markdown text
+*.msbuild text
+# Binary files that should not be normalized or diffed
+*.png binary
+*.jpg binary
+*.gif binary
+*.ico binary
+*.rc binary
+*.pfx binary
+*.snk binary
+*.dll binary
+*.exe binary
+*.lib binary
+*.exp binary
+*.pdb binary
+*.sdf binary
+*.7z binary
+# Generated file should just use CRLF, it's fiiine
+SolutionInfo.cs text eol=crlf diff=csharp
+*.mht filter=lfs diff=lfs merge=lfs -text
+*.ppam filter=lfs diff=lfs merge=lfs -text
+*.wmv filter=lfs diff=lfs merge=lfs -text
+*.btif filter=lfs diff=lfs merge=lfs -text
+*.fla filter=lfs diff=lfs merge=lfs -text
+*.qt filter=lfs diff=lfs merge=lfs -text
+*.xlam filter=lfs diff=lfs merge=lfs -text
+*.xm filter=lfs diff=lfs merge=lfs -text
+*.djvu filter=lfs diff=lfs merge=lfs -text
+*.woff filter=lfs diff=lfs merge=lfs -text
+*.a filter=lfs diff=lfs merge=lfs -text
+*.bak filter=lfs diff=lfs merge=lfs -text
+*.lha filter=lfs diff=lfs merge=lfs -text
+*.mpg filter=lfs diff=lfs merge=lfs -text
+*.xltm filter=lfs diff=lfs merge=lfs -text
+*.eol filter=lfs diff=lfs merge=lfs -text
+*.ipa filter=lfs diff=lfs merge=lfs -text
+*.ttf filter=lfs diff=lfs merge=lfs -text
+*.uvm filter=lfs diff=lfs merge=lfs -text
+*.cmx filter=lfs diff=lfs merge=lfs -text
+*.dng filter=lfs diff=lfs merge=lfs -text
+*.xltx filter=lfs diff=lfs merge=lfs -text
+*.fli filter=lfs diff=lfs merge=lfs -text
+*.wmx filter=lfs diff=lfs merge=lfs -text
+*.jxr filter=lfs diff=lfs merge=lfs -text
+*.pyv filter=lfs diff=lfs merge=lfs -text
+*.s7z filter=lfs diff=lfs merge=lfs -text
+*.csv filter=lfs diff=lfs merge=lfs -text
+*.pptm filter=lfs diff=lfs merge=lfs -text
+*.rz filter=lfs diff=lfs merge=lfs -text
+*.wm filter=lfs diff=lfs merge=lfs -text
+*.xlsx filter=lfs diff=lfs merge=lfs -text
+*.bh filter=lfs diff=lfs merge=lfs -text
+*.dat filter=lfs diff=lfs merge=lfs -text
+*.mid filter=lfs diff=lfs merge=lfs -text
+*.mpga filter=lfs diff=lfs merge=lfs -text
+*.ogg filter=lfs diff=lfs merge=lfs -text
+*.s3m filter=lfs diff=lfs merge=lfs -text
+*.mar filter=lfs diff=lfs merge=lfs -text
+*.movie filter=lfs diff=lfs merge=lfs -text
+*.pptx filter=lfs diff=lfs merge=lfs -text
+*.dll filter=lfs diff=lfs merge=lfs -text
+*.docm filter=lfs diff=lfs merge=lfs -text
+*.m3u filter=lfs diff=lfs merge=lfs -text
+*.mov filter=lfs diff=lfs merge=lfs -text
+*.aac filter=lfs diff=lfs merge=lfs -text
+*.jar filter=lfs diff=lfs merge=lfs -text
+*.midi filter=lfs diff=lfs merge=lfs -text
+*.mobi filter=lfs diff=lfs merge=lfs -text
+*.potm filter=lfs diff=lfs merge=lfs -text
+*.woff2 filter=lfs diff=lfs merge=lfs -text
+*.cab filter=lfs diff=lfs merge=lfs -text
+*.dmg filter=lfs diff=lfs merge=lfs -text
+*.pdf filter=lfs diff=lfs merge=lfs -text
+*.war filter=lfs diff=lfs merge=lfs -text
+*.bz2 filter=lfs diff=lfs merge=lfs -text
+*.icns filter=lfs diff=lfs merge=lfs -text
+*.slk filter=lfs diff=lfs merge=lfs -text
+*.wbmp filter=lfs diff=lfs merge=lfs -text
+*.xpm filter=lfs diff=lfs merge=lfs -text
+*.xmind filter=lfs diff=lfs merge=lfs -text
+*.3g2 filter=lfs diff=lfs merge=lfs -text
+*.m4v filter=lfs diff=lfs merge=lfs -text
+*.pic filter=lfs diff=lfs merge=lfs -text
+*.uvi filter=lfs diff=lfs merge=lfs -text
+*.uvp filter=lfs diff=lfs merge=lfs -text
+*.xls filter=lfs diff=lfs merge=lfs -text
+*.jpgv filter=lfs diff=lfs merge=lfs -text
+*.mka filter=lfs diff=lfs merge=lfs -text
+*.swf filter=lfs diff=lfs merge=lfs -text
+*.uvs filter=lfs diff=lfs merge=lfs -text
+*.wav filter=lfs diff=lfs merge=lfs -text
+*.ecelp4800 filter=lfs diff=lfs merge=lfs -text
+*.mng filter=lfs diff=lfs merge=lfs -text
+*.pps filter=lfs diff=lfs merge=lfs -text
+*.whl filter=lfs diff=lfs merge=lfs -text
+*.arj filter=lfs diff=lfs merge=lfs -text
+*.lzh filter=lfs diff=lfs merge=lfs -text
+*.raw filter=lfs diff=lfs merge=lfs -text
+*.rlc filter=lfs diff=lfs merge=lfs -text
+*.sgi filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.au filter=lfs diff=lfs merge=lfs -text
+*.dcm filter=lfs diff=lfs merge=lfs -text
+*.GIF filter=lfs diff=lfs merge=lfs -text
+*.resources filter=lfs diff=lfs merge=lfs -text
+*.txz filter=lfs diff=lfs merge=lfs -text
+*.rar filter=lfs diff=lfs merge=lfs -text
+*.sil filter=lfs diff=lfs merge=lfs -text
+*.bk filter=lfs diff=lfs merge=lfs -text
+*.DS_Store filter=lfs diff=lfs merge=lfs -text
+*.ief filter=lfs diff=lfs merge=lfs -text
+*.JPEG filter=lfs diff=lfs merge=lfs -text
+*.pbm filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.sketch filter=lfs diff=lfs merge=lfs -text
+*.tbz2 filter=lfs diff=lfs merge=lfs -text
+*.nef filter=lfs diff=lfs merge=lfs -text
+*.oga filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+*.ecelp7470 filter=lfs diff=lfs merge=lfs -text
+*.xlt filter=lfs diff=lfs merge=lfs -text
+*.exe filter=lfs diff=lfs merge=lfs -text
+*.mp4 filter=lfs diff=lfs merge=lfs -text
+*.pnm filter=lfs diff=lfs merge=lfs -text
+*.ttc filter=lfs diff=lfs merge=lfs -text
+*.wdp filter=lfs diff=lfs merge=lfs -text
+*.xbm filter=lfs diff=lfs merge=lfs -text
+*.ecelp9600 filter=lfs diff=lfs merge=lfs -text
+*.pot filter=lfs diff=lfs merge=lfs -text
+*.wvx filter=lfs diff=lfs merge=lfs -text
+*.uvu filter=lfs diff=lfs merge=lfs -text
+*.asf filter=lfs diff=lfs merge=lfs -text
+*.dxf filter=lfs diff=lfs merge=lfs -text
+*.flv filter=lfs diff=lfs merge=lfs -text
+*.mdi filter=lfs diff=lfs merge=lfs -text
+*.pcx filter=lfs diff=lfs merge=lfs -text
+*.tiff filter=lfs diff=lfs merge=lfs -text
+*.bzip2 filter=lfs diff=lfs merge=lfs -text
+*.deb filter=lfs diff=lfs merge=lfs -text
+*.graffle filter=lfs diff=lfs merge=lfs -text
+*.h261 filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.ppm filter=lfs diff=lfs merge=lfs -text
+*.tif filter=lfs diff=lfs merge=lfs -text
+*.ppt filter=lfs diff=lfs merge=lfs -text
+*.fbs filter=lfs diff=lfs merge=lfs -text
+*.gzip filter=lfs diff=lfs merge=lfs -text
+*.o filter=lfs diff=lfs merge=lfs -text
+*.sub filter=lfs diff=lfs merge=lfs -text
+*.z filter=lfs diff=lfs merge=lfs -text
+*.alz filter=lfs diff=lfs merge=lfs -text
+*.BMP filter=lfs diff=lfs merge=lfs -text
+*.dotm filter=lfs diff=lfs merge=lfs -text
+*.key filter=lfs diff=lfs merge=lfs -text
+*.rgb filter=lfs diff=lfs merge=lfs -text
+*.f4v filter=lfs diff=lfs merge=lfs -text
+*.iso filter=lfs diff=lfs merge=lfs -text
+*.ai filter=lfs diff=lfs merge=lfs -text
+*.dtshd filter=lfs diff=lfs merge=lfs -text
+*.fpx filter=lfs diff=lfs merge=lfs -text
+*.shar filter=lfs diff=lfs merge=lfs -text
+*.img filter=lfs diff=lfs merge=lfs -text
+*.rmf filter=lfs diff=lfs merge=lfs -text
+*.xz filter=lfs diff=lfs merge=lfs -text
+*.eot filter=lfs diff=lfs merge=lfs -text
+*.wma filter=lfs diff=lfs merge=lfs -text
+*.cpio filter=lfs diff=lfs merge=lfs -text
+*.cr2 filter=lfs diff=lfs merge=lfs -text
+*.adp filter=lfs diff=lfs merge=lfs -text
+*.mpeg filter=lfs diff=lfs merge=lfs -text
+*.npx filter=lfs diff=lfs merge=lfs -text
+*.pdb filter=lfs diff=lfs merge=lfs -text
+*.PNG filter=lfs diff=lfs merge=lfs -text
+*.xwd filter=lfs diff=lfs merge=lfs -text
+*.egg filter=lfs diff=lfs merge=lfs -text
+*.ppsx filter=lfs diff=lfs merge=lfs -text
+*.mp4a filter=lfs diff=lfs merge=lfs -text
+*.pages filter=lfs diff=lfs merge=lfs -text
+*.baml filter=lfs diff=lfs merge=lfs -text
+*.bin filter=lfs diff=lfs merge=lfs -text
+*.class filter=lfs diff=lfs merge=lfs -text
+*.h264 filter=lfs diff=lfs merge=lfs -text
+*.lib filter=lfs diff=lfs merge=lfs -text
+*.mmr filter=lfs diff=lfs merge=lfs -text
+*.dot filter=lfs diff=lfs merge=lfs -text
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.JPG filter=lfs diff=lfs merge=lfs -text
+*.m4a filter=lfs diff=lfs merge=lfs -text
+*.so filter=lfs diff=lfs merge=lfs -text
+*.tgz filter=lfs diff=lfs merge=lfs -text
+*.thmx filter=lfs diff=lfs merge=lfs -text
+*.3ds filter=lfs diff=lfs merge=lfs -text
+*.bmp filter=lfs diff=lfs merge=lfs -text
+*.ogv filter=lfs diff=lfs merge=lfs -text
+*.xif filter=lfs diff=lfs merge=lfs -text
+*.aiff filter=lfs diff=lfs merge=lfs -text
+*.dts filter=lfs diff=lfs merge=lfs -text
+*.rip filter=lfs diff=lfs merge=lfs -text
+*.vob filter=lfs diff=lfs merge=lfs -text
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.fh filter=lfs diff=lfs merge=lfs -text
+*.flac filter=lfs diff=lfs merge=lfs -text
+*.g3 filter=lfs diff=lfs merge=lfs -text
+*.jpm filter=lfs diff=lfs merge=lfs -text
+*.ppsm filter=lfs diff=lfs merge=lfs -text
+*.potx filter=lfs diff=lfs merge=lfs -text
+*.zipx filter=lfs diff=lfs merge=lfs -text
+*.dsk filter=lfs diff=lfs merge=lfs -text
+*.ico filter=lfs diff=lfs merge=lfs -text
+*.ktx filter=lfs diff=lfs merge=lfs -text
+*.lz filter=lfs diff=lfs merge=lfs -text
+*.numbers filter=lfs diff=lfs merge=lfs -text
+*.3gp filter=lfs diff=lfs merge=lfs -text
+*.fst filter=lfs diff=lfs merge=lfs -text
+*.scpt filter=lfs diff=lfs merge=lfs -text
+*.epub filter=lfs diff=lfs merge=lfs -text
+*.rmvb filter=lfs diff=lfs merge=lfs -text
+*.webm filter=lfs diff=lfs merge=lfs -text
+*.docx filter=lfs diff=lfs merge=lfs -text
+*.pgm filter=lfs diff=lfs merge=lfs -text
+*.pya filter=lfs diff=lfs merge=lfs -text
+*.rtf filter=lfs diff=lfs merge=lfs -text
+*.smv filter=lfs diff=lfs merge=lfs -text
+*.tga filter=lfs diff=lfs merge=lfs -text
+*.cur filter=lfs diff=lfs merge=lfs -text
+*.dwg filter=lfs diff=lfs merge=lfs -text
+*.lvp filter=lfs diff=lfs merge=lfs -text
+*.pyo filter=lfs diff=lfs merge=lfs -text
+*.apk filter=lfs diff=lfs merge=lfs -text
+*.ar filter=lfs diff=lfs merge=lfs -text
+*.caf filter=lfs diff=lfs merge=lfs -text
+*.doc filter=lfs diff=lfs merge=lfs -text
+*.h263 filter=lfs diff=lfs merge=lfs -text
+*.xlsm filter=lfs diff=lfs merge=lfs -text
+*.mp3 filter=lfs diff=lfs merge=lfs -text
+*.mxu filter=lfs diff=lfs merge=lfs -text
+*.wax filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.mj2 filter=lfs diff=lfs merge=lfs -text
+*.otf filter=lfs diff=lfs merge=lfs -text
+*.udf filter=lfs diff=lfs merge=lfs -text
+*.aif filter=lfs diff=lfs merge=lfs -text
+*.lzma filter=lfs diff=lfs merge=lfs -text
+*.pyc filter=lfs diff=lfs merge=lfs -text
+*.weba filter=lfs diff=lfs merge=lfs -text
+*.webp filter=lfs diff=lfs merge=lfs -text
+*.cgm filter=lfs diff=lfs merge=lfs -text
+*.mkv filter=lfs diff=lfs merge=lfs -text
+*.ppa filter=lfs diff=lfs merge=lfs -text
+*.uvh filter=lfs diff=lfs merge=lfs -text
+*.xpi filter=lfs diff=lfs merge=lfs -text
+*.psd filter=lfs diff=lfs merge=lfs -text
+*.xlsb filter=lfs diff=lfs merge=lfs -text
+*.tbz filter=lfs diff=lfs merge=lfs -text
+*.wim filter=lfs diff=lfs merge=lfs -text
+*.ape filter=lfs diff=lfs merge=lfs -text
+*.avi filter=lfs diff=lfs merge=lfs -text
+*.dex filter=lfs diff=lfs merge=lfs -text
+*.dra filter=lfs diff=lfs merge=lfs -text
+*.dvb filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.xla filter=lfs diff=lfs merge=lfs -text
+*.fvt filter=lfs diff=lfs merge=lfs -text
+*.lzo filter=lfs diff=lfs merge=lfs -text
+*.pea filter=lfs diff=lfs merge=lfs -text
+*.ras filter=lfs diff=lfs merge=lfs -text
+*.tlz filter=lfs diff=lfs merge=lfs -text
+*.viv filter=lfs diff=lfs merge=lfs -text
+*.winmd filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..9d06ed1
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+- package-ecosystem: nuget
+ directory: "/"
+ schedule:
+ interval: monthly
+ time: "00:00"
+ open-pull-requests-limit: 20
+- package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ # Check for updates to GitHub Actions every weekday
+ interval: "monthly"
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
new file mode 100644
index 0000000..602e8f6
--- /dev/null
+++ b/.github/workflows/ci-build.yml
@@ -0,0 +1,88 @@
+name: Build
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+env:
+ configuration: Release
+ productNamespacePrefix: "ReactiveMarbles"
+
+jobs:
+ build:
+ runs-on: windows-2022
+ outputs:
+ nbgv: ${{ steps.nbgv.outputs.SemVer2 }}
+ steps:
+ - name: Get Current Visual Studio Information
+ shell: bash
+ run: |
+ dotnet tool update -g dotnet-vs
+ echo "-- About RELEASE --"
+ vs where release
+
+ - name: Update Visual Studio Latest Release
+ shell: bash
+ run: |
+ echo "-- Update RELEASE --"
+ vs update release Enterprise
+ vs modify release Enterprise +mobile +desktop +uwp +web
+ echo "-- About RELEASE Updated --"
+ vs where release
+
+ - name: Checkout
+ uses: actions/checkout@v3.5.0
+ with:
+ fetch-depth: 0
+ lfs: true
+
+ - name: Install .NET 6 & 7
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: |
+ 6.0.x
+ 7.0.x
+
+ - name: Install DotNet workloads
+ shell: bash
+ run: |
+ dotnet workload install android ios tvos macos maui maccatalyst
+
+ - name: NBGV
+ id: nbgv
+ uses: dotnet/nbgv@master
+ with:
+ setAllVars: true
+
+ - name: NuGet Restore
+ run: dotnet restore
+ working-directory: src
+
+ - name: Build
+ run: dotnet build --configuration=${{ env.configuration }} --verbosity=minimal --no-restore
+ working-directory: src
+
+ - name: Run Unit Tests and Generate Coverage
+ uses: glennawatson/coverlet-msbuild@v2.1
+ with:
+ project-files: '**/*Tests*.csproj'
+ no-build: true
+ exclude-filter: '[${{env.productNamespacePrefix}}.*.Tests.*]*'
+ include-filter: '[${{env.productNamespacePrefix}}*]*'
+ output-format: cobertura
+ configuration: ${{ env.configuration }}
+
+ - name: Pack
+ run: dotnet pack --configuration=${{ env.configuration }} --verbosity=minimal --no-restore
+ working-directory: src
+
+ - name: Upload Code Coverage
+ uses: codecov/codecov-action@v3
+
+ - name: Create NuGet Artifacts
+ uses: actions/upload-artifact@master
+ with:
+ name: nuget
+ path: '**/*.nupkg'
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
new file mode 100644
index 0000000..307b83a
--- /dev/null
+++ b/.github/workflows/lock.yml
@@ -0,0 +1,31 @@
+name: 'Lock Threads'
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+
+concurrency:
+ group: lock
+
+jobs:
+ action:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v4
+ with:
+ github-token: ${{ github.token }}
+ issue-inactive-days: '14'
+ pr-inactive-days: '14'
+ issue-comment: >
+ This issue has been automatically locked since there
+ has not been any recent activity after it was closed.
+ Please open a new issue for related bugs.
+ pr-comment: >
+ This pull request has been automatically locked since there
+ has not been any recent activity after it was closed.
+ Please open a new issue for related bugs.
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..be9814a
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,98 @@
+name: Build and Release
+
+on:
+ push:
+ branches: [ main ]
+
+env:
+ configuration: Release
+ productNamespacePrefix: "ReactiveMarbles"
+
+jobs:
+ release:
+ runs-on: windows-2022
+ environment:
+ name: release
+ outputs:
+ nbgv: ${{ steps.nbgv.outputs.SemVer2 }}
+ steps:
+ - name: Get Current Visual Studio Information
+ shell: bash
+ run: |
+ dotnet tool update -g dotnet-vs
+ echo "-- About RELEASE --"
+ vs where release
+
+ - name: Update Visual Studio Latest Release
+ shell: bash
+ run: |
+ echo "-- Update RELEASE --"
+ vs update release Enterprise
+ vs modify release Enterprise +mobile +desktop +uwp +web
+ echo "-- About RELEASE Updated --"
+ vs where release
+
+ - name: Checkout
+ uses: actions/checkout@v3.5.0
+ with:
+ fetch-depth: 0
+ lfs: true
+
+ - name: Install .NET 6 & 7
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: |
+ 6.0.x
+ 7.0.x
+
+ - name: NBGV
+ id: nbgv
+ uses: dotnet/nbgv@master
+ with:
+ setAllVars: true
+
+ - name: NuGet Restore
+ run: dotnet restore
+ working-directory: src
+
+ - name: Build
+ run: dotnet build --configuration=${{ env.configuration }} --verbosity=minimal --no-restore
+ working-directory: src
+
+ - uses: nuget/setup-nuget@v1
+ name: Setup NuGet
+
+ - name: Pack
+ run: dotnet pack --configuration=${{ env.configuration }} --verbosity=minimal --no-restore
+ working-directory: src
+
+ # Decode the base 64 encoded pfx and save the Signing_Certificate
+ - name: Sign NuGet packages
+ shell: pwsh
+ run: |
+ $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.SIGNING_CERTIFICATE }}")
+ [IO.File]::WriteAllBytes("GitHubActionsWorkflow.pfx", $pfx_cert_byte)
+ $secure_password = ConvertTo-SecureString ${{ secrets.SIGN_CERTIFICATE_PASSWORD }} –asplaintext –force
+ Import-PfxCertificate -FilePath GitHubActionsWorkflow.pfx -Password $secure_password -CertStoreLocation Cert:\CurrentUser\My
+ nuget sign -Timestamper http://timestamp.digicert.com -CertificateFingerprint ${{ secrets.SIGN_CERTIFICATE_HASH }} **/*.nupkg
+
+ - name: Changelog
+ uses: glennawatson/ChangeLog@v1
+ id: changelog
+
+ - name: Create Release
+ uses: actions/create-release@v1.1.4
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
+ with:
+ tag_name: ${{ steps.nbgv.outputs.SemVer2 }}
+ release_name: ${{ steps.nbgv.outputs.SemVer2 }}
+ body: |
+ ${{ steps.changelog.outputs.commitLog }}
+
+ - name: NuGet Push
+ env:
+ NUGET_AUTH_TOKEN: ${{ secrets.NUGET_API_KEY }}
+ SOURCE_URL: https://api.nuget.org/v3/index.json
+ run: |
+ dotnet nuget push -s ${{ env.SOURCE_URL }} -k ${{ env.NUGET_AUTH_TOKEN }} **/*.nupkg
diff --git a/images/logo.png b/images/logo.png
new file mode 100644
index 0000000..70c7e75
--- /dev/null
+++ b/images/logo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:722b25de1d0f7577e122a4d96c9d96f4019f7a34283a3612ae244a68eea7fc17
+size 47204
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 0000000..0d0e89f
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,57 @@
+
+
+ Debug;Release;Design
+ true
+ $(NoWarn);1591;1701;1702;1705;VSX1000;IDE0190;IDE1006
+ AnyCPU
+ $(MSBuildProjectName.Contains('Tests'))
+ embedded
+ Chris Pulman, Glenn Watson
+ Copyright (c) $([System.DateTime]::Now.ToString(yyyy)) ReactiveUI Association Inc
+ MIT
+ https://github.com/reactivemarbles/Navigation
+ Provides a Navigation Framework for ReactiveMarbles based projects.
+ logo.png
+ README.md
+ chrispulman;glennawatson
+ Navigation;inpc;reactive;functional
+ https://github.com/reactivemarbles/Navigation/releases
+ https://github.com/reactivemarbles/Navigation
+ git
+ true
+
+
+ true
+
+ true
+
+ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+ true
+ true
+ true
+ $(MSBuildThisFileDirectory)
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ReactiveMarbles.Navigation.sln b/src/ReactiveMarbles.Navigation.sln
new file mode 100644
index 0000000..86e10b7
--- /dev/null
+++ b/src/ReactiveMarbles.Navigation.sln
@@ -0,0 +1,100 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33530.505
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveMarbles.ViewModel.Wpf", "ViewModel.Wpf\ReactiveMarbles.ViewModel.Wpf.csproj", "{6B5A20B7-CAA7-4CBF-B48F-6EFFE9414610}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveMarbles.ViewModel.Core", "ViewModel.Core\ReactiveMarbles.ViewModel.Core.csproj", "{990C0E6D-0C09-482D-8AFA-F135EC206C43}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveMarbles.ViewModel.WinForms", "ViewModel.WinForms\ReactiveMarbles.ViewModel.WinForms.csproj", "{CD830D09-4558-403B-9504-C0CFD90FDC3B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionConfig", "SolutionConfig", "{B9D5E36A-C2F2-4D70-B5D0-797574468968}"
+ ProjectSection(SolutionItems) = preProject
+ ..\.editorconfig = ..\.editorconfig
+ ..\.gitattributes = ..\.gitattributes
+ ..\.gitignore = ..\.gitignore
+ ..\.github\workflows\ci-build.yml = ..\.github\workflows\ci-build.yml
+ Directory.Build.props = Directory.Build.props
+ ..\LICENSE = ..\LICENSE
+ ..\README.md = ..\README.md
+ ..\.github\workflows\release.yml = ..\.github\workflows\release.yml
+ stylecop.json = stylecop.json
+ ..\version.json = ..\version.json
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{6E654C37-7F3A-4E17-83C1-CA8BC9B54476}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViewModel.Wpf.Example", "ViewModel.Wpf.Example\ViewModel.Wpf.Example.csproj", "{5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViewModel.WinForms.Example", "ViewModel.WinForms.Example\ViewModel.WinForms.Example.csproj", "{67688407-5509-4158-B9C3-5C2076E4946C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveMarbles.ViewModel.MAUI", "ReactiveMarbles.ViewModel.MAUI\ReactiveMarbles.ViewModel.MAUI.csproj", "{267783AB-7239-42A1-9596-22F53B8884D1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ViewModel.MAUI.Example", "ViewModel.MAUI.Example\ViewModel.MAUI.Example.csproj", "{DE39BF42-E055-4797-9807-6AAA44089FE5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Design|Any CPU = Design|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6B5A20B7-CAA7-4CBF-B48F-6EFFE9414610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B5A20B7-CAA7-4CBF-B48F-6EFFE9414610}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B5A20B7-CAA7-4CBF-B48F-6EFFE9414610}.Design|Any CPU.ActiveCfg = Design|Any CPU
+ {6B5A20B7-CAA7-4CBF-B48F-6EFFE9414610}.Design|Any CPU.Build.0 = Design|Any CPU
+ {6B5A20B7-CAA7-4CBF-B48F-6EFFE9414610}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B5A20B7-CAA7-4CBF-B48F-6EFFE9414610}.Release|Any CPU.Build.0 = Release|Any CPU
+ {990C0E6D-0C09-482D-8AFA-F135EC206C43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {990C0E6D-0C09-482D-8AFA-F135EC206C43}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {990C0E6D-0C09-482D-8AFA-F135EC206C43}.Design|Any CPU.ActiveCfg = Design|Any CPU
+ {990C0E6D-0C09-482D-8AFA-F135EC206C43}.Design|Any CPU.Build.0 = Design|Any CPU
+ {990C0E6D-0C09-482D-8AFA-F135EC206C43}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {990C0E6D-0C09-482D-8AFA-F135EC206C43}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CD830D09-4558-403B-9504-C0CFD90FDC3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CD830D09-4558-403B-9504-C0CFD90FDC3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CD830D09-4558-403B-9504-C0CFD90FDC3B}.Design|Any CPU.ActiveCfg = Design|Any CPU
+ {CD830D09-4558-403B-9504-C0CFD90FDC3B}.Design|Any CPU.Build.0 = Design|Any CPU
+ {CD830D09-4558-403B-9504-C0CFD90FDC3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CD830D09-4558-403B-9504-C0CFD90FDC3B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E}.Design|Any CPU.ActiveCfg = Design|Any CPU
+ {5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E}.Design|Any CPU.Build.0 = Design|Any CPU
+ {5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {67688407-5509-4158-B9C3-5C2076E4946C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {67688407-5509-4158-B9C3-5C2076E4946C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {67688407-5509-4158-B9C3-5C2076E4946C}.Design|Any CPU.ActiveCfg = Design|Any CPU
+ {67688407-5509-4158-B9C3-5C2076E4946C}.Design|Any CPU.Build.0 = Design|Any CPU
+ {67688407-5509-4158-B9C3-5C2076E4946C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {67688407-5509-4158-B9C3-5C2076E4946C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {267783AB-7239-42A1-9596-22F53B8884D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {267783AB-7239-42A1-9596-22F53B8884D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {267783AB-7239-42A1-9596-22F53B8884D1}.Design|Any CPU.ActiveCfg = Design|Any CPU
+ {267783AB-7239-42A1-9596-22F53B8884D1}.Design|Any CPU.Build.0 = Design|Any CPU
+ {267783AB-7239-42A1-9596-22F53B8884D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {267783AB-7239-42A1-9596-22F53B8884D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Design|Any CPU.ActiveCfg = Design|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Design|Any CPU.Build.0 = Design|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Design|Any CPU.Deploy.0 = Design|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE39BF42-E055-4797-9807-6AAA44089FE5}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {5E67BC1D-9AD7-4DDA-B095-6EFE99AD2C2E} = {6E654C37-7F3A-4E17-83C1-CA8BC9B54476}
+ {67688407-5509-4158-B9C3-5C2076E4946C} = {6E654C37-7F3A-4E17-83C1-CA8BC9B54476}
+ {DE39BF42-E055-4797-9807-6AAA44089FE5} = {6E654C37-7F3A-4E17-83C1-CA8BC9B54476}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {489B52FF-BC1B-435B-8412-301C8A46D473}
+ EndGlobalSection
+EndGlobal
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/CoreRegistrationBuilderMixins.cs b/src/ReactiveMarbles.ViewModel.MAUI/CoreRegistrationBuilderMixins.cs
new file mode 100644
index 0000000..06c49ee
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/CoreRegistrationBuilderMixins.cs
@@ -0,0 +1,37 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Reactive.Concurrency;
+using ReactiveMarbles.Mvvm;
+
+namespace ReactiveMarbles.Locator;
+
+///
+/// CoreRegistrationBuilderMixins.
+///
+public static class CoreRegistrationBuilderMixins
+{
+ ///
+ /// Uses the WPF thread schedulers.
+ ///
+ /// The builder.
+ /// The Builder.
+ /// builder.
+ public static CoreRegistrationBuilder UseMauiThreadSchedulers(this CoreRegistrationBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+#if WINUI
+ builder.WithMainThreadScheduler(new MauiDispatcherScheduler(() => MauiWinUIScheduler.Current));
+#elif MAUIANDROID
+ builder.WithMainThreadScheduler(MauiAndroidScheduler.MainThreadScheduler);
+#elif MAUIMAC
+ builder.WithMainThreadScheduler(new MauiDispatcherScheduler(() => new MauiMacScheduler()));
+#endif
+ return builder.WithTaskPoolScheduler(TaskPoolScheduler.Default);
+ }
+}
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/NavigationShell.cs b/src/ReactiveMarbles.ViewModel.MAUI/NavigationShell.cs
new file mode 100644
index 0000000..b439586
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/NavigationShell.cs
@@ -0,0 +1,504 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using DynamicData;
+using ReactiveMarbles.Locator;
+using ReactiveMarbles.Mvvm;
+using ReactiveMarbles.ViewModel.Core;
+
+namespace ReactiveMarbles.ViewModel.MAUI;
+
+///
+/// NavigationShell.
+///
+public class NavigationShell : Shell, ISetNavigation, IViewModelRoutedViewHost, IUseNavigation
+{
+ ///
+ /// The navigate back is enabled property.
+ ///
+ public static readonly BindableProperty CanNavigateBackProperty = BindableProperty.Create(
+ nameof(CanNavigateBack),
+ typeof(bool),
+ typeof(NavigationShell),
+ false);
+
+ ///
+ /// The host name property.
+ ///
+ public static readonly BindableProperty NameProperty = BindableProperty.Create(
+ nameof(Name),
+ typeof(string),
+ typeof(NavigationShell),
+ string.Empty,
+ BindingMode.Default,
+ propertyChanged: NameChanged);
+
+ ///
+ /// The navigate back is enabled property.
+ ///
+ public static readonly BindableProperty NavigateBackIsEnabledProperty = BindableProperty.Create(
+ nameof(NavigateBackIsEnabled),
+ typeof(bool),
+ typeof(NavigationShell),
+ true);
+
+ private readonly ISubject _canNavigateBackSubject = new Subject();
+ private readonly ISubject _currentViewModel = new Subject();
+ private IRxNavBase? __currentViewModel;
+ private IAmViewFor? _currentView;
+ private IAmViewFor? _lastView;
+ private bool _navigateBack;
+ private bool _resetStack;
+ private IRxNavBase? _toViewModel;
+ private bool _userInstigated;
+ private ICoreRegistration? _coreRegistration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NavigationShell() =>
+ CurrentViewModel.Subscribe(vm =>
+ {
+ if (vm is IRxNavBase rxo && _userInstigated)
+ {
+ __currentViewModel = rxo;
+ if (!_navigateBack)
+ {
+ NavigationStack.Add(__currentViewModel);
+ }
+ else
+ {
+ // Navigate Back
+ if (NavigationStack?.Count > 1)
+ {
+ NavigationStack.Remove(NavigationStack.Last());
+ }
+ }
+ }
+
+ if (_currentView != null)
+ {
+ GotoPage();
+ }
+
+ _navigateBack = false;
+
+ CanNavigateBack = NavigationStack?.Count > 1;
+ _canNavigateBackSubject.OnNext(CanNavigateBack);
+ });
+
+ ///
+ /// Gets or sets a value indicating whether [navigate back is enabled].
+ ///
+ /// true if [navigate back is enabled]; otherwise, false.
+ public bool CanNavigateBack
+ {
+ get => (bool)GetValue(CanNavigateBackProperty);
+ set => SetValue(CanNavigateBackProperty, value);
+ }
+
+ ///
+ /// Gets the can navigate back observable.
+ ///
+ ///
+ /// The can navigate back observable.
+ ///
+ public IObservable CanNavigateBackObservable => _canNavigateBackSubject;
+
+ ///
+ /// Gets the current view model.
+ ///
+ ///
+ /// The current view model.
+ ///
+ public IObservable CurrentViewModel => _currentViewModel.Publish().RefCount();
+
+ ///
+ /// Gets or sets the name of the host.
+ ///
+ ///
+ /// The name of the host.
+ ///
+ public string Name
+ {
+ get => (string)GetValue(NameProperty);
+ set => SetValue(NameProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether [navigate back is enabled].
+ ///
+ ///
+ /// true if [navigate back is enabled]; otherwise, false.
+ ///
+ public bool NavigateBackIsEnabled
+ {
+ get => (bool)GetValue(NavigateBackIsEnabledProperty);
+ set => SetValue(NavigateBackIsEnabledProperty, value);
+ }
+
+ ///
+ /// Gets the navigation stack.
+ ///
+ ///
+ /// The navigation stack.
+ ///
+ public ObservableCollection NavigationStack { get; } = new();
+
+ ///
+ /// Gets a value indicating whether [requires setup].
+ ///
+ ///
+ /// true if [requires setup]; otherwise, false.
+ ///
+ public bool RequiresSetup => true;
+
+ ///
+ /// Clears the history.
+ ///
+ public void ClearHistory() => NavigationStack.Clear();
+
+ ///
+ /// Navigates the ViewModel contract.
+ ///
+ /// The Type.
+ /// The contract.
+ /// The parameter.
+ public void Navigate(string? contract = null, object? parameter = null)
+ where T : class, IRxNavBase => InternalNavigate(contract, parameter);
+
+ ///
+ /// Navigates the specified contract.
+ ///
+ /// The view model.
+ /// The parameter.
+ public void Navigate(IRxNavBase viewModel, object? parameter = null)
+ => InternalNavigate(viewModel, parameter);
+
+ ///
+ /// Navigates and resets.
+ ///
+ /// The Type.
+ /// The contract.
+ /// The parameter.
+ public void NavigateAndReset(string? contract = null, object? parameter = null)
+ where T : class, IRxNavBase
+ {
+ _resetStack = true;
+ InternalNavigate(contract, parameter);
+ }
+
+ ///
+ /// Navigates the and reset.
+ ///
+ /// The view model.
+ /// The parameter.
+ public void NavigateAndReset(IRxNavBase viewModel, object? parameter = null)
+ {
+ _resetStack = true;
+ InternalNavigate(viewModel, parameter);
+ }
+
+ ///
+ /// Navigates back.
+ ///
+ /// The parameter.
+ public void NavigateBack(object? parameter = null)
+ {
+ if (NavigateBackIsEnabled && CanNavigateBack && NavigationStack.Count > 1)
+ {
+ _userInstigated = true;
+ _navigateBack = true;
+
+ // Get the previous View
+ var count = NavigationStack.Count;
+ _toViewModel = NavigationStack[count - 2];
+
+ var ea = new ViewModelNavigatingEventArgs(__currentViewModel, _toViewModel, NavigationType.Back, _lastView, Name, parameter);
+ if (_currentView is INotifiyNavigation { ISetupNavigating: true })
+ {
+ ViewModelRoutedViewHostMixins.SetWhenNavigating.OnNext(ea);
+ }
+ else
+ {
+ ViewModelRoutedViewHostMixins.ResultNavigating[Name].OnNext(ea);
+ }
+ }
+
+ CanNavigateBack = NavigationStack.Count > 1;
+ _canNavigateBackSubject.OnNext(CanNavigateBack);
+ }
+
+ ///
+ /// Refreshes this instance.
+ ///
+ public void Refresh()
+ {
+ // Keep existing view
+ if (CurrentPage == null && _currentView != null)
+ {
+ GotoPage();
+ }
+
+ if (!NavigateBackIsEnabled)
+ {
+ // cleanup while Navigation Back is disabled
+ while (NavigationStack.Count > 1)
+ {
+ NavigationStack.RemoveAt(0);
+ }
+ }
+ }
+
+ ///
+ /// Setups this instance.
+ ///
+ /// NavigationShell Name not set.
+ public void Setup()
+ {
+ if (string.IsNullOrWhiteSpace(Name))
+ {
+ throw new ArgumentNullException(Name, "NavigationShell Name not set");
+ }
+
+ _coreRegistration = ServiceLocator.Current().GetService();
+
+ var navigatingEvent = Observable.FromEvent, ShellNavigatingEventArgs>(
+ eventHandler =>
+ {
+ void Handler(object? sender, ShellNavigatingEventArgs e) => eventHandler(e);
+ return Handler;
+ },
+ x => Navigating += x,
+ x => Navigating -= x);
+
+ var navigatedEvent = Observable.FromEvent, ShellNavigatedEventArgs>(
+ eventHandler =>
+ {
+ void Handler(object? sender, ShellNavigatedEventArgs e) => eventHandler(e);
+ return Handler;
+ },
+ x => Navigated += x,
+ x => Navigated -= x);
+
+ navigatingEvent.Subscribe(e =>
+ {
+ if ((e.Source == ShellNavigationSource.Pop || e.Source == ShellNavigationSource.PopToRoot) && !CanNavigateBack)
+ {
+ // Cancel navigate back
+ e.Cancel();
+ }
+
+ CanNavigateBack = NavigationStack?.Count > 1;
+ _canNavigateBackSubject.OnNext(CanNavigateBack);
+ });
+
+ navigatedEvent
+ .Subscribe(e =>
+ {
+ var navigatingForward = false;
+ try
+ {
+ var f = e.Current.Location.OriginalString;
+ Debug.WriteLine($"Current {f}");
+ if (e.Previous != null)
+ {
+ var s = e.Previous.Location.OriginalString;
+ Debug.WriteLine($"Previous {s}");
+ }
+
+ if ((e.Source == ShellNavigationSource.Pop || e.Source == ShellNavigationSource.PopToRoot) && NavigationStack.Count > 1)
+ {
+ // Navigating back
+ if (!_userInstigated)
+ {
+ if (NavigationStack.Count > 1)
+ {
+ NavigationStack.RemoveAt(NavigationStack.Count - 1);
+ }
+
+ CanNavigateBack = NavigationStack?.Count > 1;
+ _canNavigateBackSubject.OnNext(CanNavigateBack);
+ }
+ }
+
+ if (!_userInstigated && (e.Source == ShellNavigationSource.Push || e.Source == ShellNavigationSource.Insert || e.Source == ShellNavigationSource.ShellItemChanged || e.Source == ShellNavigationSource.ShellSectionChanged))
+ {
+ // navigating forward
+ navigatingForward = true;
+ }
+
+ if (CurrentPage is IAmViewFor page && !_userInstigated)
+ {
+ // don't replace view model if vm is null
+ var vm = __currentViewModel;
+ if (vm != null)
+ {
+ page.ViewModel ??= vm;
+ }
+
+ if (navigatingForward && page.ViewModel is IRxNavBase pvm)
+ {
+ NavigationStack?.Add(pvm);
+ }
+ }
+ }
+ finally
+ {
+ _userInstigated = false;
+ }
+ });
+
+ // requested should return result here
+ ViewModelRoutedViewHostMixins.ResultNavigating[Name].DistinctUntilChanged()
+ .ObserveOn(_coreRegistration.MainThreadScheduler)
+ .Subscribe(e =>
+ {
+ var fromView = _currentView as INotifiyNavigation;
+ if (fromView?.ISetupNavigating == false || fromView?.ISetupNavigating == null)
+ {
+ // No view is setup for recieving navigation notifications.
+ __currentViewModel?.WhenNavigating(e);
+ }
+
+ if (!e.Cancel)
+ {
+ var nea = new ViewModelNavigationEventArgs(__currentViewModel, _toViewModel, _navigateBack ? NavigationType.Back : NavigationType.New, e.View, Name, e.NavigationParameter);
+ var toView = e.View as INotifiyNavigation;
+ var callVmNavTo = toView == null || !toView!.ISetupNavigatedTo;
+ var callVmNavFrom = fromView == null || !fromView!.ISetupNavigatedTo;
+ var cvm = __currentViewModel;
+ _toViewModel ??= e.View?.ViewModel as IRxNavBase;
+ var tvm = _toViewModel;
+
+ if (_navigateBack)
+ {
+ if (tvm != null)
+ {
+ _currentView = ServiceLocator.Current().GetView(tvm);
+ _currentViewModel.OnNext(tvm);
+ foreach (var host in ViewModelRoutedViewHostMixins.NavigationHost.Where(x => x.Key != Name).Select(x => x.Key))
+ {
+ ViewModelRoutedViewHostMixins.NavigationHost[host].Refresh();
+ }
+ }
+ }
+ else if (tvm != null && _resetStack)
+ {
+ NavigationStack.Clear();
+ _currentViewModel.OnNext(tvm);
+ }
+ else if (tvm != null && _currentView != null)
+ {
+ _currentViewModel.OnNext(tvm);
+ }
+
+ if (toView?.ISetupNavigatedTo == true || fromView?.ISetupNavigatedFrom == true)
+ {
+ ViewModelRoutedViewHostMixins.SetWhenNavigated.OnNext(nea);
+ }
+
+ if (callVmNavTo)
+ {
+ tvm?.WhenNavigatedTo(nea, ViewModelRoutedViewHostMixins.CurrentViewDisposable[Name]);
+ }
+
+ if (callVmNavFrom)
+ {
+ cvm?.WhenNavigatedFrom(nea);
+ }
+ }
+
+ CanNavigateBack = NavigationStack?.Count > 1;
+ _canNavigateBackSubject.OnNext(CanNavigateBack);
+ _resetStack = false;
+ });
+
+ OnAppearing();
+ }
+
+ ///
+ /// Converts to page.
+ ///
+ /// The item.
+ /// A Page.
+ protected static Page? ToPage(object item) => item as Page;
+
+ private static void NameChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is NavigationShell ns)
+ {
+ ns.SetMainNavigationHost(ns);
+ }
+ }
+
+ private async void GotoPage()
+ {
+ var page = ToPage(_currentView!);
+
+ if (NavigationStack.Count == 1)
+ {
+ await Navigation.PopToRootAsync(true);
+ }
+ else if (_navigateBack)
+ {
+ await Navigation.PopAsync(true);
+ }
+ else
+ {
+ await Navigation.PushAsync(page, true);
+ }
+
+ if (CurrentPage is IAmViewFor p && __currentViewModel is not null)
+ {
+ // don't replace view model if vm is null
+ p.ViewModel = __currentViewModel;
+ }
+ }
+
+ private void InternalNavigate(string? contract, object? parameter)
+ where T : class, IRxNavBase
+ {
+ _userInstigated = true;
+ _toViewModel = ServiceLocator.Current().GetServiceWithContract(contract);
+ _lastView = _currentView;
+
+ // NOTE: This gets a new instance of the View
+ _currentView = ServiceLocator.Current().GetView(contract);
+
+ var ea = new ViewModelNavigatingEventArgs(__currentViewModel, _toViewModel, NavigationType.New, _currentView, Name, parameter);
+ if (_currentView is INotifiyNavigation { ISetupNavigating: true })
+ {
+ ViewModelRoutedViewHostMixins.SetWhenNavigating.OnNext(ea);
+ }
+ else
+ {
+ ViewModelRoutedViewHostMixins.ResultNavigating[Name].OnNext(ea);
+ }
+ }
+
+ private void InternalNavigate(IRxNavBase viewModel, object? parameter)
+ {
+ _userInstigated = true;
+ _toViewModel = viewModel;
+ _lastView = _currentView;
+
+ // NOTE: This gets a new instance of the View
+ _currentView = ServiceLocator.Current().GetView(viewModel);
+
+ var ea = new ViewModelNavigatingEventArgs(__currentViewModel, _toViewModel, NavigationType.New, _currentView, Name, parameter);
+ if (_currentView is INotifiyNavigation { ISetupNavigating: true })
+ {
+ ViewModelRoutedViewHostMixins.SetWhenNavigating.OnNext(ea);
+ }
+ else
+ {
+ ViewModelRoutedViewHostMixins.ResultNavigating[Name].OnNext(ea);
+ }
+ }
+}
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/ReactiveMarbles.ViewModel.MAUI.csproj b/src/ReactiveMarbles.ViewModel.MAUI/ReactiveMarbles.ViewModel.MAUI.csproj
new file mode 100644
index 0000000..e0efeaf
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/ReactiveMarbles.ViewModel.MAUI.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net6.0-android;net6.0-ios;net6.0-maccatalyst;net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net6.0-windows10.0.19041.0;net7.0-windows10.0.19041.0
+ enable
+ enable
+ true
+ false
+
+
+ WINUI
+
+
+ MAUIANDROID
+
+
+ MAUIMAC
+
+
+ MAUIMAC
+
+
+
+
+
+
+
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/RxContentPage{TViewModel}.cs b/src/ReactiveMarbles.ViewModel.MAUI/RxContentPage{TViewModel}.cs
new file mode 100644
index 0000000..dec3927
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/RxContentPage{TViewModel}.cs
@@ -0,0 +1,53 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveMarbles.ViewModel.Core;
+
+namespace ReactiveMarbles.ViewModel.MAUI;
+
+///
+/// This is an that is also an .
+///
+/// The type of the view model.
+///
+public class RxContentPage : ContentPage, IAmViewFor
+ where TViewModel : class, IRxNavBase
+{
+ ///
+ /// The view model bindable property.
+ ///
+ public static readonly BindableProperty ViewModelProperty = BindableProperty.Create(
+ nameof(ViewModel),
+ typeof(TViewModel),
+ typeof(RxContentPage),
+ default(TViewModel),
+ BindingMode.OneWay,
+ propertyChanged: OnViewModelChanged);
+
+ ///
+ /// Gets or sets the ViewModel to display.
+ ///
+ public TViewModel? ViewModel
+ {
+ get => (TViewModel)GetValue(ViewModelProperty);
+ set => SetValue(ViewModelProperty, value);
+ }
+
+ ///
+ object? IAmViewFor.ViewModel
+ {
+ get => ViewModel;
+ set => ViewModel = (TViewModel?)value;
+ }
+
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ ViewModel = BindingContext as TViewModel;
+ }
+
+ private static void OnViewModelChanged(BindableObject bindableObject, object oldValue, object newValue) =>
+ bindableObject.BindingContext = newValue;
+}
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/RxShellContent{TViewModel}.cs b/src/ReactiveMarbles.ViewModel.MAUI/RxShellContent{TViewModel}.cs
new file mode 100644
index 0000000..4e3d239
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/RxShellContent{TViewModel}.cs
@@ -0,0 +1,88 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveMarbles.Locator;
+using ReactiveMarbles.ViewModel.Core;
+
+namespace ReactiveMarbles.ViewModel.MAUI;
+
+///
+/// ReactiveShellContent.
+///
+/// The type of the view model.
+///
+public class RxShellContent : ShellContent
+ where TViewModel : class, IRxNavBase
+{
+ ///
+ /// The contract property.
+ ///
+ public static readonly BindableProperty ContractProperty = BindableProperty.Create(
+ nameof(Contract),
+ typeof(string),
+ typeof(RxShellContent),
+ null,
+ BindingMode.Default,
+ propertyChanged: ViewModelChanged);
+
+ ///
+ /// The view model property.
+ ///
+ public static readonly BindableProperty ViewModelProperty = BindableProperty.Create(
+ nameof(ViewModel),
+ typeof(TViewModel),
+ typeof(RxShellContent),
+ default(TViewModel),
+ BindingMode.Default,
+ propertyChanged: ViewModelChanged);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RxShellContent()
+ {
+ var view = ServiceLocator.Current().GetServiceWithContract>(Contract);
+ if (view is not null)
+ {
+ ContentTemplate = new DataTemplate(() => view);
+ }
+ }
+
+ ///
+ /// Gets or sets the view model.
+ ///
+ ///
+ /// The view model.
+ ///
+ public TViewModel? ViewModel
+ {
+ get => (TViewModel)GetValue(ViewModelProperty);
+ set => SetValue(ViewModelProperty, value);
+ }
+
+ ///
+ /// Gets or sets the contract for the view.
+ ///
+ ///
+ /// The contract.
+ ///
+ public string? Contract
+ {
+ get => (string?)GetValue(ContractProperty);
+ set => SetValue(ContractProperty, value);
+ }
+
+ private static void ViewModelChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is RxShellContent svm)
+ {
+ var view = ServiceLocator.Current().GetServiceWithContract>(svm.Contract);
+
+ if (view is not null)
+ {
+ svm.ContentTemplate = new DataTemplate(() => view);
+ }
+ }
+ }
+}
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/RxShell{TViewModel}.cs b/src/ReactiveMarbles.ViewModel.MAUI/RxShell{TViewModel}.cs
new file mode 100644
index 0000000..0a44c87
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/RxShell{TViewModel}.cs
@@ -0,0 +1,52 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveMarbles.ViewModel.Core;
+
+namespace ReactiveMarbles.ViewModel.MAUI;
+
+///
+/// ReactiveShell.
+///
+/// The type of the view model.
+///
+public class RxShell : Shell, IAmViewFor
+ where TViewModel : class, IRxNavBase
+{
+ ///
+ /// The view model bindable property.
+ ///
+ public static readonly BindableProperty ViewModelProperty = BindableProperty.Create(
+ nameof(ViewModel),
+ typeof(TViewModel),
+ typeof(RxShell),
+ default(TViewModel),
+ BindingMode.OneWay,
+ propertyChanged: OnViewModelChanged);
+
+ ///
+ /// Gets or sets the ViewModel to display.
+ ///
+ public TViewModel? ViewModel
+ {
+ get => (TViewModel)GetValue(ViewModelProperty);
+ set => SetValue(ViewModelProperty, value);
+ }
+
+ ///
+ object? IAmViewFor.ViewModel
+ {
+ get => ViewModel;
+ set => ViewModel = (TViewModel?)value;
+ }
+
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ ViewModel = BindingContext as TViewModel;
+ }
+
+ private static void OnViewModelChanged(BindableObject bindableObject, object oldValue, object newValue) => bindableObject.BindingContext = newValue;
+}
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiAndroidScheduler.cs b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiAndroidScheduler.cs
new file mode 100644
index 0000000..15ab064
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiAndroidScheduler.cs
@@ -0,0 +1,99 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+#if MAUIANDROID
+using System;
+using System.Reactive.Disposables;
+using Android.OS;
+
+namespace System.Reactive.Concurrency;
+
+///
+/// MauiAndroidScheduler is a scheduler that schedules items on a running
+/// Activity's main thread. This is the moral equivalent of
+/// DispatcherScheduler.
+///
+public class MauiAndroidScheduler : IScheduler
+{
+ private readonly Handler _handler;
+ private readonly long _looperId;
+
+ static MauiAndroidScheduler() =>
+ MainThreadScheduler = new MauiAndroidScheduler(new Handler(Looper.MainLooper!), Looper.MainLooper?.Thread?.Id);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The handler.
+ /// The thread identifier associated with handler.
+ public MauiAndroidScheduler(Handler handler, long? threadIdAssociatedWithHandler)
+ {
+ _handler = handler;
+ _looperId = threadIdAssociatedWithHandler ?? -1;
+ }
+
+ ///
+ /// Gets a common instance to avoid allocations to the MainThread for the MauiAndroidScheduler.
+ ///
+ public static IScheduler MainThreadScheduler { get; }
+
+ ///
+ public DateTimeOffset Now => DateTimeOffset.Now;
+
+ ///
+ public IDisposable Schedule(TState state, Func action)
+ {
+ var isCancelled = false;
+ var innerDisp = new SerialDisposable() { Disposable = Disposable.Empty };
+
+ _handler.Post(() =>
+ {
+ if (isCancelled)
+ {
+ return;
+ }
+
+ innerDisp.Disposable = action(this, state);
+ });
+
+ return new CompositeDisposable(
+ Disposable.Create(() => isCancelled = true),
+ innerDisp);
+ }
+
+ ///
+ public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) // TODO: Create Test
+ {
+ var isCancelled = false;
+ var innerDisp = new SerialDisposable() { Disposable = Disposable.Empty };
+
+ _handler.PostDelayed(
+ () =>
+ {
+ if (isCancelled)
+ {
+ return;
+ }
+
+ innerDisp.Disposable = action(this, state);
+ },
+ dueTime.Ticks / 10 / 1000);
+
+ return new CompositeDisposable(
+ Disposable.Create(() => isCancelled = true),
+ innerDisp);
+ }
+
+ ///
+ public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) // TODO: Create Test
+ {
+ if (dueTime <= Now)
+ {
+ return Schedule(state, action);
+ }
+
+ return Schedule(state, dueTime - Now, action);
+ }
+}
+#endif
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiDispatcherScheduler.cs b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiDispatcherScheduler.cs
new file mode 100644
index 0000000..75c1d3e
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiDispatcherScheduler.cs
@@ -0,0 +1,64 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+#if WINUI || MAUIMAC
+
+namespace System.Reactive.Concurrency;
+
+///
+/// MauiWinUIDispatcherScheduler.
+///
+public class MauiDispatcherScheduler : IScheduler
+{
+ private readonly Func _schedulerFactory;
+ private IScheduler? _scheduler;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A func which will return a new scheduler.
+ public MauiDispatcherScheduler(Func schedulerFactory)
+ {
+ _schedulerFactory = schedulerFactory;
+ AttemptToCreateScheduler();
+ }
+
+ ///
+ public DateTimeOffset Now => AttemptToCreateScheduler().Now;
+
+ ///
+ public IDisposable Schedule(TState state, Func action) =>
+ AttemptToCreateScheduler().Schedule(state, action);
+
+ ///
+ public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) => // TODO: Create Test
+ AttemptToCreateScheduler().Schedule(state, dueTime, action);
+
+ ///
+ public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) => // TODO: Create Test
+ AttemptToCreateScheduler().Schedule(state, dueTime, action);
+
+ private IScheduler AttemptToCreateScheduler()
+ {
+ if (_scheduler is not null)
+ {
+ return _scheduler;
+ }
+
+ try
+ {
+ _scheduler = _schedulerFactory();
+ return _scheduler;
+ }
+ catch (InvalidOperationException)
+ {
+ return CurrentThreadScheduler.Instance;
+ }
+ catch (ArgumentNullException)
+ {
+ return CurrentThreadScheduler.Instance;
+ }
+ }
+}
+#endif
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiMacScheduler.cs b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiMacScheduler.cs
new file mode 100644
index 0000000..00d2fff
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiMacScheduler.cs
@@ -0,0 +1,72 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+#if MAUIMAC
+
+using System;
+using System.Reactive.Disposables;
+using CoreFoundation;
+using Foundation;
+using NSAction = System.Action;
+
+namespace System.Reactive.Concurrency;
+
+///
+/// MauiMacScheduler.
+///
+public class MauiMacScheduler : IScheduler
+{
+ ///
+ public DateTimeOffset Now => DateTimeOffset.Now;
+
+ ///
+ public IDisposable Schedule(TState state, Func action)
+ {
+ var innerDisp = new SingleAssignmentDisposable();
+
+ DispatchQueue.MainQueue.DispatchAsync(new NSAction(() =>
+ {
+ if (!innerDisp.IsDisposed)
+ {
+ innerDisp.Disposable = action(this, state);
+ }
+ }));
+
+ return innerDisp;
+ }
+
+ ///
+ public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action)
+ {
+ if (dueTime <= Now)
+ {
+ return Schedule(state, action);
+ }
+
+ return Schedule(state, dueTime - Now, action);
+ }
+
+ ///
+ public IDisposable Schedule(TState state, TimeSpan dueTime, Func action)
+ {
+ var innerDisp = Disposable.Empty;
+ var isCancelled = false;
+
+ var timer = NSTimer.CreateScheduledTimer(dueTime, _ =>
+ {
+ if (!isCancelled)
+ {
+ innerDisp = action(this, state);
+ }
+ });
+
+ return Disposable.Create(() =>
+ {
+ isCancelled = true;
+ timer.Invalidate();
+ innerDisp.Dispose();
+ });
+ }
+}
+#endif
diff --git a/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiWinUIScheduler.cs b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiWinUIScheduler.cs
new file mode 100644
index 0000000..d466a8e
--- /dev/null
+++ b/src/ReactiveMarbles.ViewModel.MAUI/Schedulers/MauiWinUIScheduler.cs
@@ -0,0 +1,201 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+#if WINUI
+using System.Reactive.Disposables;
+using Microsoft.UI.Dispatching;
+
+namespace System.Reactive.Concurrency;
+
+///
+/// MauiWinUIScheduler.
+///
+///
+///
+public class MauiWinUIScheduler : LocalScheduler, ISchedulerPeriodic
+{
+ ///
+ /// Initializes a new instance of the class.
+ /// Constructs a that schedules units of work on the given .
+ ///
+ /// to schedule work on.
+ /// is null.
+ public MauiWinUIScheduler(DispatcherQueue dispatcherQueue)
+ {
+ DispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue));
+ Priority = DispatcherQueuePriority.Normal;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Constructs a DispatcherScheduler that schedules units of work on the given at the given priority.
+ ///
+ /// to schedule work on.
+ /// Priority at which units of work are scheduled.
+ /// is null.
+ public MauiWinUIScheduler(DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority)
+ {
+ DispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue));
+ Priority = priority;
+ }
+
+ ///
+ /// Gets the scheduler that schedules work on the for the current thread.
+ ///
+ public static MauiWinUIScheduler Current
+ {
+ get
+ {
+ var dispatcher = DispatcherQueue.GetForCurrentThread() ?? throw new InvalidOperationException("There is no current dispatcher thread");
+ return new MauiWinUIScheduler(dispatcher);
+ }
+ }
+
+ ///
+ /// Gets the />.
+ ///
+ public DispatcherQueue DispatcherQueue { get; }
+
+ ///
+ /// Gets the priority at which work items will be dispatched.
+ ///
+ public DispatcherQueuePriority Priority { get; }
+
+ ///
+ /// Schedules an action to be executed on the dispatcher.
+ ///
+ /// The type of the state passed to the scheduled action.
+ /// State passed to the action to be executed.
+ /// Action to be executed.
+ /// The disposable object used to cancel the scheduled action (best effort).
+ /// is null.
+ public override IDisposable Schedule(TState state, Func action)
+ {
+ if (action == null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ var d = new SingleAssignmentDisposable();
+
+ DispatcherQueue.TryEnqueue(
+ Priority,
+ () =>
+ {
+ if (!d.IsDisposed)
+ {
+ d.Disposable = action(this, state);
+ }
+ });
+
+ return d;
+ }
+
+ ///
+ /// Schedules an action to be executed after on the dispatcherQueue, using a object.
+ ///
+ /// The type of the state passed to the scheduled action.
+ /// State passed to the action to be executed.
+ /// Relative time after which to execute the action.
+ /// Action to be executed.
+ /// The disposable object used to cancel the scheduled action (best effort).
+ /// is null.
+ public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action)
+ {
+ if (action == null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ var dt = Scheduler.Normalize(dueTime);
+ if (dt.Ticks == 0)
+ {
+ return Schedule(state, action);
+ }
+
+ return ScheduleSlow(state, dt, action);
+ }
+
+ ///
+ /// Schedules a periodic piece of work on the dispatcherQueue, using a object.
+ ///
+ /// The type of the state passed to the scheduled action.
+ /// Initial state passed to the action upon the first iteration.
+ /// Period for running the work periodically.
+ /// Action to be executed, potentially updating the state.
+ /// The disposable object used to cancel the scheduled recurring action (best effort).
+ /// is null.
+ /// is less than .
+ public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action)
+ {
+ if (period < TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(period));
+ }
+
+ if (action == null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ var timer = DispatcherQueue.CreateTimer();
+
+ var state1 = state;
+
+ timer.Tick += (_, __) => state1 = action(state1);
+
+ timer.Interval = period;
+ timer.Start();
+
+ return Disposable.Create(() =>
+ {
+ var t = Interlocked.Exchange(ref timer, null);
+ if (t != null)
+ {
+ t.Stop();
+ action = static _ => _;
+ }
+ });
+ }
+
+ private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action)
+ {
+ var d = new MultipleAssignmentDisposable();
+
+ var timer = DispatcherQueue.CreateTimer();
+
+ timer.Tick += (s, e) =>
+ {
+ var t = Interlocked.Exchange(ref timer, null);
+ if (t != null)
+ {
+ try
+ {
+ d.Disposable = action(this, state);
+ }
+ finally
+ {
+ t.Stop();
+ action = static (s, t) => Disposable.Empty;
+ }
+ }
+ };
+
+ timer.Interval = dueTime;
+ timer.Start();
+
+ d.Disposable = Disposable.Create(() =>
+ {
+ var t = Interlocked.Exchange(ref timer, null);
+ if (t != null)
+ {
+ t.Stop();
+ action = static (s, t) => Disposable.Empty;
+ }
+ });
+
+ return d;
+ }
+}
+#endif
diff --git a/src/ViewModel.Core/IAmViewFor.cs b/src/ViewModel.Core/IAmViewFor.cs
new file mode 100644
index 0000000..8446542
--- /dev/null
+++ b/src/ViewModel.Core/IAmViewFor.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// IAmViewFor.
+///
+public interface IAmViewFor
+{
+ ///
+ /// Gets or sets the View Model associated with the View.
+ ///
+ object? ViewModel { get; set; }
+}
diff --git a/src/ViewModel.Core/IAmViewFor{T}.cs b/src/ViewModel.Core/IAmViewFor{T}.cs
new file mode 100644
index 0000000..c759052
--- /dev/null
+++ b/src/ViewModel.Core/IAmViewFor{T}.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// IAmViewFor.
+///
+/// The type of ViewModel.
+public interface IAmViewFor : IAmViewFor
+where T : class
+{
+ ///
+ /// Gets or sets the ViewModel corresponding to this specific View. This should be
+ /// a DependencyProperty if you're using XAML.
+ ///
+ new T? ViewModel { get; set; }
+}
diff --git a/src/ViewModel.Core/INotifiyRoutableViewModel.cs b/src/ViewModel.Core/INotifiyRoutableViewModel.cs
new file mode 100644
index 0000000..0a40572
--- /dev/null
+++ b/src/ViewModel.Core/INotifiyRoutableViewModel.cs
@@ -0,0 +1,47 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Reactive.Disposables;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// INotifiy Routable ViewModel.
+///
+///
+public interface INotifiyRoutableViewModel : Mvvm.IRxObject, IUseHostedNavigation
+{
+ ///
+ /// Gets the name.
+ ///
+ ///
+ /// The name.
+ ///
+ string? Name { get; }
+
+ ///
+ /// Raises the event.
+ ///
+ ///
+ /// The instance containing the event data.
+ ///
+ void WhenNavigatedFrom(IViewModelNavigationEventArgs e);
+
+ ///
+ /// Raises the event.
+ ///
+ ///
+ /// The instance containing the event data.
+ ///
+ /// The disposables.
+ void WhenNavigatedTo(IViewModelNavigationEventArgs e, CompositeDisposable disposables);
+
+ ///
+ /// Raises the event.
+ ///
+ ///
+ /// The instance containing the event data.
+ ///
+ void WhenNavigating(IViewModelNavigatingEventArgs e);
+}
diff --git a/src/ViewModel.Core/IRxNavBase.cs b/src/ViewModel.Core/IRxNavBase.cs
new file mode 100644
index 0000000..7b54971
--- /dev/null
+++ b/src/ViewModel.Core/IRxNavBase.cs
@@ -0,0 +1,15 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Reactive.Disposables;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// interface for RxBase.
+///
+///
+public interface IRxNavBase : INotifiyRoutableViewModel, ICancelable, IAmBuilt
+{
+}
diff --git a/src/ViewModel.Core/IViewModelRoutedViewHost.cs b/src/ViewModel.Core/IViewModelRoutedViewHost.cs
new file mode 100644
index 0000000..c581342
--- /dev/null
+++ b/src/ViewModel.Core/IViewModelRoutedViewHost.cs
@@ -0,0 +1,122 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Collections.ObjectModel;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// IViewModel Routed ViewHost.
+///
+public interface IViewModelRoutedViewHost
+{
+ ///
+ /// Gets the navigation stack.
+ ///
+ ///
+ /// The navigation stack.
+ ///
+ ObservableCollection NavigationStack { get; }
+
+ ///
+ /// Gets the current view model.
+ ///
+ ///
+ /// The current view model.
+ ///
+ IObservable CurrentViewModel { get; }
+
+ ///
+ /// Gets or sets a value indicating whether [navigate back is enabled].
+ ///
+ ///
+ /// true if [navigate back is enabled]; otherwise, false.
+ ///
+ bool CanNavigateBack { get; set; }
+
+ ///
+ /// Gets the can navigate back observable.
+ ///
+ ///
+ /// The can navigate back observable.
+ ///
+ IObservable CanNavigateBackObservable { get; }
+
+ ///
+ /// Gets or sets a value indicating whether [navigate back is enabled].
+ ///
+ ///
+ /// true if [navigate back is enabled]; otherwise, false.
+ ///
+ bool NavigateBackIsEnabled { get; set; }
+
+ ///
+ /// Gets or sets the name of the host.
+ ///
+ ///
+ /// The name of the host.
+ ///
+ string Name { get; set; }
+
+ ///
+ /// Gets a value indicating whether [requires setup].
+ ///
+ ///
+ /// true if [requires setup]; otherwise, false.
+ ///
+ bool RequiresSetup { get; }
+
+ ///
+ /// Clears the history.
+ ///
+ void ClearHistory();
+
+ ///
+ /// Setups this instance.
+ ///
+ void Setup();
+
+ ///
+ /// Navigates the specified contract.
+ ///
+ /// The Type.
+ /// The contract.
+ /// The parameter.
+ void Navigate(string? contract = null, object? parameter = null)
+ where T : class, IRxNavBase;
+
+ ///
+ /// Navigates the specified contract.
+ ///
+ /// The view model.
+ /// The parameter.
+ void Navigate(IRxNavBase viewModel, object? parameter = null);
+
+ ///
+ /// Navigates the and reset.
+ ///
+ /// The Type.
+ /// The contract.
+ /// The parameter.
+ void NavigateAndReset(string? contract = null, object? parameter = null)
+ where T : class, IRxNavBase;
+
+ ///
+ /// Navigates the and reset.
+ ///
+ /// The view model.
+ /// The parameter.
+ void NavigateAndReset(IRxNavBase viewModel, object? parameter = null);
+
+ ///
+ /// Navigates the back.
+ ///
+ /// The parameter.
+ void NavigateBack(object? parameter = null);
+
+ ///
+ /// Refreshes this instance.
+ ///
+ void Refresh();
+}
diff --git a/src/ViewModel.Core/MagicInterfaces/IAmBuilt.cs b/src/ViewModel.Core/MagicInterfaces/IAmBuilt.cs
new file mode 100644
index 0000000..f579c82
--- /dev/null
+++ b/src/ViewModel.Core/MagicInterfaces/IAmBuilt.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// IAmBuilt.
+///
+public interface IAmBuilt
+{
+}
diff --git a/src/ViewModel.Core/MagicInterfaces/INotifiyNavigation.cs b/src/ViewModel.Core/MagicInterfaces/INotifiyNavigation.cs
new file mode 100644
index 0000000..e9aa926
--- /dev/null
+++ b/src/ViewModel.Core/MagicInterfaces/INotifiyNavigation.cs
@@ -0,0 +1,46 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Reactive.Disposables;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// INotifiy Navigation.
+///
+///
+public interface INotifiyNavigation : ICancelable
+{
+ ///
+ /// Gets or sets a value indicating whether [i setup navigated to].
+ ///
+ ///
+ /// true if [i setup navigated to]; otherwise, false.
+ ///
+ bool ISetupNavigatedTo { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether [i setup navigated from].
+ ///
+ ///
+ /// true if [i setup navigated from]; otherwise, false.
+ ///
+ bool ISetupNavigatedFrom { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether [i setup navigating].
+ ///
+ ///
+ /// true if [i setup navigating]; otherwise, false.
+ ///
+ bool ISetupNavigating { get; set; }
+
+ ///
+ /// Gets the clean up.
+ ///
+ ///
+ /// The clean up.
+ ///
+ CompositeDisposable CleanUp { get; }
+}
diff --git a/src/ViewModel.Core/MagicInterfaces/ISetNavigation.cs b/src/ViewModel.Core/MagicInterfaces/ISetNavigation.cs
new file mode 100644
index 0000000..8505e8a
--- /dev/null
+++ b/src/ViewModel.Core/MagicInterfaces/ISetNavigation.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// Enables setting of ViewModelRoutedViewHost.
+///
+public interface ISetNavigation
+{
+ ///
+ /// Gets the name.
+ ///
+ ///
+ /// The name.
+ ///
+ string Name { get; }
+}
diff --git a/src/ViewModel.Core/MagicInterfaces/IUseHostedNavigation.cs b/src/ViewModel.Core/MagicInterfaces/IUseHostedNavigation.cs
new file mode 100644
index 0000000..b84c762
--- /dev/null
+++ b/src/ViewModel.Core/MagicInterfaces/IUseHostedNavigation.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// Enables Navigation commands specified by host name.
+///
+public interface IUseHostedNavigation
+{
+}
diff --git a/src/ViewModel.Core/MagicInterfaces/IUseNavigation.cs b/src/ViewModel.Core/MagicInterfaces/IUseNavigation.cs
new file mode 100644
index 0000000..52adf2e
--- /dev/null
+++ b/src/ViewModel.Core/MagicInterfaces/IUseNavigation.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// I Use Navigation.
+///
+public interface IUseNavigation
+{
+ ///
+ /// Gets the name.
+ ///
+ ///
+ /// The name.
+ ///
+ string Name { get; }
+}
diff --git a/src/ViewModel.Core/NavigationEvents/IViewModelNavigatingEventArgs.cs b/src/ViewModel.Core/NavigationEvents/IViewModelNavigatingEventArgs.cs
new file mode 100644
index 0000000..06972b4
--- /dev/null
+++ b/src/ViewModel.Core/NavigationEvents/IViewModelNavigatingEventArgs.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// IView Model Navigating EventArgs.
+///
+public interface IViewModelNavigatingEventArgs : IViewModelNavigationEventArgs
+{
+ ///
+ /// Gets or sets a value indicating whether this is cancel.
+ ///
+ ///
+ /// true if cancel; otherwise, false.
+ ///
+ bool Cancel { get; set; }
+}
diff --git a/src/ViewModel.Core/NavigationEvents/IViewModelNavigationBaseEventArgs.cs b/src/ViewModel.Core/NavigationEvents/IViewModelNavigationBaseEventArgs.cs
new file mode 100644
index 0000000..f251b44
--- /dev/null
+++ b/src/ViewModel.Core/NavigationEvents/IViewModelNavigationBaseEventArgs.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// IView Model Navigation Base Event Args.
+///
+public interface IViewModelNavigationBaseEventArgs
+{
+ ///
+ /// Gets from.
+ ///
+ ///
+ /// From.
+ ///
+ IRxNavBase? From { get; }
+
+ ///
+ /// Gets the navigation parameter.
+ ///
+ ///
+ /// The navigation parameter.
+ ///
+ object? NavigationParameter { get; }
+
+ ///
+ /// Gets to.
+ ///
+ ///
+ /// To.
+ ///
+ IRxNavBase? To { get; }
+}
diff --git a/src/ViewModel.Core/NavigationEvents/IViewModelNavigationEventArgs.cs b/src/ViewModel.Core/NavigationEvents/IViewModelNavigationEventArgs.cs
new file mode 100644
index 0000000..a3a87fe
--- /dev/null
+++ b/src/ViewModel.Core/NavigationEvents/IViewModelNavigationEventArgs.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// I View Model Navigation EventArgs.
+///
+public interface IViewModelNavigationEventArgs : IViewModelNavigationBaseEventArgs
+{
+ ///
+ /// Gets or sets the name of the host.
+ ///
+ ///
+ /// The name of the host.
+ ///
+ string HostName { get; set; }
+
+ ///
+ /// Gets the type of the navigation.
+ ///
+ ///
+ /// The type of the navigation.
+ ///
+ NavigationType NavigationType { get; }
+
+ ///
+ /// Gets or sets the view.
+ ///
+ ///
+ /// The view.
+ ///
+ IAmViewFor? View { get; set; }
+}
diff --git a/src/ViewModel.Core/NavigationEvents/ViewModelNavigatingEventArgs.cs b/src/ViewModel.Core/NavigationEvents/ViewModelNavigatingEventArgs.cs
new file mode 100644
index 0000000..2969923
--- /dev/null
+++ b/src/ViewModel.Core/NavigationEvents/ViewModelNavigatingEventArgs.cs
@@ -0,0 +1,36 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// View Model Navigating Event Args.
+///
+[DataContract]
+public class ViewModelNavigatingEventArgs : ViewModelNavigationEventArgs, IViewModelNavigatingEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// From.
+ /// To.
+ /// Type of the nav.
+ /// The view.
+ /// The hostName.
+ /// The parmeter.
+ public ViewModelNavigatingEventArgs(IRxNavBase? from, IRxNavBase? to, NavigationType navType, IAmViewFor? view, string hostName, object? parmeter = null)
+ : base(from, to, navType, view, hostName, parmeter)
+ {
+ }
+
+ ///
+ /// Gets or sets a value indicating whether this
+ /// is canceled.
+ ///
+ /// true if cancel; otherwise, false.
+ [DataMember]
+ public bool Cancel { get; set; }
+}
diff --git a/src/ViewModel.Core/NavigationEvents/ViewModelNavigationBaseEventArgs.cs b/src/ViewModel.Core/NavigationEvents/ViewModelNavigationBaseEventArgs.cs
new file mode 100644
index 0000000..561f919
--- /dev/null
+++ b/src/ViewModel.Core/NavigationEvents/ViewModelNavigationBaseEventArgs.cs
@@ -0,0 +1,37 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// View Model Navigation Base Event Args.
+///
+///
+[DataContract]
+public abstract class ViewModelNavigationBaseEventArgs
+ : EventArgs, IViewModelNavigationBaseEventArgs
+{
+ ///
+ /// Gets or sets where is Navigating from.
+ ///
+ /// From.
+ [DataMember]
+ public IRxNavBase? From { get; protected set; }
+
+ ///
+ /// Gets or sets the navigation parameter.
+ ///
+ /// The navigation parameter.
+ [DataMember]
+ public object? NavigationParameter { get; protected set; }
+
+ ///
+ /// Gets or sets where is Navigating to.
+ ///
+ /// To.
+ [DataMember]
+ public IRxNavBase? To { get; protected set; }
+}
diff --git a/src/ViewModel.Core/NavigationEvents/ViewModelNavigationEventArgs.cs b/src/ViewModel.Core/NavigationEvents/ViewModelNavigationEventArgs.cs
new file mode 100644
index 0000000..142115f
--- /dev/null
+++ b/src/ViewModel.Core/NavigationEvents/ViewModelNavigationEventArgs.cs
@@ -0,0 +1,54 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Runtime.Serialization;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// View Model Navigation EventArgs.
+///
+[DataContract]
+public class ViewModelNavigationEventArgs : ViewModelNavigationBaseEventArgs, IViewModelNavigationEventArgs
+{
+ /// Initializes a new instance of the class.
+ /// From.
+ /// To.
+ /// Type of the nav.
+ /// The view.
+ /// The Hostname.
+ /// The parmeter.
+ public ViewModelNavigationEventArgs(IRxNavBase? from, IRxNavBase? to, NavigationType navType, IAmViewFor? view, string hostName, object? parmeter = null)
+ {
+ From = from;
+ To = to;
+ View = view;
+ NavigationType = navType;
+ NavigationParameter = parmeter;
+ HostName = hostName;
+ }
+
+ ///
+ /// Gets or sets the name of the host.
+ ///
+ ///
+ /// The name of the host.
+ ///
+ [DataMember]
+ public string HostName { get; set; }
+
+ ///
+ /// Gets or sets the type of the navigation.
+ ///
+ /// The type of the navigation.
+ [DataMember]
+ public NavigationType NavigationType { get; protected set; }
+
+ ///
+ /// Gets or sets the view.
+ ///
+ /// The view.
+ [DataMember]
+ public IAmViewFor? View { get; set; }
+}
diff --git a/src/ViewModel.Core/NavigationType.cs b/src/ViewModel.Core/NavigationType.cs
new file mode 100644
index 0000000..5841ac3
--- /dev/null
+++ b/src/ViewModel.Core/NavigationType.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// Identifies the types of navigation that are supported.
+///
+public enum NavigationType
+{
+ ///
+ /// Navigating to new content.
+ ///
+ New = 0,
+
+ ///
+ /// Navigating back in the back navigation history.
+ ///
+ Back = 1,
+
+ ///
+ /// Reloading the current content.
+ ///
+ Refresh = 2
+}
diff --git a/src/ViewModel.Core/ReactiveMarbles.ViewModel.Core.csproj b/src/ViewModel.Core/ReactiveMarbles.ViewModel.Core.csproj
new file mode 100644
index 0000000..d2ea1ac
--- /dev/null
+++ b/src/ViewModel.Core/ReactiveMarbles.ViewModel.Core.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0;net6.0;net6.0-android;net6.0-ios;net6.0-tvos;net6.0-macos;net7.0;net7.0-android;net7.0-ios;net7.0-tvos;net7.0-macos
+ $(TargetFrameworks);net462;net472;net6.0-windows10.0.19041.0;net7.0-windows10.0.19041.0
+ enable
+ false
+ latest
+ enable
+
+
+
+
+
+
+
diff --git a/src/ViewModel.Core/RxNavBase.cs b/src/ViewModel.Core/RxNavBase.cs
new file mode 100644
index 0000000..9ec8e9f
--- /dev/null
+++ b/src/ViewModel.Core/RxNavBase.cs
@@ -0,0 +1,84 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Reactive.Disposables;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// Rx Object.
+///
+///
+///
+public abstract class RxNavBase : Mvvm.RxObject, IRxNavBase
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected RxNavBase()
+ {
+ }
+
+ ///
+ /// Gets the URL path segment.
+ ///
+ ///
+ /// The URL path segment.
+ ///
+ public string? Name => GetType().FullName;
+
+ ///
+ /// Gets a value indicating whether this instance is disposed.
+ ///
+ /// true if this instance is disposed; otherwise, false.
+ public bool IsDisposed => Disposables?.IsDisposed == true;
+
+ ///
+ /// Gets the disposables.
+ ///
+ ///
+ /// The disposables.
+ ///
+ protected CompositeDisposable Disposables { get; } = new CompositeDisposable();
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting
+ /// unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ public virtual void WhenNavigatedFrom(IViewModelNavigationEventArgs e)
+ {
+ }
+
+ ///
+ public virtual void WhenNavigatedTo(IViewModelNavigationEventArgs e, CompositeDisposable disposables)
+ {
+ }
+
+ ///
+ public virtual void WhenNavigating(IViewModelNavigatingEventArgs e)
+ {
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ ///
+ /// true to release both managed and unmanaged resources; false to release only
+ /// unmanaged resources.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!IsDisposed && disposing)
+ {
+ Disposables?.Dispose();
+ }
+ }
+}
diff --git a/src/ViewModel.Core/RxObjectMixins.cs b/src/ViewModel.Core/RxObjectMixins.cs
new file mode 100644
index 0000000..f56e170
--- /dev/null
+++ b/src/ViewModel.Core/RxObjectMixins.cs
@@ -0,0 +1,34 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using ReactiveMarbles.Locator;
+
+namespace ReactiveMarbles.ViewModel.Core;
+
+///
+/// RxObjectMixins.
+///
+public static class RxObjectMixins
+{
+ private static readonly ReplaySubject _buildCompleteSubject = new(1);
+
+ ///
+ /// Sets the IOC container build complete, Execute this once after completion of IOC registrations.
+ ///
+ /// The dummy.
+#pragma warning disable RCS1175 // Unused 'this' parameter.
+ public static void SetupComplete(this IEditServices dummy) => _buildCompleteSubject.OnNext(Unit.Default);
+
+ ///
+ /// Gets the build complete.
+ ///
+ /// The dummy.
+ /// The action.
+ /// The build complete.
+ public static void BuildComplete(this IAmBuilt dummy, Action action) => _buildCompleteSubject.Subscribe(_ => action());
+#pragma warning restore RCS1175 // Unused 'this' parameter.
+}
diff --git a/src/ViewModel.Core/ServiceLocatorMixins.cs b/src/ViewModel.Core/ServiceLocatorMixins.cs
new file mode 100644
index 0000000..f8a8621
--- /dev/null
+++ b/src/ViewModel.Core/ServiceLocatorMixins.cs
@@ -0,0 +1,123 @@
+// Copyright (c) 2019-2023 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveMarbles.ViewModel.Core;
+
+namespace ReactiveMarbles.Locator;
+
+///
+/// ServiceLocatorMixins.
+///
+public static class ServiceLocatorMixins
+{
+ ///
+ /// Adds the navigation view.
+ ///
+ /// The type of the view.
+ /// The type of the view model.
+ /// The service locator.
+ public static void AddNavigationView(this IServiceLocator serviceLocator)
+ where TView : class, IAmViewFor, new()
+ where TViewModel : class, IRxNavBase
+ {
+ if (serviceLocator == null)
+ {
+ throw new ArgumentNullException(nameof(serviceLocator));
+ }
+
+ serviceLocator.AddService