waterrmsi hai 1 semana
pai
achega
334a2efb83
Modificáronse 100 ficheiros con 2748 adicións e 0 borrados
  1. BIN=BIN
      program/.vs/ProjectEvaluation/transportmauiapp.metadata.v10.bin
  2. BIN=BIN
      program/.vs/ProjectEvaluation/transportmauiapp.projects.v10.bin
  3. BIN=BIN
      program/.vs/ProjectEvaluation/transportmauiapp.strings.v10.bin
  4. BIN=BIN
      program/.vs/TransportMauiApp.slnx/DesignTimeBuild/.dtbcache.v2
  5. BIN=BIN
      program/.vs/TransportMauiApp.slnx/FileContentIndex/97469883-6636-49b5-86dd-4ba7484fab0b.vsidx
  6. BIN=BIN
      program/.vs/TransportMauiApp.slnx/FileContentIndex/a9500817-cd94-435a-89a9-c7f85bfbb26d.vsidx
  7. BIN=BIN
      program/.vs/TransportMauiApp.slnx/v18/.futdcache.v2
  8. BIN=BIN
      program/.vs/TransportMauiApp.slnx/v18/.suo
  9. 57 0
      program/.vs/TransportMauiApp.slnx/v18/DocumentLayout.backup.json
  10. 57 0
      program/.vs/TransportMauiApp.slnx/v18/DocumentLayout.json
  11. 22 0
      program/TransportMauiApp.sln
  12. 3 0
      program/TransportMauiApp.slnx
  13. BIN=BIN
      program/TransportMauiApp/.vs/TransportMauiApp.slnx/FileContentIndex/6deb5bbb-230c-472d-a554-6fb0d4174278.vsidx
  14. BIN=BIN
      program/TransportMauiApp/.vs/TransportMauiApp.slnx/v18/.wsuo
  15. 23 0
      program/TransportMauiApp/.vs/TransportMauiApp.slnx/v18/DocumentLayout.backup.json
  16. 23 0
      program/TransportMauiApp/.vs/TransportMauiApp.slnx/v18/DocumentLayout.json
  17. BIN=BIN
      program/TransportMauiApp/.vs/TransportMauiApp/v18/workspaceFileList.bin
  18. 6 0
      program/TransportMauiApp/.vs/VSWorkspaceState.json
  19. BIN=BIN
      program/TransportMauiApp/.vs/slnx.sqlite
  20. 14 0
      program/TransportMauiApp/App.xaml
  21. 16 0
      program/TransportMauiApp/App.xaml.cs
  22. 15 0
      program/TransportMauiApp/AppShell.xaml
  23. 9 0
      program/TransportMauiApp/AppShell.xaml.cs
  24. 6 0
      program/TransportMauiApp/MainPage.xaml
  25. 848 0
      program/TransportMauiApp/MainPage.xaml.cs
  26. 24 0
      program/TransportMauiApp/MauiProgram.cs
  27. 17 0
      program/TransportMauiApp/Models/AppTransport.cs
  28. 16 0
      program/TransportMauiApp/Models/Driver.cs
  29. 16 0
      program/TransportMauiApp/Models/FlightInfo.cs
  30. 12 0
      program/TransportMauiApp/Models/TransitRoute.cs
  31. 16 0
      program/TransportMauiApp/Models/TransportFlight.cs
  32. 40 0
      program/TransportMauiApp/Models/UserSession.cs
  33. 6 0
      program/TransportMauiApp/Platforms/Android/AndroidManifest.xml
  34. 10 0
      program/TransportMauiApp/Platforms/Android/MainActivity.cs
  35. 15 0
      program/TransportMauiApp/Platforms/Android/MainApplication.cs
  36. 6 0
      program/TransportMauiApp/Platforms/Android/Resources/values/colors.xml
  37. 9 0
      program/TransportMauiApp/Platforms/MacCatalyst/AppDelegate.cs
  38. 14 0
      program/TransportMauiApp/Platforms/MacCatalyst/Entitlements.plist
  39. 40 0
      program/TransportMauiApp/Platforms/MacCatalyst/Info.plist
  40. 15 0
      program/TransportMauiApp/Platforms/MacCatalyst/Program.cs
  41. 8 0
      program/TransportMauiApp/Platforms/Windows/App.xaml
  42. 24 0
      program/TransportMauiApp/Platforms/Windows/App.xaml.cs
  43. 46 0
      program/TransportMauiApp/Platforms/Windows/Package.appxmanifest
  44. 17 0
      program/TransportMauiApp/Platforms/Windows/app.manifest
  45. 9 0
      program/TransportMauiApp/Platforms/iOS/AppDelegate.cs
  46. 32 0
      program/TransportMauiApp/Platforms/iOS/Info.plist
  47. 15 0
      program/TransportMauiApp/Platforms/iOS/Program.cs
  48. 51 0
      program/TransportMauiApp/Platforms/iOS/Resources/PrivacyInfo.xcprivacy
  49. 8 0
      program/TransportMauiApp/Properties/launchSettings.json
  50. 4 0
      program/TransportMauiApp/Resources/AppIcon/appicon.svg
  51. 8 0
      program/TransportMauiApp/Resources/AppIcon/appiconfg.svg
  52. BIN=BIN
      program/TransportMauiApp/Resources/Fonts/OpenSans-Regular.ttf
  53. BIN=BIN
      program/TransportMauiApp/Resources/Fonts/OpenSans-Semibold.ttf
  54. BIN=BIN
      program/TransportMauiApp/Resources/Images/dotnet_bot.png
  55. 15 0
      program/TransportMauiApp/Resources/Raw/AboutAssets.txt
  56. 8 0
      program/TransportMauiApp/Resources/Splash/splash.svg
  57. 44 0
      program/TransportMauiApp/Resources/Styles/Colors.xaml
  58. 434 0
      program/TransportMauiApp/Resources/Styles/Styles.xaml
  59. 577 0
      program/TransportMauiApp/Services/DatabaseService.cs
  60. 53 0
      program/TransportMauiApp/TransportMauiApp.csproj
  61. 25 0
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/install.iss
  62. 15 0
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/AboutAssets.txt
  63. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/BCrypt-Net-Next.dll
  64. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/CoreMessagingXP.dll
  65. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/DWriteCore.dll
  66. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/DwmSceneI.dll
  67. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/MRM.dll
  68. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.DirectManipulation.dll
  69. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Configuration.Abstractions.dll
  70. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Configuration.dll
  71. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.DependencyInjection.Abstractions.dll
  72. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.DependencyInjection.dll
  73. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Diagnostics.Abstractions.dll
  74. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.FileProviders.Abstractions.dll
  75. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Hosting.Abstractions.dll
  76. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Logging.Abstractions.dll
  77. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Logging.Debug.dll
  78. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Logging.dll
  79. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Options.dll
  80. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Primitives.dll
  81. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Foundation.winmd
  82. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Canvas.Interop.dll
  83. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Canvas.dll
  84. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Display.dll
  85. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Imaging.Projection.dll
  86. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Imaging.dll
  87. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Imaging.winmd
  88. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.ImagingInternal.winmd
  89. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Internal.Imaging.winmd
  90. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.winmd
  91. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.IO.RecyclableMemoryStream.dll
  92. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.InputStateManager.dll
  93. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.InteractiveExperiences.Projection.dll
  94. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Internal.FrameworkUdk.dll
  95. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Controls.Xaml.dll
  96. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Controls.dll
  97. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Essentials.dll
  98. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll
  99. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Graphics.dll
  100. BIN=BIN
      program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.dll

BIN=BIN
program/.vs/ProjectEvaluation/transportmauiapp.metadata.v10.bin


BIN=BIN
program/.vs/ProjectEvaluation/transportmauiapp.projects.v10.bin


BIN=BIN
program/.vs/ProjectEvaluation/transportmauiapp.strings.v10.bin


BIN=BIN
program/.vs/TransportMauiApp.slnx/DesignTimeBuild/.dtbcache.v2


BIN=BIN
program/.vs/TransportMauiApp.slnx/FileContentIndex/97469883-6636-49b5-86dd-4ba7484fab0b.vsidx


BIN=BIN
program/.vs/TransportMauiApp.slnx/FileContentIndex/a9500817-cd94-435a-89a9-c7f85bfbb26d.vsidx


BIN=BIN
program/.vs/TransportMauiApp.slnx/v18/.futdcache.v2


BIN=BIN
program/.vs/TransportMauiApp.slnx/v18/.suo


+ 57 - 0
program/.vs/TransportMauiApp.slnx/v18/DocumentLayout.backup.json

@@ -0,0 +1,57 @@
+{
+  "Version": 1,
+  "WorkspaceRootPath": "D:\\practice2026\\program\\",
+  "Documents": [
+    {
+      "AbsoluteMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|d:\\practice2026\\program\\transportmauiapp\\services\\databaseservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|solutionrelative:transportmauiapp\\services\\databaseservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|d:\\practice2026\\program\\transportmauiapp\\mainpage.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|solutionrelative:transportmauiapp\\mainpage.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    }
+  ],
+  "DocumentGroupContainers": [
+    {
+      "Orientation": 0,
+      "VerticalTabListWidth": 256,
+      "DocumentGroups": [
+        {
+          "DockedWidth": 200,
+          "SelectedChildIndex": 2,
+          "Children": [
+            {
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{269a02dc-6af8-11d3-bdc4-00c04f688e50}"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 1,
+              "Title": "MainPage.xaml.cs",
+              "DocumentMoniker": "D:\\practice2026\\program\\TransportMauiApp\\MainPage.xaml.cs",
+              "RelativeDocumentMoniker": "TransportMauiApp\\MainPage.xaml.cs",
+              "ToolTip": "D:\\practice2026\\program\\TransportMauiApp\\MainPage.xaml.cs",
+              "RelativeToolTip": "TransportMauiApp\\MainPage.xaml.cs",
+              "ViewState": "AgIAAOUAAAAAAAAAAAAiwPMAAAAwAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2026-06-03T16:47:00.457Z"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 0,
+              "Title": "DatabaseService.cs",
+              "DocumentMoniker": "D:\\practice2026\\program\\TransportMauiApp\\Services\\DatabaseService.cs",
+              "RelativeDocumentMoniker": "TransportMauiApp\\Services\\DatabaseService.cs",
+              "ToolTip": "D:\\practice2026\\program\\TransportMauiApp\\Services\\DatabaseService.cs",
+              "RelativeToolTip": "TransportMauiApp\\Services\\DatabaseService.cs",
+              "ViewState": "AgIAAEEAAAAAAAAAAAAwwFwAAABFAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2026-06-03T15:03:41.873Z",
+              "EditorCaption": ""
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}

+ 57 - 0
program/.vs/TransportMauiApp.slnx/v18/DocumentLayout.json

@@ -0,0 +1,57 @@
+{
+  "Version": 1,
+  "WorkspaceRootPath": "D:\\practice2026\\program\\",
+  "Documents": [
+    {
+      "AbsoluteMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|d:\\practice2026\\program\\transportmauiapp\\services\\databaseservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|solutionrelative:transportmauiapp\\services\\databaseservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|d:\\practice2026\\program\\transportmauiapp\\mainpage.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{3A38460F-72BB-F27B-1999-4A662842E9A2}|TransportMauiApp\\TransportMauiApp.csproj|solutionrelative:transportmauiapp\\mainpage.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    }
+  ],
+  "DocumentGroupContainers": [
+    {
+      "Orientation": 0,
+      "VerticalTabListWidth": 256,
+      "DocumentGroups": [
+        {
+          "DockedWidth": 200,
+          "SelectedChildIndex": 2,
+          "Children": [
+            {
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{269a02dc-6af8-11d3-bdc4-00c04f688e50}"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 1,
+              "Title": "MainPage.xaml.cs",
+              "DocumentMoniker": "D:\\practice2026\\program\\TransportMauiApp\\MainPage.xaml.cs",
+              "RelativeDocumentMoniker": "TransportMauiApp\\MainPage.xaml.cs",
+              "ToolTip": "D:\\practice2026\\program\\TransportMauiApp\\MainPage.xaml.cs",
+              "RelativeToolTip": "TransportMauiApp\\MainPage.xaml.cs",
+              "ViewState": "AgIAAOUAAAAAAAAAAAAiwPMAAAAwAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2026-06-03T16:47:00.457Z"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 0,
+              "Title": "DatabaseService.cs",
+              "DocumentMoniker": "D:\\practice2026\\program\\TransportMauiApp\\Services\\DatabaseService.cs",
+              "RelativeDocumentMoniker": "TransportMauiApp\\Services\\DatabaseService.cs",
+              "ToolTip": "D:\\practice2026\\program\\TransportMauiApp\\Services\\DatabaseService.cs",
+              "RelativeToolTip": "TransportMauiApp\\Services\\DatabaseService.cs",
+              "ViewState": "AgIAAE4AAAAAAAAAAAAmwFwAAABFAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2026-06-03T15:03:41.873Z",
+              "EditorCaption": ""
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}

+ 22 - 0
program/TransportMauiApp.sln

@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransportMauiApp", "TransportMauiApp\TransportMauiApp.csproj", "{2D7A1CAA-7B06-4B5C-B2A5-527477E4B85D}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{2D7A1CAA-7B06-4B5C-B2A5-527477E4B85D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2D7A1CAA-7B06-4B5C-B2A5-527477E4B85D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2D7A1CAA-7B06-4B5C-B2A5-527477E4B85D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2D7A1CAA-7B06-4B5C-B2A5-527477E4B85D}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

+ 3 - 0
program/TransportMauiApp.slnx

@@ -0,0 +1,3 @@
+<Solution>
+  <Project Path="TransportMauiApp/TransportMauiApp.csproj" />
+</Solution>

BIN=BIN
program/TransportMauiApp/.vs/TransportMauiApp.slnx/FileContentIndex/6deb5bbb-230c-472d-a554-6fb0d4174278.vsidx


BIN=BIN
program/TransportMauiApp/.vs/TransportMauiApp.slnx/v18/.wsuo


+ 23 - 0
program/TransportMauiApp/.vs/TransportMauiApp.slnx/v18/DocumentLayout.backup.json

@@ -0,0 +1,23 @@
+{
+  "Version": 1,
+  "WorkspaceRootPath": "D:\\practice2026\\program\\TransportMauiApp\\",
+  "Documents": [],
+  "DocumentGroupContainers": [
+    {
+      "Orientation": 0,
+      "VerticalTabListWidth": 256,
+      "DocumentGroups": [
+        {
+          "DockedWidth": 200,
+          "SelectedChildIndex": -1,
+          "Children": [
+            {
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{269a02dc-6af8-11d3-bdc4-00c04f688e50}"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}

+ 23 - 0
program/TransportMauiApp/.vs/TransportMauiApp.slnx/v18/DocumentLayout.json

@@ -0,0 +1,23 @@
+{
+  "Version": 1,
+  "WorkspaceRootPath": "D:\\practice2026\\program\\TransportMauiApp\\",
+  "Documents": [],
+  "DocumentGroupContainers": [
+    {
+      "Orientation": 0,
+      "VerticalTabListWidth": 256,
+      "DocumentGroups": [
+        {
+          "DockedWidth": 200,
+          "SelectedChildIndex": -1,
+          "Children": [
+            {
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{269a02dc-6af8-11d3-bdc4-00c04f688e50}"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}

BIN=BIN
program/TransportMauiApp/.vs/TransportMauiApp/v18/workspaceFileList.bin


+ 6 - 0
program/TransportMauiApp/.vs/VSWorkspaceState.json

@@ -0,0 +1,6 @@
+{
+  "ExpandedNodes": [
+    ""
+  ],
+  "PreviewInSolutionExplorer": false
+}

BIN=BIN
program/TransportMauiApp/.vs/slnx.sqlite


+ 14 - 0
program/TransportMauiApp/App.xaml

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

+ 16 - 0
program/TransportMauiApp/App.xaml.cs

@@ -0,0 +1,16 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace TransportMauiApp;
+
+public partial class App : Application
+{
+	public App()
+	{
+		InitializeComponent();
+	}
+
+	protected override Window CreateWindow(IActivationState? activationState)
+	{
+		return new Window(new AppShell());
+	}
+}

+ 15 - 0
program/TransportMauiApp/AppShell.xaml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<Shell
+    x:Class="TransportMauiApp.AppShell"
+    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+    xmlns:local="clr-namespace:TransportMauiApp"
+    Title="Учет транспорта"
+    FlyoutBehavior="Disabled">
+
+    <ShellContent
+        Title="Учет транспорта"
+        ContentTemplate="{DataTemplate local:MainPage}"
+        Route="MainPage" />
+
+</Shell>

+ 9 - 0
program/TransportMauiApp/AppShell.xaml.cs

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

+ 6 - 0
program/TransportMauiApp/MainPage.xaml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             x:Class="TransportMauiApp.MainPage"
+             BackgroundColor="#F4F7FA">
+</ContentPage>

+ 848 - 0
program/TransportMauiApp/MainPage.xaml.cs

@@ -0,0 +1,848 @@
+using TransportMauiApp.Models;
+using TransportMauiApp.Services;
+using Microsoft.Maui.Controls.Shapes;
+
+namespace TransportMauiApp;
+
+public partial class MainPage : ContentPage
+{
+    private readonly DatabaseService _database = new();
+    private readonly VerticalStackLayout _content = new() { Spacing = 14 };
+    private readonly List<Button> _sectionButtons = [];
+
+    private UserSession? _session;
+    private bool CanEdit => _session?.IsAdministrator == true;
+
+    private List<Driver> _drivers = [];
+    private List<AppTransport> _transports = [];
+    private List<TransitRoute> _routes = [];
+    private List<TransportFlight> _flights = [];
+
+    public MainPage()
+    {
+        InitializeComponent();
+        Title = "Учет транспорта";
+        ShowLogin();
+    }
+
+    private void ShowLogin()
+    {
+        var loginEntry = CreateEntry("Логин");
+        var passwordEntry = CreateEntry("Пароль");
+        passwordEntry.IsPassword = true;
+
+        var statusLabel = new Label
+        {
+            TextColor = Colors.Firebrick,
+            FontSize = 14,
+            IsVisible = false
+        };
+
+        var loginButton = new Button
+        {
+            Text = "Войти",
+            HeightRequest = 46,
+            BackgroundColor = Color.FromArgb("#1D4ED8"),
+            TextColor = Colors.White,
+            CornerRadius = 8
+        };
+
+        loginButton.Clicked += async (_, _) =>
+        {
+            statusLabel.IsVisible = false;
+            loginButton.IsEnabled = false;
+
+            try
+            {
+                var login = loginEntry.Text?.Trim() ?? string.Empty;
+                var password = passwordEntry.Text ?? string.Empty;
+                var session = await _database.AuthenticateAsync(login, password);
+
+                if (session is null)
+                {
+                    statusLabel.Text = "Неверный логин или пароль.";
+                    statusLabel.IsVisible = true;
+                    return;
+                }
+
+                _session = session;
+                await LoadLookupsAsync();
+                ShowWorkspace();
+            }
+            catch (Exception ex)
+            {
+                statusLabel.Text = $"Ошибка подключения: {ex.Message}";
+                statusLabel.IsVisible = true;
+            }
+            finally
+            {
+                loginButton.IsEnabled = true;
+            }
+        };
+
+        Content = new Grid
+        {
+            Padding = new Thickness(22),
+            Children =
+            {
+                new Border
+                {
+                    StrokeThickness = 1,
+                    Stroke = Color.FromArgb("#D7DEE8"),
+                    BackgroundColor = Colors.White,
+                    StrokeShape = new RoundRectangle { CornerRadius = 8 },
+                    MaximumWidthRequest = 460,
+                    VerticalOptions = LayoutOptions.Center,
+                    HorizontalOptions = LayoutOptions.Center,
+                    Padding = new Thickness(24),
+                    Content = new VerticalStackLayout
+                    {
+                        Spacing = 14,
+                        Children =
+                        {
+                            new Label
+                            {
+                                Text = "Авторизация",
+                                FontSize = 26,
+                                FontAttributes = FontAttributes.Bold,
+                                TextColor = Color.FromArgb("#172033")
+                            },
+                            loginEntry,
+                            passwordEntry,
+                            loginButton,
+                            statusLabel
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    private void ShowWorkspace()
+    {
+        _sectionButtons.Clear();
+        _content.Children.Clear();
+
+        var header = new Grid
+        {
+            ColumnDefinitions =
+            {
+                new ColumnDefinition(GridLength.Star),
+                new ColumnDefinition(GridLength.Auto)
+            },
+            RowDefinitions =
+            {
+                new RowDefinition(GridLength.Auto),
+                new RowDefinition(GridLength.Auto)
+            },
+            ColumnSpacing = 12
+        };
+
+        header.Add(new Label
+        {
+            Text = "Учет транспорта",
+            FontSize = 26,
+            FontAttributes = FontAttributes.Bold,
+            TextColor = Color.FromArgb("#172033")
+        }, 0, 0);
+
+        var logoutButton = new Button
+        {
+            Text = "Выйти",
+            BackgroundColor = Color.FromArgb("#EEF2F7"),
+            TextColor = Color.FromArgb("#172033"),
+            CornerRadius = 8
+        };
+        logoutButton.Clicked += (_, _) =>
+        {
+            _session = null;
+            ShowLogin();
+        };
+        header.Add(logoutButton, 1, 0);
+
+        header.Add(new Label
+        {
+            Text = $"Пользователь: {_session?.Login} | Роль: {_session?.DisplayRole} | Режим: {(CanEdit ? "полный доступ" : "только чтение")}",
+            FontSize = 14,
+            TextColor = Color.FromArgb("#526070")
+        }, 0, 1);
+
+        var tabs = new HorizontalStackLayout
+        {
+            Spacing = 8,
+            Children =
+            {
+                CreateSectionButton("Водители", ShowDriversSection),
+                CreateSectionButton("Транспорт", ShowTransportsSection),
+                CreateSectionButton("Маршруты", ShowRoutesSection),
+                CreateSectionButton("Рейсы", ShowFlightsSection),
+                CreateSectionButton("Информация о рейсах", ShowFlightInfoSection)
+            }
+        };
+
+        Content = new ScrollView
+        {
+            Content = new VerticalStackLayout
+            {
+                Padding = new Thickness(20),
+                Spacing = 16,
+                Children =
+                {
+                    header,
+                    new ScrollView { Orientation = ScrollOrientation.Horizontal, Content = tabs },
+                    _content
+                }
+            }
+        };
+
+        ShowDriversSection();
+    }
+
+    private Button CreateSectionButton(string text, Action action)
+    {
+        var button = new Button
+        {
+            Text = text,
+            CornerRadius = 8,
+            Padding = new Thickness(14, 8),
+            BackgroundColor = Colors.White,
+            BorderColor = Color.FromArgb("#D7DEE8"),
+            BorderWidth = 1,
+            TextColor = Color.FromArgb("#172033")
+        };
+
+        button.Clicked += (_, _) =>
+        {
+            foreach (var tab in _sectionButtons)
+            {
+                tab.BackgroundColor = Colors.White;
+                tab.TextColor = Color.FromArgb("#172033");
+            }
+
+            button.BackgroundColor = Color.FromArgb("#1D4ED8");
+            button.TextColor = Colors.White;
+            action();
+        };
+
+        _sectionButtons.Add(button);
+        return button;
+    }
+
+    private async Task LoadLookupsAsync()
+    {
+        _drivers = [.. await _database.GetDriversAsync()];
+        _transports = [.. await _database.GetTransportsAsync()];
+        _routes = [.. await _database.GetRoutesAsync()];
+        _flights = [.. await _database.GetFlightsAsync()];
+    }
+
+    private async void ShowDriversSection()
+    {
+        await LoadDriversSectionAsync();
+    }
+
+    private async Task LoadDriversSectionAsync()
+    {
+        _content.Children.Clear();
+        await RunSafelyAsync(async () =>
+        {
+            _drivers = [.. await _database.GetDriversAsync()];
+            var recordsCount = await _database.GetDriversCountAsync();
+            var list = CreateCollectionView(_drivers);
+
+            var nameEntry = CreateEntry("Имя");
+            var surnameEntry = CreateEntry("Фамилия");
+            var patronimicEntry = CreateEntry("Отчество");
+            Driver? selected = null;
+
+            list.SelectionChanged += (_, e) =>
+            {
+                selected = e.CurrentSelection.FirstOrDefault() as Driver;
+                if (selected is null)
+                {
+                    return;
+                }
+
+                nameEntry.Text = selected.Name;
+                surnameEntry.Text = selected.Surname;
+                patronimicEntry.Text = selected.Patronimic;
+            };
+
+            _content.Children.Add(CreateSectionTitle("Водители", recordsCount));
+            _content.Children.Add(list);
+            if (CanEdit)
+            {
+                _content.Children.Add(CreateForm(
+                    [nameEntry, surnameEntry, patronimicEntry],
+                    CreateCrudButtons(
+                        add: async () =>
+                        {
+                            if (!ValidateRequired(nameEntry, surnameEntry, patronimicEntry))
+                            {
+                                await DisplayAlertAsync("Проверка", "Заполните все поля.", "OK");
+                                return;
+                            }
+
+                            await _database.AddDriverAsync(new Driver
+                            {
+                                Name = nameEntry.Text!.Trim(),
+                                Surname = surnameEntry.Text!.Trim(),
+                                Patronimic = patronimicEntry.Text!.Trim()
+                            });
+                            ClearEntries(nameEntry, surnameEntry, patronimicEntry);
+                            await LoadDriversSectionAsync();
+                        },
+                        update: async () =>
+                        {
+                            if (selected is null || !ValidateRequired(nameEntry, surnameEntry, patronimicEntry))
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите запись и заполните все поля.", "OK");
+                                return;
+                            }
+
+                            selected.Name = nameEntry.Text!.Trim();
+                            selected.Surname = surnameEntry.Text!.Trim();
+                            selected.Patronimic = patronimicEntry.Text!.Trim();
+                            await _database.UpdateDriverAsync(selected);
+                            await LoadDriversSectionAsync();
+                        },
+                        delete: async () =>
+                        {
+                            if (selected is null)
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
+                                return;
+                            }
+
+                            await _database.DeleteDriverAsync(selected.DriverId);
+                            await LoadDriversSectionAsync();
+                        })));
+            }
+        });
+    }
+
+    private async void ShowTransportsSection()
+    {
+        await LoadTransportsSectionAsync();
+    }
+
+    private async Task LoadTransportsSectionAsync()
+    {
+        _content.Children.Clear();
+        await RunSafelyAsync(async () =>
+        {
+            await LoadLookupsAsync();
+            var recordsCount = await _database.GetTransportsCountAsync();
+            var list = CreateCollectionView(_transports);
+            var branchEntry = CreateEntry("Филиал");
+            var modelEntry = CreateEntry("Модель");
+            var plateEntry = CreateEntry("Госномер");
+            var driverPicker = CreatePicker("Водитель", _drivers);
+            AppTransport? selected = null;
+
+            list.SelectionChanged += (_, e) =>
+            {
+                selected = e.CurrentSelection.FirstOrDefault() as AppTransport;
+                if (selected is null)
+                {
+                    return;
+                }
+
+                branchEntry.Text = selected.Branch;
+                modelEntry.Text = selected.Model;
+                plateEntry.Text = selected.LicensePlateNumber;
+                driverPicker.SelectedItem = _drivers.FirstOrDefault(driver => driver.DriverId == selected.DriverId);
+            };
+
+            _content.Children.Add(CreateSectionTitle("Транспорт", recordsCount));
+            _content.Children.Add(list);
+            if (CanEdit)
+            {
+                var assignButton = new Button
+                {
+                    Text = "Назначить водителя процедурой",
+                    BackgroundColor = Color.FromArgb("#0F766E"),
+                    TextColor = Colors.White,
+                    CornerRadius = 8
+                };
+                assignButton.Clicked += async (_, _) =>
+                {
+                    if (selected is null || driverPicker.SelectedItem is not Driver driver)
+                    {
+                        await DisplayAlertAsync("Проверка", "Выберите транспорт и водителя.", "OK");
+                        return;
+                    }
+
+                    await RunSafelyAsync(async () =>
+                    {
+                        await _database.AssignDriverToTransportAsync(selected.TransportId, driver.DriverId);
+                        await LoadTransportsSectionAsync();
+                    });
+                };
+
+                _content.Children.Add(CreateForm(
+                    [branchEntry, modelEntry, plateEntry, driverPicker],
+                    CreateCrudButtons(
+                        add: async () =>
+                        {
+                            if (!ValidateRequired(branchEntry, modelEntry, plateEntry))
+                            {
+                                await DisplayAlertAsync("Проверка", "Заполните филиал, модель и госномер.", "OK");
+                                return;
+                            }
+
+                            await _database.AddTransportAsync(new AppTransport
+                            {
+                                Branch = branchEntry.Text!.Trim(),
+                                Model = modelEntry.Text!.Trim(),
+                                LicensePlateNumber = plateEntry.Text!.Trim(),
+                                DriverId = (driverPicker.SelectedItem as Driver)?.DriverId
+                            });
+                            await LoadTransportsSectionAsync();
+                        },
+                        update: async () =>
+                        {
+                            if (selected is null || !ValidateRequired(branchEntry, modelEntry, plateEntry))
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите запись и заполните обязательные поля.", "OK");
+                                return;
+                            }
+
+                            selected.Branch = branchEntry.Text!.Trim();
+                            selected.Model = modelEntry.Text!.Trim();
+                            selected.LicensePlateNumber = plateEntry.Text!.Trim();
+                            selected.DriverId = (driverPicker.SelectedItem as Driver)?.DriverId;
+                            await _database.UpdateTransportAsync(selected);
+                            await LoadTransportsSectionAsync();
+                        },
+                        delete: async () =>
+                        {
+                            if (selected is null)
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
+                                return;
+                            }
+
+                            await _database.DeleteTransportAsync(selected.TransportId);
+                            await LoadTransportsSectionAsync();
+                        },
+                        extra: assignButton)));
+            }
+        });
+    }
+
+    private async void ShowRoutesSection()
+    {
+        await LoadRoutesSectionAsync();
+    }
+
+    private async Task LoadRoutesSectionAsync()
+    {
+        _content.Children.Clear();
+        await RunSafelyAsync(async () =>
+        {
+            _routes = [.. await _database.GetRoutesAsync()];
+            var recordsCount = await _database.GetRoutesCountAsync();
+            var list = CreateCollectionView(_routes);
+            var routeNameEntry = CreateEntry("Название маршрута");
+            TransitRoute? selected = null;
+
+            list.SelectionChanged += (_, e) =>
+            {
+                selected = e.CurrentSelection.FirstOrDefault() as TransitRoute;
+                routeNameEntry.Text = selected?.RouteName;
+            };
+
+            _content.Children.Add(CreateSectionTitle("Маршруты", recordsCount));
+            _content.Children.Add(list);
+            if (CanEdit)
+            {
+                _content.Children.Add(CreateForm(
+                    [routeNameEntry],
+                    CreateCrudButtons(
+                        add: async () =>
+                        {
+                            if (!ValidateRequired(routeNameEntry))
+                            {
+                                await DisplayAlertAsync("Проверка", "Введите название маршрута.", "OK");
+                                return;
+                            }
+
+                            await _database.AddRouteAsync(new TransitRoute { RouteName = routeNameEntry.Text!.Trim() });
+                            await LoadRoutesSectionAsync();
+                        },
+                        update: async () =>
+                        {
+                            if (selected is null || !ValidateRequired(routeNameEntry))
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите запись и заполните название.", "OK");
+                                return;
+                            }
+
+                            selected.RouteName = routeNameEntry.Text!.Trim();
+                            await _database.UpdateRouteAsync(selected);
+                            await LoadRoutesSectionAsync();
+                        },
+                        delete: async () =>
+                        {
+                            if (selected is null)
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
+                                return;
+                            }
+
+                            await _database.DeleteRouteAsync(selected.RouteId);
+                            await LoadRoutesSectionAsync();
+                        })));
+            }
+        });
+    }
+
+    private async void ShowFlightsSection()
+    {
+        await LoadFlightsSectionAsync();
+    }
+
+    private async Task LoadFlightsSectionAsync()
+    {
+        _content.Children.Clear();
+        await RunSafelyAsync(async () =>
+        {
+            await LoadLookupsAsync();
+            var recordsCount = await _database.GetFlightsCountAsync();
+            var list = CreateCollectionView(_flights);
+            var durationEntry = CreateEntry("Длительность HH:mm:ss");
+            var transportPicker = CreatePicker("Транспорт", _transports);
+            var routePicker = CreatePicker("Маршрут", _routes);
+            TransportFlight? selected = null;
+
+            list.SelectionChanged += (_, e) =>
+            {
+                selected = e.CurrentSelection.FirstOrDefault() as TransportFlight;
+                if (selected is null)
+                {
+                    return;
+                }
+
+                durationEntry.Text = selected.Duration.ToString(@"hh\:mm\:ss");
+                transportPicker.SelectedItem = _transports.FirstOrDefault(transport => transport.TransportId == selected.TransportId);
+                routePicker.SelectedItem = _routes.FirstOrDefault(route => route.RouteId == selected.RouteId);
+            };
+
+            _content.Children.Add(CreateSectionTitle("Рейсы", recordsCount));
+            _content.Children.Add(list);
+            if (CanEdit)
+            {
+                _content.Children.Add(CreateForm(
+                    [durationEntry, transportPicker, routePicker],
+                    CreateCrudButtons(
+                        add: async () =>
+                        {
+                            var flight = await CreateFlightFromFormAsync(durationEntry, transportPicker, routePicker);
+                            if (flight is null)
+                            {
+                                return;
+                            }
+
+                            await _database.AddFlightAsync(flight);
+                            await LoadFlightsSectionAsync();
+                        },
+                        update: async () =>
+                        {
+                            var flight = await CreateFlightFromFormAsync(durationEntry, transportPicker, routePicker);
+                            if (selected is null || flight is null)
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите рейс и заполните поля.", "OK");
+                                return;
+                            }
+
+                            flight.FlightId = selected.FlightId;
+                            await _database.UpdateFlightAsync(flight);
+                            await LoadFlightsSectionAsync();
+                        },
+                        delete: async () =>
+                        {
+                            if (selected is null)
+                            {
+                                await DisplayAlertAsync("Проверка", "Выберите запись.", "OK");
+                                return;
+                            }
+
+                            await _database.DeleteFlightAsync(selected.FlightId);
+                            await LoadFlightsSectionAsync();
+                        })));
+            }
+        });
+    }
+
+    private async void ShowFlightInfoSection()
+    {
+        _content.Children.Clear();
+        await RunSafelyAsync(async () =>
+        {
+            await LoadLookupsAsync();
+            var routePicker = CreatePicker("Фильтр по маршруту", _routes);
+            var flightInfo = await _database.GetFlightInfoAsync();
+            var recordsCount = await _database.GetFlightInfoCountAsync();
+            var list = CreateCollectionView(flightInfo);
+
+            var allButton = new Button
+            {
+                Text = "Все рейсы",
+                BackgroundColor = Color.FromArgb("#1D4ED8"),
+                TextColor = Colors.White,
+                CornerRadius = 8
+            };
+            allButton.Clicked += async (_, _) =>
+            {
+                await RunSafelyAsync(async () => list.ItemsSource = await _database.GetFlightInfoAsync());
+            };
+
+            var byRouteButton = new Button
+            {
+                Text = "По выбранному маршруту",
+                BackgroundColor = Color.FromArgb("#0F766E"),
+                TextColor = Colors.White,
+                CornerRadius = 8
+            };
+            byRouteButton.Clicked += async (_, _) =>
+            {
+                if (routePicker.SelectedItem is not TransitRoute route)
+                {
+                    await DisplayAlertAsync("Проверка", "Выберите маршрут.", "OK");
+                    return;
+                }
+
+                await RunSafelyAsync(async () => list.ItemsSource = await _database.GetFlightsByRouteAsync(route.RouteId));
+            };
+
+            _content.Children.Add(CreateSectionTitle("Информация о рейсах", recordsCount));
+            _content.Children.Add(new Border
+            {
+                StrokeThickness = 1,
+                Stroke = Color.FromArgb("#D7DEE8"),
+                BackgroundColor = Colors.White,
+                StrokeShape = new RoundRectangle { CornerRadius = 8 },
+                Padding = new Thickness(12),
+                Content = new VerticalStackLayout
+                {
+                    Spacing = 10,
+                    Children =
+                    {
+                        routePicker,
+                        new HorizontalStackLayout
+                        {
+                            Spacing = 8,
+                            Children = { allButton, byRouteButton }
+                        }
+                    }
+                }
+            });
+            _content.Children.Add(list);
+        });
+    }
+
+    private async Task<TransportFlight?> CreateFlightFromFormAsync(Entry durationEntry, Picker transportPicker, Picker routePicker)
+    {
+        if (!TimeSpan.TryParse(durationEntry.Text, out var duration) || duration <= TimeSpan.Zero)
+        {
+            await DisplayAlertAsync("Проверка", "Длительность должна быть больше 00:00:00 в формате HH:mm:ss.", "OK");
+            return null;
+        }
+
+        if (transportPicker.SelectedItem is not AppTransport transport || routePicker.SelectedItem is not TransitRoute route)
+        {
+            await DisplayAlertAsync("Проверка", "Выберите транспорт и маршрут.", "OK");
+            return null;
+        }
+
+        return new TransportFlight
+        {
+            Duration = duration,
+            TransportId = transport.TransportId,
+            RouteId = route.RouteId
+        };
+    }
+
+    private static Border CreateSectionTitle(string text, int recordsCount)
+    {
+        return new Border
+        {
+            StrokeThickness = 0,
+            BackgroundColor = Color.FromArgb("#E8EEF6"),
+            StrokeShape = new RoundRectangle { CornerRadius = 8 },
+            Padding = new Thickness(14, 10),
+            Content = new Label
+            {
+                Text = $"{text}: {recordsCount} {GetRecordsWord(recordsCount)}",
+                FontSize = 20,
+                FontAttributes = FontAttributes.Bold,
+                TextColor = Color.FromArgb("#172033")
+            }
+        };
+    }
+
+    private static string GetRecordsWord(int count)
+    {
+        var lastTwoDigits = count % 100;
+        if (lastTwoDigits is >= 11 and <= 14)
+        {
+            return "записей";
+        }
+
+        return (count % 10) switch
+        {
+            1 => "запись",
+            >= 2 and <= 4 => "записи",
+            _ => "записей"
+        };
+    }
+
+    private static Entry CreateEntry(string placeholder)
+    {
+        return new Entry
+        {
+            Placeholder = placeholder,
+            BackgroundColor = Colors.White,
+            TextColor = Color.FromArgb("#172033"),
+            PlaceholderColor = Color.FromArgb("#7B8794")
+        };
+    }
+
+    private static Picker CreatePicker<T>(string title, IReadOnlyList<T> items)
+    {
+        return new Picker
+        {
+            Title = title,
+            ItemsSource = items.ToList(),
+            BackgroundColor = Colors.White,
+            TextColor = Color.FromArgb("#172033"),
+            TitleColor = Color.FromArgb("#7B8794")
+        };
+    }
+
+    private static CollectionView CreateCollectionView<T>(IEnumerable<T> items)
+    {
+        return new CollectionView
+        {
+            ItemsSource = items.ToList(),
+            SelectionMode = SelectionMode.Single,
+            EmptyView = "Нет данных",
+            MinimumHeightRequest = 220,
+            ItemTemplate = new DataTemplate(() =>
+            {
+                var label = new Label
+                {
+                    FontSize = 15,
+                    TextColor = Color.FromArgb("#172033"),
+                    LineBreakMode = LineBreakMode.WordWrap
+                };
+                label.SetBinding(Label.TextProperty, ".");
+
+                return new Border
+                {
+                    StrokeThickness = 1,
+                    Stroke = Color.FromArgb("#D7DEE8"),
+                    BackgroundColor = Colors.White,
+                    StrokeShape = new RoundRectangle { CornerRadius = 8 },
+                    Margin = new Thickness(0, 0, 0, 8),
+                    Padding = new Thickness(12),
+                    Content = label
+                };
+            })
+        };
+    }
+
+    private static Border CreateForm(IReadOnlyList<View> fields, HorizontalStackLayout buttons)
+    {
+        var stack = new VerticalStackLayout { Spacing = 10 };
+        foreach (var field in fields)
+        {
+            stack.Children.Add(field);
+        }
+
+        stack.Children.Add(buttons);
+
+        return new Border
+        {
+            StrokeThickness = 1,
+            Stroke = Color.FromArgb("#D7DEE8"),
+            BackgroundColor = Colors.White,
+            StrokeShape = new RoundRectangle { CornerRadius = 8 },
+            Padding = new Thickness(12),
+            Content = stack
+        };
+    }
+
+    private HorizontalStackLayout CreateCrudButtons(Func<Task> add, Func<Task> update, Func<Task> delete, Button? extra = null)
+    {
+        var layout = new HorizontalStackLayout { Spacing = 8 };
+        layout.Children.Add(CreateActionButton("Добавить", "#1D4ED8", add));
+        layout.Children.Add(CreateActionButton("Сохранить", "#0F766E", update));
+        layout.Children.Add(CreateActionButton("Удалить", "#B42318", delete));
+
+        if (extra is not null)
+        {
+            layout.Children.Add(extra);
+        }
+
+        return layout;
+    }
+
+    private Button CreateActionButton(string text, string color, Func<Task> action)
+    {
+        var button = new Button
+        {
+            Text = text,
+            BackgroundColor = Color.FromArgb(color),
+            TextColor = Colors.White,
+            CornerRadius = 8
+        };
+
+        button.Clicked += async (_, _) =>
+        {
+            if (!CanEdit)
+            {
+                await DisplayAlertAsync("Доступ запрещен", "Менеджер может только читать данные.", "OK");
+                return;
+            }
+
+            await RunSafelyAsync(action);
+        };
+
+        return button;
+    }
+
+    private static bool ValidateRequired(params Entry[] entries)
+    {
+        foreach (var entry in entries)
+        {
+            if (string.IsNullOrWhiteSpace(entry.Text))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static void ClearEntries(params Entry[] entries)
+    {
+        foreach (var entry in entries)
+        {
+            entry.Text = string.Empty;
+        }
+    }
+
+    private async Task RunSafelyAsync(Func<Task> action)
+    {
+        try
+        {
+            await action();
+        }
+        catch (Exception ex)
+        {
+            await DisplayAlertAsync("Ошибка", ex.Message, "OK");
+        }
+    }
+}

+ 24 - 0
program/TransportMauiApp/MauiProgram.cs

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

+ 17 - 0
program/TransportMauiApp/Models/AppTransport.cs

@@ -0,0 +1,17 @@
+namespace TransportMauiApp.Models;
+
+public sealed class AppTransport
+{
+    public int TransportId { get; set; }
+    public string Branch { get; set; } = string.Empty;
+    public string Model { get; set; } = string.Empty;
+    public string LicensePlateNumber { get; set; } = string.Empty;
+    public int? DriverId { get; set; }
+    public string DriverFullName { get; set; } = string.Empty;
+
+    public override string ToString()
+    {
+        var driver = string.IsNullOrWhiteSpace(DriverFullName) ? "водитель не назначен" : DriverFullName;
+        return $"{TransportId}: {Branch} {Model}, {LicensePlateNumber}, {driver}";
+    }
+}

+ 16 - 0
program/TransportMauiApp/Models/Driver.cs

@@ -0,0 +1,16 @@
+namespace TransportMauiApp.Models;
+
+public sealed class Driver
+{
+    public int DriverId { get; set; }
+    public string Name { get; set; } = string.Empty;
+    public string Surname { get; set; } = string.Empty;
+    public string Patronimic { get; set; } = string.Empty;
+
+    public string FullName => $"{Surname} {Name} {Patronimic}".Trim();
+
+    public override string ToString()
+    {
+        return $"{DriverId}: {FullName}";
+    }
+}

+ 16 - 0
program/TransportMauiApp/Models/FlightInfo.cs

@@ -0,0 +1,16 @@
+namespace TransportMauiApp.Models;
+
+public sealed class FlightInfo
+{
+    public int FlightId { get; set; }
+    public TimeSpan Duration { get; set; }
+    public string RouteName { get; set; } = string.Empty;
+    public string Model { get; set; } = string.Empty;
+    public string LicensePlateNumber { get; set; } = string.Empty;
+    public string DriverFio { get; set; } = string.Empty;
+
+    public override string ToString()
+    {
+        return $"{FlightId}: {RouteName}, {Duration:hh\\:mm\\:ss}, {Model} {LicensePlateNumber}, {DriverFio}";
+    }
+}

+ 12 - 0
program/TransportMauiApp/Models/TransitRoute.cs

@@ -0,0 +1,12 @@
+namespace TransportMauiApp.Models;
+
+public sealed class TransitRoute
+{
+    public int RouteId { get; set; }
+    public string RouteName { get; set; } = string.Empty;
+
+    public override string ToString()
+    {
+        return $"{RouteId}: {RouteName}";
+    }
+}

+ 16 - 0
program/TransportMauiApp/Models/TransportFlight.cs

@@ -0,0 +1,16 @@
+namespace TransportMauiApp.Models;
+
+public sealed class TransportFlight
+{
+    public int FlightId { get; set; }
+    public TimeSpan Duration { get; set; }
+    public int? TransportId { get; set; }
+    public int? RouteId { get; set; }
+    public string TransportName { get; set; } = string.Empty;
+    public string RouteName { get; set; } = string.Empty;
+
+    public override string ToString()
+    {
+        return $"{FlightId}: {Duration:hh\\:mm\\:ss}, {TransportName}, {RouteName}";
+    }
+}

+ 40 - 0
program/TransportMauiApp/Models/UserSession.cs

@@ -0,0 +1,40 @@
+namespace TransportMauiApp.Models;
+
+public sealed class UserSession
+{
+    public int UserId { get; set; }
+    public string Login { get; set; } = string.Empty;
+    public int? RoleId { get; set; }
+    public string RoleName { get; set; } = string.Empty;
+
+    public bool IsAdministrator
+    {
+        get
+        {
+            var normalizedRole = RoleName.Trim().ToLowerInvariant();
+            return normalizedRole is "admin" or "administrator" or "администратор"
+                || normalizedRole.Contains("admin")
+                || normalizedRole.Contains("админ")
+                || (RoleId == 1 && string.IsNullOrWhiteSpace(RoleName));
+        }
+    }
+
+    public string DisplayRole
+    {
+        get
+        {
+            if (IsAdministrator)
+            {
+                return "Администратор";
+            }
+
+            var normalizedRole = RoleName.Trim().ToLowerInvariant();
+            if (normalizedRole is "manager" or "менеджер" || normalizedRole.Contains("manager") || normalizedRole.Contains("менедж"))
+            {
+                return "Менеджер";
+            }
+
+            return string.IsNullOrWhiteSpace(RoleName) ? $"Роль #{RoleId}" : RoleName;
+        }
+    }
+}

+ 6 - 0
program/TransportMauiApp/Platforms/Android/AndroidManifest.xml

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

+ 10 - 0
program/TransportMauiApp/Platforms/Android/MainActivity.cs

@@ -0,0 +1,10 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace TransportMauiApp;
+
+[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
+{
+}

+ 15 - 0
program/TransportMauiApp/Platforms/Android/MainApplication.cs

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

+ 6 - 0
program/TransportMauiApp/Platforms/Android/Resources/values/colors.xml

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

+ 9 - 0
program/TransportMauiApp/Platforms/MacCatalyst/AppDelegate.cs

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

+ 14 - 0
program/TransportMauiApp/Platforms/MacCatalyst/Entitlements.plist

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

+ 40 - 0
program/TransportMauiApp/Platforms/MacCatalyst/Info.plist

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

+ 15 - 0
program/TransportMauiApp/Platforms/MacCatalyst/Program.cs

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

+ 8 - 0
program/TransportMauiApp/Platforms/Windows/App.xaml

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

+ 24 - 0
program/TransportMauiApp/Platforms/Windows/App.xaml.cs

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

+ 46 - 0
program/TransportMauiApp/Platforms/Windows/Package.appxmanifest

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

+ 17 - 0
program/TransportMauiApp/Platforms/Windows/app.manifest

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

+ 9 - 0
program/TransportMauiApp/Platforms/iOS/AppDelegate.cs

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

+ 32 - 0
program/TransportMauiApp/Platforms/iOS/Info.plist

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

+ 15 - 0
program/TransportMauiApp/Platforms/iOS/Program.cs

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

+ 51 - 0
program/TransportMauiApp/Platforms/iOS/Resources/PrivacyInfo.xcprivacy

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

+ 8 - 0
program/TransportMauiApp/Properties/launchSettings.json

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

+ 4 - 0
program/TransportMauiApp/Resources/AppIcon/appicon.svg

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

+ 8 - 0
program/TransportMauiApp/Resources/AppIcon/appiconfg.svg

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

BIN=BIN
program/TransportMauiApp/Resources/Fonts/OpenSans-Regular.ttf


BIN=BIN
program/TransportMauiApp/Resources/Fonts/OpenSans-Semibold.ttf


BIN=BIN
program/TransportMauiApp/Resources/Images/dotnet_bot.png


+ 15 - 0
program/TransportMauiApp/Resources/Raw/AboutAssets.txt

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

+ 8 - 0
program/TransportMauiApp/Resources/Splash/splash.svg

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

+ 44 - 0
program/TransportMauiApp/Resources/Styles/Colors.xaml

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

+ 434 - 0
program/TransportMauiApp/Resources/Styles/Styles.xaml

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

+ 577 - 0
program/TransportMauiApp/Services/DatabaseService.cs

@@ -0,0 +1,577 @@
+using Npgsql;
+using TransportMauiApp.Models;
+
+namespace TransportMauiApp.Services;
+
+public sealed class DatabaseService
+{
+    private const string ConnectionString =
+        "Host=edu-pg.itiscaf.ru;Port=5432;Database=dbwork;Username=krylatov_go;Password=o#GQ0&n$t47;Search Path=krylatov_go;Include Error Detail=true";
+
+    private readonly NpgsqlDataSource _dataSource = NpgsqlDataSource.Create(ConnectionString);
+
+    public async Task<UserSession?> AuthenticateAsync(string login, string password)
+    {
+        const string sql = """
+            select u.user_id, u.login, u.password, u.role_id, coalesce(r.role_name, '')
+            from users u
+            left join role r on r.role_id = u.role_id
+            where u.login = @login
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        command.Parameters.AddWithValue("login", login);
+
+        await using var reader = await command.ExecuteReaderAsync();
+        if (!await reader.ReadAsync())
+        {
+            return null;
+        }
+
+        var passwordHash = reader.GetString(2);
+        var passwordIsValid = false;
+
+        try
+        {
+            passwordIsValid = BCrypt.Net.BCrypt.Verify(password, passwordHash);
+        }
+        catch (Exception)
+        {
+            passwordIsValid = false;
+        }
+
+        if (!passwordIsValid)
+        {
+            return null;
+        }
+
+        return new UserSession
+        {
+            UserId = reader.GetInt32(0),
+            Login = reader.GetString(1),
+            RoleId = reader.IsDBNull(3) ? null : reader.GetInt32(3),
+            RoleName = reader.GetString(4)
+        };
+    }
+
+    public Task<int> GetDriversCountAsync()
+    {
+        return GetCountAsync("select count(*) from driver");
+    }
+
+    public Task<int> GetTransportsCountAsync()
+    {
+        return GetCountAsync("select count(*) from transport");
+    }
+
+    public Task<int> GetRoutesCountAsync()
+    {
+        return GetCountAsync("select count(*) from route");
+    }
+
+    public Task<int> GetFlightsCountAsync()
+    {
+        return GetCountAsync("select count(*) from transport_flight");
+    }
+
+    public Task<int> GetFlightInfoCountAsync()
+    {
+        return GetCountAsync("select count(*) from flight_info_view");
+    }
+
+    public async Task<IReadOnlyList<Driver>> GetDriversAsync()
+    {
+        const string sql = """
+            select driver_id, name, surname, patronimic
+            from driver
+            order by driver_id
+            """;
+
+        var result = new List<Driver>();
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        await using var reader = await command.ExecuteReaderAsync();
+
+        while (await reader.ReadAsync())
+        {
+            result.Add(new Driver
+            {
+                DriverId = reader.GetInt32(0),
+                Name = reader.GetString(1),
+                Surname = reader.GetString(2),
+                Patronimic = reader.GetString(3)
+            });
+        }
+
+        return result;
+    }
+
+    public async Task<int> AddDriverAsync(Driver driver)
+    {
+        const string sql = """
+            insert into driver (name, surname, patronimic)
+            values (@name, @surname, @patronimic)
+            returning driver_id
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await SyncSerialSequenceAsync(connection, "driver");
+
+        await using var command = new NpgsqlCommand(sql, connection);
+        command.Parameters.AddWithValue("name", driver.Name);
+        command.Parameters.AddWithValue("surname", driver.Surname);
+        command.Parameters.AddWithValue("patronimic", driver.Patronimic);
+        return Convert.ToInt32(await command.ExecuteScalarAsync());
+    }
+
+    public async Task UpdateDriverAsync(Driver driver)
+    {
+        const string sql = """
+            update driver
+            set name = @name, surname = @surname, patronimic = @patronimic
+            where driver_id = @driver_id
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        command.Parameters.AddWithValue("driver_id", driver.DriverId);
+        command.Parameters.AddWithValue("name", driver.Name);
+        command.Parameters.AddWithValue("surname", driver.Surname);
+        command.Parameters.AddWithValue("patronimic", driver.Patronimic);
+        await command.ExecuteNonQueryAsync();
+    }
+
+    public async Task DeleteDriverAsync(int driverId)
+    {
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var transaction = await connection.BeginTransactionAsync();
+
+        try
+        {
+            await ExecuteNonQueryAsync(
+                connection,
+                transaction,
+                "update transport set driver_id = null where driver_id = @driver_id",
+                command => command.Parameters.AddWithValue("driver_id", driverId));
+
+            await ExecuteNonQueryAsync(
+                connection,
+                transaction,
+                "delete from driver where driver_id = @driver_id",
+                command => command.Parameters.AddWithValue("driver_id", driverId));
+
+            await transaction.CommitAsync();
+        }
+        catch
+        {
+            await transaction.RollbackAsync();
+            throw;
+        }
+    }
+
+    public async Task<IReadOnlyList<AppTransport>> GetTransportsAsync()
+    {
+        const string sql = """
+            select t.transport_id, t.branch, t.model, t.license_plate_number, t.driver_id,
+                   coalesce(d.surname || ' ' || d.name || ' ' || d.patronimic, '')
+            from transport t
+            left join driver d on d.driver_id = t.driver_id
+            order by t.transport_id
+            """;
+
+        var result = new List<AppTransport>();
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        await using var reader = await command.ExecuteReaderAsync();
+
+        while (await reader.ReadAsync())
+        {
+            result.Add(new AppTransport
+            {
+                TransportId = reader.GetInt32(0),
+                Branch = reader.GetString(1),
+                Model = reader.GetString(2),
+                LicensePlateNumber = reader.GetString(3),
+                DriverId = reader.IsDBNull(4) ? null : reader.GetInt32(4),
+                DriverFullName = reader.GetString(5)
+            });
+        }
+
+        return result;
+    }
+
+    public async Task<int> AddTransportAsync(AppTransport transport)
+    {
+        const string sql = """
+            insert into transport (branch, model, license_plate_number, driver_id)
+            values (@branch, @model, @license_plate_number, @driver_id)
+            returning transport_id
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await SyncSerialSequenceAsync(connection, "transport");
+
+        await using var command = new NpgsqlCommand(sql, connection);
+        AddTransportParameters(command, transport);
+        return Convert.ToInt32(await command.ExecuteScalarAsync());
+    }
+
+    public async Task UpdateTransportAsync(AppTransport transport)
+    {
+        const string sql = """
+            update transport
+            set branch = @branch,
+                model = @model,
+                license_plate_number = @license_plate_number,
+                driver_id = @driver_id
+            where transport_id = @transport_id
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        command.Parameters.AddWithValue("transport_id", transport.TransportId);
+        AddTransportParameters(command, transport);
+        await command.ExecuteNonQueryAsync();
+    }
+
+    public async Task DeleteTransportAsync(int transportId)
+    {
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var transaction = await connection.BeginTransactionAsync();
+
+        try
+        {
+            await ExecuteNonQueryAsync(
+                connection,
+                transaction,
+                "update transport_flight set transport_id = null where transport_id = @transport_id",
+                command => command.Parameters.AddWithValue("transport_id", transportId));
+
+            await ExecuteNonQueryAsync(
+                connection,
+                transaction,
+                "delete from transport where transport_id = @transport_id",
+                command => command.Parameters.AddWithValue("transport_id", transportId));
+
+            await transaction.CommitAsync();
+        }
+        catch
+        {
+            await transaction.RollbackAsync();
+            throw;
+        }
+    }
+
+    public async Task AssignDriverToTransportAsync(int transportId, int driverId)
+    {
+        const string sql = "call assign_driver_to_transport(@p_transport_id, @p_driver_id)";
+        await ExecuteNonQueryAsync(sql, command =>
+        {
+            command.Parameters.AddWithValue("p_transport_id", transportId);
+            command.Parameters.AddWithValue("p_driver_id", driverId);
+        });
+    }
+
+    public async Task<IReadOnlyList<TransitRoute>> GetRoutesAsync()
+    {
+        const string sql = """
+            select route_id, route_name
+            from route
+            order by route_id
+            """;
+
+        var result = new List<TransitRoute>();
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        await using var reader = await command.ExecuteReaderAsync();
+
+        while (await reader.ReadAsync())
+        {
+            result.Add(new TransitRoute
+            {
+                RouteId = reader.GetInt32(0),
+                RouteName = reader.GetString(1)
+            });
+        }
+
+        return result;
+    }
+
+    public async Task<int> AddRouteAsync(TransitRoute route)
+    {
+        const string sql = """
+            insert into route (route_name)
+            values (@route_name)
+            returning route_id
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await SyncSerialSequenceAsync(connection, "route");
+
+        await using var command = new NpgsqlCommand(sql, connection);
+        command.Parameters.AddWithValue("route_name", route.RouteName);
+        return Convert.ToInt32(await command.ExecuteScalarAsync());
+    }
+
+    public async Task UpdateRouteAsync(TransitRoute route)
+    {
+        const string sql = "update route set route_name = @route_name where route_id = @route_id";
+        await ExecuteNonQueryAsync(sql, command =>
+        {
+            command.Parameters.AddWithValue("route_id", route.RouteId);
+            command.Parameters.AddWithValue("route_name", route.RouteName);
+        });
+    }
+
+    public async Task DeleteRouteAsync(int routeId)
+    {
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var transaction = await connection.BeginTransactionAsync();
+
+        try
+        {
+            await ExecuteNonQueryAsync(
+                connection,
+                transaction,
+                "update transport_flight set route_id = null where route_id = @route_id",
+                command => command.Parameters.AddWithValue("route_id", routeId));
+
+            await ExecuteNonQueryAsync(
+                connection,
+                transaction,
+                "update route_point set route_id = null where route_id = @route_id",
+                command => command.Parameters.AddWithValue("route_id", routeId));
+
+            await ExecuteNonQueryAsync(
+                connection,
+                transaction,
+                "delete from route where route_id = @route_id",
+                command => command.Parameters.AddWithValue("route_id", routeId));
+
+            await transaction.CommitAsync();
+        }
+        catch
+        {
+            await transaction.RollbackAsync();
+            throw;
+        }
+    }
+
+    public async Task<IReadOnlyList<TransportFlight>> GetFlightsAsync()
+    {
+        const string sql = """
+            select tf.flight_id, tf.duration, tf.transport_id, tf.route_id,
+                   coalesce(t.model || ' ' || t.license_plate_number, ''),
+                   coalesce(r.route_name, '')
+            from transport_flight tf
+            left join transport t on t.transport_id = tf.transport_id
+            left join route r on r.route_id = tf.route_id
+            order by tf.flight_id
+            """;
+
+        var result = new List<TransportFlight>();
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        await using var reader = await command.ExecuteReaderAsync();
+
+        while (await reader.ReadAsync())
+        {
+            result.Add(ReadFlight(reader));
+        }
+
+        return result;
+    }
+
+    public async Task<int> AddFlightAsync(TransportFlight flight)
+    {
+        const string sql = """
+            insert into transport_flight (duration, transport_id, route_id)
+            values (@duration, @transport_id, @route_id)
+            returning flight_id
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await SyncSerialSequenceAsync(connection, "transport_flight");
+
+        await using var command = new NpgsqlCommand(sql, connection);
+        AddFlightParameters(command, flight);
+        return Convert.ToInt32(await command.ExecuteScalarAsync());
+    }
+
+    public async Task UpdateFlightAsync(TransportFlight flight)
+    {
+        const string sql = """
+            update transport_flight
+            set duration = @duration,
+                transport_id = @transport_id,
+                route_id = @route_id
+            where flight_id = @flight_id
+            """;
+
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        command.Parameters.AddWithValue("flight_id", flight.FlightId);
+        AddFlightParameters(command, flight);
+        await command.ExecuteNonQueryAsync();
+    }
+
+    public async Task DeleteFlightAsync(int flightId)
+    {
+        const string sql = "delete from transport_flight where flight_id = @flight_id";
+        await ExecuteNonQueryAsync(sql, command => command.Parameters.AddWithValue("flight_id", flightId));
+    }
+
+    public async Task<IReadOnlyList<FlightInfo>> GetFlightInfoAsync()
+    {
+        const string sql = """
+            select flight_id, duration, route_name, model, license_plate_number, driver_fio
+            from flight_info_view
+            order by flight_id
+            """;
+
+        var result = new List<FlightInfo>();
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        await using var reader = await command.ExecuteReaderAsync();
+
+        while (await reader.ReadAsync())
+        {
+            result.Add(new FlightInfo
+            {
+                FlightId = reader.GetInt32(0),
+                Duration = reader.GetTimeSpan(1),
+                RouteName = reader.GetString(2),
+                Model = reader.GetString(3),
+                LicensePlateNumber = reader.GetString(4),
+                DriverFio = reader.GetString(5)
+            });
+        }
+
+        return result;
+    }
+
+    public async Task<IReadOnlyList<FlightInfo>> GetFlightsByRouteAsync(int routeId)
+    {
+        const string sql = """
+            select f.flight_id, f.duration, r.route_name, f.model, f.license_plate_number, f.driver_full_name
+            from get_flights_by_route(@p_route_id) f
+            join route r on r.route_id = @p_route_id
+            order by f.flight_id
+            """;
+
+        var result = new List<FlightInfo>();
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        command.Parameters.AddWithValue("p_route_id", routeId);
+        await using var reader = await command.ExecuteReaderAsync();
+
+        while (await reader.ReadAsync())
+        {
+            result.Add(new FlightInfo
+            {
+                FlightId = reader.GetInt32(0),
+                Duration = reader.GetTimeSpan(1),
+                RouteName = reader.GetString(2),
+                Model = reader.GetString(3),
+                LicensePlateNumber = reader.GetString(4),
+                DriverFio = reader.GetString(5)
+            });
+        }
+
+        return result;
+    }
+
+    private static TransportFlight ReadFlight(NpgsqlDataReader reader)
+    {
+        return new TransportFlight
+        {
+            FlightId = reader.GetInt32(0),
+            Duration = reader.GetTimeSpan(1),
+            TransportId = reader.IsDBNull(2) ? null : reader.GetInt32(2),
+            RouteId = reader.IsDBNull(3) ? null : reader.GetInt32(3),
+            TransportName = reader.GetString(4),
+            RouteName = reader.GetString(5)
+        };
+    }
+
+    private static void AddTransportParameters(NpgsqlCommand command, AppTransport transport)
+    {
+        command.Parameters.AddWithValue("branch", transport.Branch);
+        command.Parameters.AddWithValue("model", transport.Model);
+        command.Parameters.AddWithValue("license_plate_number", transport.LicensePlateNumber);
+        command.Parameters.AddWithValue("driver_id", (object?)transport.DriverId ?? DBNull.Value);
+    }
+
+    private static void AddFlightParameters(NpgsqlCommand command, TransportFlight flight)
+    {
+        command.Parameters.AddWithValue("duration", flight.Duration);
+        command.Parameters.AddWithValue("transport_id", (object?)flight.TransportId ?? DBNull.Value);
+        command.Parameters.AddWithValue("route_id", (object?)flight.RouteId ?? DBNull.Value);
+    }
+
+    private static async Task SyncSerialSequenceAsync(NpgsqlConnection connection, string tableName)
+    {
+        var sql = tableName switch
+        {
+            "driver" => """
+                select setval(
+                    pg_get_serial_sequence('driver', 'driver_id'),
+                    coalesce((select max(driver_id) from driver), 0) + 1,
+                    false
+                )
+                """,
+            "transport" => """
+                select setval(
+                    pg_get_serial_sequence('transport', 'transport_id'),
+                    coalesce((select max(transport_id) from transport), 0) + 1,
+                    false
+                )
+                """,
+            "route" => """
+                select setval(
+                    pg_get_serial_sequence('route', 'route_id'),
+                    coalesce((select max(route_id) from route), 0) + 1,
+                    false
+                )
+                """,
+            "transport_flight" => """
+                select setval(
+                    pg_get_serial_sequence('transport_flight', 'flight_id'),
+                    coalesce((select max(flight_id) from transport_flight), 0) + 1,
+                    false
+                )
+                """,
+            _ => throw new ArgumentOutOfRangeException(nameof(tableName), tableName, "Sequence sync is not configured for this table.")
+        };
+
+        await using var command = new NpgsqlCommand(sql, connection);
+        await command.ExecuteNonQueryAsync();
+    }
+
+    private async Task ExecuteNonQueryAsync(string sql, Action<NpgsqlCommand> configure)
+    {
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        configure(command);
+        await command.ExecuteNonQueryAsync();
+    }
+
+    private async Task<int> GetCountAsync(string sql)
+    {
+        await using var connection = await _dataSource.OpenConnectionAsync();
+        await using var command = new NpgsqlCommand(sql, connection);
+        return Convert.ToInt32(await command.ExecuteScalarAsync());
+    }
+
+    private static async Task ExecuteNonQueryAsync(
+        NpgsqlConnection connection,
+        NpgsqlTransaction transaction,
+        string sql,
+        Action<NpgsqlCommand> configure)
+    {
+        await using var command = new NpgsqlCommand(sql, connection, transaction);
+        configure(command);
+        await command.ExecuteNonQueryAsync();
+    }
+}

+ 53 - 0
program/TransportMauiApp/TransportMauiApp.csproj

@@ -0,0 +1,53 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
+		<TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+		<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
+		<TargetPlatformIdentifier>windows</TargetPlatformIdentifier>
+		<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
+
+		<OutputType>Exe</OutputType>
+		<RootNamespace>TransportMauiApp</RootNamespace>
+		<UseMaui>true</UseMaui>
+		<SingleProject>true</SingleProject>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<Nullable>enable</Nullable>
+
+		<MauiXamlInflator>SourceGen</MauiXamlInflator>
+
+		<ApplicationTitle>TransportMauiApp</ApplicationTitle>
+		<ApplicationId>com.companyname.transportmauiapp</ApplicationId>
+		<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
+		<ApplicationVersion>1</ApplicationVersion>
+		<WindowsPackageType>None</WindowsPackageType>
+		<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
+		<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
+	</PropertyGroup>
+
+	<ItemGroup>
+		<!-- App Icon -->
+		<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
+
+		<!-- Splash Screen -->
+		<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
+
+		<!-- Images -->
+		<MauiImage Include="Resources\Images\*" />
+		<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
+		
+		<!-- Custom Fonts -->
+		<MauiFont Include="Resources\Fonts\*" />
+
+		<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
+		<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
+	</ItemGroup>
+
+	<ItemGroup>
+		<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
+		<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0" />
+		<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
+		<PackageReference Include="Npgsql" Version="10.0.3" />
+	</ItemGroup>
+
+</Project>

+ 25 - 0
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/install.iss

@@ -0,0 +1,25 @@
+[Setup]
+AppName=Учет транспорта ПМ-11
+AppVersion=1.0.0
+DefaultDirName={autopf}\PM11
+DefaultGroupName=Учет транспорта ПМ-11
+OutputBaseFilename=Setup_PM11
+OutputDir=.\output
+Compression=lzma2
+ArchitecturesInstallIn64BitMode=x64
+MinVersion=10.0.17763
+
+[Languages]
+Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
+
+[Files]
+Source: "D:\practice2026\program\TransportMauiApp\bin\Debug\net10.0-windows10.0.19041.0\win-x64\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
+
+[Icons]
+Name: "{group}\ИС ПМ-11"; Filename: "{app}\TransportMauiApp.exe"
+Name: "{commondesktop}\ИС ПМ-11"; Filename: "{app}\TransportMauiApp.exe"
+
+[Run]
+Filename: "{app}\TransportMauiApp.exe"; \
+  Description: "Запустить приложение"; \
+  Flags: nowait postinstall skipifsilent

+ 15 - 0
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/AboutAssets.txt

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

BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/BCrypt-Net-Next.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/CoreMessagingXP.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/DWriteCore.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/DwmSceneI.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/MRM.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.DirectManipulation.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Configuration.Abstractions.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Configuration.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.DependencyInjection.Abstractions.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.DependencyInjection.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Diagnostics.Abstractions.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.FileProviders.Abstractions.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Hosting.Abstractions.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Logging.Abstractions.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Logging.Debug.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Logging.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Options.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Extensions.Primitives.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Foundation.winmd


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Canvas.Interop.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Canvas.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Display.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Imaging.Projection.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Imaging.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Imaging.winmd


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.ImagingInternal.winmd


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.Internal.Imaging.winmd


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Graphics.winmd


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.IO.RecyclableMemoryStream.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.InputStateManager.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.InteractiveExperiences.Projection.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Internal.FrameworkUdk.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Controls.Xaml.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Controls.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Essentials.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Graphics.Win2D.WinUI.Desktop.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.Graphics.dll


BIN=BIN
program/TransportMauiApp/bin/Debug/net10.0-windows10.0.19041.0/win-x64/Microsoft.Maui.dll


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio