Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d512963b5 | |||
| 98d6d61f16 | |||
| 5fd97deada | |||
| c11b361655 | |||
| bb5aac2944 | |||
| 76eb71946f | |||
| 544b0c9591 | |||
| 83118103d9 | |||
| 5ecf6c7537 | |||
| 7540b6e6ad | |||
| 8937332942 | |||
| a0345cad94 | |||
| 6b4bffe5ec | |||
| 093679c9bb |
247
.editorconfig
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
# Entfernen Sie die folgende Zeile, wenn Sie EDITORCONFIG-Einstellungen von höheren Verzeichnissen vererben möchten.
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# C#-Dateien
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
#### Wichtige EditorConfig-Optionen ####
|
||||||
|
|
||||||
|
# Einzüge und Abstände
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = tab
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
# Einstellungen für neue Zeilen
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
#### .NET Codeaktionen ####
|
||||||
|
|
||||||
|
# Typmitglied
|
||||||
|
dotnet_hide_advanced_members = false
|
||||||
|
dotnet_member_insertion_location = with_other_members_of_the_same_kind
|
||||||
|
dotnet_property_generation_behavior = prefer_throwing_properties
|
||||||
|
|
||||||
|
# Symbolsuche
|
||||||
|
dotnet_search_reference_assemblies = true
|
||||||
|
|
||||||
|
#### .NET-Codierungskonventionen ####
|
||||||
|
|
||||||
|
# Using-Direktiven organisieren
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = false
|
||||||
|
file_header_template = unset
|
||||||
|
|
||||||
|
# this.- und Me.-Einstellungen
|
||||||
|
dotnet_style_qualification_for_event = false
|
||||||
|
dotnet_style_qualification_for_field = false
|
||||||
|
dotnet_style_qualification_for_method = false
|
||||||
|
dotnet_style_qualification_for_property = false
|
||||||
|
|
||||||
|
# Einstellungen für Sprachschlüsselwörter und BCL-Typen
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||||
|
dotnet_style_predefined_type_for_member_access = true
|
||||||
|
|
||||||
|
# Einstellungen für Klammern
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||||
|
|
||||||
|
# Einstellungen für Modifizierer
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||||
|
|
||||||
|
# Einstellungen für Ausdrucksebene
|
||||||
|
dotnet_prefer_system_hash_code = true
|
||||||
|
dotnet_style_coalesce_expression = true
|
||||||
|
dotnet_style_collection_initializer = true
|
||||||
|
dotnet_style_explicit_tuple_names = true
|
||||||
|
dotnet_style_namespace_match_folder = true
|
||||||
|
dotnet_style_null_propagation = true
|
||||||
|
dotnet_style_object_initializer = true
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_prefer_auto_properties = true
|
||||||
|
dotnet_style_prefer_collection_expression = when_types_loosely_match
|
||||||
|
dotnet_style_prefer_compound_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true
|
||||||
|
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true
|
||||||
|
|
||||||
|
# Einstellungen für Felder
|
||||||
|
dotnet_style_readonly_field = true
|
||||||
|
|
||||||
|
# Einstellungen für Parameter
|
||||||
|
dotnet_code_quality_unused_parameters = all
|
||||||
|
|
||||||
|
# Unterdrückungseinstellungen
|
||||||
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
|
||||||
|
# Einstellungen für neue Zeilen
|
||||||
|
dotnet_style_allow_multiple_blank_lines_experimental = true
|
||||||
|
dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||||
|
|
||||||
|
#### C#-Codierungskonventionen ####
|
||||||
|
|
||||||
|
# Var-Einstellungen
|
||||||
|
csharp_style_var_elsewhere = false
|
||||||
|
csharp_style_var_for_built_in_types = false
|
||||||
|
csharp_style_var_when_type_is_apparent = false
|
||||||
|
|
||||||
|
# Ausdruckskörpermember
|
||||||
|
csharp_style_expression_bodied_accessors = true
|
||||||
|
csharp_style_expression_bodied_constructors = false
|
||||||
|
csharp_style_expression_bodied_indexers = true
|
||||||
|
csharp_style_expression_bodied_lambdas = true
|
||||||
|
csharp_style_expression_bodied_local_functions = false
|
||||||
|
csharp_style_expression_bodied_methods = false
|
||||||
|
csharp_style_expression_bodied_operators = false
|
||||||
|
csharp_style_expression_bodied_properties = true
|
||||||
|
|
||||||
|
# Einstellungen für den Musterabgleich
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||||
|
csharp_style_prefer_extended_property_pattern = true
|
||||||
|
csharp_style_prefer_not_pattern = true
|
||||||
|
csharp_style_prefer_pattern_matching = true
|
||||||
|
csharp_style_prefer_switch_expression = true
|
||||||
|
|
||||||
|
# Einstellungen für NULL-Überprüfung
|
||||||
|
csharp_style_conditional_delegate_call = true
|
||||||
|
|
||||||
|
# Einstellungen für Modifizierer
|
||||||
|
csharp_prefer_static_anonymous_function = true
|
||||||
|
csharp_prefer_static_local_function = true
|
||||||
|
csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async
|
||||||
|
csharp_style_prefer_readonly_struct = true
|
||||||
|
csharp_style_prefer_readonly_struct_member = true
|
||||||
|
|
||||||
|
# Einstellungen für Codeblöcke
|
||||||
|
csharp_prefer_braces = true
|
||||||
|
csharp_prefer_simple_using_statement = true
|
||||||
|
csharp_prefer_system_threading_lock = true
|
||||||
|
csharp_style_namespace_declarations = block_scoped
|
||||||
|
csharp_style_prefer_method_group_conversion = true
|
||||||
|
csharp_style_prefer_primary_constructors = true
|
||||||
|
csharp_style_prefer_simple_property_accessors = true
|
||||||
|
csharp_style_prefer_top_level_statements = true
|
||||||
|
|
||||||
|
# Einstellungen für Ausdrucksebene
|
||||||
|
csharp_prefer_simple_default_expression = true
|
||||||
|
csharp_style_deconstructed_variable_declaration = true
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||||
|
csharp_style_inlined_variable_declaration = true
|
||||||
|
csharp_style_prefer_implicitly_typed_lambda_expression = true
|
||||||
|
csharp_style_prefer_index_operator = true
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true
|
||||||
|
csharp_style_prefer_range_operator = true
|
||||||
|
csharp_style_prefer_tuple_swap = true
|
||||||
|
csharp_style_prefer_unbound_generic_type_in_nameof = true
|
||||||
|
csharp_style_prefer_utf8_string_literals = true
|
||||||
|
csharp_style_throw_expression = true
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||||
|
|
||||||
|
# Einstellungen für using-Anweisungen
|
||||||
|
csharp_using_directive_placement = outside_namespace
|
||||||
|
|
||||||
|
# Einstellungen für neue Zeilen
|
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||||
|
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
|
||||||
|
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
|
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
|
||||||
|
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||||
|
|
||||||
|
#### C#-Formatierungsregeln ####
|
||||||
|
|
||||||
|
# Einstellungen für neue Zeilen
|
||||||
|
csharp_new_line_before_catch = false
|
||||||
|
csharp_new_line_before_else = false
|
||||||
|
csharp_new_line_before_finally = false
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = none
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
# Einstellungen für Einrückung
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
# Einstellungen für Abstände
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
# Umbrucheinstellungen
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
|
|
||||||
|
#### Benennungsstile ####
|
||||||
|
|
||||||
|
# Benennungsregeln
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
# Symbolspezifikationen
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
# Benennungsstile
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
2
.gitignore
vendored
@@ -396,3 +396,5 @@ FodyWeavers.xsd
|
|||||||
|
|
||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
/.idea
|
||||||
|
/var
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,4 +1,5 @@
|
|||||||
<?xml version = "1.0" encoding = "UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:local="clr-namespace:Jugenddienst_Stunden"
|
xmlns:local="clr-namespace:Jugenddienst_Stunden"
|
||||||
|
|||||||
@@ -4,12 +4,11 @@
|
|||||||
/// Die Hauptanwendungsklasse.
|
/// Die Hauptanwendungsklasse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class App : Application {
|
public partial class App : Application {
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initialisiert eine neue Instanz der <see cref="App"/>-Klasse.
|
||||||
/// Initialisiert eine neue Instanz der <see cref="App"/>-Klasse.
|
/// </summary>
|
||||||
/// </summary>
|
public App() {
|
||||||
public App() {
|
InitializeComponent();
|
||||||
InitializeComponent();
|
MainPage = new AppShell();
|
||||||
MainPage = new AppShell();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<Shell
|
<Shell
|
||||||
x:Class="Jugenddienst_Stunden.AppShell"
|
x:Class="Jugenddienst_Stunden.AppShell"
|
||||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
@@ -10,7 +11,8 @@
|
|||||||
<ShellContent
|
<ShellContent
|
||||||
Title="Stunden"
|
Title="Stunden"
|
||||||
ContentTemplate="{DataTemplate views:StundenPage}"
|
ContentTemplate="{DataTemplate views:StundenPage}"
|
||||||
Icon="{OnPlatform 'icon_watch.png', iOS='icon_watch_ios.png', MacCatalyst='icon_watch_ios.png'}" Route="StundenPage" />
|
Icon="{OnPlatform 'icon_watch.png', iOS='icon_watch_ios.png', MacCatalyst='icon_watch_ios.png'}"
|
||||||
|
Route="StundenPage" />
|
||||||
|
|
||||||
<ShellContent
|
<ShellContent
|
||||||
Title="Notizen"
|
Title="Notizen"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
namespace Jugenddienst_Stunden;
|
namespace Jugenddienst_Stunden;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AppShell.xaml.cs
|
/// AppShell.xaml.cs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -13,7 +14,7 @@ public partial class AppShell : Shell {
|
|||||||
Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage));
|
Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage));
|
||||||
Routing.RegisterRoute(nameof(Views.StundePage), typeof(Views.StundePage));
|
Routing.RegisterRoute(nameof(Views.StundePage), typeof(Views.StundePage));
|
||||||
|
|
||||||
|
//Muss ich die registrieren?
|
||||||
|
Routing.RegisterRoute(nameof(Views.LoginPage), typeof(Views.LoginPage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,21 @@
|
|||||||
|
|
||||||
/// Gib true zurück, wenn die Collection Werte enthält
|
/// Gib true zurück, wenn die Collection Werte enthält
|
||||||
namespace Jugenddienst_Stunden.Converter {
|
namespace Jugenddienst_Stunden.Converter {
|
||||||
internal class CollectionVisibilityConverter : IValueConverter {
|
internal class CollectionVisibilityConverter : IValueConverter {
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
if (value is IEnumerable<object> collection) {
|
if (value is IEnumerable<object> collection) {
|
||||||
if ((string)parameter == "Invert")
|
if ((string)parameter == "Invert")
|
||||||
return !collection.Any();
|
return !collection.Any();
|
||||||
return collection.Any();
|
return collection.Any();
|
||||||
}
|
}
|
||||||
if ((string)parameter == "Invert")
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
if ((string)parameter == "Invert")
|
||||||
throw new NotImplementedException();
|
return true;
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Converter;
|
||||||
|
|
||||||
|
public sealed class EventArgsPassThroughConverter : IValueConverter {
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value;
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value;
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Converter;
|
namespace Jugenddienst_Stunden.Converter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Falls ein int als bool dargestellt werden soll
|
/// Falls ein int als bool dargestellt werden soll
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IntBoolConverter : IValueConverter {
|
public class IntBoolConverter : IValueConverter {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Konvertiert einen int in einen bool
|
/// Konvertiert einen int in einen bool
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -18,6 +18,7 @@ public class IntBoolConverter : IValueConverter {
|
|||||||
if (value is int) {
|
if (value is int) {
|
||||||
return (int)value != 0;
|
return (int)value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ public class IntBoolConverter : IValueConverter {
|
|||||||
if (value is bool) {
|
if (value is bool) {
|
||||||
return (bool)value ? 1 : 0;
|
return (bool)value ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
15
Jugenddienst Stunden/Converter/InverseBoolConverter.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Converter;
|
||||||
|
|
||||||
|
public sealed class InverseBoolConverter : IValueConverter {
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
if (value is bool b) return !b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||||
|
if (value is bool b) return !b;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,37 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Converter;
|
namespace Jugenddienst_Stunden.Converter;
|
||||||
|
|
||||||
internal class SecondsTimeConverter : IValueConverter {
|
internal class SecondsTimeConverter : IValueConverter {
|
||||||
private int seconds;
|
private int seconds;
|
||||||
|
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
/// <summary>
|
||||||
if (value is null)
|
/// Konvertiert eine Sekundenangabe nach Stunden:Minuten, auch bei mehr als 24 Stunden
|
||||||
return "0:0";
|
/// </summary>
|
||||||
if (value is int) {
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
seconds = (int)value;
|
if (value is null)
|
||||||
} else {
|
return "0:0";
|
||||||
int.TryParse((string?)value, out seconds);
|
if (value is int) {
|
||||||
}
|
seconds = (int)value;
|
||||||
|
}
|
||||||
|
|
||||||
TimeSpan time = TimeSpan.FromSeconds(seconds);
|
if (value is double) {
|
||||||
|
seconds = (int)Math.Round((double)value);
|
||||||
|
} else {
|
||||||
|
int.TryParse((string?)value, out seconds);
|
||||||
|
}
|
||||||
|
|
||||||
return (int)time.TotalHours + ":" + Math.Abs(time.Minutes);
|
TimeSpan time = TimeSpan.FromSeconds(seconds);
|
||||||
|
|
||||||
//return time.ToString(@"hh\:mm");
|
return (int)time.TotalHours + ":" + Math.Abs(time.Minutes);
|
||||||
//return time.ToString(@"hh\:mm\:ss");
|
|
||||||
|
|
||||||
//return "00:00";
|
//return time.ToString(@"hh\:mm");
|
||||||
}
|
//return time.ToString(@"hh\:mm\:ss");
|
||||||
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
//return "00:00";
|
||||||
throw new NotImplementedException();
|
}
|
||||||
}
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Converter;
|
namespace Jugenddienst_Stunden.Converter;
|
||||||
internal class StringVisibilityConverter : IValueConverter {
|
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
|
||||||
if (value is string strValue) {
|
|
||||||
return !string.IsNullOrEmpty(strValue.Replace("Server: ",""));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
internal class StringVisibilityConverter : IValueConverter {
|
||||||
throw new NotImplementedException();
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
}
|
if (value is string strValue) {
|
||||||
|
return !string.IsNullOrEmpty(strValue.Replace("Server: ", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,14 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Exceptions;
|
namespace Jugenddienst_Stunden.Exceptions;
|
||||||
|
|
||||||
public class NoDataException : Exception {
|
public class NoDataException : Exception {
|
||||||
public NoDataException() : base("Keine Daten gefunden") { }
|
public NoDataException() : base("Keine Daten gefunden") {
|
||||||
public NoDataException(string message) : base(message) { }
|
}
|
||||||
public NoDataException(string message, Exception inner) : base(message, inner) { }
|
|
||||||
|
public NoDataException(string message) : base(message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoDataException(string message, Exception inner) : base(message, inner) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
15
Jugenddienst Stunden/Infrastructure/AlertService.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal sealed class AlertService : IAlertService {
|
||||||
|
public event EventHandler<string>? AlertRaised;
|
||||||
|
public void Raise(string message) {
|
||||||
|
AlertRaised?.Invoke(this, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
150
Jugenddienst Stunden/Infrastructure/ApiClient.cs
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using ZXing.Aztec.Internal;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal sealed class ApiClient : IApiClient {
|
||||||
|
private readonly HttpClient _http;
|
||||||
|
private readonly JsonSerializerOptions _json;
|
||||||
|
private readonly ApiOptions _options;
|
||||||
|
private readonly IAppSettings _settings;
|
||||||
|
|
||||||
|
public ApiClient(HttpClient http, ApiOptions options, ITokenProvider tokenProvider, IAppSettings settings) {
|
||||||
|
_http = http;
|
||||||
|
_options = options;
|
||||||
|
_settings = settings;
|
||||||
|
|
||||||
|
// Timeout nur einmalig beim Erstellen setzen – spätere Änderungen an HttpClient.Timeout
|
||||||
|
// nach der ersten Verwendung führen zu InvalidOperationException.
|
||||||
|
if (_http.Timeout != options.Timeout)
|
||||||
|
_http.Timeout = options.Timeout;
|
||||||
|
// Standardmäßig JSON akzeptieren; doppelte Einträge vermeiden
|
||||||
|
if (!_http.DefaultRequestHeaders.Accept.Any(h => h.MediaType?.Equals("application/json", StringComparison.OrdinalIgnoreCase) == true))
|
||||||
|
_http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
|
||||||
|
// KEINE globalen Header/Properties mehr dynamisch setzen. Authorization wird pro Request gesetzt.
|
||||||
|
|
||||||
|
_json = new JsonSerializerOptions {
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
WriteIndented = false,
|
||||||
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||||
|
};
|
||||||
|
|
||||||
|
// Globale Converter: erlauben numerische Felder auch als Strings (z.B. user.id)
|
||||||
|
_json.Converters.Add(new Jugenddienst_Stunden.Models.JsonFlexibleInt32Converter());
|
||||||
|
_json.Converters.Add(new Jugenddienst_Stunden.Models.JsonFlexibleNullableInt32Converter());
|
||||||
|
|
||||||
|
// WICHTIG: HttpClient.BaseAddress NICHT dynamisch setzen oder ändern – das verursacht Exceptions,
|
||||||
|
// sobald bereits Requests gestartet wurden. Wir bauen stattdessen absolute URIs pro Request.
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> GetAsync<T>(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default)
|
||||||
|
=> SendAsync<T>(HttpMethod.Get, path, null, query, ct);
|
||||||
|
|
||||||
|
public async Task<T> SendAsync<T>(HttpMethod method, string path, object? body = null,
|
||||||
|
IDictionary<string, string?>? query = null, CancellationToken ct = default) {
|
||||||
|
// Absolute URI aus aktuellem Settings‑BaseUrl bauen, ohne HttpClient.BaseAddress zu nutzen.
|
||||||
|
var uri = BuildAbsoluteUri(_settings.ApiUrl, path, query);
|
||||||
|
using var req = new HttpRequestMessage(method, uri);
|
||||||
|
// Authorization PRO REQUEST setzen (immer, wenn Token vorhanden ist)
|
||||||
|
// Hinweis: Das QR-Token kann RFC-unzulässige Zeichen (z. B. '|') enthalten.
|
||||||
|
// AuthenticationHeaderValue würde solche Werte ablehnen. Daher ohne Validierung setzen.
|
||||||
|
var currentToken = _settings.ApiKey;
|
||||||
|
if (!string.IsNullOrWhiteSpace(currentToken)) {
|
||||||
|
// Vorherige Header (falls vorhanden) entfernen, um Duplikate zu vermeiden
|
||||||
|
req.Headers.Remove("Authorization");
|
||||||
|
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {currentToken}");
|
||||||
|
}
|
||||||
|
if (body is HttpContent httpContent) {
|
||||||
|
req.Content = httpContent;
|
||||||
|
} else if (body is not null) {
|
||||||
|
req.Content = JsonContent.Create(body, options: _json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sicherstellen, dass Accept: application/json auch auf Request-Ebene vorhanden ist (z. B. für LoginWithToken GET)
|
||||||
|
if (!req.Headers.Accept.Any(h => h.MediaType?.Equals("application/json", StringComparison.OrdinalIgnoreCase) == true)) {
|
||||||
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var res = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false);
|
||||||
|
var text = await res.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
throw ApiException.From(res.StatusCode, TryGetMessage(text), text);
|
||||||
|
|
||||||
|
if (res.StatusCode != System.Net.HttpStatusCode.OK) {
|
||||||
|
// Verhalten wie in BaseFunc: bei Fehlerstatus -> "message" aus Body lesen und mit dessen Inhalt eine Exception werfen.
|
||||||
|
try {
|
||||||
|
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
|
||||||
|
using var doc = JsonDocument.Parse(text, options);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
// GetProperty wirft, wenn "message" fehlt — das entspricht dem bisherigen Verhalten in BaseFunc.
|
||||||
|
var messageElement = root.GetProperty("message");
|
||||||
|
if (messageElement.ValueKind != JsonValueKind.String)
|
||||||
|
throw ApiException.From(res.StatusCode, "Fehler: 'message' ist null.", text);
|
||||||
|
|
||||||
|
var message = messageElement.GetString() ?? throw ApiException.From(res.StatusCode, "Fehler: 'message' ist null.", text);
|
||||||
|
throw ApiException.From(res.StatusCode, message, text);
|
||||||
|
} catch (ApiException) {
|
||||||
|
throw;
|
||||||
|
} catch (Exception) {
|
||||||
|
// Fallback: Wenn Parsing fehlschlägt oder "message" fehlt, konsistente Fehlermeldung wie BaseFunc
|
||||||
|
throw ApiException.From(res.StatusCode, "Fehler: 'message' ist null.", text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(T) == typeof(void) || typeof(T) == typeof(object) || string.IsNullOrWhiteSpace(text))
|
||||||
|
return default!;
|
||||||
|
|
||||||
|
var obj = System.Text.Json.JsonSerializer.Deserialize<T>(text, _json);
|
||||||
|
if (obj is null)
|
||||||
|
throw new ApiException("Fehler beim Deserialisieren der Daten.");
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteAsync(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default)
|
||||||
|
=> SendAsync<object>(HttpMethod.Delete, path, null, query, ct);
|
||||||
|
|
||||||
|
// Entfernt: EnsureBaseAddress – wir ändern BaseAddress nicht mehr dynamisch.
|
||||||
|
|
||||||
|
private static string TryGetMessage(string text) {
|
||||||
|
try {
|
||||||
|
using var doc = JsonDocument.Parse(text);
|
||||||
|
if (doc.RootElement.TryGetProperty("message", out var m) && m.ValueKind == JsonValueKind.String)
|
||||||
|
return m.GetString() ?? text;
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri BuildAbsoluteUri(string baseUrl, string path, IDictionary<string, string?>? query) {
|
||||||
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"ApiUrl ist leer. Bitte zuerst eine gültige Server-URL setzen (Preferences key 'apiUrl').");
|
||||||
|
|
||||||
|
// Basis muss absolut sein (z. B. https://host/appapi/)
|
||||||
|
var baseUri = new Uri(baseUrl, UriKind.Absolute);
|
||||||
|
|
||||||
|
// Pfad relativ zur Basis aufbauen
|
||||||
|
string relativePath = path ?? string.Empty;
|
||||||
|
if (query is not null && query.Count > 0) {
|
||||||
|
var sb = new StringBuilder(relativePath);
|
||||||
|
sb.Append(relativePath.Contains('?') ? '&' : '?');
|
||||||
|
sb.Append(string.Join('&', query
|
||||||
|
.Where(kv => kv.Value is not null)
|
||||||
|
.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value!)}")));
|
||||||
|
relativePath = sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn path bereits absolut ist, direkt verwenden
|
||||||
|
if (Uri.TryCreate(relativePath, UriKind.Absolute, out var absoluteFromPath))
|
||||||
|
return absoluteFromPath;
|
||||||
|
|
||||||
|
return new Uri(baseUri, relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Jugenddienst Stunden/Infrastructure/ApiOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal sealed class ApiOptions {
|
||||||
|
public required string BaseUrl { get; init; }
|
||||||
|
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(15);
|
||||||
|
}
|
||||||
23
Jugenddienst Stunden/Infrastructure/Exceptions.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal class ApiException : Exception {
|
||||||
|
public HttpStatusCode StatusCode { get; }
|
||||||
|
public string? ResponseBody { get; }
|
||||||
|
|
||||||
|
public ApiException(string message, HttpStatusCode statusCode = 0, string? responseBody = null,
|
||||||
|
Exception? inner = null)
|
||||||
|
: base(message, inner) {
|
||||||
|
StatusCode = statusCode;
|
||||||
|
ResponseBody = responseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiException From(HttpStatusCode statusCode, string message, string? responseBody = null)
|
||||||
|
=> new ApiException(message, statusCode, responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ValidationException : Exception {
|
||||||
|
public ValidationException(string message) : base(message) {
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Jugenddienst Stunden/Infrastructure/NullApiClient.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal sealed class NullApiClient : IApiClient {
|
||||||
|
private readonly string _message;
|
||||||
|
public NullApiClient(string message) => _message = message ?? "API nicht konfiguriert.";
|
||||||
|
|
||||||
|
public Task<T> GetAsync<T>(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default)
|
||||||
|
=> Task.FromException<T>(new InvalidOperationException(_message));
|
||||||
|
|
||||||
|
public Task<T> SendAsync<T>(HttpMethod method, string path, object? body = null,
|
||||||
|
IDictionary<string, string?>? query = null, CancellationToken ct = default)
|
||||||
|
=> Task.FromException<T>(new InvalidOperationException(_message));
|
||||||
|
|
||||||
|
public Task DeleteAsync(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default)
|
||||||
|
=> Task.FromException(new InvalidOperationException(_message));
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal sealed class PreferencesAppSettings : IAppSettings {
|
||||||
|
public string ApiUrl {
|
||||||
|
get => Preferences.Default.Get("apiUrl", "");
|
||||||
|
set => Preferences.Default.Set("apiUrl", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ApiKey {
|
||||||
|
get => Preferences.Default.Get("apiKey", "");
|
||||||
|
set => Preferences.Default.Set("apiKey", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int EmployeeId {
|
||||||
|
get => Preferences.Default.Get("EmployeeId", 0);
|
||||||
|
set => Preferences.Default.Set("EmployeeId", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name {
|
||||||
|
get => Preferences.Default.Get("name", "Nicht");
|
||||||
|
set => Preferences.Default.Set("name", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Surname {
|
||||||
|
get => Preferences.Default.Get("surname", "Eingeloggt");
|
||||||
|
set => Preferences.Default.Set("surname", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Jugenddienst Stunden/Infrastructure/RequestLoggingHandler.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
internal sealed class RequestLoggingHandler : DelegatingHandler {
|
||||||
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
|
||||||
|
// Log outgoing request URI + headers
|
||||||
|
Debug.WriteLine($"[Http] Request: {request.Method} {request.RequestUri}");
|
||||||
|
foreach (var h in request.Headers) {
|
||||||
|
Debug.WriteLine($"[Http] RequestHeader: {h.Key} = {string.Join(", ", h.Value)}");
|
||||||
|
}
|
||||||
|
if (request.Content is not null) {
|
||||||
|
foreach (var h in request.Content.Headers) {
|
||||||
|
Debug.WriteLine($"[Http] ContentHeader: {h.Key} = {string.Join(", ", h.Value)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Log response status + Location (bei Redirects) + final request URI used by handler
|
||||||
|
Debug.WriteLine($"[Http] Response: {(int)response.StatusCode} {response.ReasonPhrase}");
|
||||||
|
if (response.Headers.Location is not null)
|
||||||
|
Debug.WriteLine($"[Http] Response Location: {response.Headers.Location}");
|
||||||
|
if (response.RequestMessage?.RequestUri is not null)
|
||||||
|
Debug.WriteLine($"[Http] Final RequestUri: {response.RequestMessage.RequestUri}");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Jugenddienst Stunden/Infrastructure/SettingsTokenProvider.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal sealed class SettingsTokenProvider : ITokenProvider {
|
||||||
|
private readonly IAppSettings _settings;
|
||||||
|
public SettingsTokenProvider(IAppSettings settings) => _settings = settings;
|
||||||
|
|
||||||
|
public string GetToken() => _settings.ApiKey;
|
||||||
|
}
|
||||||
7
Jugenddienst Stunden/Infrastructure/TokenProvider.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal sealed class GlobalVarTokenProvider : ITokenProvider {
|
||||||
|
public string? GetToken() => Models.GlobalVar.ApiKey;
|
||||||
|
}
|
||||||
11
Jugenddienst Stunden/Interfaces/IAlertService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
internal interface IAlertService {
|
||||||
|
event EventHandler<string> AlertRaised;
|
||||||
|
void Raise(string message);
|
||||||
|
}
|
||||||
10
Jugenddienst Stunden/Interfaces/IApiClient.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
internal interface IApiClient {
|
||||||
|
Task<T> GetAsync<T>(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default);
|
||||||
|
|
||||||
|
Task<T> SendAsync<T>(HttpMethod method, string path, object? body = null,
|
||||||
|
IDictionary<string, string?>? query = null, CancellationToken ct = default);
|
||||||
|
|
||||||
|
Task DeleteAsync(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default);
|
||||||
|
}
|
||||||
10
Jugenddienst Stunden/Interfaces/IAppSettings.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
public interface IAppSettings {
|
||||||
|
string ApiUrl { get; set; }
|
||||||
|
string ApiKey { get; set; }
|
||||||
|
|
||||||
|
int EmployeeId { get; set; }
|
||||||
|
string Name { get; set; }
|
||||||
|
string Surname { get; set; }
|
||||||
|
}
|
||||||
8
Jugenddienst Stunden/Interfaces/IAuthService.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
|
public interface IAuthService {
|
||||||
|
Task<User> LoginWithCredentials(string username, string password, string serverUrl, CancellationToken ct = default);
|
||||||
|
Task<User> LoginWithToken(string token, CancellationToken ct = default);
|
||||||
|
}
|
||||||
17
Jugenddienst Stunden/Interfaces/IHoursRepository.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Repository‑Schnittstelle für Datenzugriff (API/Storage) rund um Stunden.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IHoursRepository {
|
||||||
|
Task<BaseResponse> LoadBase(string query);
|
||||||
|
Task<Settings> LoadSettings();
|
||||||
|
Task<Hours> LoadData();
|
||||||
|
Task<User> LoadUser(string apiKey);
|
||||||
|
Task<List<DayTime>> LoadDay(DateTime date);
|
||||||
|
Task<DayTime> LoadEntry(int id);
|
||||||
|
Task<DayTime> SaveEntry(DayTime stunde);
|
||||||
|
Task DeleteEntry(DayTime stunde);
|
||||||
|
}
|
||||||
16
Jugenddienst Stunden/Interfaces/IHoursService.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fachlicher Service für Stunden – konsumiert Repository und stellt VM‑freundliche Methoden bereit.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHoursService {
|
||||||
|
Task<(Hours hours, Settings settings)> GetMonthSummaryAsync(DateTime monthDate);
|
||||||
|
Task<(List<DayTime> dayTimes, Settings settings)> GetDayWithSettingsAsync(DateTime date);
|
||||||
|
Task<List<DayTime>> GetDayRangeAsync(DateTime from, DateTime to);
|
||||||
|
Task<Settings> GetSettingsAsync();
|
||||||
|
Task<DayTime> GetEntryAsync(int id);
|
||||||
|
Task<DayTime> SaveEntryAsync(DayTime stunde);
|
||||||
|
Task DeleteEntryAsync(DayTime stunde);
|
||||||
|
}
|
||||||
5
Jugenddienst Stunden/Interfaces/ITokenProvider.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
internal interface ITokenProvider {
|
||||||
|
string? GetToken();
|
||||||
|
}
|
||||||
@@ -1,312 +1,312 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- <TargetFrameworks>net8.0-maccatalyst;net9.0-android35.0</TargetFrameworks> -->
|
<!-- <TargetFrameworks>net8.0-maccatalyst;net9.0-android35.0</TargetFrameworks> -->
|
||||||
<TargetFrameworks>net9.0-android35.0</TargetFrameworks>
|
<TargetFrameworks>net9.0-android35.0</TargetFrameworks>
|
||||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||||
|
|
||||||
<!-- Note for MacCatalyst:
|
<!-- Note for MacCatalyst:
|
||||||
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
|
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
|
||||||
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
|
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
|
||||||
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
|
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
|
||||||
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
|
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
|
||||||
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
|
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
|
||||||
|
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RootNamespace>Jugenddienst_Stunden</RootNamespace>
|
<RootNamespace>Jugenddienst_Stunden</RootNamespace>
|
||||||
<UseMaui>true</UseMaui>
|
<UseMaui>true</UseMaui>
|
||||||
<SingleProject>true</SingleProject>
|
<SingleProject>true</SingleProject>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
<!-- Display name -->
|
<!-- Display name -->
|
||||||
<ApplicationTitle>Jugenddienst Stunden</ApplicationTitle>
|
<ApplicationTitle>Jugenddienst Stunden</ApplicationTitle>
|
||||||
|
|
||||||
<!-- App Identifier -->
|
<!-- App Identifier -->
|
||||||
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
||||||
|
|
||||||
<!-- Versions -->
|
<!-- Versions -->
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
|
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">29.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">29.0</SupportedOSPlatformVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<PackageIcon>paket_icon.png</PackageIcon>
|
<PackageIcon>paket_icon.png</PackageIcon>
|
||||||
<NeutralLanguage>de</NeutralLanguage>
|
<NeutralLanguage>de</NeutralLanguage>
|
||||||
<PackageVersion>1.0.8</PackageVersion>
|
<PackageVersion>1.0.9</PackageVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-maccatalyst|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-maccatalyst|AnyCPU'">
|
||||||
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<Optimize>False</Optimize>
|
<Optimize>False</Optimize>
|
||||||
<Deterministic>True</Deterministic>
|
<Deterministic>True</Deterministic>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-maccatalyst|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-maccatalyst|AnyCPU'">
|
||||||
<Optimize>True</Optimize>
|
<Optimize>True</Optimize>
|
||||||
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<Deterministic>True</Deterministic>
|
<Deterministic>True</Deterministic>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android34.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android34.0|AnyCPU'">
|
||||||
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
||||||
<Debugger>Xamarin</Debugger>
|
<Debugger>Xamarin</Debugger>
|
||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
<Optimize>False</Optimize>
|
<Optimize>False</Optimize>
|
||||||
<Deterministic>True</Deterministic>
|
<Deterministic>True</Deterministic>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<AndroidKeyStore>False</AndroidKeyStore>
|
<AndroidKeyStore>False</AndroidKeyStore>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DefaultLanguage>de-de</DefaultLanguage>
|
<DefaultLanguage>de-de</DefaultLanguage>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.26100.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.26100.0|AnyCPU'">
|
||||||
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION</DefineConstants>
|
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION</DefineConstants>
|
||||||
<Deterministic>True</Deterministic>
|
<Deterministic>True</Deterministic>
|
||||||
<Optimize>False</Optimize>
|
<Optimize>False</Optimize>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android34.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android34.0|AnyCPU'">
|
||||||
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
||||||
<Debugger>Xamarin</Debugger>
|
<Debugger>Xamarin</Debugger>
|
||||||
<DebugSymbols>False</DebugSymbols>
|
<DebugSymbols>False</DebugSymbols>
|
||||||
<Optimize>True</Optimize>
|
<Optimize>True</Optimize>
|
||||||
<Deterministic>True</Deterministic>
|
<Deterministic>True</Deterministic>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<RunAOTCompilation>False</RunAOTCompilation>
|
<RunAOTCompilation>False</RunAOTCompilation>
|
||||||
<PublishTrimmed>True</PublishTrimmed>
|
<PublishTrimmed>True</PublishTrimmed>
|
||||||
<AndroidKeyStore>False</AndroidKeyStore>
|
<AndroidKeyStore>False</AndroidKeyStore>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.26100.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.26100.0|AnyCPU'">
|
||||||
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
<ApplicationId>com.companyname.jugenddienststunden</ApplicationId>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<Optimize>True</Optimize>
|
<Optimize>True</Optimize>
|
||||||
<Deterministic>True</Deterministic>
|
<Deterministic>True</Deterministic>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<IncludeSymbols>True</IncludeSymbols>
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
|
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
|
||||||
<AssemblyVersion>1.0.8</AssemblyVersion>
|
<AssemblyVersion>1.0.9</AssemblyVersion>
|
||||||
<FileVersion>1.0.8</FileVersion>
|
<FileVersion>1.0.9</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android|AnyCPU'">
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android|AnyCPU'">
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.19041.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.19041.0|AnyCPU'">
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION</DefineConstants>
|
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION</DefineConstants>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.19041.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.19041.0|AnyCPU'">
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-android35.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-android35.0|AnyCPU'">
|
||||||
<WarningLevel>8</WarningLevel>
|
<WarningLevel>8</WarningLevel>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<EnableLLVM>True</EnableLLVM>
|
<EnableLLVM>True</EnableLLVM>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net9.0-android35.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net9.0-android35.0|AnyCPU'">
|
||||||
<WarningLevel>8</WarningLevel>
|
<WarningLevel>8</WarningLevel>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
<EnableLLVM>True</EnableLLVM>
|
<EnableLLVM>True</EnableLLVM>
|
||||||
<DebugSymbols>False</DebugSymbols>
|
<DebugSymbols>False</DebugSymbols>
|
||||||
<AndroidEnableProfiledAot>False</AndroidEnableProfiledAot>
|
<AndroidEnableProfiledAot>False</AndroidEnableProfiledAot>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-ios|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-ios|AnyCPU'">
|
||||||
<WarningLevel>8</WarningLevel>
|
<WarningLevel>8</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net9.0-ios|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net9.0-ios|AnyCPU'">
|
||||||
<WarningLevel>8</WarningLevel>
|
<WarningLevel>8</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-windows10.0.26100.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-windows10.0.26100.0|AnyCPU'">
|
||||||
<WarningLevel>8</WarningLevel>
|
<WarningLevel>8</WarningLevel>
|
||||||
<NoWarn>1701;1702</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
<WarningsAsErrors>$(WarningsAsErrors);NU1605</WarningsAsErrors>
|
<WarningsAsErrors>$(WarningsAsErrors);NU1605</WarningsAsErrors>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net9.0-windows10.0.26100.0|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net9.0-windows10.0.26100.0|AnyCPU'">
|
||||||
<WarningLevel>8</WarningLevel>
|
<WarningLevel>8</WarningLevel>
|
||||||
<NoWarn>1701;1702</NoWarn>
|
<NoWarn>1701;1702</NoWarn>
|
||||||
<WarningsAsErrors>$(WarningsAsErrors);NU1605</WarningsAsErrors>
|
<WarningsAsErrors>$(WarningsAsErrors);NU1605</WarningsAsErrors>
|
||||||
<ApplicationDisplayVersion>1.0.8</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.9</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>9</ApplicationVersion>
|
<ApplicationVersion>10</ApplicationVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.26100.0</TargetFrameworks>
|
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.26100.0</TargetFrameworks>
|
||||||
<WindowsPackageType>None</WindowsPackageType>
|
<WindowsPackageType>None</WindowsPackageType>
|
||||||
<!-- <TargetFrameworks>;net9.0-android35.0</TargetFrameworks> -->
|
<!-- <TargetFrameworks>;net9.0-android35.0</TargetFrameworks> -->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- App Icon -->
|
<!-- App Icon -->
|
||||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
|
||||||
|
|
||||||
<!-- Splash Screen -->
|
<!-- Splash Screen -->
|
||||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#F7931D" BaseSize="128,128" />
|
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#F7931D" BaseSize="128,128"/>
|
||||||
|
|
||||||
<!-- Splash Screen (Windows fix) -->
|
<!-- Splash Screen (Windows fix) -->
|
||||||
<!--<MauiImage Include="Resources\Images\logo_splash_win.svg" Color="#F7931D" BaseSize="208,208" />-->
|
<!--<MauiImage Include="Resources\Images\logo_splash_win.svg" Color="#F7931D" BaseSize="208,208" />-->
|
||||||
|
|
||||||
<!-- Images -->
|
<!-- Images -->
|
||||||
<MauiImage Include="Resources\Images\*" />
|
<MauiImage Include="Resources\Images\*"/>
|
||||||
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
|
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185"/>
|
||||||
|
|
||||||
<!-- Custom Fonts -->
|
<!-- Custom Fonts -->
|
||||||
<MauiFont Include="Resources\Fonts\*" />
|
<MauiFont Include="Resources\Fonts\*"/>
|
||||||
|
|
||||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-100.png" />
|
<None Remove="Resources\Windows\%24placeholder%24.scale-100.png"/>
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-125.png" />
|
<None Remove="Resources\Windows\%24placeholder%24.scale-125.png"/>
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-150.png" />
|
<None Remove="Resources\Windows\%24placeholder%24.scale-150.png"/>
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-200.png" />
|
<None Remove="Resources\Windows\%24placeholder%24.scale-200.png"/>
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-400.png" />
|
<None Remove="Resources\Windows\%24placeholder%24.scale-400.png"/>
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-100.png" />
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-100.png"/>
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-125.png" />
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-125.png"/>
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-150.png" />
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-150.png"/>
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-200.png" />
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-200.png"/>
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-400.png" />
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-400.png"/>
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-100.png" />
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-100.png"/>
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-125.png" />
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-125.png"/>
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-150.png" />
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-150.png"/>
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-200.png" />
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-200.png"/>
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-400.png" />
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-400.png"/>
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-100.png" />
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-100.png"/>
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-125.png" />
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-125.png"/>
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-150.png" />
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-150.png"/>
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-200.png" />
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-200.png"/>
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-400.png" />
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-400.png"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-100.png" />
|
<Content Include="Resources\Windows\$placeholder$.scale-100.png"/>
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-125.png" />
|
<Content Include="Resources\Windows\$placeholder$.scale-125.png"/>
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-150.png" />
|
<Content Include="Resources\Windows\$placeholder$.scale-150.png"/>
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-200.png" />
|
<Content Include="Resources\Windows\$placeholder$.scale-200.png"/>
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-400.png" />
|
<Content Include="Resources\Windows\$placeholder$.scale-400.png"/>
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-100.png" />
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-100.png"/>
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-125.png" />
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-125.png"/>
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-150.png" />
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-150.png"/>
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-200.png" />
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-200.png"/>
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-400.png" />
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-400.png"/>
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-100.png" />
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-100.png"/>
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-125.png" />
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-125.png"/>
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-150.png" />
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-150.png"/>
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-200.png" />
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-200.png"/>
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-400.png" />
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-400.png"/>
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-100.png" />
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-100.png"/>
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-125.png" />
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-125.png"/>
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-150.png" />
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-150.png"/>
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-200.png" />
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-200.png"/>
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-400.png" />
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-400.png"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\paket_icon.png">
|
<None Include="..\paket_icon.png">
|
||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Maui" Version="11.1.0" />
|
<PackageReference Include="CommunityToolkit.Maui" Version="12.2.0"/>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.40">
|
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.110">
|
||||||
<TreatAsUsed>true</TreatAsUsed>
|
<TreatAsUsed>true</TreatAsUsed>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.40" />
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.110"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.9"/>
|
||||||
<PackageReference Include="Microsoft.Maui.Graphics" Version="9.0.40" />
|
<PackageReference Include="Microsoft.Maui.Graphics" Version="9.0.110"/>
|
||||||
<PackageReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task" Version="9.0.2" />
|
<PackageReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task" Version="9.0.9"/>
|
||||||
<PackageReference Include="Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk" Version="9.0.2" />
|
<PackageReference Include="Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk" Version="9.0.9"/>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
<PackageReference Include="ZXing.Net.Maui.Controls" Version="0.4.0" />
|
<PackageReference Include="ZXing.Net.Maui.Controls" Version="0.5.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Properties\Resources.Designer.cs">
|
<Compile Update="Properties\Resources.Designer.cs">
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Update="Views\LoginPage.xaml.cs">
|
<Compile Update="Views\LoginPage.xaml.cs">
|
||||||
<DependentUpon>LoginPage.xaml</DependentUpon>
|
<DependentUpon>LoginPage.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Update="Views\StundePage.xaml.cs">
|
<Compile Update="Views\StundePage.xaml.cs">
|
||||||
<DependentUpon>StundePage.xaml</DependentUpon>
|
<DependentUpon>StundePage.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Properties\Resources.resx">
|
<EmbeddedResource Update="Properties\Resources.resx">
|
||||||
<Generator>ResXFileCodeGenerator</Generator>
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<MauiXaml Update="Views\LoginPage.xaml">
|
<MauiXaml Update="Views\LoginPage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
<MauiXaml Update="Views\AllNotesPage.xaml">
|
<MauiXaml Update="Views\AllNotesPage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
<MauiXaml Update="Views\StundePage.xaml">
|
<MauiXaml Update="Views\StundePage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
<MauiXaml Update="Views\NotePage.xaml">
|
<MauiXaml Update="Views\NotePage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
<MauiXaml Update="Views\StundenPage.xaml">
|
<MauiXaml Update="Views\StundenPage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
using CommunityToolkit.Maui;
|
using CommunityToolkit.Maui;
|
||||||
using Jugenddienst_Stunden.Models;
|
using Jugenddienst_Stunden.Models;
|
||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Repositories;
|
||||||
|
using Jugenddienst_Stunden.Services;
|
||||||
|
using Jugenddienst_Stunden.Infrastructure;
|
||||||
|
using Jugenddienst_Stunden.Validators;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ZXing.Net.Maui.Controls;
|
using ZXing.Net.Maui.Controls;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Jugenddienst_Stunden.ViewModels;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden;
|
namespace Jugenddienst_Stunden;
|
||||||
|
|
||||||
@@ -9,35 +16,96 @@ namespace Jugenddienst_Stunden;
|
|||||||
/// Das Hauptprogramm.
|
/// Das Hauptprogramm.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class MauiProgram {
|
public static class MauiProgram {
|
||||||
|
public static MauiApp CreateMauiApp() {
|
||||||
|
var builder = MauiApp.CreateBuilder();
|
||||||
|
builder
|
||||||
|
.UseMauiApp<App>()
|
||||||
|
// Initialize the .NET MAUI Community Toolkit by adding the below line of code
|
||||||
|
.UseMauiCommunityToolkit(options => { options.SetShouldEnableSnackbarOnWindows(true); })
|
||||||
|
.ConfigureFonts(fonts => {
|
||||||
|
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||||
|
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||||
|
})
|
||||||
|
//.UseBarcodeScanning();
|
||||||
|
.UseBarcodeReader();
|
||||||
|
|
||||||
public static MauiApp CreateMauiApp() {
|
//#if DEBUG
|
||||||
var builder = MauiApp.CreateBuilder();
|
// if (string.IsNullOrWhiteSpace(GlobalVar.ApiKey)) {
|
||||||
builder
|
// GlobalVar.ApiKey = Preferences.Default.Get("apiKey",
|
||||||
.UseMauiApp<App>()
|
// "MTQxfHNkdFptQkNZTXlPT3ZyMHNBZDl0UnVxNExMRXxodHRwOi8vaG91cnMuZGF1bmkubWluZS5udTo4MS9hcHBhcGk=");
|
||||||
// Initialize the .NET MAUI Community Toolkit by adding the below line of code
|
// GlobalVar.Name = Preferences.Default.Get("name", "Testserver: Isabell");
|
||||||
.UseMauiCommunityToolkit(options => {
|
// GlobalVar.Surname = Preferences.Default.Get("surname", "Biasi");
|
||||||
options.SetShouldEnableSnackbarOnWindows(true);
|
// GlobalVar.EmployeeId = Preferences.Default.Get("EmployeeId", 141);
|
||||||
})
|
// GlobalVar.ApiUrl = Preferences.Default.Get("apiUrl", "https://hours.dauni.mine.nu/appapi");
|
||||||
.ConfigureFonts(fonts => {
|
// }
|
||||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
|
||||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
|
||||||
})
|
|
||||||
//.UseBarcodeScanning();
|
|
||||||
.UseBarcodeReader();
|
|
||||||
|
|
||||||
#if DEBUG
|
// builder.Logging.AddDebug();
|
||||||
if (GlobalVar.ApiKey == null) {
|
//#endif
|
||||||
GlobalVar.ApiKey = Preferences.Default.Get("apiKey", "MTQxfHNkdFptQkNZTXlPT3ZyMHNBZDl0UnVxNExMRXxodHRwOi8vaG91cnMuZGF1bmkubWluZS5udTo4MS9hcHBhcGk=");
|
|
||||||
GlobalVar.Name = Preferences.Default.Get("name", "Testserver: Isabell");
|
|
||||||
GlobalVar.Surname = Preferences.Default.Get("surname", "Biasi");
|
|
||||||
GlobalVar.EmployeeId = Preferences.Default.Get("EmployeeId", 141);
|
|
||||||
GlobalVar.ApiUrl = Preferences.Default.Get("apiUrl", "https://hours.dauni.mine.nu/appapi");
|
|
||||||
}
|
|
||||||
builder.Logging.AddDebug();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return builder.Build();
|
// DI: AlertService für globale Alerts (z. B. leere ApiUrl)
|
||||||
}
|
builder.Services.AddSingleton<IAlertService, AlertService>();
|
||||||
|
|
||||||
|
// DI: Settings aus Preferences (Single Source of Truth bleibt Preferences)
|
||||||
|
builder.Services.AddSingleton<IAppSettings, PreferencesAppSettings>();
|
||||||
|
|
||||||
|
// DI: ApiOptions IMMER aus aktuellen Settings erzeugen (nicht beim Start einfrieren)
|
||||||
|
builder.Services.AddTransient(sp => new ApiOptions {
|
||||||
|
BaseUrl = sp.GetRequiredService<IAppSettings>().ApiUrl, Timeout = TimeSpan.FromSeconds(15)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Token Provider soll ebenfalls aus Settings/Preferences lesen
|
||||||
|
builder.Services.AddSingleton<ITokenProvider, SettingsTokenProvider>();
|
||||||
|
|
||||||
|
// HttpClient + ApiClient
|
||||||
|
// Configure HttpClient with RequestLoggingHandler and disable automatic redirects for diagnosis
|
||||||
|
builder.Services.AddTransient<RequestLoggingHandler>();
|
||||||
|
builder.Services.AddSingleton<HttpClient>(sp => {
|
||||||
|
var nativeHandler = new HttpClientHandler { AllowAutoRedirect = false };
|
||||||
|
var logging = sp.GetRequiredService<RequestLoggingHandler>();
|
||||||
|
logging.InnerHandler = nativeHandler;
|
||||||
|
// HttpClient.Timeout will be adjusted by ApiClient if needed
|
||||||
|
return new HttpClient(logging, disposeHandler: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IApiClient>(sp => {
|
||||||
|
var alert = sp.GetRequiredService<IAlertService>();
|
||||||
|
try {
|
||||||
|
return new ApiClient(
|
||||||
|
sp.GetRequiredService<HttpClient>(),
|
||||||
|
sp.GetRequiredService<ApiOptions>(),
|
||||||
|
sp.GetRequiredService<ITokenProvider>(),
|
||||||
|
sp.GetRequiredService<IAppSettings>());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Alert an UI/VM weiterreichen
|
||||||
|
alert.Raise(e.Message);
|
||||||
|
// Fallback: NullApiClient liefert beim Aufruf aussagekräftige Exception
|
||||||
|
return new NullApiClient(e.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DI: Infrastruktur
|
||||||
|
//builder.Services.AddSingleton(new ApiOptions { BaseUrl = GlobalVar.ApiUrl, Timeout = TimeSpan.FromSeconds(15) });
|
||||||
|
//builder.Services.AddSingleton<ITokenProvider, GlobalVarTokenProvider>();
|
||||||
|
//builder.Services.AddSingleton<HttpClient>(_ => new HttpClient());
|
||||||
|
//builder.Services.AddSingleton<IApiClient>(sp => new ApiClient(
|
||||||
|
// sp.GetRequiredService<HttpClient>(),
|
||||||
|
// sp.GetRequiredService<ApiOptions>(),
|
||||||
|
// sp.GetRequiredService<ITokenProvider>()));
|
||||||
|
|
||||||
|
// DI: Validatoren
|
||||||
|
builder.Services.AddSingleton<IHoursValidator, HoursValidator>();
|
||||||
|
|
||||||
|
// DI: Services & Repositories
|
||||||
|
builder.Services.AddSingleton<IHoursRepository, HoursRepository>();
|
||||||
|
builder.Services.AddSingleton<IHoursService, HoursService>();
|
||||||
|
builder.Services.AddSingleton<IAuthService, AuthService>();
|
||||||
|
|
||||||
|
// DI: Views/ViewModels
|
||||||
|
builder.Services.AddTransient<ViewModels.StundenViewModel>();
|
||||||
|
builder.Services.AddTransient<Views.StundenPage>();
|
||||||
|
builder.Services.AddTransient<ViewModels.LoginViewModel>();
|
||||||
|
builder.Services.AddTransient<Views.LoginPage>();
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,186 +7,175 @@ using System.Text.Json;
|
|||||||
namespace Jugenddienst_Stunden.Models;
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
internal static class BaseFunc {
|
internal static class BaseFunc {
|
||||||
|
internal static async Task<string> GetApiDataWithAuthAsync(string url, string token) {
|
||||||
|
if (Connectivity.Current.NetworkAccess == NetworkAccess.None)
|
||||||
|
throw new Exception("Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
throw new Exception("Kein APIKEY, bitte zuerst Login durchführen");
|
||||||
|
|
||||||
|
// Erstellen eines HttpClient-Objekts
|
||||||
|
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
||||||
|
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
|
||||||
|
// Hinzufügen des Bearer-Tokens zum Authorization-Header
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
|
// Senden der Anfrage und Abrufen der Antwort
|
||||||
|
using (HttpResponseMessage HttpResponseMessage = await client.GetAsync(url).ConfigureAwait(false)) {
|
||||||
|
var byteArray = await HttpResponseMessage.Content.ReadAsByteArrayAsync();
|
||||||
|
string responseData = Encoding.UTF8.GetString(byteArray);
|
||||||
|
//using (HttpContent HttpContent = HttpResponseMessage.Content) {
|
||||||
|
// //responseData = await HttpContent.ReadAsStringAsync();
|
||||||
|
//}
|
||||||
|
if (HttpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) {
|
||||||
|
return responseData;
|
||||||
|
} else {
|
||||||
|
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
|
||||||
|
using (JsonDocument doc = JsonDocument.Parse(responseData, options)) {
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
string message = root.GetProperty("message").GetString() ??
|
||||||
|
throw new Exception("Fehler: 'message' ist null.");
|
||||||
|
throw new Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal static async Task<string> GetApiDataWithAuthAsync(string url, string token) {
|
internal static async Task<User> AuthUserPass(string user, string pass, string url) {
|
||||||
|
var values = new Dictionary<string, string> { { "user", user }, { "pass", pass } };
|
||||||
|
|
||||||
if (Connectivity.Current.NetworkAccess == NetworkAccess.None)
|
var content = new FormUrlEncodedContent(values);
|
||||||
throw new Exception("Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.");
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(token))
|
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
||||||
throw new Exception("Kein APIKEY, bitte zuerst Login durchführen");
|
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
|
||||||
// Erstellen eines HttpClient-Objekts
|
// Senden der Anfrage und Abrufen der Antwort
|
||||||
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
using (HttpResponseMessage HttpResponseMessage =
|
||||||
|
await client.PostAsync(url, content).ConfigureAwait(false)) {
|
||||||
|
if (!HttpResponseMessage.IsSuccessStatusCode) {
|
||||||
|
//throw new Exception("Fehler beim Einloggen " + HttpResponseMessage.Content);
|
||||||
|
var byteArray = await HttpResponseMessage.Content.ReadAsByteArrayAsync();
|
||||||
|
string responseData = Encoding.UTF8.GetString(byteArray);
|
||||||
|
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
|
||||||
|
using (JsonDocument doc = JsonDocument.Parse(responseData, options)) {
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
string message = root.GetProperty("message").GetString() ??
|
||||||
|
throw new Exception("Fehler: 'message' ist null.");
|
||||||
|
throw new Exception(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
// Überprüfen, ob die Anfrage erfolgreich war
|
||||||
|
if (HttpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) {
|
||||||
|
using (HttpContent HttpContent = HttpResponseMessage.Content) {
|
||||||
|
// Lesen und Rückgabe der Antwort als String
|
||||||
|
|
||||||
// Hinzufügen des Bearer-Tokens zum Authorization-Header
|
string responseData = await HttpContent.ReadAsStringAsync();
|
||||||
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(responseData) ??
|
||||||
|
throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
|
//User userData = System.Text.Json.JsonSerializer.Deserialize<User>(responseData) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
|
return res.user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Senden der Anfrage und Abrufen der Antwort
|
return null;
|
||||||
using (HttpResponseMessage HttpResponseMessage = await client.GetAsync(url).ConfigureAwait(false)) {
|
}
|
||||||
var byteArray = await HttpResponseMessage.Content.ReadAsByteArrayAsync();
|
|
||||||
string responseData = Encoding.UTF8.GetString(byteArray);
|
|
||||||
//using (HttpContent HttpContent = HttpResponseMessage.Content) {
|
|
||||||
// //responseData = await HttpContent.ReadAsStringAsync();
|
|
||||||
//}
|
|
||||||
if (HttpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) {
|
|
||||||
return responseData;
|
|
||||||
} else {
|
|
||||||
var options = new JsonDocumentOptions {
|
|
||||||
AllowTrailingCommas = true
|
|
||||||
};
|
|
||||||
using (JsonDocument doc = JsonDocument.Parse(responseData, options)) {
|
|
||||||
JsonElement root = doc.RootElement;
|
|
||||||
string message = root.GetProperty("message").GetString() ?? throw new Exception("Fehler: 'message' ist null.");
|
|
||||||
throw new Exception(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notiz laden
|
||||||
|
/// </summary>
|
||||||
|
internal static Note Load(string filename) {
|
||||||
|
filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);
|
||||||
|
|
||||||
|
if (!File.Exists(filename))
|
||||||
|
throw new FileNotFoundException("Unable to find file on local storage.", filename);
|
||||||
|
|
||||||
internal static async Task<User> AuthUserPass(string user, string pass, string url) {
|
return
|
||||||
|
new() { Date = File.GetLastWriteTime(filename) };
|
||||||
|
}
|
||||||
|
|
||||||
var values = new Dictionary<string, string>
|
/// <summary>
|
||||||
{
|
/// Stundeneintrag speichern
|
||||||
{ "user", user },
|
/// </summary>
|
||||||
{ "pass", pass }
|
internal static async Task SaveItemAsync(string url, string token, DayTime item, bool isNewItem = false) {
|
||||||
};
|
//Uhrzeiten sollten sinnvolle Werte haben - außer bei Freistellungen, da wäre eigentlich null
|
||||||
|
if (item.TimeSpanVon == item.TimeSpanBis && item.FreistellungAktiv == null) {
|
||||||
|
throw new Exception("Beginn und Ende sind gleich");
|
||||||
|
}
|
||||||
|
|
||||||
var content = new FormUrlEncodedContent(values);
|
if (item.TimeSpanBis < item.TimeSpanVon) {
|
||||||
|
throw new Exception("Ende ist vor Beginn");
|
||||||
|
}
|
||||||
|
|
||||||
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
TimeSpan span = TimeSpan.Zero;
|
||||||
|
span += item.TimeSpanBis - item.TimeSpanVon;
|
||||||
|
if (span.Hours > 10) {
|
||||||
|
//Hier vielleicht eine Abfrage, ob mehr als 10 Stunden gesund sind?
|
||||||
|
//Das müsste aber das ViewModel machen
|
||||||
|
}
|
||||||
|
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
//Gemeinde ist ein Pflichtfeld
|
||||||
|
if (item.GemeindeAktiv == null && GlobalVar.Settings.GemeindeAktivSet) {
|
||||||
|
throw new Exception("Gemeinde nicht gewählt");
|
||||||
|
}
|
||||||
|
|
||||||
// Senden der Anfrage und Abrufen der Antwort
|
//Projekt ist ein Pflichtfeld
|
||||||
using (HttpResponseMessage HttpResponseMessage = await client.PostAsync(url, content).ConfigureAwait(false)) {
|
if (item.ProjektAktiv == null && GlobalVar.Settings.ProjektAktivSet) {
|
||||||
if (!HttpResponseMessage.IsSuccessStatusCode)
|
throw new Exception("Projekt nicht gewählt");
|
||||||
{
|
}
|
||||||
//throw new Exception("Fehler beim Einloggen " + HttpResponseMessage.Content);
|
|
||||||
var byteArray = await HttpResponseMessage.Content.ReadAsByteArrayAsync();
|
|
||||||
string responseData = Encoding.UTF8.GetString(byteArray);
|
|
||||||
var options = new JsonDocumentOptions {
|
|
||||||
AllowTrailingCommas = true
|
|
||||||
};
|
|
||||||
using (JsonDocument doc = JsonDocument.Parse(responseData, options)) {
|
|
||||||
JsonElement root = doc.RootElement;
|
|
||||||
string message = root.GetProperty("message").GetString() ?? throw new Exception("Fehler: 'message' ist null.");
|
|
||||||
throw new Exception(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Überprüfen, ob die Anfrage erfolgreich war
|
//Keine Beschreibung
|
||||||
if (HttpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) {
|
if (string.IsNullOrEmpty(item.Description) && item.FreistellungAktiv == null) {
|
||||||
using (HttpContent HttpContent = HttpResponseMessage.Content) {
|
throw new Exception("Keine Beschreibung");
|
||||||
// Lesen und Rückgabe der Antwort als String
|
}
|
||||||
|
|
||||||
string responseData = await HttpContent.ReadAsStringAsync();
|
//Keine Beschreibung
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(responseData) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
if (string.IsNullOrEmpty(item.Description)) {
|
||||||
//User userData = System.Text.Json.JsonSerializer.Deserialize<User>(responseData) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
item.Description = item.FreistellungAktiv.Name;
|
||||||
return res.user;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
||||||
return null;
|
//HttpClient client = new HttpClient();
|
||||||
}
|
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
/// <summary>
|
//string json = JsonSerializer.Serialize<DayTime>(item);
|
||||||
/// Notiz laden
|
string json = JsonConvert.SerializeObject(item);
|
||||||
/// </summary>
|
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
internal static Note Load(string filename) {
|
|
||||||
filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);
|
|
||||||
|
|
||||||
if (!File.Exists(filename))
|
HttpResponseMessage? response = null;
|
||||||
throw new FileNotFoundException("Unable to find file on local storage.", filename);
|
if (isNewItem)
|
||||||
|
response = await client.PostAsync(url, content);
|
||||||
|
else
|
||||||
|
response = await client.PutAsync(url, content);
|
||||||
|
|
||||||
return
|
if (!response.IsSuccessStatusCode) {
|
||||||
new() {
|
throw new Exception("Fehler beim Speichern " + response.Content);
|
||||||
Date = File.GetLastWriteTime(filename)
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stundeneintrag speichern
|
/// Stundeneintrag löschen
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static async Task SaveItemAsync(string url, string token, DayTime item, bool isNewItem = false) {
|
internal static async Task DeleteItemAsync(string url, string token) {
|
||||||
|
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
||||||
|
//HttpClient client = new HttpClient();
|
||||||
|
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
//Uhrzeiten sollten sinnvolle Werte haben - außer bei Freistellungen, da wäre eigentlich null
|
HttpResponseMessage response = await client.DeleteAsync(url);
|
||||||
if (item.TimeSpanVon == item.TimeSpanBis && item.FreistellungAktiv == null) {
|
|
||||||
throw new Exception("Beginn und Ende sind gleich");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.TimeSpanBis < item.TimeSpanVon) {
|
|
||||||
throw new Exception("Ende ist vor Beginn");
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeSpan span = TimeSpan.Zero;
|
|
||||||
span += item.TimeSpanBis - item.TimeSpanVon;
|
|
||||||
if (span.Hours > 10) {
|
|
||||||
//Hier vielleicht eine Abfrage, ob mehr als 10 Stunden gesund sind?
|
|
||||||
//Das müsste aber das ViewModel machen
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gemeinde ist ein Pflichtfeld
|
|
||||||
if (item.GemeindeAktiv == null && GlobalVar.Settings.GemeindeAktivSet) {
|
|
||||||
throw new Exception("Gemeinde nicht gewählt");
|
|
||||||
}
|
|
||||||
//Projekt ist ein Pflichtfeld
|
|
||||||
if (item.ProjektAktiv == null && GlobalVar.Settings.ProjektAktivSet) {
|
|
||||||
throw new Exception("Projekt nicht gewählt");
|
|
||||||
}
|
|
||||||
//Keine Beschreibung
|
|
||||||
if (string.IsNullOrEmpty(item.Description) && item.FreistellungAktiv == null) {
|
|
||||||
throw new Exception("Keine Beschreibung");
|
|
||||||
}
|
|
||||||
//Keine Beschreibung
|
|
||||||
if (string.IsNullOrEmpty(item.Description)) {
|
|
||||||
item.Description = item.FreistellungAktiv.Name;
|
|
||||||
}
|
|
||||||
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
|
||||||
//HttpClient client = new HttpClient();
|
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
|
||||||
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
||||||
|
|
||||||
//string json = JsonSerializer.Serialize<DayTime>(item);
|
|
||||||
string json = JsonConvert.SerializeObject(item);
|
|
||||||
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
HttpResponseMessage? response = null;
|
|
||||||
if (isNewItem)
|
|
||||||
response = await client.PostAsync(url, content);
|
|
||||||
else
|
|
||||||
response = await client.PutAsync(url, content);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode) {
|
|
||||||
throw new Exception("Fehler beim Speichern " + response.Content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stundeneintrag löschen
|
|
||||||
/// </summary>
|
|
||||||
internal static async Task DeleteItemAsync(string url, string token) {
|
|
||||||
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
|
||||||
|
|
||||||
//HttpClient client = new HttpClient();
|
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
|
||||||
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
||||||
|
|
||||||
HttpResponseMessage response = await client.DeleteAsync(url);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
throw new Exception("Fehler beim Löschen " + response.Content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new Exception("Fehler beim Löschen " + response.Content);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
21
Jugenddienst Stunden/Models/ConfirmationEventArgs.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
public sealed class ConfirmationEventArgs : System.EventArgs {
|
||||||
|
public string Title { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
public string ConfirmText { get; }
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource<bool> _tcs = new();
|
||||||
|
|
||||||
|
public ConfirmationEventArgs(string title, string message, string confirmText = "OK") {
|
||||||
|
Title = title;
|
||||||
|
Message = message;
|
||||||
|
ConfirmText = confirmText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> Task => _tcs.Task;
|
||||||
|
|
||||||
|
public void SetResult(bool result) => _tcs.TrySetResult(result);
|
||||||
|
}
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
using Jugenddienst_Stunden.Types;
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Models;
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
internal static class GlobalVar {
|
internal static class GlobalVar {
|
||||||
public static string ApiKey {
|
public static string ApiKey {
|
||||||
get => Preferences.Default.Get("apiKey", "");
|
get => Preferences.Default.Get("apiKey", "");
|
||||||
set => Preferences.Default.Set("apiKey", value);
|
set => Preferences.Default.Set("apiKey", value);
|
||||||
}
|
}
|
||||||
public static int EmployeeId {
|
|
||||||
get => Preferences.Default.Get("EmployeeId", 0);
|
public static int EmployeeId {
|
||||||
set => Preferences.Default.Set("EmployeeId", value);
|
get => Preferences.Default.Get("EmployeeId", 0);
|
||||||
}
|
set => Preferences.Default.Set("EmployeeId", value);
|
||||||
public static string Name {
|
}
|
||||||
get => Preferences.Default.Get("name", "Nicht");
|
|
||||||
set => Preferences.Default.Set("name", value);
|
public static string Name {
|
||||||
}
|
get => Preferences.Default.Get("name", "Nicht");
|
||||||
public static string Surname {
|
set => Preferences.Default.Set("name", value);
|
||||||
get => Preferences.Default.Get("surname", "Eingeloggt");
|
}
|
||||||
set => Preferences.Default.Set("surname", value);
|
|
||||||
}
|
public static string Surname {
|
||||||
public static string ApiUrl {
|
get => Preferences.Default.Get("surname", "Eingeloggt");
|
||||||
get => Preferences.Default.Get("apiUrl", "");
|
set => Preferences.Default.Set("surname", value);
|
||||||
set => Preferences.Default.Set("apiUrl", value);
|
}
|
||||||
}
|
|
||||||
public static Settings Settings { get; set; }
|
public static string ApiUrl {
|
||||||
|
get => Preferences.Default.Get("apiUrl", "");
|
||||||
|
set => Preferences.Default.Set("apiUrl", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Settings Settings { get; set; }
|
||||||
}
|
}
|
||||||
@@ -4,83 +4,90 @@ using Newtonsoft.Json;
|
|||||||
namespace Jugenddienst_Stunden.Models;
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
internal static class HoursBase {
|
internal static class HoursBase {
|
||||||
|
/// <summary>
|
||||||
|
/// Laden ... what can be: "settings", "hours", date="YYYY-MM-DD", id=<int/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Entire response</returns>
|
||||||
|
internal static async Task<BaseResponse> LoadBase(string what) {
|
||||||
|
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?" + what, GlobalVar.ApiKey);
|
||||||
|
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
||||||
|
throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Laden ... what can be: "settings", "hours", date="YYYY-MM-DD", id=<int/>
|
/// Einstellungen laden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Entire response</returns>
|
/// <returns>Settings only</returns>
|
||||||
internal static async Task<BaseResponse> LoadBase(string what) {
|
internal static async Task<Settings> LoadSettings() {
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?"+what, GlobalVar.ApiKey);
|
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?settings", GlobalVar.ApiKey);
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
||||||
return res;
|
throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
}
|
return res.settings;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Einstellungen laden
|
/// Daten laden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Settings only</returns>
|
/// <returns>Hours only</returns>
|
||||||
internal static async Task<Settings> LoadSettings() {
|
internal static async Task<Hours> LoadData() {
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?settings", GlobalVar.ApiKey);
|
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?hours", GlobalVar.ApiKey);
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
||||||
return res.settings;
|
throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
}
|
return res.hour;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Daten laden
|
/// Benutzerdaten laden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Hours only</returns>
|
/// <returns>User-Object</returns>
|
||||||
internal static async Task<Hours> LoadData() {
|
public static async Task<User> LoadUser(string apiKey) {
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?hours", GlobalVar.ApiKey);
|
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl, apiKey);
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
||||||
return res.hour;
|
throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
}
|
return res.user;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Benutzerdaten laden
|
/// Zeiten eines Tages holen
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>User-Object</returns>
|
internal static async Task<List<DayTime>> LoadDay(DateTime date) {
|
||||||
public static async Task<User> LoadUser(string apiKey) {
|
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?date=" + date.ToString("yyyy-MM-dd"),
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl, apiKey);
|
GlobalVar.ApiKey);
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
||||||
return res.user;
|
throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
}
|
return res.daytimes;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Zeiten eines Tages holen
|
/// Einzelnen Stundeneintrag holen
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static async Task<List<DayTime>> LoadDay(DateTime date) {
|
internal static async Task<DayTime> LoadEntry(int id) {
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?date=" + date.ToString("yyyy-MM-dd"), GlobalVar.ApiKey);
|
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?id=" + id, GlobalVar.ApiKey);
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
||||||
return res.daytimes;
|
throw new Exception("Fehler beim Deserialisieren der Daten");
|
||||||
}
|
res.daytime.TimeSpanVon = res.daytime.Begin.ToTimeSpan();
|
||||||
|
res.daytime.TimeSpanBis = res.daytime.End.ToTimeSpan();
|
||||||
|
return res.daytime;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Einzelnen Stundeneintrag holen
|
/// Eintrag speichern
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static async Task<DayTime> LoadEntry(int id) {
|
internal static async Task<DayTime> SaveEntry(DayTime stunde) {
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?id=" + id, GlobalVar.ApiKey);
|
//, string begin, string end, string freistellung, string bemerkung) {
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ?? throw new Exception("Fehler beim Deserialisieren der Daten");
|
bool isNew = false;
|
||||||
res.daytime.TimeSpanVon = res.daytime.Begin.ToTimeSpan();
|
if (stunde.Id == null)
|
||||||
res.daytime.TimeSpanBis = res.daytime.End.ToTimeSpan();
|
isNew = true;
|
||||||
return res.daytime;
|
await BaseFunc.SaveItemAsync(GlobalVar.ApiUrl, GlobalVar.ApiKey, stunde, isNew);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
return stunde;
|
||||||
/// Eintrag speichern
|
}
|
||||||
/// </summary>
|
|
||||||
internal static async Task<DayTime> SaveEntry(DayTime stunde) { //, string begin, string end, string freistellung, string bemerkung) {
|
|
||||||
bool isNew = false;
|
|
||||||
if (stunde.Id == null)
|
|
||||||
isNew = true;
|
|
||||||
await BaseFunc.SaveItemAsync(GlobalVar.ApiUrl, GlobalVar.ApiKey, stunde, isNew);
|
|
||||||
|
|
||||||
return stunde;
|
/// <summary>
|
||||||
}
|
/// Eintrag löschen
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
internal static async Task DeleteEntry(DayTime stunde) {
|
||||||
/// Eintrag löschen
|
await BaseFunc.DeleteItemAsync(GlobalVar.ApiUrl + "/entry/" + stunde.Id, GlobalVar.ApiKey);
|
||||||
/// </summary>
|
}
|
||||||
internal static async Task DeleteEntry(DayTime stunde) {
|
|
||||||
await BaseFunc.DeleteItemAsync(GlobalVar.ApiUrl + "/entry/" + stunde.Id, GlobalVar.ApiKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
56
Jugenddienst Stunden/Models/JsonFlexibleIntConverters.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
|
internal sealed class JsonFlexibleInt32Converter : JsonConverter<int> {
|
||||||
|
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
switch (reader.TokenType) {
|
||||||
|
case JsonTokenType.Number:
|
||||||
|
if (reader.TryGetInt32(out var n)) return n;
|
||||||
|
// Fallback via double to cover edge cases
|
||||||
|
var d = reader.GetDouble();
|
||||||
|
return (int)d;
|
||||||
|
case JsonTokenType.String:
|
||||||
|
var s = reader.GetString();
|
||||||
|
if (string.IsNullOrWhiteSpace(s)) return 0;
|
||||||
|
s = s.Trim();
|
||||||
|
// Some APIs embed id like "141|..." -> take leading numeric part
|
||||||
|
int i = 0;
|
||||||
|
int sign = 1;
|
||||||
|
int idx = 0;
|
||||||
|
if (s.StartsWith("-")) { sign = -1; idx = 1; }
|
||||||
|
for (; idx < s.Length; idx++) {
|
||||||
|
char c = s[idx];
|
||||||
|
if (c < '0' || c > '9') break;
|
||||||
|
i = i * 10 + (c - '0');
|
||||||
|
}
|
||||||
|
if (idx > 0 && (idx > 1 || sign == 1)) return i * sign;
|
||||||
|
if (int.TryParse(s, out var parsed)) return parsed;
|
||||||
|
if (long.TryParse(s, out var l)) return (int)l;
|
||||||
|
throw new JsonException($"Cannot convert string '{s}' to Int32.");
|
||||||
|
case JsonTokenType.Null:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
throw new JsonException($"Token {reader.TokenType} is not valid for Int32.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
|
||||||
|
=> writer.WriteNumberValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class JsonFlexibleNullableInt32Converter : JsonConverter<int?> {
|
||||||
|
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
if (reader.TokenType == JsonTokenType.Null) return null;
|
||||||
|
// Reuse non-nullable converter
|
||||||
|
var conv = new JsonFlexibleInt32Converter();
|
||||||
|
return conv.Read(ref reader, typeof(int), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options) {
|
||||||
|
if (value.HasValue) writer.WriteNumberValue(value.Value);
|
||||||
|
else writer.WriteNullValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,37 +11,44 @@ namespace Jugenddienst_Stunden.Models {
|
|||||||
return typeof(T).IsAssignableFrom(objectType);
|
return typeof(T).IsAssignableFrom(objectType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanWrite { get { return false; } }
|
public override bool CanWrite {
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
||||||
|
JsonSerializer serializer) {
|
||||||
var contract = serializer.ContractResolver.ResolveContract(objectType);
|
var contract = serializer.ContractResolver.ResolveContract(objectType);
|
||||||
if (!(contract is Newtonsoft.Json.Serialization.JsonObjectContract || contract is Newtonsoft.Json.Serialization.JsonDictionaryContract)) {
|
if (!(contract is Newtonsoft.Json.Serialization.JsonObjectContract ||
|
||||||
throw new JsonSerializationException(string.Format("Unsupported objectType {0} at {1}.", objectType, reader.Path));
|
contract is Newtonsoft.Json.Serialization.JsonDictionaryContract)) {
|
||||||
|
throw new JsonSerializationException(string.Format("Unsupported objectType {0} at {1}.", objectType,
|
||||||
|
reader.Path));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (reader.SkipComments().TokenType) {
|
switch (reader.SkipComments().TokenType) {
|
||||||
case JsonToken.StartArray: {
|
case JsonToken.StartArray: {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (reader.Read()) {
|
while (reader.Read()) {
|
||||||
switch (reader.TokenType) {
|
switch (reader.TokenType) {
|
||||||
case JsonToken.Comment:
|
case JsonToken.Comment:
|
||||||
break;
|
break;
|
||||||
case JsonToken.EndArray:
|
case JsonToken.EndArray:
|
||||||
return existingValue;
|
return existingValue;
|
||||||
default: {
|
default: {
|
||||||
count++;
|
count++;
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
throw new JsonSerializationException(string.Format("Too many objects at path {0}.", reader.Path));
|
throw new JsonSerializationException(string.Format("Too many objects at path {0}.",
|
||||||
existingValue = existingValue ?? contract.DefaultCreator();
|
reader.Path));
|
||||||
serializer.Populate(reader, existingValue);
|
existingValue = existingValue ?? contract.DefaultCreator();
|
||||||
}
|
serializer.Populate(reader, existingValue);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// Should not come here.
|
|
||||||
throw new JsonSerializationException(string.Format("Unclosed array at path {0}.", reader.Path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should not come here.
|
||||||
|
throw new JsonSerializationException(string.Format("Unclosed array at path {0}.", reader.Path));
|
||||||
|
}
|
||||||
|
|
||||||
case JsonToken.Null:
|
case JsonToken.Null:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
namespace Jugenddienst_Stunden.Models;
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
internal class Note {
|
internal class Note {
|
||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
public void Save() =>
|
public void Save() =>
|
||||||
File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);
|
File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename), Text);
|
||||||
|
|
||||||
public void Delete() =>
|
public void Delete() =>
|
||||||
File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));
|
File.Delete(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename));
|
||||||
@@ -31,14 +32,14 @@ File.WriteAllText(System.IO.Path.Combine(FileSystem.AppDataDirectory, Filename),
|
|||||||
// Use Linq extensions to load the *.notes.txt files.
|
// Use Linq extensions to load the *.notes.txt files.
|
||||||
return Directory
|
return Directory
|
||||||
|
|
||||||
// Select the file names from the directory
|
// Select the file names from the directory
|
||||||
.EnumerateFiles(appDataPath, "*.notes.txt")
|
.EnumerateFiles(appDataPath, "*.notes.txt")
|
||||||
|
|
||||||
// Each file name is used to load a note
|
// Each file name is used to load a note
|
||||||
.Select(filename => Note.Load(Path.GetFileName(filename)))
|
.Select(filename => Note.Load(Path.GetFileName(filename)))
|
||||||
|
|
||||||
// With the final collection of notes, order them by date
|
// With the final collection of notes, order them by date
|
||||||
.OrderByDescending(note => note.Date);
|
.OrderByDescending(note => note.Date);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Note() {
|
public Note() {
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Models;
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
internal class Operator {
|
internal class Operator {
|
||||||
public string? id;
|
public string? id;
|
||||||
public string? name;
|
public string? name;
|
||||||
public string? surname;
|
public string? surname;
|
||||||
public string? email;
|
public string? email;
|
||||||
public string? password;
|
public string? password;
|
||||||
public string? lang;
|
public string? lang;
|
||||||
public string? admin;
|
public string? admin;
|
||||||
public string? aktiv;
|
public string? aktiv;
|
||||||
public string? department;
|
public string? department;
|
||||||
public string? department_name;
|
public string? department_name;
|
||||||
public string? num;
|
public string? num;
|
||||||
public string? year;
|
public string? year;
|
||||||
|
|
||||||
public event EventHandler<string>? AlertEvent;
|
|
||||||
|
|
||||||
|
public event EventHandler<string>? AlertEvent;
|
||||||
}
|
}
|
||||||
@@ -10,19 +10,23 @@ internal class TokenData {
|
|||||||
public string Operator_id { get; set; }
|
public string Operator_id { get; set; }
|
||||||
|
|
||||||
public TokenData(string ak) {
|
public TokenData(string ak) {
|
||||||
if (string.IsNullOrEmpty(ak)) {
|
if (string.IsNullOrEmpty(ak)) {
|
||||||
throw new ArgumentException("API key cannot be null or empty", nameof(ak));
|
throw new ArgumentException("API key cannot be null or empty", nameof(ak));
|
||||||
}
|
}
|
||||||
string dat = Encoding.UTF8.GetString(Convert.FromBase64String(ak));
|
|
||||||
|
|
||||||
string[] parts = dat.Split('|');
|
string dat = Encoding.UTF8.GetString(Convert.FromBase64String(ak));
|
||||||
if (parts.Length < 3) {
|
|
||||||
throw new FormatException("API key format is invalid");
|
|
||||||
}
|
|
||||||
|
|
||||||
Token = dat.Split('|')[1]; ;
|
string[] parts = dat.Split('|');
|
||||||
Url = dat.Split('|')[2]; ;
|
if (parts.Length < 3) {
|
||||||
Operator_id = dat.Split('|')[0]; ;
|
throw new FormatException("API key format is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
Token = dat.Split('|')[1];
|
||||||
|
;
|
||||||
|
Url = dat.Split('|')[2];
|
||||||
|
;
|
||||||
|
Operator_id = dat.Split('|')[0];
|
||||||
|
;
|
||||||
ApiKey = ak;
|
ApiKey = ak;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/appicon"
|
android:icon="@mipmap/appicon"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:label="Stunden"
|
android:label="Stunden"
|
||||||
/>
|
/>
|
||||||
<!-- android:usesCleartextTraffic="true" -->
|
<!-- android:usesCleartextTraffic="true" -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -3,6 +3,9 @@ using Android.Content.PM;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden;
|
namespace Jugenddienst_Stunden;
|
||||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
|
||||||
|
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop,
|
||||||
|
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode |
|
||||||
|
ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||||
public class MainActivity : MauiAppCompatActivity {
|
public class MainActivity : MauiAppCompatActivity {
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<!-- The Mac App Store requires you specify if the app uses encryption. -->
|
<!-- The Mac App Store requires you specify if the app uses encryption. -->
|
||||||
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
|
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
|
||||||
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
|
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
|
||||||
<!-- Please indicate <true/> or <false/> here. -->
|
<!-- Please indicate <true/> or <false/> here. -->
|
||||||
|
|
||||||
<!-- Specify the category for your app here. -->
|
<!-- Specify the category for your app here. -->
|
||||||
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
|
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
|
||||||
<!-- <key>LSApplicationCategoryType</key> -->
|
<!-- <key>LSApplicationCategoryType</key> -->
|
||||||
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
|
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
|
||||||
<key>UIDeviceFamily</key>
|
<key>UIDeviceFamily</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>2</integer>
|
<integer>2</integer>
|
||||||
</array>
|
</array>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array>
|
<array>
|
||||||
<string>arm64</string>
|
<string>arm64</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/appicon.appiconset</string>
|
<string>Assets.xcassets/appicon.appiconset</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="8" xmlns="http://tizen.org/ns/packages">
|
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="8"
|
||||||
<profile name="common" />
|
xmlns="http://tizen.org/ns/packages">
|
||||||
<ui-application appid="maui-application-id-placeholder" exec="Jugenddienst Stunden.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
<profile name="common"/>
|
||||||
<label>maui-application-title-placeholder</label>
|
<ui-application appid="maui-application-id-placeholder" exec="Jugenddienst Stunden.dll" multiple="false"
|
||||||
<icon>maui-appicon-placeholder</icon>
|
nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||||
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
<label>maui-application-title-placeholder</label>
|
||||||
</ui-application>
|
<icon>maui-appicon-placeholder</icon>
|
||||||
<shortcut-list />
|
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true"/>
|
||||||
<privileges>
|
</ui-application>
|
||||||
<privilege>http://tizen.org/privilege/internet</privilege>
|
<shortcut-list/>
|
||||||
</privileges>
|
<privileges>
|
||||||
<dependencies />
|
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||||
<provides-appdefined-privileges />
|
</privileges>
|
||||||
|
<dependencies/>
|
||||||
|
<provides-appdefined-privileges/>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -19,9 +19,5 @@ namespace Jugenddienst_Stunden.WinUI {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,70 +1,74 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Package
|
<Package
|
||||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||||
IgnorableNamespaces="uap rescap com desktop">
|
IgnorableNamespaces="uap rescap com desktop">
|
||||||
|
|
||||||
<Identity Name="JugenddienstStunden" Publisher="CN=User Name" Version="0.0.0.0" />
|
<Identity Name="JugenddienstStunden" Publisher="CN=User Name" Version="0.0.0.0"/>
|
||||||
|
|
||||||
<mp:PhoneIdentity PhoneProductId="4BA4D7D7-E3C2-4BBF-92EF-0EDB5DB5CDB4" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
<mp:PhoneIdentity PhoneProductId="4BA4D7D7-E3C2-4BBF-92EF-0EDB5DB5CDB4"
|
||||||
|
PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>$placeholder$</DisplayName>
|
<DisplayName>$placeholder$</DisplayName>
|
||||||
<PublisherDisplayName>Daniel Pichler</PublisherDisplayName>
|
<PublisherDisplayName>Daniel Pichler</PublisherDisplayName>
|
||||||
<Logo>Resources\Windows\$placeholder$.png</Logo>
|
<Logo>Resources\Windows\$placeholder$.png</Logo>
|
||||||
</Properties>
|
</Properties>
|
||||||
|
|
||||||
<Dependencies>
|
<Dependencies>
|
||||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0"/>
|
||||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0"/>
|
||||||
</Dependencies>
|
</Dependencies>
|
||||||
|
|
||||||
<Resources>
|
<Resources>
|
||||||
<Resource Language="x-generate" />
|
<Resource Language="x-generate"/>
|
||||||
</Resources>
|
</Resources>
|
||||||
|
|
||||||
<Applications>
|
<Applications>
|
||||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
||||||
<uap:VisualElements
|
<uap:VisualElements
|
||||||
DisplayName="$placeholder$"
|
DisplayName="$placeholder$"
|
||||||
Description="$placeholder$"
|
Description="$placeholder$"
|
||||||
Square150x150Logo="Resources\Windows\$placeholder$.png"
|
Square150x150Logo="Resources\Windows\$placeholder$.png"
|
||||||
Square44x44Logo="Resources\Windows\$placeholder$.png"
|
Square44x44Logo="Resources\Windows\$placeholder$.png"
|
||||||
BackgroundColor="transparent">
|
BackgroundColor="transparent">
|
||||||
<uap:DefaultTile Square71x71Logo="Resources\Windows\Small\$placeholder$.png" Wide310x150Logo="Resources\Windows\Wide\$placeholder$.png" Square310x310Logo="Resources\Windows\$placeholder$.png" ShortName="Stunden"/>
|
<uap:DefaultTile Square71x71Logo="Resources\Windows\Small\$placeholder$.png"
|
||||||
<uap:SplashScreen Image="Resources\Windows\Splash\$placeholder$.png" BackgroundColor="#F7931D"/>
|
Wide310x150Logo="Resources\Windows\Wide\$placeholder$.png"
|
||||||
</uap:VisualElements>
|
Square310x310Logo="Resources\Windows\$placeholder$.png" ShortName="Stunden"/>
|
||||||
|
<uap:SplashScreen Image="Resources\Windows\Splash\$placeholder$.png" BackgroundColor="#F7931D"/>
|
||||||
|
</uap:VisualElements>
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
|
|
||||||
<!-- Specify which CLSID to activate when notification is clicked -->
|
<!-- Specify which CLSID to activate when notification is clicked -->
|
||||||
<desktop:Extension Category="windows.toastNotificationActivation">
|
<desktop:Extension Category="windows.toastNotificationActivation">
|
||||||
<desktop:ToastNotificationActivation ToastActivatorCLSID="6e919706-2634-4d97-a93c-2213b2acc334" />
|
<desktop:ToastNotificationActivation ToastActivatorCLSID="6e919706-2634-4d97-a93c-2213b2acc334"/>
|
||||||
</desktop:Extension>
|
</desktop:Extension>
|
||||||
|
|
||||||
<!-- Register COM CLSID -->
|
<!-- Register COM CLSID -->
|
||||||
<com:Extension Category="windows.comServer">
|
<com:Extension Category="windows.comServer">
|
||||||
<com:ComServer>
|
<com:ComServer>
|
||||||
<com:ExeServer Executable="Jugenddienst Stunden.exe" DisplayName="$targetnametoken$" Arguments="----AppNotificationActivated:">
|
<com:ExeServer Executable="Jugenddienst Stunden.exe" DisplayName="$targetnametoken$"
|
||||||
<!-- Example path to executable: CommunityToolkit.Maui.Sample\CommunityToolkit.Maui.Sample.exe -->
|
Arguments="----AppNotificationActivated:">
|
||||||
<com:Class Id="6e919706-2634-4d97-a93c-2213b2acc334" />
|
<!-- Example path to executable: CommunityToolkit.Maui.Sample\CommunityToolkit.Maui.Sample.exe -->
|
||||||
</com:ExeServer>
|
<com:Class Id="6e919706-2634-4d97-a93c-2213b2acc334"/>
|
||||||
</com:ComServer>
|
</com:ExeServer>
|
||||||
</com:Extension>
|
</com:ComServer>
|
||||||
|
</com:Extension>
|
||||||
|
|
||||||
</Extensions>
|
</Extensions>
|
||||||
|
|
||||||
</Application>
|
</Application>
|
||||||
</Applications>
|
</Applications>
|
||||||
|
|
||||||
<Capabilities>
|
<Capabilities>
|
||||||
<rescap:Capability Name="runFullTrust" />
|
<rescap:Capability Name="runFullTrust"/>
|
||||||
<Capability Name="internetClient"/>
|
<Capability Name="internetClient"/>
|
||||||
<DeviceCapability Name="webcam"/>
|
<DeviceCapability Name="webcam"/>
|
||||||
</Capabilities>
|
</Capabilities>
|
||||||
|
|
||||||
</Package>
|
</Package>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
<assemblyIdentity version="1.0.0.0" name="Jugenddienst Stunden.WinUI.app"/>
|
<assemblyIdentity version="1.0.0.0" name="Jugenddienst Stunden.WinUI.app"/>
|
||||||
|
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<windowsSettings>
|
<windowsSettings>
|
||||||
<!-- The combination of below two tags have the following effect:
|
<!-- The combination of below two tags have the following effect:
|
||||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||||
2) System < Windows 10 Anniversary Update
|
2) System < Windows 10 Anniversary Update
|
||||||
-->
|
-->
|
||||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor
|
||||||
</windowsSettings>
|
</dpiAwareness>
|
||||||
</application>
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIDeviceFamily</key>
|
<key>UIDeviceFamily</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<integer>2</integer>
|
<integer>2</integer>
|
||||||
</array>
|
</array>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array>
|
<array>
|
||||||
<string>arm64</string>
|
<string>arm64</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>This app uses barcode scanning to...</string>
|
<string>This app uses barcode scanning to...</string>
|
||||||
<key>XSAppIconAssets</key>
|
<key>XSAppIconAssets</key>
|
||||||
<string>Assets.xcassets/appicon.appiconset</string>
|
<string>Assets.xcassets/appicon.appiconset</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// Dieser Code wurde von einem Tool generiert.
|
// This code was generated by a tool.
|
||||||
// Laufzeitversion:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// der Code erneut generiert wird.
|
// the code is regenerated.
|
||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -13,12 +12,12 @@ namespace Jugenddienst_Stunden.Properties {
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
// -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
@@ -33,7 +32,7 @@ namespace Jugenddienst_Stunden.Properties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
@@ -47,8 +46,8 @@ namespace Jugenddienst_Stunden.Properties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
/// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
|
/// resource lookups using this strongly typed resource class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
internal static global::System.Globalization.CultureInfo Culture {
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
|||||||
@@ -1,101 +1,106 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 1.3
|
Version 1.3
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">1.3</resheader>
|
<resheader name="version">1.3</resheader>
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
<data name="Name1">this is my long string</data>
|
<data name="Name1">this is my long string</data>
|
||||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
[base64 mime encoded serialized .NET Framework object]
|
[base64 mime encoded serialized .NET Framework object]
|
||||||
</data>
|
</data>
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
<xsd:complexType>
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:complexType>
|
||||||
<xsd:element name="data">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:complexType>
|
<xsd:element name="data">
|
||||||
<xsd:sequence>
|
<xsd:complexType>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
<xsd:sequence>
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||||
</xsd:sequence>
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
</xsd:sequence>
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||||
</xsd:complexType>
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||||
</xsd:element>
|
</xsd:complexType>
|
||||||
<xsd:element name="resheader">
|
</xsd:element>
|
||||||
<xsd:complexType>
|
<xsd:element name="resheader">
|
||||||
<xsd:sequence>
|
<xsd:complexType>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
<xsd:sequence>
|
||||||
</xsd:sequence>
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
</xsd:sequence>
|
||||||
</xsd:complexType>
|
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||||
</xsd:element>
|
</xsd:complexType>
|
||||||
</xsd:choice>
|
</xsd:element>
|
||||||
</xsd:complexType>
|
</xsd:choice>
|
||||||
</xsd:element>
|
</xsd:complexType>
|
||||||
</xsd:schema>
|
</xsd:element>
|
||||||
<resheader name="resmimetype">
|
</xsd:schema>
|
||||||
<value>text/microsoft-resx</value>
|
<resheader name="resmimetype">
|
||||||
</resheader>
|
<value>text/microsoft-resx</value>
|
||||||
<resheader name="version">
|
</resheader>
|
||||||
<value>1.3</value>
|
<resheader name="version">
|
||||||
</resheader>
|
<value>1.3</value>
|
||||||
<resheader name="reader">
|
</resheader>
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<resheader name="reader">
|
||||||
</resheader>
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral,
|
||||||
<resheader name="writer">
|
PublicKeyToken=b77a5c561934e089
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral,
|
||||||
|
PublicKeyToken=b77a5c561934e089
|
||||||
|
</value>
|
||||||
|
</resheader>
|
||||||
</root>
|
</root>
|
||||||
80
Jugenddienst Stunden/Repositories/HoursRepository.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Infrastructure;
|
||||||
|
using Jugenddienst_Stunden.Models;
|
||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Repositories;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Standard-Repository, das die bestehende API-/Model-Logik kapselt.
|
||||||
|
/// </summary>
|
||||||
|
internal class HoursRepository : IHoursRepository {
|
||||||
|
private readonly IApiClient _api;
|
||||||
|
|
||||||
|
public HoursRepository(IApiClient api) {
|
||||||
|
_api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BaseResponse> LoadBase(string query) {
|
||||||
|
// Der bestehende Code übergab eine Query ohne führendes '?'
|
||||||
|
var dict = QueryToDictionary(query);
|
||||||
|
var res= await _api.GetAsync<BaseResponse>("", dict).ConfigureAwait(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Settings> LoadSettings() {
|
||||||
|
var res = await _api.GetAsync<BaseResponse>("", new Dictionary<string, string?> { ["settings"] = "1" })
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
return res.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Hours> LoadData() {
|
||||||
|
var res = await _api.GetAsync<BaseResponse>("", new Dictionary<string, string?> { ["hours"] = "1" })
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
return res.hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<User> LoadUser(string apiKey) {
|
||||||
|
// Für die erste Iteration bleibt das Token global; der Endpoint ohne Query liefert user
|
||||||
|
return _api.GetAsync<BaseResponse>("", null).ContinueWith(t => t.Result.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<DayTime>> LoadDay(DateTime date) {
|
||||||
|
var res = await _api
|
||||||
|
.GetAsync<BaseResponse>("", new Dictionary<string, string?> { ["date"] = date.ToString("yyyy-MM-dd") })
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
return res.daytimes ?? new List<DayTime>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DayTime> LoadEntry(int id) {
|
||||||
|
var res = await _api.GetAsync<BaseResponse>("", new Dictionary<string, string?> { ["id"] = id.ToString() })
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
res.daytime.TimeSpanVon = res.daytime.Begin.ToTimeSpan();
|
||||||
|
res.daytime.TimeSpanBis = res.daytime.End.ToTimeSpan();
|
||||||
|
return res.daytime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DayTime> SaveEntry(DayTime stunde) {
|
||||||
|
bool isNew = stunde.Id is null;
|
||||||
|
var method = isNew ? HttpMethod.Post : HttpMethod.Put;
|
||||||
|
await _api.SendAsync<object>(method, "", stunde).ConfigureAwait(false);
|
||||||
|
return stunde;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteEntry(DayTime stunde)
|
||||||
|
=> _api.DeleteAsync($"/entry/{stunde.Id}");
|
||||||
|
|
||||||
|
private static Dictionary<string, string?> QueryToDictionary(string query) {
|
||||||
|
var dict = new Dictionary<string, string?>();
|
||||||
|
if (string.IsNullOrWhiteSpace(query)) return dict;
|
||||||
|
var q = query.TrimStart('?');
|
||||||
|
foreach (var part in q.Split('&', StringSplitOptions.RemoveEmptyEntries)) {
|
||||||
|
var kv = part.Split('=', 2);
|
||||||
|
var key = Uri.UnescapeDataString(kv[0]);
|
||||||
|
var val = kv.Length > 1 ? Uri.UnescapeDataString(kv[1]) : null;
|
||||||
|
dict[key] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/"
|
||||||
|
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
<rect x="0" y="0" width="456" height="456" style="fill:white;"/>
|
<rect x="0" y="0" width="456" height="456" style="fill:white;"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 521 B After Width: | Height: | Size: 531 B |
@@ -1,41 +1,54 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/"
|
||||||
|
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
<g id="JD" transform="matrix(0.96485,0,0,0.96485,104.499,104.499)">
|
<g id="JD" transform="matrix(0.96485,0,0,0.96485,104.499,104.499)">
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,101.169,256)">
|
<g transform="matrix(12.0607,0,0,12.0607,101.169,256)">
|
||||||
<path d="M0,-11.938L0,-4.95C0,-3.918 -0.071,-3.151 -0.214,-2.644C-0.359,-2.069 -0.604,-1.583 -0.95,-1.184C-1.624,-0.397 -2.515,0 -3.626,0C-4.117,0 -4.588,-0.075 -5.04,-0.226L-5.04,-2.305L-4.999,-2.375C-4.509,-2.048 -4.052,-1.884 -3.626,-1.884C-3.026,-1.884 -2.612,-2.112 -2.382,-2.565C-2.141,-3.019 -2.02,-3.817 -2.02,-4.95L-2.02,-11.938L0,-11.938" style="fill-rule:nonzero;"/>
|
<path d="M0,-11.938L0,-4.95C0,-3.918 -0.071,-3.151 -0.214,-2.644C-0.359,-2.069 -0.604,-1.583 -0.95,-1.184C-1.624,-0.397 -2.515,0 -3.626,0C-4.117,0 -4.588,-0.075 -5.04,-0.226L-5.04,-2.305L-4.999,-2.375C-4.509,-2.048 -4.052,-1.884 -3.626,-1.884C-3.026,-1.884 -2.612,-2.112 -2.382,-2.565C-2.141,-3.019 -2.02,-3.817 -2.02,-4.95L-2.02,-11.938L0,-11.938"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(0,12.0607,12.0607,0,87.6884,17.5193)">
|
<g transform="matrix(0,12.0607,12.0607,0,87.6884,17.5193)">
|
||||||
<path d="M1.249,-1.246C0.559,-1.246 -0.002,-0.687 -0.002,0.002C-0.002,0.692 0.559,1.249 1.249,1.249C1.936,1.249 2.497,0.692 2.497,0.002C2.497,-0.687 1.936,-1.246 1.249,-1.246" style="fill-rule:nonzero;"/>
|
<path d="M1.249,-1.246C0.559,-1.246 -0.002,-0.687 -0.002,0.002C-0.002,0.692 0.559,1.249 1.249,1.249C1.936,1.249 2.497,0.692 2.497,0.002C2.497,-0.687 1.936,-1.246 1.249,-1.246"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,32.4324,105.748)">
|
<g transform="matrix(12.0607,0,0,12.0607,32.4324,105.748)">
|
||||||
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018" style="fill:rgb(247,147,29);fill-rule:nonzero;"/>
|
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018"
|
||||||
|
style="fill:rgb(247,147,29);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,109.437,86.7078)">
|
<g transform="matrix(12.0607,0,0,12.0607,109.437,86.7078)">
|
||||||
<path d="M0,7.464C0,4.526 2.384,2.142 5.325,2.142C6.633,2.142 7.832,2.61 8.758,3.395L8.758,-5.324L10.746,-5.324L10.746,12.598L8.758,12.598L8.758,11.534C7.832,12.318 6.633,12.788 5.325,12.788C2.384,12.788 0,10.404 0,7.464M2.113,7.401C2.113,9.234 3.598,10.719 5.432,10.719C7.263,10.719 8.747,9.234 8.747,7.401C8.747,5.569 7.263,4.083 5.432,4.083C3.598,4.083 2.113,5.569 2.113,7.401" style="fill-rule:nonzero;"/>
|
<path d="M0,7.464C0,4.526 2.384,2.142 5.325,2.142C6.633,2.142 7.832,2.61 8.758,3.395L8.758,-5.324L10.746,-5.324L10.746,12.598L8.758,12.598L8.758,11.534C7.832,12.318 6.633,12.788 5.325,12.788C2.384,12.788 0,10.404 0,7.464M2.113,7.401C2.113,9.234 3.598,10.719 5.432,10.719C7.263,10.719 8.747,9.234 8.747,7.401C8.747,5.569 7.263,4.083 5.432,4.083C3.598,4.083 2.113,5.569 2.113,7.401"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="Uhr" transform="matrix(0.0220493,0,0,0.0220493,267.879,222.51)">
|
<g id="Uhr" transform="matrix(0.0220493,0,0,0.0220493,267.879,222.51)">
|
||||||
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
||||||
<path d="M1638.49,981.86C1405.19,981.86 1216.06,792.734 1216.06,559.433C1216.06,326.132 1405.19,137.006 1638.49,137.006C1871.79,137.006 2060.92,326.132 2060.92,559.433C2060.92,792.734 1871.79,981.86 1638.49,981.86ZM1638.49,70.646C1368.54,70.646 1149.7,289.481 1149.7,559.433C1149.7,829.385 1368.54,1048.22 1638.49,1048.22C1908.44,1048.22 2127.28,829.385 2127.28,559.433C2127.28,289.481 1908.44,70.646 1638.49,70.646Z" style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
<path d="M1638.49,981.86C1405.19,981.86 1216.06,792.734 1216.06,559.433C1216.06,326.132 1405.19,137.006 1638.49,137.006C1871.79,137.006 2060.92,326.132 2060.92,559.433C2060.92,792.734 1871.79,981.86 1638.49,981.86ZM1638.49,70.646C1368.54,70.646 1149.7,289.481 1149.7,559.433C1149.7,829.385 1368.54,1048.22 1638.49,1048.22C1908.44,1048.22 2127.28,829.385 2127.28,559.433C2127.28,289.481 1908.44,70.646 1638.49,70.646Z"
|
||||||
|
style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(4.14211,-0.451661,0.451661,4.14211,-6813.16,744.211)">
|
<g transform="matrix(4.14211,-0.451661,0.451661,4.14211,-6813.16,744.211)">
|
||||||
<path d="M1677.2,559.433C1677.2,580.825 1659.85,598.166 1638.45,598.145C1617.12,598.124 1599.75,580.168 1599.78,558.84C1599.88,474.921 1621.18,308.102 1632.14,254.433C1633.54,247.532 1643.41,247.537 1644.81,254.44C1655.78,308.457 1677.2,476.326 1677.2,559.433Z" style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
<path d="M1677.2,559.433C1677.2,580.825 1659.85,598.166 1638.45,598.145C1617.12,598.124 1599.75,580.168 1599.78,558.84C1599.88,474.921 1621.18,308.102 1632.14,254.433C1633.54,247.532 1643.41,247.537 1644.81,254.44C1655.78,308.457 1677.2,476.326 1677.2,559.433Z"
|
||||||
|
style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(-4.09852,-0.750473,-0.750473,4.09852,7333.98,1287.55)">
|
<g transform="matrix(-4.09852,-0.750473,-0.750473,4.09852,7333.98,1287.55)">
|
||||||
<path d="M1595.39,590.238C1532.86,586.449 1465.21,572.938 1428.29,565.401C1422.75,564.267 1422.76,556.353 1428.29,555.229C1463.02,548.172 1526.22,535.741 1585.66,531.077C1585.22,540.595 1584.97,549.88 1584.96,558.834C1584.95,570.561 1587.38,581.586 1595.39,590.238Z" style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
<path d="M1595.39,590.238C1532.86,586.449 1465.21,572.938 1428.29,565.401C1422.75,564.267 1422.76,556.353 1428.29,555.229C1463.02,548.172 1526.22,535.741 1585.66,531.077C1585.22,540.595 1584.97,549.88 1584.96,558.834C1584.95,570.561 1587.38,581.586 1595.39,590.238Z"
|
||||||
|
style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
||||||
<path d="M1656.19,176.808C1656.19,186.583 1648.27,194.507 1638.49,194.507C1628.72,194.507 1620.79,186.583 1620.79,176.808C1620.79,167.033 1628.72,159.108 1638.49,159.108C1648.27,159.108 1656.19,167.033 1656.19,176.808Z" style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
<path d="M1656.19,176.808C1656.19,186.583 1648.27,194.507 1638.49,194.507C1628.72,194.507 1620.79,186.583 1620.79,176.808C1620.79,167.033 1628.72,159.108 1638.49,159.108C1648.27,159.108 1656.19,167.033 1656.19,176.808Z"
|
||||||
|
style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
||||||
<path d="M1255.87,541.734C1265.64,541.734 1273.57,549.658 1273.57,559.433C1273.57,569.208 1265.64,577.132 1255.87,577.132C1246.09,577.132 1238.17,569.208 1238.17,559.433C1238.17,549.658 1246.09,541.734 1255.87,541.734Z" style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
<path d="M1255.87,541.734C1265.64,541.734 1273.57,549.658 1273.57,559.433C1273.57,569.208 1265.64,577.132 1255.87,577.132C1246.09,577.132 1238.17,569.208 1238.17,559.433C1238.17,549.658 1246.09,541.734 1255.87,541.734Z"
|
||||||
|
style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
||||||
<path d="M1620.79,942.059C1620.79,932.283 1628.72,924.359 1638.49,924.359C1648.27,924.359 1656.19,932.283 1656.19,942.059C1656.19,951.834 1648.27,959.758 1638.49,959.758C1628.72,959.758 1620.79,951.834 1620.79,942.059Z" style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
<path d="M1620.79,942.059C1620.79,932.283 1628.72,924.359 1638.49,924.359C1648.27,924.359 1656.19,932.283 1656.19,942.059C1656.19,951.834 1648.27,959.758 1638.49,959.758C1628.72,959.758 1620.79,951.834 1620.79,942.059Z"
|
||||||
|
style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
<g transform="matrix(4.16667,0,0,4.16667,-6599.04,0)">
|
||||||
<path d="M2021.12,577.132C2011.34,577.132 2003.42,569.208 2003.42,559.433C2003.42,549.658 2011.34,541.734 2021.12,541.734C2030.89,541.734 2038.82,549.658 2038.82,559.433C2038.82,569.208 2030.89,577.132 2021.12,577.132Z" style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
<path d="M2021.12,577.132C2011.34,577.132 2003.42,569.208 2003.42,559.433C2003.42,549.658 2011.34,541.734 2021.12,541.734C2030.89,541.734 2038.82,549.658 2038.82,559.433C2038.82,569.208 2030.89,577.132 2021.12,577.132Z"
|
||||||
|
style="fill:rgb(0,0,6);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -1,21 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/"
|
||||||
|
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,201.169,356)">
|
<g transform="matrix(12.0607,0,0,12.0607,201.169,356)">
|
||||||
<path d="M0,-11.938L0,-4.95C0,-3.918 -0.071,-3.151 -0.214,-2.644C-0.359,-2.069 -0.604,-1.583 -0.95,-1.184C-1.624,-0.397 -2.515,0 -3.626,0C-4.117,0 -4.588,-0.075 -5.04,-0.226L-5.04,-2.305L-4.999,-2.375C-4.509,-2.048 -4.052,-1.884 -3.626,-1.884C-3.026,-1.884 -2.612,-2.112 -2.382,-2.565C-2.141,-3.019 -2.02,-3.817 -2.02,-4.95L-2.02,-11.938L0,-11.938" style="fill-rule:nonzero;"/>
|
<path d="M0,-11.938L0,-4.95C0,-3.918 -0.071,-3.151 -0.214,-2.644C-0.359,-2.069 -0.604,-1.583 -0.95,-1.184C-1.624,-0.397 -2.515,0 -3.626,0C-4.117,0 -4.588,-0.075 -5.04,-0.226L-5.04,-2.305L-4.999,-2.375C-4.509,-2.048 -4.052,-1.884 -3.626,-1.884C-3.026,-1.884 -2.612,-2.112 -2.382,-2.565C-2.141,-3.019 -2.02,-3.817 -2.02,-4.95L-2.02,-11.938L0,-11.938"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(0,12.0607,12.0607,0,187.688,117.519)">
|
<g transform="matrix(0,12.0607,12.0607,0,187.688,117.519)">
|
||||||
<path d="M1.249,-1.246C0.559,-1.246 -0.002,-0.687 -0.002,0.002C-0.002,0.692 0.559,1.249 1.249,1.249C1.936,1.249 2.497,0.692 2.497,0.002C2.497,-0.687 1.936,-1.246 1.249,-1.246" style="fill-rule:nonzero;"/>
|
<path d="M1.249,-1.246C0.559,-1.246 -0.002,-0.687 -0.002,0.002C-0.002,0.692 0.559,1.249 1.249,1.249C1.936,1.249 2.497,0.692 2.497,0.002C2.497,-0.687 1.936,-1.246 1.249,-1.246"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
||||||
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018" style="fill:rgb(247,147,29);fill-rule:nonzero;"/>
|
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018"
|
||||||
|
style="fill:rgb(247,147,29);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
||||||
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018" style="fill:white;fill-rule:nonzero;"/>
|
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018"
|
||||||
|
style="fill:white;fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,209.437,186.708)">
|
<g transform="matrix(12.0607,0,0,12.0607,209.437,186.708)">
|
||||||
<path d="M0,7.464C0,4.526 2.384,2.142 5.325,2.142C6.633,2.142 7.832,2.61 8.758,3.395L8.758,-5.324L10.746,-5.324L10.746,12.598L8.758,12.598L8.758,11.534C7.832,12.318 6.633,12.788 5.325,12.788C2.384,12.788 0,10.404 0,7.464M2.113,7.401C2.113,9.234 3.598,10.719 5.432,10.719C7.263,10.719 8.747,9.234 8.747,7.401C8.747,5.569 7.263,4.083 5.432,4.083C3.598,4.083 2.113,5.569 2.113,7.401" style="fill-rule:nonzero;"/>
|
<path d="M0,7.464C0,4.526 2.384,2.142 5.325,2.142C6.633,2.142 7.832,2.61 8.758,3.395L8.758,-5.324L10.746,-5.324L10.746,12.598L8.758,12.598L8.758,11.534C7.832,12.318 6.633,12.788 5.325,12.788C2.384,12.788 0,10.404 0,7.464M2.113,7.401C2.113,9.234 3.598,10.719 5.432,10.719C7.263,10.719 8.747,9.234 8.747,7.401C8.747,5.569 7.263,4.083 5.432,4.083C3.598,4.083 2.113,5.569 2.113,7.401"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -1,21 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
<svg width="100%" height="100%" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/"
|
||||||
|
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,201.169,356)">
|
<g transform="matrix(12.0607,0,0,12.0607,201.169,356)">
|
||||||
<path d="M0,-11.938L0,-4.95C0,-3.918 -0.071,-3.151 -0.214,-2.644C-0.359,-2.069 -0.604,-1.583 -0.95,-1.184C-1.624,-0.397 -2.515,0 -3.626,0C-4.117,0 -4.588,-0.075 -5.04,-0.226L-5.04,-2.305L-4.999,-2.375C-4.509,-2.048 -4.052,-1.884 -3.626,-1.884C-3.026,-1.884 -2.612,-2.112 -2.382,-2.565C-2.141,-3.019 -2.02,-3.817 -2.02,-4.95L-2.02,-11.938L0,-11.938" style="fill-rule:nonzero;"/>
|
<path d="M0,-11.938L0,-4.95C0,-3.918 -0.071,-3.151 -0.214,-2.644C-0.359,-2.069 -0.604,-1.583 -0.95,-1.184C-1.624,-0.397 -2.515,0 -3.626,0C-4.117,0 -4.588,-0.075 -5.04,-0.226L-5.04,-2.305L-4.999,-2.375C-4.509,-2.048 -4.052,-1.884 -3.626,-1.884C-3.026,-1.884 -2.612,-2.112 -2.382,-2.565C-2.141,-3.019 -2.02,-3.817 -2.02,-4.95L-2.02,-11.938L0,-11.938"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(0,12.0607,12.0607,0,187.688,117.519)">
|
<g transform="matrix(0,12.0607,12.0607,0,187.688,117.519)">
|
||||||
<path d="M1.249,-1.246C0.559,-1.246 -0.002,-0.687 -0.002,0.002C-0.002,0.692 0.559,1.249 1.249,1.249C1.936,1.249 2.497,0.692 2.497,0.002C2.497,-0.687 1.936,-1.246 1.249,-1.246" style="fill-rule:nonzero;"/>
|
<path d="M1.249,-1.246C0.559,-1.246 -0.002,-0.687 -0.002,0.002C-0.002,0.692 0.559,1.249 1.249,1.249C1.936,1.249 2.497,0.692 2.497,0.002C2.497,-0.687 1.936,-1.246 1.249,-1.246"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
||||||
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018" style="fill:rgb(247,147,29);fill-rule:nonzero;"/>
|
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018"
|
||||||
|
style="fill:rgb(247,147,29);fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
<g transform="matrix(12.0607,0,0,12.0607,132.432,205.748)">
|
||||||
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018" style="fill:white;fill-rule:nonzero;"/>
|
<path d="M0,-5.018C0.88,-4.169 2.079,-3.647 3.398,-3.647C6.103,-3.647 8.293,-5.838 8.293,-8.539L8.288,-8.768L10.103,-8.768L10.107,-8.539C10.107,-5.953 8.64,-3.705 6.49,-2.586C7.743,-2.48 8.98,-2.02 10.042,-1.19L10.221,-1.044L9.103,0.384L8.926,0.242C6.796,-1.425 3.719,-1.044 2.055,1.084C1.43,1.878 1.096,2.811 1.03,3.75L-0.788,3.75C-0.72,2.42 -0.256,1.095 0.626,-0.033C1.239,-0.816 1.992,-1.427 2.82,-1.857C1.229,-1.993 -0.204,-2.685 -1.283,-3.737L0,-5.018"
|
||||||
|
style="fill:white;fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(12.0607,0,0,12.0607,209.437,186.708)">
|
<g transform="matrix(12.0607,0,0,12.0607,209.437,186.708)">
|
||||||
<path d="M0,7.464C0,4.526 2.384,2.142 5.325,2.142C6.633,2.142 7.832,2.61 8.758,3.395L8.758,-5.324L10.746,-5.324L10.746,12.598L8.758,12.598L8.758,11.534C7.832,12.318 6.633,12.788 5.325,12.788C2.384,12.788 0,10.404 0,7.464M2.113,7.401C2.113,9.234 3.598,10.719 5.432,10.719C7.263,10.719 8.747,9.234 8.747,7.401C8.747,5.569 7.263,4.083 5.432,4.083C3.598,4.083 2.113,5.569 2.113,7.401" style="fill-rule:nonzero;"/>
|
<path d="M0,7.464C0,4.526 2.384,2.142 5.325,2.142C6.633,2.142 7.832,2.61 8.758,3.395L8.758,-5.324L10.746,-5.324L10.746,12.598L8.758,12.598L8.758,11.534C7.832,12.318 6.633,12.788 5.325,12.788C2.384,12.788 0,10.404 0,7.464M2.113,7.401C2.113,9.234 3.598,10.719 5.432,10.719C7.263,10.719 8.747,9.234 8.747,7.401C8.747,5.569 7.263,4.083 5.432,4.083C3.598,4.083 2.113,5.569 2.113,7.401"
|
||||||
|
style="fill-rule:nonzero;"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?xaml-comp compile="true" ?>
|
<?xaml-comp compile="true" ?>
|
||||||
|
|
||||||
<ResourceDictionary
|
<ResourceDictionary
|
||||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||||
@@ -30,18 +31,18 @@
|
|||||||
<Color x:Key="Gray900">#212121</Color>
|
<Color x:Key="Gray900">#212121</Color>
|
||||||
<Color x:Key="Gray950">#141414</Color>
|
<Color x:Key="Gray950">#141414</Color>
|
||||||
|
|
||||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}" />
|
||||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}" />
|
||||||
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}" />
|
||||||
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}" />
|
||||||
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}" />
|
||||||
|
|
||||||
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}" />
|
||||||
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}" />
|
||||||
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}" />
|
||||||
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}" />
|
||||||
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}" />
|
||||||
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}" />
|
||||||
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}" />
|
||||||
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?xaml-comp compile="true" ?>
|
<?xaml-comp compile="true" ?>
|
||||||
|
|
||||||
<ResourceDictionary
|
<ResourceDictionary
|
||||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||||||
|
|
||||||
<Style TargetType="ToolbarItem">
|
<Style TargetType="ToolbarItem">
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger TargetType="ToolbarItem" Property="VisualElement.BackgroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}">
|
<Trigger TargetType="ToolbarItem" Property="VisualElement.BackgroundColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}">
|
||||||
</Trigger>
|
</Trigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
@@ -16,39 +18,48 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="IndicatorView">
|
<Style TargetType="IndicatorView">
|
||||||
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
|
<Setter Property="IndicatorColor"
|
||||||
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="SelectedIndicatorColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Border">
|
<Style TargetType="Border">
|
||||||
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
<Setter Property="Stroke"
|
||||||
<Setter Property="StrokeShape" Value="Rectangle"/>
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
<Setter Property="StrokeThickness" Value="1"/>
|
<Setter Property="StrokeShape" Value="Rectangle" />
|
||||||
|
<Setter Property="StrokeThickness" Value="1" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="BoxView">
|
<Style TargetType="BoxView">
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
<Setter Property="BackgroundColor"
|
||||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="Color"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Button">
|
<Style TargetType="Button">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
<Setter Property="BackgroundColor"
|
||||||
<Setter Property="FontSize" Value="14"/>
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
||||||
<Setter Property="BorderWidth" Value="0"/>
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="CornerRadius" Value="8"/>
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="Padding" Value="14,10"/>
|
<Setter Property="BorderWidth" Value="0" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="Padding" Value="14,10" />
|
||||||
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
<VisualState x:Name="PointerOver" />
|
<VisualState x:Name="PointerOver" />
|
||||||
@@ -59,15 +70,16 @@
|
|||||||
|
|
||||||
<Style TargetType="CheckBox">
|
<Style TargetType="CheckBox">
|
||||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="Color"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -76,19 +88,21 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="DatePicker">
|
<Style TargetType="DatePicker">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="FontSize" Value="14"/>
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -97,20 +111,23 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Editor">
|
<Style TargetType="Editor">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="FontSize" Value="14" />
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
<Setter Property="PlaceholderColor"
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -119,20 +136,23 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Entry">
|
<Style TargetType="Entry">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="FontSize" Value="14" />
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
<Setter Property="PlaceholderColor"
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -142,18 +162,20 @@
|
|||||||
|
|
||||||
<Style TargetType="Frame">
|
<Style TargetType="Frame">
|
||||||
<Setter Property="HasShadow" Value="False" />
|
<Setter Property="HasShadow" Value="False" />
|
||||||
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
<Setter Property="BorderColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
<Setter Property="CornerRadius" Value="8" />
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="ImageButton">
|
<Style TargetType="ImageButton">
|
||||||
<Setter Property="Opacity" Value="1" />
|
<Setter Property="Opacity" Value="1" />
|
||||||
<Setter Property="BorderColor" Value="Transparent"/>
|
<Setter Property="BorderColor" Value="Transparent" />
|
||||||
<Setter Property="BorderWidth" Value="0"/>
|
<Setter Property="BorderWidth" Value="0" />
|
||||||
<Setter Property="CornerRadius" Value="0"/>
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
@@ -170,7 +192,8 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Label">
|
<Style TargetType="Label">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="FontSize" Value="14" />
|
<Setter Property="FontSize" Value="14" />
|
||||||
@@ -180,7 +203,8 @@
|
|||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -189,44 +213,53 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Span">
|
<Style TargetType="Span">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Label" x:Key="Headline">
|
<Style TargetType="Label" x:Key="Headline">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="FontSize" Value="32" />
|
<Setter Property="FontSize" Value="32" />
|
||||||
<Setter Property="HorizontalOptions" Value="Center" />
|
<Setter Property="HorizontalOptions" Value="Center" />
|
||||||
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Label" x:Key="SubHeadline">
|
<Style TargetType="Label" x:Key="SubHeadline">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="FontSize" Value="24" />
|
<Setter Property="FontSize" Value="24" />
|
||||||
<Setter Property="HorizontalOptions" Value="Center" />
|
<Setter Property="HorizontalOptions" Value="Center" />
|
||||||
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
<Setter Property="HorizontalTextAlignment" Value="Center" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="ListView">
|
<Style TargetType="ListView">
|
||||||
<Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
<Setter Property="SeparatorColor"
|
||||||
<Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
|
||||||
|
<Setter Property="RefreshControlColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Picker">
|
<Style TargetType="Picker">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="TitleColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="FontSize" Value="14"/>
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="TitleColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -235,14 +268,16 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="ProgressBar">
|
<Style TargetType="ProgressBar">
|
||||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
<Setter Property="ProgressColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="ProgressColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -251,19 +286,21 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="RadioButton">
|
<Style TargetType="RadioButton">
|
||||||
<Setter Property="BackgroundColor" Value="Transparent"/>
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="FontSize" Value="14"/>
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -272,26 +309,30 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="RefreshView">
|
<Style TargetType="RefreshView">
|
||||||
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
<Setter Property="RefreshColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="SearchBar">
|
<Style TargetType="SearchBar">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||||
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
|
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
|
||||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="FontSize" Value="14" />
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="PlaceholderColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -300,7 +341,8 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="SearchHandler">
|
<Style TargetType="SearchHandler">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
|
||||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
@@ -311,8 +353,10 @@
|
|||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="PlaceholderColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -328,18 +372,24 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Slider">
|
<Style TargetType="Slider">
|
||||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
<Setter Property="MinimumTrackColor"
|
||||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
<Setter Property="MaximumTrackColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="ThumbColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
<Setter Property="MinimumTrackColor"
|
||||||
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
|
<Setter Property="MaximumTrackColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="ThumbColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -348,11 +398,13 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="SwipeItem">
|
<Style TargetType="SwipeItem">
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Switch">
|
<Style TargetType="Switch">
|
||||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
<Setter Property="OnColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="ThumbColor" Value="{StaticResource White}" />
|
<Setter Property="ThumbColor" Value="{StaticResource White}" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
@@ -360,19 +412,24 @@
|
|||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="OnColor"
|
||||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
|
<Setter Property="ThumbColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
<VisualState x:Name="On">
|
<VisualState x:Name="On">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
|
<Setter Property="OnColor"
|
||||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
|
||||||
|
<Setter Property="ThumbColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
<VisualState x:Name="Off">
|
<VisualState x:Name="Off">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
<Setter Property="ThumbColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -381,19 +438,21 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="TimePicker">
|
<Style TargetType="TimePicker">
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
<Setter Property="TextColor"
|
||||||
<Setter Property="BackgroundColor" Value="Transparent"/>
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
|
||||||
<Setter Property="FontFamily" Value="OpenSansRegular"/>
|
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||||
<Setter Property="FontSize" Value="14"/>
|
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||||
<Setter Property="MinimumHeightRequest" Value="44"/>
|
<Setter Property="FontSize" Value="14" />
|
||||||
<Setter Property="MinimumWidthRequest" Value="44"/>
|
<Setter Property="MinimumHeightRequest" Value="44" />
|
||||||
|
<Setter Property="MinimumWidthRequest" Value="44" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
<VisualStateGroupList>
|
<VisualStateGroupList>
|
||||||
<VisualStateGroup x:Name="CommonStates">
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
<VisualState x:Name="Normal" />
|
<VisualState x:Name="Normal" />
|
||||||
<VisualState x:Name="Disabled">
|
<VisualState x:Name="Disabled">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
<Setter Property="TextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
@@ -402,34 +461,51 @@
|
|||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Page" ApplyToDerivedTypes="True">
|
<Style TargetType="Page" ApplyToDerivedTypes="True">
|
||||||
<Setter Property="Padding" Value="0"/>
|
<Setter Property="Padding" Value="0" />
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="Shell" ApplyToDerivedTypes="True">
|
<Style TargetType="Shell" ApplyToDerivedTypes="True">
|
||||||
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
<Setter Property="Shell.BackgroundColor"
|
||||||
<Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
<Setter Property="Shell.ForegroundColor"
|
||||||
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
||||||
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
|
<Setter Property="Shell.TitleColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
|
||||||
|
<Setter Property="Shell.DisabledColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="Shell.UnselectedColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
|
||||||
<Setter Property="Shell.NavBarHasShadow" Value="False" />
|
<Setter Property="Shell.NavBarHasShadow" Value="False" />
|
||||||
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
<Setter Property="Shell.TabBarBackgroundColor"
|
||||||
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||||
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
<Setter Property="Shell.TabBarForegroundColor"
|
||||||
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Shell.TabBarTitleColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="Shell.TabBarUnselectedColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="NavigationPage">
|
<Style TargetType="NavigationPage">
|
||||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
<Setter Property="BarBackgroundColor"
|
||||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
|
||||||
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
<Setter Property="BarTextColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="IconColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="TabbedPage">
|
<Style TargetType="TabbedPage">
|
||||||
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
|
<Setter Property="BarBackgroundColor"
|
||||||
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
|
||||||
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
<Setter Property="BarTextColor"
|
||||||
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
|
||||||
|
<Setter Property="UnselectedTabColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
|
||||||
|
<Setter Property="SelectedTabColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
94
Jugenddienst Stunden/Services/AuthService.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using Jugenddienst_Stunden.Infrastructure;
|
||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Models;
|
||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Services;
|
||||||
|
|
||||||
|
internal sealed class AuthService : IAuthService {
|
||||||
|
private readonly IApiClient _api;
|
||||||
|
private readonly IAppSettings _settings;
|
||||||
|
private readonly IAlertService _alerts;
|
||||||
|
|
||||||
|
public AuthService(IApiClient api, IAppSettings settings, IAlertService alerts) {
|
||||||
|
_api = api;
|
||||||
|
_settings = settings;
|
||||||
|
_alerts = alerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> LoginWithCredentials(string username, string password, string serverUrl, CancellationToken ct = default) {
|
||||||
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
|
throw new Exception("Benutzername und Passwort werden benötigt.");
|
||||||
|
|
||||||
|
var apiBase = NormalizeApiUrl(serverUrl);
|
||||||
|
_settings.ApiUrl = apiBase; // BaseAddress für IApiClient setzen
|
||||||
|
|
||||||
|
var content = new FormUrlEncodedContent(new[] {
|
||||||
|
new KeyValuePair<string, string>("user", username),
|
||||||
|
new KeyValuePair<string, string>("pass", password)
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST ohne Pfad – die API erwartet /appapi
|
||||||
|
// Wichtig: Basis-URL hat garantiert einen abschließenden Slash (…/appapi/),
|
||||||
|
// sodass ein leerer Pfad nicht zu Redirects führt (die den POST in GET verwandeln könnten).
|
||||||
|
var res = await _api.SendAsync<BaseResponse>(HttpMethod.Post, string.Empty, content, null, ct).ConfigureAwait(false);
|
||||||
|
if (res.user is null)
|
||||||
|
throw new Exception(res.message ?? "Ungültige Antwort vom Server.");
|
||||||
|
|
||||||
|
ApplyUser(res.user, apiBase);
|
||||||
|
return res.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> LoginWithToken(string token, CancellationToken ct = default) {
|
||||||
|
if (string.IsNullOrWhiteSpace(token)) throw new Exception("Kein Token erkannt.");
|
||||||
|
|
||||||
|
// QR-Token enthält die URL – extrahiere sie
|
||||||
|
var td = new TokenData(token);
|
||||||
|
// URL aus dem Token ebenfalls normalisieren, damit sie auf "/appapi/" endet
|
||||||
|
_settings.ApiUrl = NormalizeApiUrl(td.Url);
|
||||||
|
_settings.ApiKey = token;
|
||||||
|
|
||||||
|
var res = await _api.GetAsync<BaseResponse>(string.Empty, null, ct).ConfigureAwait(false);
|
||||||
|
if (res.user is null)
|
||||||
|
throw new Exception(res.message ?? "Ungültige Antwort vom Server.");
|
||||||
|
|
||||||
|
ApplyUser(res.user, td.Url);
|
||||||
|
return res.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyUser(User user, string apiBase) {
|
||||||
|
_settings.ApiUrl = apiBase;
|
||||||
|
// Wenn der Server keinen Token im User zurückliefert (QR-Login-Fall), bestehenden Token beibehalten
|
||||||
|
var tokenToUse = string.IsNullOrWhiteSpace(user.Token) ? _settings.ApiKey : user.Token;
|
||||||
|
_settings.ApiKey = tokenToUse;
|
||||||
|
_settings.EmployeeId = user.Id;
|
||||||
|
_settings.Name = user.Name;
|
||||||
|
_settings.Surname = user.Surname;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeApiUrl(string input) {
|
||||||
|
if (string.IsNullOrWhiteSpace(input)) throw new Exception("Server-URL wird benötigt.");
|
||||||
|
var url = input.Trim();
|
||||||
|
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) {
|
||||||
|
url = "https://" + url;
|
||||||
|
}
|
||||||
|
// Sicherstellen, dass der Pfad auf "/appapi" endet
|
||||||
|
if (!url.EndsWith("/appapi", StringComparison.OrdinalIgnoreCase)) {
|
||||||
|
url = url.TrimEnd('/') + "/appapi";
|
||||||
|
}
|
||||||
|
// WICHTIG: Einen abschließenden Slash erzwingen, damit relative Pfade korrekt angehängt werden
|
||||||
|
// und damit POST auf Basis-URL (leerem Pfad) nicht zu einem 301/302-Redirect führt,
|
||||||
|
// der den Body (user/pass) verlieren könnte.
|
||||||
|
//if (!url.EndsWith("/", StringComparison.Ordinal)) {
|
||||||
|
// url += "/";
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (url.EndsWith("/", StringComparison.Ordinal)) {
|
||||||
|
url = url.TrimEnd('/');
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
Jugenddienst Stunden/Services/HoursService.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
using Jugenddienst_Stunden.Validators;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Services;
|
||||||
|
|
||||||
|
internal class HoursService : IHoursService {
|
||||||
|
private readonly IHoursRepository _repo;
|
||||||
|
private readonly IHoursValidator _validator;
|
||||||
|
|
||||||
|
public HoursService(IHoursRepository repo, IHoursValidator validator) {
|
||||||
|
_repo = repo;
|
||||||
|
_validator = validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(Hours hours, Settings settings)> GetMonthSummaryAsync(DateTime monthDate) {
|
||||||
|
string q = $"hours=&year={monthDate:yyyy}&month={monthDate:MM}";
|
||||||
|
var baseRes = await _repo.LoadBase(q);
|
||||||
|
|
||||||
|
// Fallbacks, da einige Endpoints 'settings' nicht mitsenden bzw. 'hour' auslassen können
|
||||||
|
var settings = baseRes.settings ?? await _repo.LoadSettings();
|
||||||
|
var hours = baseRes.hour ?? new Hours {
|
||||||
|
daytime = new List<DayTime>(),
|
||||||
|
Nominal_day_api = new List<NominalDay>(),
|
||||||
|
Nominal_week_api = new List<NominalWeek>(),
|
||||||
|
zeit_total_daily_api = new List<TimeDay>(),
|
||||||
|
Projekte = new System.Collections.ObjectModel.Collection<Projekt>(),
|
||||||
|
Gemeinden = new System.Collections.ObjectModel.Collection<Gemeinde>(),
|
||||||
|
Freistellungen = new System.Collections.ObjectModel.Collection<Freistellung>()
|
||||||
|
};
|
||||||
|
|
||||||
|
return (hours, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(List<DayTime> dayTimes, Settings settings)> GetDayWithSettingsAsync(DateTime date) {
|
||||||
|
string q = $"date={date:yyyy-MM-dd}";
|
||||||
|
var baseRes = await _repo.LoadBase(q);
|
||||||
|
return (baseRes.daytimes ?? new List<DayTime>(), baseRes.settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<DayTime>> GetDayRangeAsync(DateTime from, DateTime to) {
|
||||||
|
string q = $"date={from:yyyy-MM-dd}&tilldate={to:yyyy-MM-dd}";
|
||||||
|
var baseRes = await _repo.LoadBase(q);
|
||||||
|
return baseRes.daytimes ?? new List<DayTime>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Settings> GetSettingsAsync() => await _repo.LoadSettings();
|
||||||
|
|
||||||
|
public async Task<DayTime> GetEntryAsync(int id) => await _repo.LoadEntry(id);
|
||||||
|
|
||||||
|
public async Task<DayTime> SaveEntryAsync(DayTime stunde) {
|
||||||
|
var settings = await _repo.LoadSettings();
|
||||||
|
_validator.Validate(stunde, settings);
|
||||||
|
return await _repo.SaveEntry(stunde);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteEntryAsync(DayTime stunde) => await _repo.DeleteEntry(stunde);
|
||||||
|
}
|
||||||
@@ -1,36 +1,37 @@
|
|||||||
using Jugenddienst_Stunden.Models;
|
using Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
internal class BaseResponse {
|
internal class BaseResponse {
|
||||||
|
public Settings settings { get; set; }
|
||||||
|
|
||||||
public Settings settings { get; set; }
|
/// <summary>
|
||||||
|
/// Monatsübersicht
|
||||||
|
/// </summary>
|
||||||
|
public Hours hour { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Monatsübersicht
|
/// Stundenliste ... für die Katz?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Hours hour { get; set; }
|
public List<Hours> hours { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stundenliste ... für die Katz?
|
/// Liste der Stundeneinträge
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Hours> hours { get; set; }
|
public List<DayTime> daytimes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Liste der Stundeneinträge
|
/// Einzelner Stundeneintrag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<DayTime> daytimes { get; set; }
|
public DayTime daytime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Einzelner Stundeneintrag
|
/// Auch irgendwie doppelt ...
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DayTime daytime { get; set; }
|
public Operator operatorVar { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
public User user { get; set; }
|
||||||
/// Auch irgendwie doppelt ...
|
|
||||||
/// </summary>
|
|
||||||
public Operator operatorVar { get; set; }
|
|
||||||
public User user { get; set; }
|
|
||||||
|
|
||||||
public int error { get; set; }
|
public int error { get; set; }
|
||||||
public string message { get; set; }
|
public string message { get; set; }
|
||||||
}
|
}
|
||||||
@@ -2,95 +2,95 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a day time entry for an employee.
|
/// Represents a day time entry for an employee.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DayTime {
|
public class DayTime {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ID des Stundeneintrages
|
/// ID des Stundeneintrages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mitarbeiter-ID
|
/// Mitarbeiter-ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int EmployeeId { get; set; }
|
public int EmployeeId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Der betreffende Tag
|
/// Der betreffende Tag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime Day { get; set; }
|
public DateTime Day { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Der Wochentag
|
/// Der Wochentag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Wday { get; set; }
|
public int Wday { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Arbeitsbeginn
|
/// Arbeitsbeginn
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeOnly Begin { get; set; }
|
public TimeOnly Begin { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Arbeitsende
|
/// Arbeitsende
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeOnly End { get; set; }
|
public TimeOnly End { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Beschreibung der Tätigkeit
|
/// Beschreibung der Tätigkeit
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Freistellung
|
/// Freistellung
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Free { get; set; }
|
public string? Free { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Freistellung genehmigt?
|
/// Freistellung genehmigt?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Approved { get; set; }
|
public bool Approved { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Das gewählte Projekt
|
/// Das gewählte Projekt
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? Projekt { get; set; }
|
public int? Projekt { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Die gewählte Gemeinde
|
/// Die gewählte Gemeinde
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? Gemeinde { get; set; }
|
public int? Gemeinde { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Nachtstunden
|
/// Nachtstunden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeOnly Night { get; set; }
|
public TimeOnly Night { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Summe Arbeitszeit (inklusive Nachstunden mit Faktor)
|
/// Summe Arbeitszeit (inklusive Nachstunden mit Faktor)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, TimeOnly> Total { get; set; }
|
public Dictionary<string, TimeOnly> Total { get; set; }
|
||||||
|
|
||||||
public TimeOnly End_print { get; set; }
|
public TimeOnly End_print { get; set; }
|
||||||
public TimeSpan TimeSpanVon { get; set; }
|
public TimeSpan TimeSpanVon { get; set; }
|
||||||
public TimeSpan TimeSpanBis { get; set; }
|
public TimeSpan TimeSpanBis { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the active Gemeinde based on the gemeinde ID.
|
/// Gets the active Gemeinde based on the gemeinde ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Gemeinde? GemeindeAktiv { get; set; }
|
public Gemeinde? GemeindeAktiv { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the active Projekt based on the projekt ID.
|
/// Gets the active Projekt based on the projekt ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Projekt? ProjektAktiv { get; set; }
|
public Projekt? ProjektAktiv { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the active Freistellung based on the Freistellung ID
|
/// Gets the active Freistellung based on the Freistellung ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Freistellung? FreistellungAktiv { get; set; }
|
public Freistellung? FreistellungAktiv { get; set; }
|
||||||
|
|
||||||
public int TimeTable { get; set; }
|
|
||||||
|
|
||||||
|
public int TimeTable { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Freistellungen: Urlaub, Zeitausgleich, Krankheit, ...
|
/// Freistellungen: Urlaub, Zeitausgleich, Krankheit, ...
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Freistellung {
|
public class Freistellung {
|
||||||
public string? Id { get; set; }
|
public string? Id { get; set; }
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
internal class Hours : ObservableObject {
|
public partial class Hours : ObservableObject {
|
||||||
public int? Zeit;
|
public double? Zeit;
|
||||||
public int? Nominal;
|
|
||||||
|
public double? Nominal;
|
||||||
|
|
||||||
//public Dictionary<DateOnly,NominalDay> nominal_day_api;
|
//public Dictionary<DateOnly,NominalDay> nominal_day_api;
|
||||||
public List<NominalDay>? Nominal_day_api;
|
public List<NominalDay>? Nominal_day_api;
|
||||||
|
|
||||||
//public Dictionary<int,NominalWeek> nominal_week_api;
|
//public Dictionary<int,NominalWeek> nominal_week_api;
|
||||||
public List<NominalWeek>? Nominal_week_api;
|
public List<NominalWeek>? Nominal_week_api;
|
||||||
|
|
||||||
//public List<string> time_line;
|
//public List<string> time_line;
|
||||||
public string? Zeit_total;
|
public double? Zeit_total;
|
||||||
|
|
||||||
//https://stackoverflow.com/questions/29449641/deserialize-json-when-a-value-can-be-an-object-or-an-empty-array/29450279#29450279
|
//https://stackoverflow.com/questions/29449641/deserialize-json-when-a-value-can-be-an-object-or-an-empty-array/29450279#29450279
|
||||||
//[JsonConverter(typeof(JsonSingleOrEmptyArrayConverter<Hours>))]
|
//[JsonConverter(typeof(JsonSingleOrEmptyArrayConverter<Hours>))]
|
||||||
@@ -21,16 +24,22 @@ internal class Hours : ObservableObject {
|
|||||||
public List<TimeDay> zeit_total_daily_api;
|
public List<TimeDay> zeit_total_daily_api;
|
||||||
public List<DayTime>? daytime;
|
public List<DayTime>? daytime;
|
||||||
//public List<string> wochensumme;
|
//public List<string> wochensumme;
|
||||||
public int overtime_month;
|
|
||||||
public int overtime;
|
[ObservableProperty] public double overtime_month;
|
||||||
|
|
||||||
|
[ObservableProperty] public double overtime;
|
||||||
//public List<string> overtime_day;
|
//public List<string> overtime_day;
|
||||||
public int zeitausgleich;
|
|
||||||
public int zeitausgleich_month;
|
[ObservableProperty] public double zeitausgleich;
|
||||||
public int holiday;
|
|
||||||
public int krankheit;
|
public double zeitausgleich_month;
|
||||||
public int weiterbildung;
|
public double holiday;
|
||||||
public int bereitschaft;
|
public double krankheit;
|
||||||
public int bereitschaft_month;
|
public double weiterbildung;
|
||||||
|
public double bereitschaft;
|
||||||
|
|
||||||
|
public double bereitschaft_month;
|
||||||
|
|
||||||
//public Operator operator_api;
|
//public Operator operator_api;
|
||||||
public DateTime Today;
|
public DateTime Today;
|
||||||
public DateTime Date;
|
public DateTime Date;
|
||||||
@@ -40,5 +49,4 @@ internal class Hours : ObservableObject {
|
|||||||
public Collection<Gemeinde> Gemeinden { get; set; }
|
public Collection<Gemeinde> Gemeinden { get; set; }
|
||||||
public Collection<Freistellung> Freistellungen { get; set; }
|
public Collection<Freistellung> Freistellungen { get; set; }
|
||||||
public int EmployeeId { get; set; }
|
public int EmployeeId { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
internal class NominalDay {
|
|
||||||
|
public class NominalDay {
|
||||||
public int day_number;
|
public int day_number;
|
||||||
public int month_number;
|
public int month_number;
|
||||||
public int hours;
|
public double hours;
|
||||||
public DateOnly date;
|
public DateOnly date;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
internal class NominalWeek {
|
public class NominalWeek {
|
||||||
public int Week_number;
|
public int Week_number;
|
||||||
public int Hours;
|
public double Hours;
|
||||||
}
|
}
|
||||||
@@ -4,35 +4,35 @@
|
|||||||
/// Einstellungen
|
/// Einstellungen
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Settings {
|
public class Settings {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sind Projekte aktiv?
|
/// Sind Projekte aktiv?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ProjektAktivSet { get; set; }
|
public bool ProjektAktivSet { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sind Gemeinden aktiv?
|
/// Sind Gemeinden aktiv?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool GemeindeAktivSet { get; set; }
|
public bool GemeindeAktivSet { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Liste der Projekte
|
/// Liste der Projekte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Projekt>? Projekte { get; set; }
|
public List<Projekt>? Projekte { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Liste der Gemeinden
|
/// Liste der Gemeinden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Gemeinde>? Gemeinden { get; set; }
|
public List<Gemeinde>? Gemeinden { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Liste der Freistellungen
|
/// Liste der Freistellungen
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Freistellung>? Freistellungen { get; set; }
|
public List<Freistellung>? Freistellungen { get; set; }
|
||||||
|
|
||||||
public List<Sollstunden> Nominal { get; set; }
|
public List<Sollstunden>? Nominal { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version der API
|
/// Version der API
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Version { get; set; }
|
public string? Version { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
public class Sollstunden {
|
public class Sollstunden {
|
||||||
public int Timetable { get; set; }
|
public int Timetable { get; set; }
|
||||||
public int Wochentag { get; set; }
|
public int Wochentag { get; set; }
|
||||||
public float Zeit { get; set; }
|
public double Zeit { get; set; }
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Summe der geleisteten Stunden.
|
/// Summe der geleisteten Stunden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal struct TimeDay {
|
public struct TimeDay {
|
||||||
public int Day { get; set; }
|
public int Day { get; set; }
|
||||||
public decimal Hours { get; set; }
|
public double Hours { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
internal class Timetable {
|
internal class Timetable {
|
||||||
public List<TimetableEntry> timetable;
|
public List<TimetableEntry> timetable;
|
||||||
public decimal wochensumme;
|
public decimal wochensumme;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
internal class TimetableEntry {
|
internal class TimetableEntry {
|
||||||
public List<TimeOnly>? Von;
|
public List<TimeOnly>? Von;
|
||||||
public List<TimeOnly>? Bis;
|
public List<TimeOnly>? Bis;
|
||||||
public decimal Summe { get; set; }
|
public decimal Summe { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
internal class User {
|
|
||||||
|
public class User {
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Surname { get; set; }
|
public string Surname { get; set; }
|
||||||
|
|||||||
31
Jugenddienst Stunden/Validators/HoursValidator.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Jugenddienst_Stunden.Infrastructure;
|
||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Validators;
|
||||||
|
|
||||||
|
internal interface IHoursValidator {
|
||||||
|
void Validate(DayTime item, Settings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class HoursValidator : IHoursValidator {
|
||||||
|
public void Validate(DayTime item, Settings settings) {
|
||||||
|
if (item.FreistellungAktiv is null && item.TimeSpanVon == item.TimeSpanBis)
|
||||||
|
throw new ValidationException("Beginn und Ende sind gleich");
|
||||||
|
|
||||||
|
if (item.TimeSpanBis < item.TimeSpanVon)
|
||||||
|
throw new ValidationException("Ende ist vor Beginn");
|
||||||
|
|
||||||
|
if (settings.GemeindeAktivSet && item.GemeindeAktiv is null)
|
||||||
|
throw new ValidationException("Gemeinde nicht gewählt");
|
||||||
|
|
||||||
|
if (settings.ProjektAktivSet && item.ProjektAktiv is null)
|
||||||
|
throw new ValidationException("Projekt nicht gewählt");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(item.Description)) {
|
||||||
|
if (item.FreistellungAktiv?.Name is string name && !string.IsNullOrWhiteSpace(name))
|
||||||
|
item.Description = name;
|
||||||
|
else
|
||||||
|
throw new ValidationException("Keine Beschreibung");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,161 @@
|
|||||||
namespace Jugenddienst_Stunden.ViewModels;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.ViewModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Die Loginseite
|
/// ViewModel für die Loginseite (MVVM)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LoginViewModel {
|
public partial class LoginViewModel : ObservableObject {
|
||||||
/// <summary>
|
private readonly IAuthService _auth;
|
||||||
/// Name der Anwendung
|
private readonly IAppSettings _settings;
|
||||||
/// </summary>
|
private readonly IAlertService? _alerts;
|
||||||
public string AppTitle => AppInfo.Name;
|
private DateTime _lastDetectionTime = DateTime.MinValue;
|
||||||
|
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
/// <summary>
|
public event EventHandler<string>? AlertEvent;
|
||||||
/// Programmversion
|
//public event EventHandler<string>? InfoEvent;
|
||||||
/// </summary>
|
public event EventHandler<ConfirmationEventArgs>? InfoEvent;
|
||||||
public string Version => AppInfo.VersionString;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kurze Mitteilung für den Anwender
|
/// Name der Anwendung
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Message { get; set; } = "Scanne den QR-Code von deinem Benutzerprofil auf der Stundenseite.";
|
public string AppTitle => AppInfo.Name;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Genutzer Server für die API
|
/// Programmversion
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Server { get; set; } = "Server: " + Preferences.Default.Get("apiUrl", "").Replace("/appapi", "").Replace("https://", "").Replace("http://", "");
|
public string Version => AppInfo.VersionString;
|
||||||
|
|
||||||
/// <summary>
|
[ObservableProperty]
|
||||||
/// Titel der Seite - im Moment der aktuelle Anwender
|
private string message = "Scanne den QR-Code von deinem Benutzerprofil auf der Stundenseite.";
|
||||||
/// </summary>
|
|
||||||
public string Title { get; set; } = Preferences.Default.Get("name", "Nicht") + " " + Preferences.Default.Get("surname", "eingeloggt");
|
[ObservableProperty]
|
||||||
|
private string? server;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? serverLabel;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string title = Preferences.Default.Get("name", "Nicht") + " " + Preferences.Default.Get("surname", "eingeloggt");
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? username;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? password;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isManualMode;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isBusy;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isDetecting;
|
||||||
|
|
||||||
|
// Explizite Command-Property für den QR-Scanner-Event, damit das Binding in XAML zuverlässig greift
|
||||||
|
public IAsyncRelayCommand<object?> QrDetectedCommand { get; }
|
||||||
|
|
||||||
|
public LoginViewModel(IAuthService auth, IAppSettings settings) {
|
||||||
|
_auth = auth;
|
||||||
|
_settings = settings;
|
||||||
|
|
||||||
|
// gespeicherte Präferenz für Logintyp laden
|
||||||
|
var lt = Preferences.Default.Get("logintype", "qr");
|
||||||
|
isManualMode = string.Equals(lt, "manual", StringComparison.OrdinalIgnoreCase);
|
||||||
|
// Scanner standardmäßig nur im QR-Modus aktivieren
|
||||||
|
IsDetecting = !isManualMode;
|
||||||
|
|
||||||
|
// Serveranzeige vorbereiten
|
||||||
|
var apiUrl = Preferences.Default.Get("apiUrl", string.Empty);
|
||||||
|
if (!string.IsNullOrWhiteSpace(apiUrl)) {
|
||||||
|
Server = apiUrl.Replace("/appapi", "").Replace("https://", "").Replace("http://", "");
|
||||||
|
ServerLabel = "Server: " + Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command initialisieren
|
||||||
|
QrDetectedCommand = new AsyncRelayCommand<object?>(QrDetectedAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DI-Konstruktor: AlertService anbinden und Alerts an VM-Event weiterreichen (analog StundeViewModel)
|
||||||
|
internal LoginViewModel(IAuthService auth, IAppSettings settings, IAlertService alertService) : this(auth, settings) {
|
||||||
|
_alerts = alertService;
|
||||||
|
if (alertService is not null) {
|
||||||
|
alertService.AlertRaised += (s, msg) => AlertEvent?.Invoke(this, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnIsManualModeChanged(bool value) {
|
||||||
|
Preferences.Default.Set("logintype", value ? "manual" : "qr");
|
||||||
|
// Scanner nur aktiv, wenn QR-Modus aktiv ist
|
||||||
|
IsDetecting = !value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task LoginAsync() {
|
||||||
|
if (IsBusy) return;
|
||||||
|
try {
|
||||||
|
IsBusy = true;
|
||||||
|
var user = await _auth.LoginWithCredentials(Username?.Trim() ?? string.Empty,
|
||||||
|
Password ?? string.Empty,
|
||||||
|
(Server ?? string.Empty).Trim());
|
||||||
|
|
||||||
|
Title = $"{user.Name} {user.Surname}";
|
||||||
|
// Info zeigen und auf Bestätigung warten
|
||||||
|
var args = new ConfirmationEventArgs("Information:", "Login erfolgreich");
|
||||||
|
InfoEvent?.Invoke(this, args);
|
||||||
|
bool confirmed = await args.Task;
|
||||||
|
if (confirmed) {
|
||||||
|
await Shell.Current.GoToAsync("//StundenPage");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (_alerts is not null) {
|
||||||
|
_alerts.Raise(ex.Message);
|
||||||
|
} else {
|
||||||
|
AlertEvent?.Invoke(this, ex.Message);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task QrDetectedAsync(object? args) {
|
||||||
|
var now = DateTime.Now;
|
||||||
|
if ((now - _lastDetectionTime) <= _detectionInterval) return;
|
||||||
|
_lastDetectionTime = now;
|
||||||
|
|
||||||
|
try {
|
||||||
|
var token = ExtractFirstBarcodeValue(args);
|
||||||
|
if (string.IsNullOrWhiteSpace(token)) return;
|
||||||
|
|
||||||
|
var user = await _auth.LoginWithToken(token);
|
||||||
|
Title = $"{user.Name} {user.Surname}";
|
||||||
|
|
||||||
|
// Info zeigen und auf Bestätigung warten
|
||||||
|
var infoArgs = new ConfirmationEventArgs("Information:", "Login erfolgreich");
|
||||||
|
InfoEvent?.Invoke(this, infoArgs);
|
||||||
|
bool confirmed = await infoArgs.Task;
|
||||||
|
if (confirmed) {
|
||||||
|
await Shell.Current.GoToAsync("//StundenPage");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (_alerts is not null) {
|
||||||
|
_alerts.Raise(ex.Message);
|
||||||
|
} else {
|
||||||
|
AlertEvent?.Invoke(this, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ExtractFirstBarcodeValue(object? args) {
|
||||||
|
try {
|
||||||
|
if (args is ZXing.Net.Maui.BarcodeDetectionEventArgs e && e.Results is not null) {
|
||||||
|
return e.Results.FirstOrDefault()?.Value;
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,10 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.ViewModels;
|
namespace Jugenddienst_Stunden.ViewModels;
|
||||||
|
|
||||||
internal class NoteViewModel : ObservableObject, IQueryAttributable {
|
internal class NoteViewModel : ObservableObject, IQueryAttributable {
|
||||||
private Models.Note _note;
|
private Models.Note _note;
|
||||||
|
|
||||||
public string Text {
|
public string Text {
|
||||||
get => _note.Text;
|
get => _note.Text;
|
||||||
set {
|
set {
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ internal class NotesViewModel : IQueryAttributable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If note isn't found, it's new; add it.
|
// If note isn't found, it's new; add it.
|
||||||
else { AllNotes.Insert(0, new NoteViewModel(Note.Load(noteId))); }
|
else {
|
||||||
|
AllNotes.Insert(0, new NoteViewModel(Note.Load(noteId)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,243 +5,275 @@ using Jugenddienst_Stunden.Types;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Repositories;
|
||||||
|
using Jugenddienst_Stunden.Services;
|
||||||
|
using Jugenddienst_Stunden.Infrastructure;
|
||||||
|
using Jugenddienst_Stunden.Validators;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.ViewModels;
|
namespace Jugenddienst_Stunden.ViewModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Viewmodel für die einzelnen Stundeneinträge / Bearbeitung
|
/// Viewmodel für die einzelnen Stundeneinträge / Bearbeitung
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
||||||
|
private readonly IHoursService _hoursService;
|
||||||
|
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Title { get; set; } = "Eintrag bearbeiten";
|
public string Title { get; set; } = "Eintrag bearbeiten";
|
||||||
public string SubTitle { get; set; } = DateTime.Today.ToString("dddd, d. MMMM yyyy");
|
public string SubTitle { get; set; } = DateTime.Today.ToString("dddd, d. MMMM yyyy");
|
||||||
|
|
||||||
//private HoursBase HoursBase = new HoursBase();
|
//private HoursBase HoursBase = new HoursBase();
|
||||||
internal Settings Settings = new Settings();
|
internal Settings Settings = new Settings();
|
||||||
|
|
||||||
public event EventHandler<string> AlertEvent;
|
public event EventHandler<string> AlertEvent;
|
||||||
public event EventHandler<string> InfoEvent;
|
public event EventHandler<string> InfoEvent;
|
||||||
public event Func<string, string, Task<bool>> ConfirmEvent;
|
|
||||||
//public event Func<string, string, string?, string?, Task<bool>> ConfirmEvent;
|
|
||||||
//public event EventHandler<ConfirmEventArgs> ConfirmEvent;
|
|
||||||
|
|
||||||
/// <summary>
|
public event Func<string, string, Task<bool>> ConfirmEvent;
|
||||||
/// Gemeinden für die Auswahlliste
|
//public event Func<string, string, string?, string?, Task<bool>> ConfirmEvent;
|
||||||
/// </summary>
|
//public event EventHandler<ConfirmEventArgs> ConfirmEvent;
|
||||||
[ObservableProperty]
|
|
||||||
private List<Gemeinde> optionsGemeinde;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Projekte für die Auswahlliste
|
/// Gemeinden für die Auswahlliste
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private List<Gemeinde> optionsGemeinde;
|
||||||
private List<Projekt> optionsProjekt;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Freistellungen für die Auswahlliste
|
/// Projekte für die Auswahlliste
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private List<Projekt> optionsProjekt;
|
||||||
private List<Freistellung> optionsFreistellung;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Vorhandene Zeiten anzeigen, wenn neuer Eintrag erstellt wird
|
/// Freistellungen für die Auswahlliste
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private List<Freistellung> optionsFreistellung;
|
||||||
private List<DayTime> dayTimes;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Aktueller Stundeneintrag
|
/// Vorhandene Zeiten anzeigen, wenn neuer Eintrag erstellt wird
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private List<DayTime> dayTimes;
|
||||||
private DayTime dayTime;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dürfen Gemeinden verwendet werden?
|
/// Aktueller Stundeneintrag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private DayTime dayTime;
|
||||||
private bool gemeindeAktivSet;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dürfen Projekte verwendet werden?
|
/// Dürfen Gemeinden verwendet werden?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private bool gemeindeAktivSet;
|
||||||
private bool projektAktivSet;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
/// <summary>
|
||||||
private bool freistellungEnabled;
|
/// Dürfen Projekte verwendet werden?
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] private bool projektAktivSet;
|
||||||
|
|
||||||
public ICommand SaveCommand { get; private set; }
|
[ObservableProperty] private bool freistellungEnabled;
|
||||||
public ICommand DeleteCommand { get; private set; }
|
|
||||||
public ICommand DeleteConfirmCommand { get; private set; }
|
public ICommand SaveCommand { get; private set; }
|
||||||
//public ICommand LoadDataCommand { get; private set; }
|
public ICommand DeleteCommand { get; private set; }
|
||||||
|
public ICommand DeleteConfirmCommand { get; private set; }
|
||||||
|
//public ICommand LoadDataCommand { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
public StundeViewModel() {
|
public StundeViewModel() : this(GetServiceOrCreate()) {
|
||||||
SaveCommand = new AsyncRelayCommand(Save);
|
LoadSettingsAsync();
|
||||||
//DeleteCommand = new AsyncRelayCommand(Delete);
|
}
|
||||||
DeleteConfirmCommand = new Command(async () => await DeleteConfirm());
|
|
||||||
}
|
|
||||||
|
|
||||||
public StundeViewModel(DayTime stunde) {
|
private static IHoursService GetServiceOrCreate() {
|
||||||
SaveCommand = new AsyncRelayCommand(Save);
|
// Fallback-Konstruktion, falls DI nicht injiziert wurde (z. B. im Designer)
|
||||||
DeleteConfirmCommand = new AsyncRelayCommand(DeleteConfirm);
|
var http = new HttpClient();
|
||||||
}
|
var options = new Infrastructure.ApiOptions { BaseUrl = GlobalVar.ApiUrl, Timeout = TimeSpan.FromSeconds(15) };
|
||||||
|
var tokenProvider = new GlobalVarTokenProvider();
|
||||||
|
var api = new ApiClient(http, options, tokenProvider, new PreferencesAppSettings());
|
||||||
|
var repo = new HoursRepository(api);
|
||||||
|
var validator = new HoursValidator();
|
||||||
|
return new HoursService(repo, validator);
|
||||||
|
}
|
||||||
|
|
||||||
private async void LoadSettingsAsync() {
|
internal StundeViewModel(IHoursService hoursService) {
|
||||||
try {
|
_hoursService = hoursService;
|
||||||
Settings = await HoursBase.LoadSettings();
|
SaveCommand = new AsyncRelayCommand(Save);
|
||||||
GlobalVar.Settings = Settings;
|
//DeleteCommand = new AsyncRelayCommand(Delete);
|
||||||
|
DeleteConfirmCommand = new Command(async () => await DeleteConfirm());
|
||||||
|
}
|
||||||
|
|
||||||
OptionsGemeinde = Settings.Gemeinden;
|
// DI-Konstruktor, der den globalen Alert-Service abonniert und Alerts an das ViewModel weiterreicht.
|
||||||
OptionsProjekt = Settings.Projekte;
|
internal StundeViewModel(IHoursService hoursService, IAlertService alertService) : this(hoursService) {
|
||||||
OptionsFreistellung = Settings.Freistellungen;
|
if (alertService is not null) {
|
||||||
|
alertService.AlertRaised += (s, msg) => AlertEvent?.Invoke(this, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GemeindeAktivSet = Settings.GemeindeAktivSet;
|
private async void LoadSettingsAsync() {
|
||||||
ProjektAktivSet = Settings.ProjektAktivSet;
|
try {
|
||||||
|
Settings = await _hoursService.GetSettingsAsync();
|
||||||
|
GlobalVar.Settings = Settings;
|
||||||
|
|
||||||
} catch (Exception e) {
|
OptionsGemeinde = Settings.Gemeinden;
|
||||||
AlertEvent?.Invoke(this, e.Message);
|
OptionsProjekt = Settings.Projekte;
|
||||||
}
|
OptionsFreistellung = Settings.Freistellungen;
|
||||||
}
|
|
||||||
|
|
||||||
async Task Save() {
|
GemeindeAktivSet = Settings.GemeindeAktivSet;
|
||||||
bool exceptionOccurred = false;
|
ProjektAktivSet = Settings.ProjektAktivSet;
|
||||||
bool proceed = true;
|
} catch (Exception e) {
|
||||||
if (DayTime.TimeSpanVon == DayTime.TimeSpanBis && DayTime.FreistellungAktiv.Name == null) {
|
AlertEvent?.Invoke(this, e.Message);
|
||||||
proceed = false;
|
}
|
||||||
AlertEvent?.Invoke(this, "Uhrzeiten sollten unterschiedlich sein");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (proceed) {
|
async Task Save() {
|
||||||
try {
|
bool exceptionOccurred = false;
|
||||||
await HoursBase.SaveEntry(DayTime);
|
bool proceed = true;
|
||||||
} catch (Exception e) {
|
|
||||||
AlertEvent?.Invoke(this, e.Message);
|
|
||||||
exceptionOccurred = true;
|
|
||||||
}
|
|
||||||
if (!exceptionOccurred) {
|
|
||||||
if (DayTime.Id != null) {
|
|
||||||
await Shell.Current.GoToAsync($"..?saved={DayTime.Id}");
|
|
||||||
} else {
|
|
||||||
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
//Arbeitszeit sollte nicht null sein
|
||||||
/// Löschen ohne Bestätigung
|
if (DayTime.TimeSpanVon == DayTime.TimeSpanBis && DayTime.FreistellungAktiv.Name == null) {
|
||||||
/// </summary>
|
proceed = false;
|
||||||
private async Task Delete() {
|
AlertEvent?.Invoke(this, "Uhrzeiten sollten unterschiedlich sein");
|
||||||
await HoursBase.DeleteEntry(DayTime);
|
}
|
||||||
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
//Projekt ist ein Pflichtfeld
|
||||||
/// Löschen mit Bestätigung
|
if (Settings.ProjektAktivSet && DayTime.ProjektAktiv.Id == 0) {
|
||||||
/// </summary>
|
proceed = false;
|
||||||
private async Task DeleteConfirm() {
|
AlertEvent?.Invoke(this, "Projekt darf nicht leer sein");
|
||||||
if (ConfirmEvent != null) {
|
}
|
||||||
bool answer = await ConfirmEvent.Invoke("Achtung", "Löschen kann nicht ungeschehen gemacht werden. Fortfahren?");
|
|
||||||
if (answer) {
|
|
||||||
//Löschen
|
|
||||||
await HoursBase.DeleteEntry(DayTime);
|
|
||||||
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
|
||||||
} else {
|
|
||||||
//nicht Löschen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
//Gemeinde ist ein Pflichtfeld
|
||||||
/// Anwenden der Query-Parameter
|
if (Settings.GemeindeAktivSet && DayTime.GemeindeAktiv.Id == 0) {
|
||||||
/// </summary>
|
proceed = false;
|
||||||
async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) {
|
AlertEvent?.Invoke(this, "Gemeinde darf nicht leer sein");
|
||||||
//load beinhaltet die ID: Eintrag bearbeiten
|
}
|
||||||
//date beinhaltet einen Tag: Neuen Eintrag erstellen
|
|
||||||
if (query.ContainsKey("load")) {
|
|
||||||
|
|
||||||
//DateTime heute = DateTime.Now;
|
if (proceed) {
|
||||||
try {
|
try {
|
||||||
//_dayTime = await HoursBase.LoadEntry(Convert.ToInt32(query["load"]));
|
await _hoursService.SaveEntryAsync(DayTime);
|
||||||
BaseResponse dat = await HoursBase.LoadBase("id=" + Convert.ToInt32(query["load"]));
|
} catch (Exception e) {
|
||||||
GlobalVar.Settings = dat.settings;
|
AlertEvent?.Invoke(this, e.Message);
|
||||||
GemeindeAktivSet = dat.settings.GemeindeAktivSet;
|
exceptionOccurred = true;
|
||||||
ProjektAktivSet = dat.settings.ProjektAktivSet;
|
}
|
||||||
|
|
||||||
DayTime = dat.daytime;
|
if (!exceptionOccurred) {
|
||||||
DayTime.TimeSpanVon = dat.daytime.Begin.ToTimeSpan();
|
if (DayTime.Id != null) {
|
||||||
DayTime.TimeSpanBis = dat.daytime.End.ToTimeSpan();
|
await Shell.Current.GoToAsync($"..?saved={DayTime.Id}");
|
||||||
|
} else {
|
||||||
|
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OptionsGemeinde = dat.settings.Gemeinden ?? new List<Gemeinde>();
|
/// <summary>
|
||||||
OptionsProjekt = dat.settings.Projekte ?? new List<Projekt>();
|
/// Löschen ohne Bestätigung
|
||||||
OptionsFreistellung = dat.settings.Freistellungen ?? new List<Freistellung>();
|
/// </summary>
|
||||||
|
private async Task Delete() {
|
||||||
|
await _hoursService.DeleteEntryAsync(DayTime);
|
||||||
|
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
||||||
|
}
|
||||||
|
|
||||||
DayTime.GemeindeAktiv = OptionsGemeinde.FirstOrDefault(Gemeinde => Gemeinde.Id == DayTime.Gemeinde) ?? new Gemeinde();
|
/// <summary>
|
||||||
DayTime.ProjektAktiv = OptionsProjekt.FirstOrDefault(Projekt => Projekt.Id == DayTime.Projekt) ?? new Projekt();
|
/// Löschen mit Bestätigung
|
||||||
DayTime.FreistellungAktiv = OptionsFreistellung.FirstOrDefault(Freistellung => Freistellung.Id == DayTime.Free) ?? new Freistellung();
|
/// </summary>
|
||||||
|
private async Task DeleteConfirm() {
|
||||||
|
if (ConfirmEvent != null) {
|
||||||
|
bool answer =
|
||||||
|
await ConfirmEvent.Invoke("Achtung", "Löschen kann nicht ungeschehen gemacht werden. Fortfahren?");
|
||||||
|
if (answer) {
|
||||||
|
//Löschen
|
||||||
|
await _hoursService.DeleteEntryAsync(DayTime);
|
||||||
|
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
||||||
|
} else {
|
||||||
|
//nicht Löschen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Evtl. noch die anderen Zeiten des gleichen Tages holen
|
/// <summary>
|
||||||
BaseResponse dat1 = await HoursBase.LoadBase("date=" + DayTime.Day.ToString("yyyy-MM-dd"));
|
/// Anwenden der Query-Parameter
|
||||||
DayTimes = dat1.daytimes;
|
/// </summary>
|
||||||
OnPropertyChanged(nameof(DayTime));
|
async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) {
|
||||||
OnPropertyChanged(nameof(DayTimes));
|
//load beinhaltet die ID: Eintrag bearbeiten
|
||||||
|
//date beinhaltet einen Tag: Neuen Eintrag erstellen
|
||||||
|
if (query.ContainsKey("load")) {
|
||||||
|
//DateTime heute = DateTime.Now;
|
||||||
|
try {
|
||||||
|
var entry = await _hoursService.GetEntryAsync(Convert.ToInt32(query["load"]));
|
||||||
|
// var settings = await _hoursService.GetSettingsAsync();
|
||||||
|
// GlobalVar.Settings = settings;
|
||||||
|
// GemeindeAktivSet = settings.GemeindeAktivSet;
|
||||||
|
// ProjektAktivSet = settings.ProjektAktivSet;
|
||||||
|
|
||||||
} catch (Exception e) {
|
DayTime = entry;
|
||||||
AlertEvent?.Invoke(this, e.Message);
|
DayTime.TimeSpanVon = entry.Begin.ToTimeSpan();
|
||||||
} finally {
|
DayTime.TimeSpanBis = entry.End.ToTimeSpan();
|
||||||
|
|
||||||
}
|
// OptionsGemeinde = settings.Gemeinden ?? new List<Gemeinde>();
|
||||||
|
// OptionsProjekt = settings.Projekte ?? new List<Projekt>();
|
||||||
|
// OptionsFreistellung = settings.Freistellungen ?? new List<Freistellung>();
|
||||||
|
|
||||||
if (System.String.IsNullOrEmpty(DayTime.Description)) {
|
DayTime.GemeindeAktiv = OptionsGemeinde.FirstOrDefault(Gemeinde => Gemeinde.Id == DayTime.Gemeinde) ??
|
||||||
InfoEvent?.Invoke(this, "Eintrag hat keinen Beschreibungstext");
|
new Gemeinde();
|
||||||
}
|
DayTime.ProjektAktiv = OptionsProjekt.FirstOrDefault(Projekt => Projekt.Id == DayTime.Projekt) ??
|
||||||
SubTitle = DayTime.Day.ToString("dddd, d. MMMM yyyy");
|
new Projekt();
|
||||||
OnPropertyChanged(nameof(SubTitle));
|
DayTime.FreistellungAktiv =
|
||||||
|
OptionsFreistellung.FirstOrDefault(Freistellung => Freistellung.Id == DayTime.Free) ??
|
||||||
|
new Freistellung();
|
||||||
|
|
||||||
FreistellungEnabled = !DayTime.Approved;
|
//Evtl. noch die anderen Zeiten des gleichen Tages holen
|
||||||
//OnPropertyChanged(nameof(DayTime));
|
var day = await _hoursService.GetDayWithSettingsAsync(DayTime.Day);
|
||||||
|
DayTimes = day.dayTimes;
|
||||||
|
OnPropertyChanged(nameof(DayTime));
|
||||||
|
OnPropertyChanged(nameof(DayTimes));
|
||||||
|
} catch (Exception e) {
|
||||||
|
AlertEvent?.Invoke(this, e.Message);
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
|
||||||
} else if (query.ContainsKey("date")) {
|
if (System.String.IsNullOrEmpty(DayTime.Description)) {
|
||||||
Title = "Neuer Eintrag";
|
InfoEvent?.Invoke(this, "Eintrag hat keinen Beschreibungstext");
|
||||||
OnPropertyChanged(nameof(Title));
|
}
|
||||||
|
|
||||||
DateTime _date = DateTime.ParseExact((string)query["date"], "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
|
SubTitle = DayTime.Day.ToString("dddd, d. MMMM yyyy");
|
||||||
//Bei neuem Eintrag die vorhandenen des gleichen Tages anzeigen
|
OnPropertyChanged(nameof(SubTitle));
|
||||||
try {
|
|
||||||
//DayTimes = await HoursBase.LoadDay(_date);
|
|
||||||
BaseResponse dat = await HoursBase.LoadBase("date=" + _date.ToString("yyyy-MM-dd"));
|
|
||||||
GlobalVar.Settings = dat.settings;
|
|
||||||
DayTimes = dat.daytimes;
|
|
||||||
|
|
||||||
OptionsGemeinde = dat.settings.Gemeinden;
|
FreistellungEnabled = !DayTime.Approved;
|
||||||
OptionsProjekt = dat.settings.Projekte;
|
//OnPropertyChanged(nameof(DayTime));
|
||||||
OptionsFreistellung = dat.settings.Freistellungen;
|
} else if (query.ContainsKey("date")) {
|
||||||
|
Title = "Neuer Eintrag";
|
||||||
|
OnPropertyChanged(nameof(Title));
|
||||||
|
|
||||||
GemeindeAktivSet = dat.settings.GemeindeAktivSet;
|
DateTime _date = DateTime.ParseExact((string)query["date"], "yyyy-MM-dd",
|
||||||
ProjektAktivSet = dat.settings.ProjektAktivSet;
|
System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
//Bei neuem Eintrag die vorhandenen des gleichen Tages anzeigen
|
||||||
|
try {
|
||||||
|
var (list, settings) = await _hoursService.GetDayWithSettingsAsync(_date);
|
||||||
|
GlobalVar.Settings = settings;
|
||||||
|
DayTimes = list;
|
||||||
|
|
||||||
} catch (Exception) {
|
OptionsGemeinde = settings.Gemeinden;
|
||||||
//Ein Tag ohne Einträge gibt eine Fehlermeldung,
|
OptionsProjekt = settings.Projekte;
|
||||||
//die soll aber ignoriert werden, weil beim Neueintrag ist das ja Wurscht
|
OptionsFreistellung = settings.Freistellungen;
|
||||||
//In dem Fall müssen die Settings aber nochmal geholt werden, weil die dann nicht geladen wurden
|
|
||||||
LoadSettingsAsync();
|
|
||||||
} finally {
|
|
||||||
DayTime = new DayTime();
|
|
||||||
DayTime.Day = _date;
|
|
||||||
DayTime.EmployeeId = GlobalVar.EmployeeId;
|
|
||||||
DayTime.GemeindeAktiv = new Gemeinde();
|
|
||||||
DayTime.ProjektAktiv = new Projekt();
|
|
||||||
DayTime.FreistellungAktiv = new Freistellung();
|
|
||||||
|
|
||||||
SubTitle = _date.ToString("dddd, d. MMMM yyyy");
|
GemeindeAktivSet = settings.GemeindeAktivSet;
|
||||||
FreistellungEnabled = true;
|
ProjektAktivSet = settings.ProjektAktivSet;
|
||||||
|
} catch (Exception) {
|
||||||
|
//Ein Tag ohne Einträge gibt eine Fehlermeldung,
|
||||||
|
//die soll aber ignoriert werden, weil beim Neueintrag ist das ja Wurscht
|
||||||
|
//In dem Fall müssen die Settings aber nochmal geholt werden, weil die dann nicht geladen wurden
|
||||||
|
// LoadSettingsAsync();
|
||||||
|
} finally {
|
||||||
|
DayTime = new DayTime();
|
||||||
|
DayTime.Day = _date;
|
||||||
|
DayTime.EmployeeId = GlobalVar.EmployeeId;
|
||||||
|
DayTime.GemeindeAktiv = new Gemeinde();
|
||||||
|
DayTime.ProjektAktiv = new Projekt();
|
||||||
|
DayTime.FreistellungAktiv = new Freistellung();
|
||||||
|
|
||||||
OnPropertyChanged(nameof(SubTitle));
|
SubTitle = _date.ToString("dddd, d. MMMM yyyy");
|
||||||
//OnPropertyChanged(nameof(DayTime));
|
FreistellungEnabled = true;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(SubTitle));
|
||||||
|
//OnPropertyChanged(nameof(DayTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,317 +5,366 @@ using Jugenddienst_Stunden.Types;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using CommunityToolkit.Maui.Alerts;
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
using CommunityToolkit.Maui.Core;
|
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.ViewModels;
|
namespace Jugenddienst_Stunden.ViewModels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel für die Stundenliste
|
/// ViewModel für die Stundenliste
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal partial class StundenViewModel : ObservableObject, IQueryAttributable, INotifyPropertyChanged {
|
public partial class StundenViewModel : ObservableObject, IQueryAttributable, INotifyPropertyChanged {
|
||||||
|
private readonly IHoursService _hoursService;
|
||||||
|
|
||||||
public ICommand NewEntryCommand { get; }
|
public ICommand NewEntryCommand { get; }
|
||||||
public ICommand SelectEntryCommand { get; }
|
public ICommand SelectEntryCommand { get; }
|
||||||
public ICommand LoadDataCommand { get; private set; }
|
public ICommand LoadDataCommand { get; private set; }
|
||||||
public ICommand LoadDayCommand { get; private set; }
|
public ICommand LoadDayCommand { get; private set; }
|
||||||
public ICommand RefreshListCommand { get; }
|
public ICommand RefreshListCommand { get; }
|
||||||
public ICommand RefreshCommand { get; }
|
public ICommand RefreshCommand { get; }
|
||||||
|
|
||||||
public event EventHandler<string> AlertEvent;
|
public event EventHandler<string> AlertEvent;
|
||||||
public event EventHandler<string> InfoEvent;
|
public event EventHandler<string> InfoEvent;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Beschriftung Button Monatsübersicht
|
/// Beschriftung Button Monatsübersicht
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private string loadOverview;
|
||||||
private string loadOverview;
|
|
||||||
|
|
||||||
//private HoursBase HoursBase = new HoursBase();
|
//private HoursBase HoursBase = new HoursBase();
|
||||||
internal Settings Settings = new Settings();
|
internal Settings Settings = new Settings();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Zu leistende Stunden
|
/// Zu leistende Stunden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private TimeOnly sollstunden;
|
||||||
private TimeOnly sollstunden;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Geleistete Stunden an einem Tag
|
/// Geleistete Stunden an einem Tag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private TimeOnly dayTotal;
|
||||||
private TimeOnly dayTotal;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Liste der Tageszeiten
|
/// Liste der Tageszeiten
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty] private List<DayTime> dayTimes = new List<DayTime>();
|
||||||
private List<DayTime> dayTimes = new List<DayTime>();
|
|
||||||
|
|
||||||
public string Title { get; set; } = GlobalVar.Name + " " + GlobalVar.Surname;
|
public string Title { get; set; } = GlobalVar.Name + " " + GlobalVar.Surname;
|
||||||
|
|
||||||
private Hours _hour;
|
[ObservableProperty] private Hours hours;
|
||||||
public Hours Hours {
|
|
||||||
get => _hour;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mindest-Datum für den Datepicker
|
|
||||||
/// </summary>
|
|
||||||
public DateTime MinimumDate {
|
|
||||||
get => DateTime.Today.AddDays(-365);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Höchst-Datum für den Datepicker
|
|
||||||
/// </summary>
|
|
||||||
public DateTime MaximumDate {
|
|
||||||
get => DateTime.Today.AddDays(60);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Heutiges Datum, wenn das Datum geändert wird, wird auch der Tag geladen
|
|
||||||
/// </summary>
|
|
||||||
private DateTime dateToday = DateTime.Today;
|
|
||||||
public DateTime DateToday {
|
|
||||||
get => dateToday;
|
|
||||||
set {
|
|
||||||
if (dateToday != value) {
|
|
||||||
dateToday = value;
|
|
||||||
LoadOverview = "Lade Summen für " + dateToday.ToString("MMMM yy");
|
|
||||||
//OnPropertyChanged();
|
|
||||||
Task.Run(() => LoadDay(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Monatsübersicht: Geleistete Stunden
|
|
||||||
/// </summary>
|
|
||||||
public string? ZeitCalculated {
|
|
||||||
get => Hours.Zeit_total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Monatsübersicht: Sollstunden
|
|
||||||
/// </summary>
|
|
||||||
public int? Nominal {
|
|
||||||
get => Hours.Nominal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Monatsübersicht: Differenz zwischen Soll und geleisteten Stunden
|
|
||||||
/// </summary>
|
|
||||||
public int? Overtime {
|
|
||||||
get => Hours.overtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Monatsübersicht: Restüberstunden insgesamt
|
|
||||||
/// </summary>
|
|
||||||
public int OvertimeMonth {
|
|
||||||
get => Hours.overtime_month;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Zeitausgleich {
|
|
||||||
get => Hours.zeitausgleich;
|
|
||||||
}
|
|
||||||
public int ZeitausgleichMonth {
|
|
||||||
get => Hours.zeitausgleich_month;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Monatsübersicht: Resturlaub
|
|
||||||
/// </summary>
|
|
||||||
public int Holiday {
|
|
||||||
get => Hours.holiday;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Seite neu laden
|
|
||||||
/// </summary>
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool isRefreshing;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dürfen Gemeinden verwendet werden?
|
/// Mindest-Datum für den Datepicker
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool GemeindeAktivSet { get; set; }
|
public DateTime MinimumDate {
|
||||||
|
get => DateTime.Today.AddDays(-365);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dürfen Projekte verwendet werden?
|
/// Höchst-Datum für den Datepicker
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ProjektAktivSet { get; set; }
|
public DateTime MaximumDate {
|
||||||
|
get => DateTime.Today.AddDays(60);
|
||||||
|
}
|
||||||
|
|
||||||
private bool doContinue = true;
|
/// <summary>
|
||||||
|
/// Heutiges Datum, wenn das Datum geändert wird, wird auch der Tag geladen
|
||||||
|
/// </summary>
|
||||||
|
private DateTime dateToday = DateTime.Today;
|
||||||
|
|
||||||
|
public DateTime DateToday {
|
||||||
|
get => dateToday;
|
||||||
|
set {
|
||||||
|
if (dateToday != value) {
|
||||||
|
dateToday = value;
|
||||||
|
LoadOverview = "Lade Summen für " + dateToday.ToString("MMMM yy");
|
||||||
|
// Task.Run(() => LoadDay(value));
|
||||||
|
// NICHT Task.Run: LoadDay aktualisiert UI-gebundene Properties
|
||||||
|
MainThread.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadDay(dateToday);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AlertEvent?.Invoke(this, ex.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Monatsübersicht: Geleistete Stunden
|
||||||
|
/// </summary>
|
||||||
|
public double? ZeitCalculated {
|
||||||
|
get => Hours.Zeit_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Monatsübersicht: Sollstunden
|
||||||
|
/// </summary>
|
||||||
|
public double? Nominal {
|
||||||
|
get => Hours.Nominal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Monatsübersicht: Differenz zwischen Soll und geleisteten Stunden
|
||||||
|
/// </summary>
|
||||||
|
public double? Overtime {
|
||||||
|
get => Hours.overtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Monatsübersicht: Restüberstunden insgesamt
|
||||||
|
/// </summary>
|
||||||
|
public double OvertimeMonth {
|
||||||
|
get => Hours.overtime_month;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Zeitausgleich {
|
||||||
|
get => Hours.zeitausgleich;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double ZeitausgleichMonth {
|
||||||
|
get => Hours.zeitausgleich_month;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Monatsübersicht: Resturlaub
|
||||||
|
/// </summary>
|
||||||
|
public double Holiday {
|
||||||
|
get => Hours.holiday;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seite neu laden
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty] private bool isRefreshing;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dürfen Gemeinden verwendet werden?
|
||||||
|
/// </summary>
|
||||||
|
public bool GemeindeAktivSet { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dürfen Projekte verwendet werden?
|
||||||
|
/// </summary>
|
||||||
|
public bool ProjektAktivSet { get; set; }
|
||||||
|
|
||||||
|
private bool doContinue = true;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CTOR (DI)
|
||||||
|
/// </summary>
|
||||||
|
public StundenViewModel(IHoursService hoursService) {
|
||||||
|
_hoursService = hoursService;
|
||||||
|
Hours = new Hours();
|
||||||
|
|
||||||
|
LoadOverview = "Lade Summen für " + DateToday.ToString("MMMM");
|
||||||
|
|
||||||
|
LoadDataCommand = new AsyncRelayCommand(LoadData);
|
||||||
|
NewEntryCommand = new AsyncRelayCommand(NewEntryAsync);
|
||||||
|
SelectEntryCommand = new AsyncRelayCommand<DayTime>(SelectEntryAsync);
|
||||||
|
RefreshListCommand = new AsyncRelayCommand(RefreshList);
|
||||||
|
RefreshCommand = new Command(async () => await RefreshItemsAsync());
|
||||||
|
|
||||||
|
// Task task = LoadDay(DateTime.Today);
|
||||||
|
// Beim Startup NICHT direkt im CTOR laden (kann Startup/Navigation blockieren)
|
||||||
|
// Stattdessen via Dispatcher "nach" dem Aufbau starten:
|
||||||
|
MainThread.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadDay(DateTime.Today);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AlertEvent?.Invoke(this, ex.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CTOR
|
/// Öffnet eine neue Stundeneingabe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StundenViewModel() {
|
private async Task NewEntryAsync() {
|
||||||
_hour = new Hours();
|
//Hier muss das Datum übergeben werden
|
||||||
|
//await Shell.Current.GoToAsync(nameof(Views.StundePage));
|
||||||
|
await Shell.Current.GoToAsync($"{nameof(Views.StundePage)}?date={dateToday:yyyy-MM-dd}");
|
||||||
|
}
|
||||||
|
|
||||||
LoadOverview = "Lade Summen für " + DateToday.ToString("MMMM");
|
/// <summary>
|
||||||
|
/// Öffnet eine bestehende Stundeneingabe
|
||||||
|
/// </summary>
|
||||||
|
private async Task SelectEntryAsync(DayTime entry) {
|
||||||
|
if (entry != null && entry.Id != null) {
|
||||||
|
//var navigationParameters = new Dictionary<string, object> { { "load", entry.id } };
|
||||||
|
//await Shell.Current.GoToAsync($"{nameof(Views.StundePage)}", navigationParameters);
|
||||||
|
await Shell.Current.GoToAsync($"{nameof(Views.StundePage)}?load={entry.Id}");
|
||||||
|
} else AlertEvent?.Invoke(this, "Auswahl enthält keine Daten");
|
||||||
|
}
|
||||||
|
|
||||||
LoadDataCommand = new AsyncRelayCommand(LoadData);
|
private async Task RefreshList() {
|
||||||
NewEntryCommand = new AsyncRelayCommand(NewEntryAsync);
|
OnPropertyChanged(nameof(DayTimes));
|
||||||
SelectEntryCommand = new AsyncRelayCommand<DayTime>(SelectEntryAsync);
|
}
|
||||||
RefreshListCommand = new AsyncRelayCommand(RefreshList);
|
|
||||||
RefreshCommand = new Command(async () => await RefreshItemsAsync());
|
|
||||||
|
|
||||||
Task task = LoadDay(DateTime.Today);
|
/// <summary>
|
||||||
}
|
/// Lädt die Monatssummen für die Übersicht
|
||||||
|
/// </summary>
|
||||||
|
private async Task LoadData() {
|
||||||
|
try {
|
||||||
|
var (hours, settings) = await _hoursService.GetMonthSummaryAsync(DateToday);
|
||||||
|
Hours = hours;
|
||||||
|
Settings = settings;
|
||||||
|
|
||||||
|
if (Settings.Version != AppInfo.Current.VersionString.Substring(0, 5)) {
|
||||||
|
InfoEvent?.Invoke(this,
|
||||||
|
"Version: " + Settings.Version + " verfügbar (" + AppInfo.Current.VersionString.Substring(0, 5) +
|
||||||
|
" installiert)");
|
||||||
|
}
|
||||||
|
|
||||||
|
//_hour = await HoursBase.LoadData();
|
||||||
|
RefreshProperties();
|
||||||
|
} catch (Exception e) {
|
||||||
|
AlertEvent?.Invoke(this, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Öffnet eine neue Stundeneingabe
|
/// Lädt die Arbeitszeiten für einen Tag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task NewEntryAsync() {
|
public async Task LoadDay(DateTime date) {
|
||||||
//Hier muss das Datum übergeben werden
|
// kleine Initialwerte sind ok, aber UI-Thread sicher setzen:
|
||||||
//await Shell.Current.GoToAsync(nameof(Views.StundePage));
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
await Shell.Current.GoToAsync($"{nameof(Views.StundePage)}?date={dateToday:yyyy-MM-dd}");
|
{
|
||||||
}
|
DayTotal = new TimeOnly(0);
|
||||||
|
Sollstunden = new TimeOnly(0);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
var (dayTimes, settings) = await _hoursService.GetDayWithSettingsAsync(date);
|
||||||
|
|
||||||
/// <summary>
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
/// Öffnet eine bestehende Stundeneingabe
|
{
|
||||||
/// </summary>
|
DayTimes = dayTimes;
|
||||||
private async Task SelectEntryAsync(DayTime entry) {
|
Settings = settings;
|
||||||
if (entry != null && entry.Id != null) {
|
GemeindeAktivSet = Settings.GemeindeAktivSet;
|
||||||
//var navigationParameters = new Dictionary<string, object> { { "load", entry.id } };
|
ProjektAktivSet = Settings.ProjektAktivSet;
|
||||||
//await Shell.Current.GoToAsync($"{nameof(Views.StundePage)}", navigationParameters);
|
|
||||||
await Shell.Current.GoToAsync($"{nameof(Views.StundePage)}?load={entry.Id}");
|
|
||||||
} else AlertEvent?.Invoke(this, "Auswahl enthält keine Daten");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshList() {
|
OnPropertyChanged(nameof(GemeindeAktivSet));
|
||||||
OnPropertyChanged(nameof(DayTimes));
|
OnPropertyChanged(nameof(ProjektAktivSet));
|
||||||
}
|
});
|
||||||
|
|
||||||
/// <summary>
|
List<Sollstunden> _soll;
|
||||||
/// Lädt die Monatssummen für die Übersicht
|
TimeSpan span = TimeSpan.Zero;
|
||||||
/// </summary>
|
bool merker = false;
|
||||||
private async Task LoadData() {
|
foreach (DayTime dt in DayTimes) {
|
||||||
try {
|
span += dt.End - dt.Begin;
|
||||||
BaseResponse dat = await HoursBase.LoadBase("hours&year=" + DateToday.ToString("yyyy") + "&month=" + DateToday.ToString("MM"));
|
//Nachtstunden dazurechnen
|
||||||
_hour = dat.hour;
|
if (dt.Night.Ticks > 0 && !merker) {
|
||||||
Settings = dat.settings;
|
span += dt.Night.ToTimeSpan() * .5;
|
||||||
|
merker = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings.Version != AppInfo.Current.VersionString.Substring(0, 5)) {
|
_soll = Settings.Nominal.Where(w => w.Timetable == dt.TimeTable && w.Wochentag == dt.Wday).ToList();
|
||||||
InfoEvent?.Invoke(this, "Version: " + Settings.Version + " verfügbar (" + AppInfo.Current.VersionString.Substring(0, 5) + " installiert)");
|
if (_soll.Count > 0)
|
||||||
}
|
{
|
||||||
//_hour = await HoursBase.LoadData();
|
var soll = TimeOnly.FromTimeSpan(TimeSpan.FromHours(_soll[0].Zeit));
|
||||||
RefreshProperties();
|
await MainThread.InvokeOnMainThreadAsync(() => Sollstunden = soll);
|
||||||
} catch (Exception e) {
|
}
|
||||||
AlertEvent?.Invoke(this, e.Message);
|
}
|
||||||
}
|
|
||||||
}
|
var total = TimeOnly.FromTimeSpan(span);
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() => DayTotal = total);
|
||||||
|
|
||||||
|
//Nach der Tagessumme die anderen Tage anhängen
|
||||||
|
if (DayTimes != null) {
|
||||||
|
var more = await _hoursService.GetDayRangeAsync(date, date.AddDays(3));
|
||||||
|
if (more != null && more.Count > 0)
|
||||||
|
{
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
|
DayTimes = DayTimes.Concat(more).ToList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
|
{
|
||||||
|
DayTimes = new List<DayTime>();
|
||||||
|
//TODO: hier könnte auch ein Fehler kommen, dann wäre InfoEvent falsch.
|
||||||
|
|
||||||
|
if (Settings.Version != null && Settings.Version != AppInfo.Current.VersionString.Substring(0, 5)) {
|
||||||
|
InfoEvent?.Invoke(this,
|
||||||
|
"Version: " + Settings.Version + " verfügbar (" + AppInfo.Current.VersionString.Substring(0, 5) +
|
||||||
|
" installiert)");
|
||||||
|
} else {
|
||||||
|
InfoEvent?.Invoke(this, e.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lädt die Arbeitszeiten für einen Tag
|
|
||||||
/// </summary>
|
|
||||||
public async Task LoadDay(DateTime date) {
|
|
||||||
DayTotal = new TimeOnly(0);
|
|
||||||
Sollstunden = new TimeOnly(0);
|
|
||||||
try {
|
|
||||||
//_dayTimes = await HoursBase.LoadDay(date);
|
|
||||||
BaseResponse dat = await HoursBase.LoadBase("date=" + date.ToString("yyyy-MM-dd"));
|
|
||||||
|
|
||||||
DayTimes = dat.daytimes;
|
} finally {
|
||||||
Settings = dat.settings;
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
GemeindeAktivSet = Settings.GemeindeAktivSet;
|
{
|
||||||
ProjektAktivSet = Settings.ProjektAktivSet;
|
OnPropertyChanged(nameof(DayTotal));
|
||||||
|
OnPropertyChanged(nameof(Sollstunden));
|
||||||
|
OnPropertyChanged(nameof(DateToday));
|
||||||
|
OnPropertyChanged(nameof(LoadOverview));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OnPropertyChanged(nameof(GemeindeAktivSet));
|
async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) {
|
||||||
OnPropertyChanged(nameof(ProjektAktivSet));
|
if (query.ContainsKey("date")) {
|
||||||
|
await LoadDay(Convert.ToDateTime(query["date"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Sollstunden> _soll;
|
/// <summary>
|
||||||
TimeSpan span = TimeSpan.Zero;
|
/// Seite aktualisieren
|
||||||
bool merker = false;
|
/// </summary>
|
||||||
foreach (DayTime dt in DayTimes) {
|
private async Task RefreshItemsAsync() {
|
||||||
span += dt.End - dt.Begin;
|
IsRefreshing = true;
|
||||||
//Nachtstunden dazurechnen
|
|
||||||
if (dt.Night.Ticks > 0 && !merker) {
|
|
||||||
span += dt.Night.ToTimeSpan() * .5;
|
|
||||||
merker = true;
|
|
||||||
}
|
|
||||||
_soll = Settings.Nominal.Where(w => w.Timetable == dt.TimeTable && w.Wochentag == dt.Wday).ToList();
|
|
||||||
if (_soll.Count > 0)
|
|
||||||
Sollstunden = TimeOnly.FromTimeSpan(TimeSpan.FromHours(_soll[0].Zeit));
|
|
||||||
}
|
|
||||||
DayTotal = TimeOnly.FromTimeSpan(span);
|
|
||||||
|
|
||||||
//Nach der Tagessumme die anderen Tage anhängen
|
//await Task.Delay(2000); // Simuliert eine Datenaktualisierung
|
||||||
if (DayTimes != null) {
|
await LoadDay(DateToday);
|
||||||
BaseResponse dat1 = await HoursBase.LoadBase("date=" + date.ToString("yyyy-MM-dd") + "&tilldate=" + date.AddDays(3).ToString("yyyy-MM-dd"));
|
|
||||||
if (dat1.daytimes != null)
|
|
||||||
DayTimes = dat.daytimes.Concat(dat1.daytimes).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
IsRefreshing = false;
|
||||||
DayTimes = new List<DayTime>();
|
}
|
||||||
//TODO: hier könnte auch ein Fehler kommen, dann wäre InfoEvent falsch.
|
|
||||||
|
|
||||||
if (Settings.Version != null && Settings.Version != AppInfo.Current.VersionString.Substring(0, 5)) {
|
|
||||||
InfoEvent?.Invoke(this, "Version: " + Settings.Version + " verfügbar (" + AppInfo.Current.VersionString.Substring(0, 5) + " installiert)");
|
|
||||||
} else { InfoEvent?.Invoke(this, e.Message); }
|
|
||||||
} finally {
|
|
||||||
OnPropertyChanged(nameof(DayTotal));
|
|
||||||
OnPropertyChanged(nameof(Sollstunden));
|
|
||||||
OnPropertyChanged(nameof(DateToday));
|
|
||||||
OnPropertyChanged(nameof(LoadOverview));
|
|
||||||
//OnPropertyChanged(nameof(DayTimes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) {
|
|
||||||
if (query.ContainsKey("date")) {
|
|
||||||
await LoadDay(Convert.ToDateTime(query["date"]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Seite aktualisieren
|
|
||||||
/// </summary>
|
|
||||||
private async Task RefreshItemsAsync() {
|
|
||||||
IsRefreshing = true;
|
|
||||||
|
|
||||||
//await Task.Delay(2000); // Simuliert eine Datenaktualisierung
|
|
||||||
await LoadDay(DateToday);
|
|
||||||
|
|
||||||
IsRefreshing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Refreshes all properties
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshProperties() {
|
|
||||||
OnPropertyChanged(nameof(Nominal));
|
|
||||||
OnPropertyChanged(nameof(Overtime));
|
|
||||||
OnPropertyChanged(nameof(OvertimeMonth));
|
|
||||||
OnPropertyChanged(nameof(Zeitausgleich));
|
|
||||||
OnPropertyChanged(nameof(ZeitCalculated));
|
|
||||||
OnPropertyChanged(nameof(Holiday));
|
|
||||||
OnPropertyChanged(nameof(Hours));
|
|
||||||
OnPropertyChanged(nameof(Title));
|
|
||||||
OnPropertyChanged(nameof(MinimumDate));
|
|
||||||
OnPropertyChanged(nameof(MaximumDate));
|
|
||||||
OnPropertyChanged(nameof(LoadOverview));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
|
|
||||||
try {
|
|
||||||
base.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
|
|
||||||
} catch (Exception ex) {
|
|
||||||
AlertEvent?.Invoke(this, ex.Message);
|
|
||||||
//Console.WriteLine($"Fehler bei OnPropertyChanged: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes all properties
|
||||||
|
/// </summary>
|
||||||
|
private void RefreshProperties() {
|
||||||
|
OnPropertyChanged(nameof(Hours));
|
||||||
|
OnPropertyChanged(nameof(Title));
|
||||||
|
OnPropertyChanged(nameof(Nominal));
|
||||||
|
OnPropertyChanged(nameof(Overtime));
|
||||||
|
OnPropertyChanged(nameof(OvertimeMonth));
|
||||||
|
OnPropertyChanged(nameof(Zeitausgleich));
|
||||||
|
OnPropertyChanged(nameof(ZeitCalculated));
|
||||||
|
OnPropertyChanged(nameof(Holiday));
|
||||||
|
OnPropertyChanged(nameof(MinimumDate));
|
||||||
|
OnPropertyChanged(nameof(MaximumDate));
|
||||||
|
OnPropertyChanged(nameof(LoadOverview));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
|
||||||
|
try {
|
||||||
|
base.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
AlertEvent?.Invoke(this, ex.Message);
|
||||||
|
//Console.WriteLine($"Fehler bei OnPropertyChanged: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||||
@@ -13,7 +14,8 @@
|
|||||||
|
|
||||||
<!-- Add an item to the toolbar -->
|
<!-- Add an item to the toolbar -->
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="Neue Notiz" Command="{Binding NewCommand}" IconImageSource="{FontImage Glyph='+', Color=Black, Size=22}" />
|
<ToolbarItem Text="Neue Notiz" Command="{Binding NewCommand}"
|
||||||
|
IconImageSource="{FontImage Glyph='+', Color=Black, Size=22}" />
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ContentPage.Behaviors>
|
<ContentPage.Behaviors>
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
</ContentPage.Behaviors>
|
</ContentPage.Behaviors>
|
||||||
|
|
||||||
<VerticalStackLayout Margin="20,0,0,0">
|
<VerticalStackLayout Margin="20,0,0,0">
|
||||||
<Label Text="Werden nur lokal gespeichert"/>
|
<Label Text="Werden nur lokal gespeichert" />
|
||||||
|
|
||||||
<!-- Display notes in a list -->
|
<!-- Display notes in a list -->
|
||||||
<CollectionView x:Name="notesCollection"
|
<CollectionView x:Name="notesCollection"
|
||||||
@@ -31,9 +33,9 @@
|
|||||||
Margin="0,20,0,0"
|
Margin="0,20,0,0"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChangedCommand="{Binding SelectNoteCommand}"
|
SelectionChangedCommand="{Binding SelectNoteCommand}"
|
||||||
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
|
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
|
||||||
|
|
||||||
<!-- Designate how the collection of items are laid out -->
|
<!-- Designate how the collection of items is laid out -->
|
||||||
<CollectionView.ItemsLayout>
|
<CollectionView.ItemsLayout>
|
||||||
<LinearItemsLayout Orientation="Vertical" ItemSpacing="10" />
|
<LinearItemsLayout Orientation="Vertical" ItemSpacing="10" />
|
||||||
</CollectionView.ItemsLayout>
|
</CollectionView.ItemsLayout>
|
||||||
@@ -42,8 +44,8 @@
|
|||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackLayout>
|
<StackLayout>
|
||||||
<Label Text="{Binding Text}" FontSize="22"/>
|
<Label Text="{Binding Text}" FontSize="22" />
|
||||||
<Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/>
|
<Label Text="{Binding Date}" FontSize="14" TextColor="Silver" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
namespace Jugenddienst_Stunden.Views;
|
namespace Jugenddienst_Stunden.Views;
|
||||||
|
|
||||||
public partial class AllNotesPage : ContentPage
|
public partial class AllNotesPage : ContentPage {
|
||||||
{
|
public AllNotesPage() {
|
||||||
public AllNotesPage()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) {
|
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||||
@@ -9,13 +10,13 @@
|
|||||||
Title="{Binding Title}">
|
Title="{Binding Title}">
|
||||||
|
|
||||||
|
|
||||||
<ContentPage.BindingContext>
|
<!-- BindingContext wird via DI im Code-Behind gesetzt -->
|
||||||
<models:LoginViewModel />
|
|
||||||
</ContentPage.BindingContext>
|
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<conv:StringVisibilityConverter x:Key="StringVisibilityConverter" />
|
<conv:StringVisibilityConverter x:Key="StringVisibilityConverter" />
|
||||||
|
<conv:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||||
|
<conv:EventArgsPassThroughConverter x:Key="EventArgsPassThroughConverter" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
@@ -29,40 +30,48 @@
|
|||||||
<VerticalStackLayout Spacing="10" Margin="15,0">
|
<VerticalStackLayout Spacing="10" Margin="15,0">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<HorizontalStackLayout Spacing="5" HorizontalOptions="Start" Grid.Column="0">
|
<HorizontalStackLayout Spacing="5" HorizontalOptions="Start" Grid.Column="0">
|
||||||
<Label FontSize="20" FontAttributes="Bold" Text="{Binding AppTitle}" Margin="0,7,0,0" />
|
<Label FontSize="20" FontAttributes="Bold" Text="{Binding AppTitle}" Margin="0,7,0,0" />
|
||||||
<Label FontSize="16" Text="{Binding Version}" Margin="0,11,0,0" />
|
<Label FontSize="16" Text="{Binding Version}" Margin="0,11,0,0" />
|
||||||
</HorizontalStackLayout>
|
</HorizontalStackLayout>
|
||||||
<Grid Grid.Column="1" ColumnDefinitions="*,50" ColumnSpacing="10">
|
<Grid Grid.Column="1" ColumnDefinitions="*,50" ColumnSpacing="10">
|
||||||
<Label Text="Login QR/manuell" VerticalOptions="Center" Grid.Column="0"/>
|
<Label Text="Login QR/manuell" VerticalOptions="Center" Grid.Column="0" />
|
||||||
<Switch x:Name="LoginSwitch" IsToggled="False" Toggled="Switch_Toggled" VerticalOptions="Center" Grid.Column="1"/>
|
<Switch x:Name="LoginSwitch" IsToggled="{Binding IsManualMode}" VerticalOptions="Center"
|
||||||
|
Grid.Column="1" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Label x:Name="ServerLabel" Text="{Binding Server}" IsVisible="{Binding Server, Converter={StaticResource StringVisibilityConverter}}" />
|
<Label x:Name="ServerLabel" Text="{Binding ServerLabel}"
|
||||||
|
IsVisible="{Binding Server, Converter={StaticResource StringVisibilityConverter}}" />
|
||||||
|
|
||||||
<VerticalStackLayout x:Name="LoginQR" Margin="0,20,0,0">
|
<VerticalStackLayout x:Name="LoginQR" Margin="0,20,0,0" IsVisible="{Binding IsManualMode, Converter={StaticResource InverseBoolConverter}}">
|
||||||
<Label Text="Login mit QR-Code" FontSize="32" HorizontalOptions="Start" />
|
<Label Text="Login mit QR-Code" FontSize="32" HorizontalOptions="Start" />
|
||||||
<Label x:Name="Message" Text="{Binding Message}" Margin="0,15" />
|
<Label x:Name="Message" Text="{Binding Message}" Margin="0,15" />
|
||||||
|
|
||||||
<Border HeightRequest="300" Padding="0">
|
<Border HeightRequest="300" Padding="0">
|
||||||
<zxing:CameraBarcodeReaderView
|
<zxing:CameraBarcodeReaderView
|
||||||
x:Name="barcodeScannerView"
|
x:Name="barcodeScannerView"
|
||||||
BarcodesDetected="BarcodesDetected"
|
VerticalOptions="FillAndExpand"
|
||||||
HorizontalOptions="FillAndExpand"
|
IsDetecting="{Binding IsDetecting}">
|
||||||
VerticalOptions="FillAndExpand"/>
|
<zxing:CameraBarcodeReaderView.Behaviors>
|
||||||
|
<toolkit:EventToCommandBehavior EventName="BarcodesDetected"
|
||||||
|
Command="{Binding QrDetectedCommand}"
|
||||||
|
EventArgsConverter="{StaticResource EventArgsPassThroughConverter}" />
|
||||||
|
</zxing:CameraBarcodeReaderView.Behaviors>
|
||||||
|
</zxing:CameraBarcodeReaderView>
|
||||||
</Border>
|
</Border>
|
||||||
</VerticalStackLayout>
|
</VerticalStackLayout>
|
||||||
|
|
||||||
<VerticalStackLayout x:Name="LoginManual" Spacing="25">
|
<VerticalStackLayout x:Name="LoginManual" Spacing="25" IsVisible="{Binding IsManualMode}">
|
||||||
<Label Text="Manueller Login" FontSize="32" HorizontalOptions="Start" Margin="0, 20, 0, 0" />
|
<Label Text="Manueller Login" FontSize="32" HorizontalOptions="Start" Margin="0, 20, 0, 0" />
|
||||||
<Entry x:Name="UsernameEntry" Placeholder="Benutzername (Mailadresse)" Keyboard="Email" />
|
<Entry x:Name="UsernameEntry" Text="{Binding Username}" Placeholder="Benutzername (Mailadresse)" Keyboard="Email" />
|
||||||
<Entry x:Name="PasswordEntry" Placeholder="Passwort" IsPassword="True" />
|
<Entry x:Name="PasswordEntry" Text="{Binding Password}" Placeholder="Passwort" IsPassword="True" />
|
||||||
<Entry x:Name="ServerEntry" Placeholder="Server (gleich wie im Browser)" Keyboard="Url" />
|
<Entry x:Name="ServerEntry" Text="{Binding Server}" Placeholder="Server (gleich wie im Browser)" Keyboard="Url" />
|
||||||
<Button Text="Login" Clicked="OnLoginButtonClicked" TextColor="{AppThemeBinding Dark={StaticResource White}, Light={StaticResource White}}" />
|
<Button Text="Login" Command="{Binding LoginCommand}"
|
||||||
|
TextColor="{AppThemeBinding Dark={StaticResource White}, Light={StaticResource White}}" />
|
||||||
</VerticalStackLayout>
|
</VerticalStackLayout>
|
||||||
</VerticalStackLayout>
|
</VerticalStackLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Jugenddienst_Stunden.Models;
|
using Jugenddienst_Stunden.Models;
|
||||||
using Jugenddienst_Stunden.Types;
|
using Jugenddienst_Stunden.Types;
|
||||||
|
using Jugenddienst_Stunden.ViewModels;
|
||||||
using ZXing.Net.Maui;
|
using ZXing.Net.Maui;
|
||||||
|
|
||||||
|
|
||||||
@@ -9,197 +10,204 @@ namespace Jugenddienst_Stunden.Views;
|
|||||||
/// Die Loginseite mit dem Barcodescanner
|
/// Die Loginseite mit dem Barcodescanner
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class LoginPage : ContentPage {
|
public partial class LoginPage : ContentPage {
|
||||||
|
private DateTime _lastDetectionTime;
|
||||||
private DateTime _lastDetectionTime;
|
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
||||||
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CTOR
|
/// CTOR
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LoginPage() {
|
public LoginPage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
barcodeScannerView.Options = new BarcodeReaderOptions {
|
// BindingContext via DI beziehen, falls nicht bereits gesetzt
|
||||||
Formats = BarcodeFormat.QrCode,
|
try {
|
||||||
AutoRotate = true,
|
if (BindingContext is null) {
|
||||||
Multiple = false
|
var sp = Application.Current?.Handler?.MauiContext?.Services
|
||||||
};
|
?? throw new InvalidOperationException("DI container ist nicht verfügbar.");
|
||||||
|
BindingContext = sp.GetRequiredService<LoginViewModel>();
|
||||||
|
}
|
||||||
|
} catch (Exception) {
|
||||||
|
// Ignorieren: Fallback bleibt leerer BindingContext
|
||||||
|
}
|
||||||
|
|
||||||
//if (BindingContext is LoginViewModel vm) {
|
if (BindingContext is LoginViewModel vm) {
|
||||||
// vm.AlertEvent += Vm_AlertEvent;
|
vm.AlertEvent += async (_, msg) => await DisplayAlert("Fehler:", msg, "OK");
|
||||||
// vm.InfoEvent += Vm_InfoEvent;
|
//vm.InfoEvent += async (_, msg) => await DisplayAlert("Information:", msg, "OK");
|
||||||
// vm.MsgEvent += Vm_MsgEvent;
|
// Neues InfoEvent: Dialog anzeigen und nach Bestätigung das Result setzen
|
||||||
//}
|
vm.InfoEvent += async (_, infoArgs) => {
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(async () => {
|
||||||
|
await DisplayAlert(infoArgs.Title, infoArgs.Message, infoArgs.ConfirmText);
|
||||||
|
infoArgs.SetResult(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
//Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken
|
barcodeScannerView.Options =
|
||||||
bool sqr = true;
|
new BarcodeReaderOptions { Formats = BarcodeFormat.QrCode, AutoRotate = true, Multiple = false };
|
||||||
bool sma = false;
|
|
||||||
if (Preferences.Default.Get("logintype", "") == "manual") {
|
// Fallback-Verkabelung: Falls das EventToCommandBehavior in XAML nicht greift,
|
||||||
sqr = false;
|
// leiten wir das Kamera-Event manuell an das ViewModel-Command weiter.
|
||||||
sma = true;
|
barcodeScannerView.BarcodesDetected += (s, e) => {
|
||||||
LoginSwitch.IsToggled = true;
|
if (BindingContext is LoginViewModel vm && vm.QrDetectedCommand is not null) {
|
||||||
Message.IsVisible = false;
|
// Sicherstellen, dass die Command-Ausführung im UI-Thread erfolgt
|
||||||
} else {
|
MainThread.BeginInvokeOnMainThread(async () => {
|
||||||
LoginSwitch.IsToggled = false;
|
if (vm.QrDetectedCommand.CanExecute(e)) {
|
||||||
Message.IsVisible = true;
|
await vm.QrDetectedCommand.ExecuteAsync(e);
|
||||||
}
|
}
|
||||||
LoginQR.IsVisible = sqr;
|
});
|
||||||
LoginManual.IsVisible = sma;
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
//if (BindingContext is LoginViewModel vm) {
|
||||||
|
// vm.AlertEvent += Vm_AlertEvent;
|
||||||
|
// vm.InfoEvent += Vm_InfoEvent;
|
||||||
|
// vm.MsgEvent += Vm_MsgEvent;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken
|
||||||
|
// MVVM übernimmt Umschalten über IsManualMode im ViewModel; keine Code-Behind-Umschaltung mehr
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Nach der Erkennung des Barcodes wird der Benutzer eingeloggt
|
/// Nach der Erkennung des Barcodes wird der Benutzer eingeloggt
|
||||||
/// ZXing.Net.Maui.Controls 0.4.4
|
/// ZXing.Net.Maui.Controls 0.4.4
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void BarcodesDetected(object sender, BarcodeDetectionEventArgs e) {
|
private void BarcodesDetected(object sender, BarcodeDetectionEventArgs e) {
|
||||||
|
var currentTime = DateTime.Now;
|
||||||
|
if ((currentTime - _lastDetectionTime) > _detectionInterval) {
|
||||||
|
_lastDetectionTime = currentTime;
|
||||||
|
foreach (var barcode in e.Results) {
|
||||||
|
if (GlobalVar.ApiKey != barcode.Value) {
|
||||||
|
_ = MainThread.InvokeOnMainThreadAsync(async () => {
|
||||||
|
//await DisplayAlert("Barcode erkannt", $"Barcode: {barcode.Format} - {barcode.Value}", "OK");
|
||||||
|
|
||||||
var currentTime = DateTime.Now;
|
try {
|
||||||
if ((currentTime - _lastDetectionTime) > _detectionInterval) {
|
var tokendata = new TokenData(barcode.Value);
|
||||||
_lastDetectionTime = currentTime;
|
GlobalVar.ApiUrl = tokendata.Url;
|
||||||
foreach (var barcode in e.Results) {
|
User user = await HoursBase.LoadUser(barcode.Value);
|
||||||
if (GlobalVar.ApiKey != barcode.Value) {
|
|
||||||
_ = MainThread.InvokeOnMainThreadAsync(async () => {
|
|
||||||
//await DisplayAlert("Barcode erkannt", $"Barcode: {barcode.Format} - {barcode.Value}", "OK");
|
|
||||||
|
|
||||||
try {
|
GlobalVar.ApiKey = barcode.Value;
|
||||||
var tokendata = new TokenData(barcode.Value);
|
GlobalVar.Name = user.Name;
|
||||||
GlobalVar.ApiUrl = tokendata.Url;
|
GlobalVar.Surname = user.Surname;
|
||||||
User user = await HoursBase.LoadUser(barcode.Value);
|
GlobalVar.EmployeeId = user.Id;
|
||||||
|
|
||||||
GlobalVar.ApiKey = barcode.Value;
|
Title = user.Name + " " + user.Surname;
|
||||||
GlobalVar.Name = user.Name;
|
//Auf der Loginseite wird der Server als Info ohne Protokoll und ohne /appapi angezeigt
|
||||||
GlobalVar.Surname = user.Surname;
|
ServerLabel.Text = "Server: " + tokendata.Url.Replace("/appapi", "").Replace("https://", "")
|
||||||
GlobalVar.EmployeeId = user.Id;
|
.Replace("http://", "");
|
||||||
|
|
||||||
Title = user.Name + " " + user.Surname;
|
|
||||||
//Auf der Loginseite wird der Server als Info ohne Protokoll und ohne /appapi angezeigt
|
|
||||||
ServerLabel.Text = "Server: " + tokendata.Url.Replace("/appapi", "").Replace("https://", "").Replace("http://", "");
|
|
||||||
|
|
||||||
|
|
||||||
await DisplayAlert("Login erfolgreich", user.Name + " " + user.Surname, "OK");
|
await DisplayAlert("Login erfolgreich", user.Name + " " + user.Surname, "OK");
|
||||||
if (Navigation.NavigationStack.Count > 1) {
|
if (Navigation.NavigationStack.Count > 1) {
|
||||||
//Beim ersten Start ohne Login, wird man automatisch auf die Loginseite geleitet. Danach in der History zur<75>ck
|
//Beim ersten Start ohne Login, wird man automatisch auf die Loginseite geleitet. Danach in der History zur<75>ck
|
||||||
await Navigation.PopAsync();
|
await Navigation.PopAsync();
|
||||||
} else {
|
} else {
|
||||||
//Beim manuellen Wechsel auf die Loginseite leiten wir nach erfolgreichem Login auf die Stunden<65>bersicht
|
//Beim manuellen Wechsel auf die Loginseite leiten wir nach erfolgreichem Login auf die Stunden<65>bersicht
|
||||||
await Shell.Current.GoToAsync($"//StundenPage");
|
await Shell.Current.GoToAsync($"//StundenPage");
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
await DisplayAlert("Fehler", e.Message, "OK");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
MainThread.InvokeOnMainThreadAsync(() => {
|
||||||
|
DisplayAlert("Bereits eingeloggt",
|
||||||
|
Preferences.Default.Get("name", "") + " " + Preferences.Default.Get("surname", ""),
|
||||||
|
"OK");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
protected override void OnDisappearing() {
|
||||||
await DisplayAlert("Fehler", e.Message, "OK");
|
base.OnDisappearing();
|
||||||
}
|
|
||||||
|
|
||||||
});
|
barcodeScannerView.CameraLocation = CameraLocation.Front;
|
||||||
} else {
|
// IsDetecting wird via Binding vom ViewModel gesteuert
|
||||||
MainThread.InvokeOnMainThreadAsync(() => {
|
}
|
||||||
DisplayAlert("Bereits eingeloggt",
|
|
||||||
Preferences.Default.Get("name", "") + " " + Preferences.Default.Get("surname", ""),
|
|
||||||
"OK");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
protected override void OnAppearing() {
|
||||||
|
base.OnAppearing();
|
||||||
|
|
||||||
protected override void OnDisappearing() {
|
// IsDetecting wird via Binding vom ViewModel gesteuert
|
||||||
base.OnDisappearing();
|
barcodeScannerView.CameraLocation = CameraLocation.Rear;
|
||||||
|
}
|
||||||
|
|
||||||
barcodeScannerView.CameraLocation = CameraLocation.Front;
|
public bool IsCameraAvailable() {
|
||||||
barcodeScannerView.IsDetecting = false;
|
var status = Permissions.CheckStatusAsync<Permissions.Camera>().Result;
|
||||||
}
|
if (status != PermissionStatus.Granted) {
|
||||||
|
status = Permissions.RequestAsync<Permissions.Camera>().Result;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnAppearing() {
|
return status != PermissionStatus.Granted;
|
||||||
base.OnAppearing();
|
}
|
||||||
|
|
||||||
barcodeScannerView.IsDetecting = true;
|
private async void OnLoginButtonClicked(object sender, EventArgs e) {
|
||||||
barcodeScannerView.CameraLocation = CameraLocation.Rear;
|
var username = UsernameEntry.Text;
|
||||||
}
|
var password = PasswordEntry.Text;
|
||||||
|
var server = ServerEntry.Text;
|
||||||
public bool IsCameraAvailable() {
|
|
||||||
var status = Permissions.CheckStatusAsync<Permissions.Camera>().Result;
|
|
||||||
if (status != PermissionStatus.Granted) {
|
|
||||||
status = Permissions.RequestAsync<Permissions.Camera>().Result;
|
|
||||||
}
|
|
||||||
return status != PermissionStatus.Granted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnLoginButtonClicked(object sender, EventArgs e) {
|
|
||||||
var username = UsernameEntry.Text;
|
|
||||||
var password = PasswordEntry.Text;
|
|
||||||
var server = ServerEntry.Text;
|
|
||||||
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(server)) {
|
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(server)) {
|
||||||
await DisplayAlert("Fehler", "Bitte alle Felder ausf<73>llen", "OK");
|
await DisplayAlert("Fehler", "Bitte alle Felder ausf<73>llen", "OK");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
Uri uri = new Uri(InputUrlWithSchema(server));
|
|
||||||
|
|
||||||
Types.User response = await BaseFunc.AuthUserPass(username, password, uri.Scheme + "://" + uri.Authority + "/appapi");
|
try {
|
||||||
|
Uri uri = new Uri(InputUrlWithSchema(server));
|
||||||
|
|
||||||
GlobalVar.ApiKey = response.Token;
|
Types.User response =
|
||||||
GlobalVar.Name = response.Name;
|
await BaseFunc.AuthUserPass(username, password, uri.Scheme + "://" + uri.Authority + "/appapi");
|
||||||
GlobalVar.Surname = response.Surname;
|
|
||||||
GlobalVar.EmployeeId = response.Id;
|
|
||||||
GlobalVar.ApiUrl = uri.Scheme + "://" + uri.Authority + "/appapi";
|
|
||||||
|
|
||||||
Title = response.Name + " " + response.Surname;
|
GlobalVar.ApiKey = response.Token;
|
||||||
//ServerLabel.Text = "Server: " + server.Replace("/appapi", "").Replace("https://", "").Replace("http://", "");
|
GlobalVar.Name = response.Name;
|
||||||
ServerLabel.Text = "Server: " + uri.Authority;
|
GlobalVar.Surname = response.Surname;
|
||||||
|
GlobalVar.EmployeeId = response.Id;
|
||||||
|
GlobalVar.ApiUrl = uri.Scheme + "://" + uri.Authority + "/appapi";
|
||||||
|
|
||||||
await DisplayAlert("Login erfolgreich", response.Name + " " + response.Surname, "OK");
|
Title = response.Name + " " + response.Surname;
|
||||||
if (Navigation.NavigationStack.Count > 1)
|
//ServerLabel.Text = "Server: " + server.Replace("/appapi", "").Replace("https://", "").Replace("http://", "");
|
||||||
await Navigation.PopAsync();
|
ServerLabel.Text = "Server: " + uri.Authority;
|
||||||
else {
|
|
||||||
await Shell.Current.GoToAsync($"//StundenPage");
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
await DisplayAlert("Fehler", ex.Message, "OK");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
await DisplayAlert("Login erfolgreich", response.Name + " " + response.Surname, "OK");
|
||||||
/// Aus einer URL ohne Schema eine URL mit Schema machen
|
if (Navigation.NavigationStack.Count > 1)
|
||||||
/// </summary>
|
await Navigation.PopAsync();
|
||||||
private static string InputUrlWithSchema(string url) {
|
else {
|
||||||
if (!url.StartsWith("http://") && !url.StartsWith("https://")) {
|
await Shell.Current.GoToAsync($"//StundenPage");
|
||||||
url = "https://" + url;
|
}
|
||||||
}
|
} catch (Exception ex) {
|
||||||
if (url.StartsWith("http://")) {
|
await DisplayAlert("Fehler", ex.Message, "OK");
|
||||||
url = url.Replace("http://", "https://");
|
}
|
||||||
}
|
}
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken
|
/// <summary>
|
||||||
private void Switch_Toggled(object sender, ToggledEventArgs e) {
|
/// Aus einer URL ohne Schema eine URL mit Schema machen
|
||||||
var switcher = (Switch)sender;
|
/// </summary>
|
||||||
|
private static string InputUrlWithSchema(string url) {
|
||||||
|
if (!url.StartsWith("http://") && !url.StartsWith("https://")) {
|
||||||
|
url = "https://" + url;
|
||||||
|
}
|
||||||
|
|
||||||
if (switcher.IsToggled) {
|
if (url.StartsWith("http://")) {
|
||||||
LoginQR.IsVisible = false;
|
url = url.Replace("http://", "https://");
|
||||||
LoginManual.IsVisible = true;
|
}
|
||||||
Message.IsVisible = false;
|
|
||||||
Preferences.Default.Set("logintype", "manual");
|
|
||||||
} else {
|
|
||||||
LoginQR.IsVisible = true;
|
|
||||||
LoginManual.IsVisible = false;
|
|
||||||
Message.IsVisible = true;
|
|
||||||
Preferences.Default.Set("logintype", "qr");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
//private void Vm_AlertEvent(object? sender, string e) {
|
//Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken
|
||||||
// DisplayAlert("Fehler:", e, "OK");
|
// Umschalt-Logik erfolgt über Binding an IsManualMode im ViewModel
|
||||||
//}
|
|
||||||
//private void Vm_InfoEvent(object? sender, string e) {
|
//private void Vm_AlertEvent(object? sender, string e) {
|
||||||
// DisplayAlert("Information:", e, "OK");
|
// DisplayAlert("Fehler:", e, "OK");
|
||||||
//}
|
//}
|
||||||
//private async Task Vm_MsgEvent(string title, string message) {
|
//private void Vm_InfoEvent(object? sender, string e) {
|
||||||
// await DisplayAlert(title, message, "OK");
|
// DisplayAlert("Information:", e, "OK");
|
||||||
//}
|
//}
|
||||||
|
//private async Task Vm_MsgEvent(string title, string message) {
|
||||||
|
// await DisplayAlert(title, message, "OK");
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace Jugenddienst_Stunden.Views;
|
|||||||
/// Einzelne Notiz
|
/// Einzelne Notiz
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class NotePage : ContentPage {
|
public partial class NotePage : ContentPage {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CTOR
|
/// CTOR
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||||
@@ -31,20 +32,26 @@
|
|||||||
<Border>
|
<Border>
|
||||||
<Border.Padding>
|
<Border.Padding>
|
||||||
<OnPlatform x:TypeArguments="Thickness" Default="0,15,10,0">
|
<OnPlatform x:TypeArguments="Thickness" Default="0,15,10,0">
|
||||||
<On Platform="Android" Value="0,4,10,8"/>
|
<On Platform="Android" Value="0,4,10,8" />
|
||||||
<On Platform="WPF" Value="0,15,10,0"/>
|
<On Platform="WPF" Value="0,15,10,0" />
|
||||||
</OnPlatform>
|
</OnPlatform>
|
||||||
</Border.Padding>
|
</Border.Padding>
|
||||||
|
|
||||||
<FlexLayout Direction="Row" AlignItems="Start" Wrap="Wrap" JustifyContent="SpaceBetween">
|
<FlexLayout Direction="Row" AlignItems="Start" Wrap="Wrap" JustifyContent="SpaceBetween">
|
||||||
<HorizontalStackLayout Spacing="10">
|
<HorizontalStackLayout Spacing="10">
|
||||||
<Label Text="Beginn" VerticalTextAlignment="Center" HorizontalTextAlignment="End" MinimumWidthRequest="60"></Label>
|
<Label Text="Beginn" VerticalTextAlignment="Center" HorizontalTextAlignment="End"
|
||||||
<TimePicker x:Name="TimeBegin" HorizontalOptions="Center" Format="HH:mm" MinimumWidthRequest="80" Time="{Binding DayTime.TimeSpanVon}" />
|
MinimumWidthRequest="60">
|
||||||
|
</Label>
|
||||||
|
<TimePicker x:Name="TimeBegin" HorizontalOptions="Center" Format="HH:mm" MinimumWidthRequest="80"
|
||||||
|
Time="{Binding DayTime.TimeSpanVon}" />
|
||||||
</HorizontalStackLayout>
|
</HorizontalStackLayout>
|
||||||
|
|
||||||
<HorizontalStackLayout Spacing="10">
|
<HorizontalStackLayout Spacing="10">
|
||||||
<Label Text="Ende" VerticalTextAlignment="Center" HorizontalTextAlignment="End" MinimumWidthRequest="60"></Label>
|
<Label Text="Ende" VerticalTextAlignment="Center" HorizontalTextAlignment="End"
|
||||||
<TimePicker x:Name="TimeEnd" Format="HH:mm" MinimumWidthRequest="80" Time="{Binding DayTime.TimeSpanBis}" />
|
MinimumWidthRequest="60">
|
||||||
|
</Label>
|
||||||
|
<TimePicker x:Name="TimeEnd" Format="HH:mm" MinimumWidthRequest="80"
|
||||||
|
Time="{Binding DayTime.TimeSpanBis}" />
|
||||||
</HorizontalStackLayout>
|
</HorizontalStackLayout>
|
||||||
</FlexLayout>
|
</FlexLayout>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -52,29 +59,28 @@
|
|||||||
<Border>
|
<Border>
|
||||||
<Border.Padding>
|
<Border.Padding>
|
||||||
<OnPlatform x:TypeArguments="Thickness" Default="5">
|
<OnPlatform x:TypeArguments="Thickness" Default="5">
|
||||||
<On Platform="Android" Value="5,4,5,8"/>
|
<On Platform="Android" Value="5,4,5,8" />
|
||||||
<On Platform="WPF" Value="5"/>
|
<On Platform="WPF" Value="5" />
|
||||||
</OnPlatform>
|
</OnPlatform>
|
||||||
</Border.Padding>
|
</Border.Padding>
|
||||||
<!--<Grid ColumnDefinitions="*,*,*">
|
|
||||||
<Picker x:Name="pick_gemeinde" Title="Gemeinde" ItemsSource="{Binding OptionsGemeinde}" SelectedItem="{Binding DayTime.GemeindeAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="0" IsVisible="{Binding GemeindeAktivSet}">
|
|
||||||
</Picker>
|
|
||||||
<Picker x:Name="pick_projekt" Title="Projekt" ItemsSource="{Binding OptionsProjekt}" SelectedItem="{Binding DayTime.ProjektAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="1" IsVisible="{Binding ProjektAktivSet}">
|
|
||||||
</Picker>
|
|
||||||
<Picker x:Name="pick_freistellung" Title="Freistellung" ItemsSource="{Binding OptionsFreistellung}" SelectedItem="{Binding DayTime.FreistellungAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="2" IsEnabled="{Binding FreistellungEnabled}">
|
|
||||||
</Picker>
|
|
||||||
</Grid>-->
|
|
||||||
<HorizontalStackLayout>
|
<HorizontalStackLayout>
|
||||||
<Picker x:Name="pick_gemeinde" Title="Gemeinde" ItemsSource="{Binding OptionsGemeinde}" SelectedItem="{Binding DayTime.GemeindeAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" IsVisible="{Binding GemeindeAktivSet}">
|
<Picker x:Name="pick_gemeinde" Title="Gemeinde" ItemsSource="{Binding OptionsGemeinde}"
|
||||||
|
SelectedItem="{Binding DayTime.GemeindeAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}"
|
||||||
|
IsVisible="{Binding GemeindeAktivSet}">
|
||||||
</Picker>
|
</Picker>
|
||||||
<Picker x:Name="pick_projekt" Title="Projekt" ItemsSource="{Binding OptionsProjekt}" SelectedItem="{Binding DayTime.ProjektAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" IsVisible="{Binding ProjektAktivSet}">
|
<Picker x:Name="pick_projekt" Title="Projekt" ItemsSource="{Binding OptionsProjekt}"
|
||||||
|
SelectedItem="{Binding DayTime.ProjektAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}"
|
||||||
|
IsVisible="{Binding ProjektAktivSet}">
|
||||||
</Picker>
|
</Picker>
|
||||||
<Picker x:Name="pick_freistellung" Title="Freistellung" ItemsSource="{Binding OptionsFreistellung}" SelectedItem="{Binding DayTime.FreistellungAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" IsEnabled="{Binding FreistellungEnabled}">
|
<Picker x:Name="pick_freistellung" Title="Freistellung" ItemsSource="{Binding OptionsFreistellung}"
|
||||||
|
SelectedItem="{Binding DayTime.FreistellungAktiv, Mode=TwoWay}"
|
||||||
|
ItemDisplayBinding="{Binding Name}" IsEnabled="{Binding FreistellungEnabled}">
|
||||||
</Picker>
|
</Picker>
|
||||||
</HorizontalStackLayout>
|
</HorizontalStackLayout>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Editor Placeholder="Beschreibung" Text="{Binding DayTime.Description}" MinimumHeightRequest="40" AutoSize="TextChanges" FontSize="18" />
|
<Editor Placeholder="Beschreibung" Text="{Binding DayTime.Description}" MinimumHeightRequest="40"
|
||||||
|
AutoSize="TextChanges" FontSize="18" />
|
||||||
|
|
||||||
<Grid ColumnDefinitions="*,*" ColumnSpacing="4">
|
<Grid ColumnDefinitions="*,*" ColumnSpacing="4">
|
||||||
<Button Grid.Column="1" Text="Speichern"
|
<Button Grid.Column="1" Text="Speichern"
|
||||||
@@ -85,15 +91,18 @@
|
|||||||
IsEnabled="{Binding DayTime.Id, Converter={StaticResource IntBoolConverter}}"
|
IsEnabled="{Binding DayTime.Id, Converter={StaticResource IntBoolConverter}}"
|
||||||
IsVisible="{Binding FreistellungEnabled}"
|
IsVisible="{Binding FreistellungEnabled}"
|
||||||
BackgroundColor="{StaticResource Gray500}"
|
BackgroundColor="{StaticResource Gray500}"
|
||||||
TextColor="{AppThemeBinding Dark={StaticResource White}, Light={StaticResource White}}"/>
|
TextColor="{AppThemeBinding Dark={StaticResource White}, Light={StaticResource White}}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
<BoxView HeightRequest="1" Margin="3,10" />
|
<BoxView HeightRequest="1" Margin="3,10" />
|
||||||
|
|
||||||
<Label Text="Noch keine Einträge vorhanden" IsVisible="{Binding DayTimes, Converter={StaticResource CollectionVisibilityConverter}, ConverterParameter=Invert}" Margin="6,0,0,0"/>
|
<Label Text="Noch keine Einträge vorhanden"
|
||||||
|
IsVisible="{Binding DayTimes, Converter={StaticResource CollectionVisibilityConverter}, ConverterParameter=Invert}"
|
||||||
|
Margin="6,0,0,0" />
|
||||||
|
|
||||||
<StackLayout Margin="6,0,0,0" IsVisible="{Binding DayTimes, Converter={StaticResource CollectionVisibilityConverter}}">
|
<StackLayout Margin="6,0,0,0"
|
||||||
|
IsVisible="{Binding DayTimes, Converter={StaticResource CollectionVisibilityConverter}}">
|
||||||
<Label>
|
<Label>
|
||||||
<Label.FormattedText>
|
<Label.FormattedText>
|
||||||
<FormattedString>
|
<FormattedString>
|
||||||
@@ -106,12 +115,12 @@
|
|||||||
|
|
||||||
<ScrollView IsVisible="{Binding DayTimes, Converter={StaticResource CollectionVisibilityConverter}}">
|
<ScrollView IsVisible="{Binding DayTimes, Converter={StaticResource CollectionVisibilityConverter}}">
|
||||||
<CollectionView
|
<CollectionView
|
||||||
ItemsSource="{Binding DayTimes}"
|
ItemsSource="{Binding DayTimes}"
|
||||||
x:Name="stundeItems" Margin="0"
|
x:Name="stundeItems" Margin="0"
|
||||||
HeightRequest="350"
|
HeightRequest="350"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChangedCommand="{Binding SelectEntryCommand}"
|
SelectionChangedCommand="{Binding SelectEntryCommand}"
|
||||||
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
|
SelectionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=SelectedItem}">
|
||||||
|
|
||||||
<CollectionView.ItemsLayout>
|
<CollectionView.ItemsLayout>
|
||||||
<LinearItemsLayout Orientation="Vertical" ItemSpacing="0" />
|
<LinearItemsLayout Orientation="Vertical" ItemSpacing="0" />
|
||||||
@@ -129,15 +138,18 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<HorizontalStackLayout Grid.Row="0" Grid.Column="0">
|
<HorizontalStackLayout Grid.Row="0" Grid.Column="0">
|
||||||
<Label Grid.Column="0" Text="{Binding Begin}"/>
|
<Label Grid.Column="0" Text="{Binding Begin}" />
|
||||||
<Label Text="bis" Padding="5,0,5,0"/>
|
<Label Text="bis" Padding="5,0,5,0" />
|
||||||
<Label Text="{Binding End}"/>
|
<Label Text="{Binding End}" />
|
||||||
<Label Text="{Binding GemeindeAktiv.Name}" Margin="10,0,0,0" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.GemeindeAktivSet}"/>
|
<Label Text="{Binding GemeindeAktiv.Name}" Margin="10,0,0,0"
|
||||||
<Label Text="{Binding ProjektAktiv.Name}" Margin="10,0,0,0" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.ProjektAktivSet}"/>
|
IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.GemeindeAktivSet}" />
|
||||||
<Label Text="{Binding FreistellungAktiv.Name}" Margin="10,0,0,0"/>
|
<Label Text="{Binding ProjektAktiv.Name}" Margin="10,0,0,0"
|
||||||
|
IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.ProjektAktivSet}" />
|
||||||
|
<Label Text="{Binding FreistellungAktiv.Name}" Margin="10,0,0,0" />
|
||||||
</HorizontalStackLayout>
|
</HorizontalStackLayout>
|
||||||
|
|
||||||
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Description}" Padding="0,0,0,15"/>
|
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Description}"
|
||||||
|
Padding="0,0,0,15" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
|
|||||||
@@ -10,45 +10,43 @@ namespace Jugenddienst_Stunden.Views;
|
|||||||
/// Einzelner Stundeneintrag
|
/// Einzelner Stundeneintrag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StundePage : ContentPage {
|
public partial class StundePage : ContentPage {
|
||||||
|
/// <summary>
|
||||||
|
/// CTOR
|
||||||
|
/// </summary>
|
||||||
|
public StundePage() {
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
/// <summary>
|
if (BindingContext is StundeViewModel vm) {
|
||||||
/// CTOR
|
vm.AlertEvent += Vm_AlertEvent;
|
||||||
/// </summary>
|
vm.InfoEvent += Vm_InfoEvent;
|
||||||
public StundePage() {
|
vm.ConfirmEvent += ShowConfirm;
|
||||||
InitializeComponent();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (BindingContext is StundeViewModel vm) {
|
private void Vm_AlertEvent(object? sender, string e) {
|
||||||
vm.AlertEvent += Vm_AlertEvent;
|
DisplayAlert("Fehler:", e, "OK");
|
||||||
vm.InfoEvent += Vm_InfoEvent;
|
}
|
||||||
vm.ConfirmEvent += ShowConfirm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Vm_AlertEvent(object? sender, string e) {
|
private async Task<bool> ShowConfirm(string title, string message) {
|
||||||
DisplayAlert("Fehler:", e, "OK");
|
return await DisplayAlert(title, message, "Passt!", "Na, nor decht nit.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> ShowConfirm(string title, string message) {
|
private void Vm_InfoEvent(object? sender, string e) {
|
||||||
return await DisplayAlert(title, message, "Passt!", "Na, nor decht nit.");
|
MainThread.BeginInvokeOnMainThread(async () => {
|
||||||
}
|
CancellationTokenSource cts = new CancellationTokenSource();
|
||||||
|
ToastDuration duration = ToastDuration.Short;
|
||||||
|
double fontSize = 20;
|
||||||
|
var toast = Toast.Make(e, duration, fontSize);
|
||||||
|
await toast.Show(cts.Token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void Vm_InfoEvent(object? sender, string e) {
|
//private async Task<bool> ShowConfirm(string title, string message, string ok, string not_ok) {
|
||||||
MainThread.BeginInvokeOnMainThread(async () => {
|
// return await DisplayAlert(title, message, ok, not_ok);
|
||||||
CancellationTokenSource cts = new CancellationTokenSource();
|
//}
|
||||||
ToastDuration duration = ToastDuration.Short;
|
|
||||||
double fontSize = 20;
|
|
||||||
var toast = Toast.Make(e, duration, fontSize);
|
|
||||||
await toast.Show(cts.Token);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//private async Task<bool> ShowConfirm(string title, string message, string ok, string not_ok) {
|
|
||||||
// return await DisplayAlert(title, message, ok, not_ok);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private async void ShowConfirm(object? sender, ConfirmEventArgs e) {
|
|
||||||
// bool result = await DisplayAlert(e.Title, e.Message, e.Ok, e.NotOk);
|
|
||||||
// e.Result = result;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
//private async void ShowConfirm(object? sender, ConfirmEventArgs e) {
|
||||||
|
// bool result = await DisplayAlert(e.Title, e.Message, e.Ok, e.NotOk);
|
||||||
|
// e.Result = result;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||||
@@ -8,17 +9,14 @@
|
|||||||
x:Class="Jugenddienst_Stunden.Views.StundenPage"
|
x:Class="Jugenddienst_Stunden.Views.StundenPage"
|
||||||
Title="{Binding Title}">
|
Title="{Binding Title}">
|
||||||
|
|
||||||
<ContentPage.BindingContext>
|
|
||||||
<models:StundenViewModel />
|
|
||||||
</ContentPage.BindingContext>
|
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<conv:SecondsTimeConverter x:Key="secToTime" />
|
<conv:SecondsTimeConverter x:Key="secToTime" />
|
||||||
<FontImageSource x:Key="ToolbarIcon"
|
<FontImageSource x:Key="ToolbarIcon"
|
||||||
Glyph="+"
|
Glyph="+"
|
||||||
Size="22"
|
Size="22"
|
||||||
Color="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"/>
|
Color="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
@@ -30,21 +28,23 @@
|
|||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<!--<ToolbarItem Text="Lade Liste" Command="{Binding RefreshListCommand}"/>-->
|
<!--<ToolbarItem Text="Lade Liste" Command="{Binding RefreshListCommand}"/>-->
|
||||||
<ToolbarItem Text="Neuer Eintrag" IconImageSource="{StaticResource ToolbarIcon}" Command="{Binding NewEntryCommand}" />
|
<ToolbarItem Text="Neuer Eintrag" IconImageSource="{StaticResource ToolbarIcon}"
|
||||||
|
Command="{Binding NewEntryCommand}" />
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<RefreshView x:Name="MyRefreshView" Command="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}" Margin="10" Padding="10">
|
<RefreshView x:Name="MyRefreshView" Command="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}"
|
||||||
|
Margin="10" Padding="10">
|
||||||
<Grid RowDefinitions="50,*,Auto,80">
|
<Grid RowDefinitions="50,*,Auto,80">
|
||||||
<!--<VerticalStackLayout Spacing="10" Margin="10">-->
|
<!--<VerticalStackLayout Spacing="10" Margin="10">-->
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto" ColumnDefinitions="Auto,*" HeightRequest="50" Grid.Row="0">
|
<Grid RowDefinitions="Auto" ColumnDefinitions="Auto,*" HeightRequest="50" Grid.Row="0">
|
||||||
<DatePicker Grid.Column="0" MinimumDate="{Binding MinimumDate}"
|
<DatePicker Grid.Column="0" MinimumDate="{Binding MinimumDate}"
|
||||||
MaximumDate="{Binding MaximumDate}"
|
MaximumDate="{Binding MaximumDate}"
|
||||||
Date="{Binding DateToday}" Format="dddd, d. MMMM yyyy" />
|
Date="{Binding DateToday}" Format="dddd, d. MMMM yyyy" />
|
||||||
<Border Grid.Column="1" Margin="15,0,0,0" Padding="15,0,0,0" ToolTipProperties.Text="Tagessumme">
|
<Border Grid.Column="1" Margin="15,0,0,0" Padding="15,0,0,0" ToolTipProperties.Text="Tagessumme">
|
||||||
<HorizontalStackLayout>
|
<HorizontalStackLayout>
|
||||||
<Label Text="{Binding DayTotal,StringFormat='{}{0:HH:mm}'}" VerticalOptions="Center"></Label>
|
<Label Text="{Binding DayTotal,StringFormat='{}{0:HH:mm}'}" VerticalOptions="Center"></Label>
|
||||||
<Label Text="/" VerticalOptions="Center" Margin="3,0"/>
|
<Label Text="/" VerticalOptions="Center" Margin="3,0" />
|
||||||
<Label Text="{Binding Sollstunden,StringFormat='{}{0:HH:mm}'}" VerticalOptions="Center"></Label>
|
<Label Text="{Binding Sollstunden,StringFormat='{}{0:HH:mm}'}" VerticalOptions="Center"></Label>
|
||||||
</HorizontalStackLayout>
|
</HorizontalStackLayout>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -72,27 +72,29 @@
|
|||||||
<VisualStateGroup Name="CommonStates">
|
<VisualStateGroup Name="CommonStates">
|
||||||
<VisualState Name="Normal">
|
<VisualState Name="Normal">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource TransparentColor}}" />
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource TransparentColor}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
<VisualState Name="Selected">
|
<VisualState Name="Selected">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
</VisualState>
|
</VisualState>
|
||||||
</VisualStateGroup>
|
</VisualStateGroup>
|
||||||
</VisualStateManager.VisualStateGroups>
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<HorizontalStackLayout>
|
<HorizontalStackLayout>
|
||||||
<HorizontalStackLayout.Triggers>
|
<HorizontalStackLayout.Triggers>
|
||||||
<DataTrigger TargetType="HorizontalStackLayout" Binding="{Binding Approved}" Value="True">
|
<DataTrigger TargetType="HorizontalStackLayout" Binding="{Binding Approved}"
|
||||||
|
Value="True">
|
||||||
<Setter Property="BackgroundColor" Value="LightCoral" />
|
<Setter Property="BackgroundColor" Value="LightCoral" />
|
||||||
<Setter Property="Padding" Value="4"/>
|
<Setter Property="Padding" Value="4" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</HorizontalStackLayout.Triggers>
|
</HorizontalStackLayout.Triggers>
|
||||||
<Label Text="{Binding Day, StringFormat='{0:dddd, dd. MMMM}'}"/>
|
<Label Text="{Binding Day, StringFormat='{0:dddd, dd. MMMM}'}" />
|
||||||
<Label Text="von" Padding="5,0,5,0" />
|
<Label Text="von" Padding="5,0,5,0" />
|
||||||
<Label Text="{Binding Begin}" />
|
<Label Text="{Binding Begin}" />
|
||||||
<Label Text="bis" Padding="5,0,5,0" />
|
<Label Text="bis" Padding="5,0,5,0" />
|
||||||
@@ -101,17 +103,20 @@
|
|||||||
|
|
||||||
<HorizontalStackLayout HorizontalOptions="FillAndExpand">
|
<HorizontalStackLayout HorizontalOptions="FillAndExpand">
|
||||||
<HorizontalStackLayout.Triggers>
|
<HorizontalStackLayout.Triggers>
|
||||||
<DataTrigger TargetType="HorizontalStackLayout" Binding="{Binding Approved}" Value="True">
|
<DataTrigger TargetType="HorizontalStackLayout" Binding="{Binding Approved}"
|
||||||
|
Value="True">
|
||||||
<Setter Property="BackgroundColor" Value="LightCoral" />
|
<Setter Property="BackgroundColor" Value="LightCoral" />
|
||||||
<Setter Property="Padding" Value="4"/>
|
<Setter Property="Padding" Value="4" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</HorizontalStackLayout.Triggers>
|
</HorizontalStackLayout.Triggers>
|
||||||
<Label Text="{Binding GemeindeAktiv.Name}" Margin="0,0,10,0" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.GemeindeAktivSet}" />
|
<Label Text="{Binding GemeindeAktiv.Name}" Margin="0,0,10,0"
|
||||||
<Label Text="{Binding ProjektAktiv.Name}" Margin="0,0,10,0" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.ProjektAktivSet}" />
|
IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.GemeindeAktivSet}" />
|
||||||
|
<Label Text="{Binding ProjektAktiv.Name}" Margin="0,0,10,0"
|
||||||
|
IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.ProjektAktivSet}" />
|
||||||
<Label Text="{Binding FreistellungAktiv.Name}" IsVisible="{Binding Approved}" />
|
<Label Text="{Binding FreistellungAktiv.Name}" IsVisible="{Binding Approved}" />
|
||||||
</HorizontalStackLayout>
|
</HorizontalStackLayout>
|
||||||
|
|
||||||
<Label Text="{Binding Description}" Padding="0,0,0,15"/>
|
<Label Text="{Binding Description}" Padding="0,0,0,15" />
|
||||||
</VerticalStackLayout>
|
</VerticalStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
@@ -133,12 +138,20 @@
|
|||||||
<Label Grid.Row="1" Grid.Column="2" Text="Zeitausgleich:" Margin="15,0,0,0" />
|
<Label Grid.Row="1" Grid.Column="2" Text="Zeitausgleich:" Margin="15,0,0,0" />
|
||||||
<Label Grid.Row="2" Grid.Column="2" Text="Resturlaub:" Margin="15,0,0,0" />
|
<Label Grid.Row="2" Grid.Column="2" Text="Resturlaub:" Margin="15,0,0,0" />
|
||||||
|
|
||||||
<Label Grid.Row="0" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Nominal, Converter={StaticResource secToTime}}" ToolTipProperties.Text="Sollstunden" />
|
<Label Grid.Row="0" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0"
|
||||||
<Label Grid.Row="1" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding ZeitCalculated, Converter={StaticResource secToTime}}" ToolTipProperties.Text="Geleistete Stunden" />
|
Text="{Binding Nominal, Converter={StaticResource secToTime}}"
|
||||||
<Label Grid.Row="2" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding OvertimeMonth, Converter={StaticResource secToTime}}" />
|
ToolTipProperties.Text="Sollstunden" />
|
||||||
<Label Grid.Row="0" Grid.Column="3" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Overtime, Converter={StaticResource secToTime}}" />
|
<Label Grid.Row="1" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0"
|
||||||
<Label Grid.Row="1" Grid.Column="3" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Zeitausgleich, Converter={StaticResource secToTime}}" />
|
Text="{Binding ZeitCalculated, Converter={StaticResource secToTime}}"
|
||||||
<Label Grid.Row="2" Grid.Column="3" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Holiday, Converter={StaticResource secToTime}}" />
|
ToolTipProperties.Text="Geleistete Stunden" />
|
||||||
|
<Label Grid.Row="2" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0"
|
||||||
|
Text="{Binding OvertimeMonth, Converter={StaticResource secToTime}}" />
|
||||||
|
<Label Grid.Row="0" Grid.Column="3" HorizontalTextAlignment="End" Padding="0,0,5,0"
|
||||||
|
Text="{Binding Overtime, Converter={StaticResource secToTime}}" />
|
||||||
|
<Label Grid.Row="1" Grid.Column="3" HorizontalTextAlignment="End" Padding="0,0,5,0"
|
||||||
|
Text="{Binding Zeitausgleich, Converter={StaticResource secToTime}}" />
|
||||||
|
<Label Grid.Row="2" Grid.Column="3" HorizontalTextAlignment="End" Padding="0,0,5,0"
|
||||||
|
Text="{Binding Holiday, Converter={StaticResource secToTime}}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|||||||
@@ -9,60 +9,80 @@ namespace Jugenddienst_Stunden.Views;
|
|||||||
/// Code-Behind f<>r die Stunden-<2D>bersicht
|
/// Code-Behind f<>r die Stunden-<2D>bersicht
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StundenPage : ContentPage {
|
public partial class StundenPage : ContentPage {
|
||||||
|
/// <summary>
|
||||||
|
/// CTOR (f<>r Shell/XAML DataTemplate erforderlich)
|
||||||
|
/// </summary>
|
||||||
|
public StundenPage() : this(
|
||||||
|
(Application.Current?.Handler?.MauiContext?.Services
|
||||||
|
?? throw new InvalidOperationException("DI container ist nicht verf<72>gbar."))
|
||||||
|
.GetRequiredService<StundenViewModel>()) {
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CTOR
|
/// CTOR (DI)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StundenPage() {
|
public StundenPage(StundenViewModel vm) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
BindingContext = vm;
|
||||||
|
|
||||||
if (BindingContext is StundenViewModel vm) {
|
vm.AlertEvent += Vm_AlertEvent;
|
||||||
vm.AlertEvent += Vm_AlertEvent;
|
vm.InfoEvent += Vm_InfoEvent;
|
||||||
vm.InfoEvent += Vm_InfoEvent;
|
|
||||||
}
|
|
||||||
if (!CheckLogin()) {
|
|
||||||
NavigateToTargetPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
// Navigation NICHT im CTOR ausf<73>hren (Shell/Navigation-Stack ist hier oft noch nicht ?ready?)
|
||||||
|
// if (!CheckLogin()) {
|
||||||
|
// NavigateToTargetPage();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
private void Vm_AlertEvent(object? sender, string e) {
|
private void Vm_AlertEvent(object? sender, string e) {
|
||||||
MainThread.BeginInvokeOnMainThread(async () => {
|
MainThread.BeginInvokeOnMainThread(async () => { await DisplayAlert("Fehler:", e, "OK"); });
|
||||||
await DisplayAlert("Fehler:", e, "OK");
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
//private void Vm_InfoEvent(object? sender, string e) {
|
|
||||||
// DisplayAlert("Information:", e, "OK");
|
|
||||||
//}
|
|
||||||
//private void Vm_InfoEvent(object? sender, string e) {
|
|
||||||
// MainThread.BeginInvokeOnMainThread(async () => {
|
|
||||||
// await DisplayAlert("Information:", e, "OK");
|
|
||||||
// });
|
|
||||||
//}
|
|
||||||
private void Vm_InfoEvent(object? sender, string e) {
|
|
||||||
MainThread.BeginInvokeOnMainThread(async () => {
|
|
||||||
CancellationTokenSource cts = new CancellationTokenSource();
|
|
||||||
ToastDuration duration = ToastDuration.Short;
|
|
||||||
double fontSize = 16;
|
|
||||||
var toast = Toast.Make(e, duration, fontSize);
|
|
||||||
await toast.Show(cts.Token);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
//private void Vm_InfoEvent(object? sender, string e) {
|
||||||
/// Beim Laden der Seite den Titel setzen
|
// DisplayAlert("Information:", e, "OK");
|
||||||
/// </summary>
|
//}
|
||||||
protected override void OnAppearing() {
|
//private void Vm_InfoEvent(object? sender, string e) {
|
||||||
base.OnAppearing();
|
// MainThread.BeginInvokeOnMainThread(async () => {
|
||||||
Title = Preferences.Default.Get("name", "Nicht") + " " + Preferences.Default.Get("surname", "eingeloggt");
|
// await DisplayAlert("Information:", e, "OK");
|
||||||
}
|
// });
|
||||||
|
//}
|
||||||
|
private void Vm_InfoEvent(object? sender, string e) {
|
||||||
|
MainThread.BeginInvokeOnMainThread(async () => {
|
||||||
|
CancellationTokenSource cts = new CancellationTokenSource();
|
||||||
|
ToastDuration duration = ToastDuration.Short;
|
||||||
|
double fontSize = 16;
|
||||||
|
var toast = Toast.Make(e, duration, fontSize);
|
||||||
|
await toast.Show(cts.Token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private bool CheckLogin() {
|
/// <summary>
|
||||||
return Preferences.Default.Get("apiKey", "") != "";
|
/// Beim Laden der Seite den Titel setzen
|
||||||
}
|
/// </summary>
|
||||||
|
protected override async void OnAppearing() {
|
||||||
|
base.OnAppearing();
|
||||||
|
Title = Preferences.Default.Get("name", "Nicht") + " " + Preferences.Default.Get("surname", "eingeloggt");
|
||||||
|
|
||||||
private async void NavigateToTargetPage() {
|
if (!CheckLogin()) {
|
||||||
await Navigation.PushAsync(new LoginPage());
|
try {
|
||||||
}
|
await NavigateToTargetPage();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
await DisplayAlert("Fehler:", ex.Message, "OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckLogin() {
|
||||||
|
return Preferences.Default.Get("apiKey", "") != "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// private async void NavigateToTargetPage() {
|
||||||
|
// await Navigation.PushAsync(new LoginPage());
|
||||||
|
// }
|
||||||
|
|
||||||
|
private Task NavigateToTargetPage() {
|
||||||
|
// Shell-Navigation statt Navigation.PushAsync
|
||||||
|
// Voraussetzung: LoginPage-Route ist in AppShell registriert (Routing.RegisterRoute(...))
|
||||||
|
return Shell.Current.GoToAsync(nameof(Views.LoginPage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BIN
Uhr.afdesign
|
Before Width: | Height: | Size: 33 KiB |
BIN
appicon.afdesign
BIN
paket_icon.png
|
Before Width: | Height: | Size: 5.8 KiB |