diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..be34592 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,99 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +gehGassiApp is a .NET MAUI mobile application for iOS and Android that connects dog owners with dog walkers. The app enables users to find dog walkers, book walks, manage payments, and communicate via real-time messaging. + +## Build Commands + +Build solution: +```bash +dotnet build gehGassiApp/gehGassiApp.sln +``` + +Build specific configuration (Debug, Release, Staging, DebugStaging): +```bash +dotnet build gehGassiApp/gehGassiApp.sln -c Debug +``` + +Build for iOS: +```bash +dotnet build gehGassiApp/gehGassiApp/gehGassiApp.csproj -f net8.0-ios +``` + +Build for Android: +```bash +dotnet build gehGassiApp/gehGassiApp/gehGassiApp.csproj -f net8.0-android +``` + +## Database Migrations + +Migrations use EF Core with the DbConfig project as startup project: +```powershell +# Add migration +add-migration MigrationName -Project gehGassiApp.Data -StartupProject gehGassiApp.DbConfig + +# Remove last migration +remove-migration -Project gehGassiApp.Data -StartupProject gehGassiApp.DbConfig + +# Update database +update-database -Project gehGassiApp.Data -StartupProject gehGassiApp.DbConfig +``` + +## Architecture + +### Solution Structure + +The solution follows a layered architecture with these projects: + +- **gehGassiApp** - Main MAUI app with Views, ViewModels, Controls, and platform-specific code +- **gehGassiApp.Core** - Business logic layer: services, interfaces, messaging, helpers +- **gehGassiApp.Domain** - Domain models organized by feature (Dogs, Walks, Users, Payments, etc.) +- **gehGassiApp.Data** - Data access layer with EF Core, SQLite local database, Repository/UnitOfWork pattern +- **gehGassiApp.DbConfig** - Console app for running EF Core migrations +- **gehGassi.LocalNotifications** - Cross-platform local push notification library +- **gehGassi.Toolkit** - Shared utilities +- **gehGassi.Dto** - Shared DTOs (external project from backend repo at `../../gehGassi_backend/gehGassi.Dto`) + +### MVVM Pattern + +The app uses CommunityToolkit.Mvvm for MVVM implementation: +- Views are XAML pages in `gehGassiApp/Views/` +- ViewModels are in `gehGassiApp/ViewModels/` +- Views and ViewModels are registered in `MauiProgram.cs` via DI + +### Dependency Injection + +Services are registered as singletons in `MauiProgram.cs`. Key service interfaces are defined in `gehGassiApp.Core/Interfaces/`. + +### Backend Communication + +- REST API communication via `ICommunicationService` +- Real-time messaging via SignalR through `IAppHubService` +- Backend URL: `https://backend.gehgassi.com` (production) + +### Local Data + +- SQLite database using EF Core +- Database path: `gehgassidb.db` in app local storage +- Repository pattern via `IRepository` and `IUnitOfWork` + +## Build Configurations + +| Configuration | Environment | App ID | +|--------------|-------------|--------| +| Debug | Local dev server | com.gehgassi.local | +| DebugStaging | Staging server (debug) | com.gehgassi.local | +| Staging | Staging server | com.gehgassi.local | +| Release | Production | com.gehgassi.gehgassi | + +## Key Dependencies + +- Microsoft.Maui.Controls 8.0.100 +- CommunityToolkit.Maui / CommunityToolkit.Mvvm +- Microsoft.EntityFrameworkCore.Sqlite 8.0.12 +- Microsoft.AspNetCore.SignalR.Client 8.0.12 +- TinyInsights.Maui.AppInsights (Azure Application Insights) +- Plugin.InAppBilling (in-app purchases) diff --git a/gehGassiApp/gehGassiApp.Core/Interfaces/IPublicWalkRequestService.cs b/gehGassiApp/gehGassiApp.Core/Interfaces/IPublicWalkRequestService.cs index 4f19616..a4ea1d9 100644 --- a/gehGassiApp/gehGassiApp.Core/Interfaces/IPublicWalkRequestService.cs +++ b/gehGassiApp/gehGassiApp.Core/Interfaces/IPublicWalkRequestService.cs @@ -163,9 +163,10 @@ namespace gehGassiApp.Core.Interfaces /// Id der öffentlichen Anfrage /// Id des akzeptierten Angebotes /// Id des Hundebesitzers + /// Zahlungsart für den Walk /// Aktuelles Accesstoken /// CancellationToken /// Erstellter Walk wenn erfolgreich, false sonst - Task AcceptResponseAsync(string publicWalkRequestId, string publicWalkResponseId, string appUserId, string accessToken, CancellationToken token); + Task AcceptResponseAsync(string publicWalkRequestId, string publicWalkResponseId, string appUserId, WalkPaymentType? paymentType, string accessToken, CancellationToken token); } } diff --git a/gehGassiApp/gehGassiApp.Core/Services/CommunicationService.cs b/gehGassiApp/gehGassiApp.Core/Services/CommunicationService.cs index c7cc88d..10a6616 100644 --- a/gehGassiApp/gehGassiApp.Core/Services/CommunicationService.cs +++ b/gehGassiApp/gehGassiApp.Core/Services/CommunicationService.cs @@ -1540,7 +1540,7 @@ namespace gehGassiApp.Core.Services var stringContent = new StringContent(json, Encoding.UTF8, "application/json"); - var serverResult = await _httpClient.PostAsync($"api/Account/SetAppUserTypeEx", stringContent, token).ConfigureAwait(false); + var serverResult = await _httpClient.PostAsync($"api/Account/SetAppUserTypeNoMango", stringContent, token).ConfigureAwait(false); if (serverResult.IsSuccessStatusCode) { diff --git a/gehGassiApp/gehGassiApp.Core/Services/PublicWalkRequestService.cs b/gehGassiApp/gehGassiApp.Core/Services/PublicWalkRequestService.cs index 5ac7e18..38c6acf 100644 --- a/gehGassiApp/gehGassiApp.Core/Services/PublicWalkRequestService.cs +++ b/gehGassiApp/gehGassiApp.Core/Services/PublicWalkRequestService.cs @@ -487,16 +487,18 @@ namespace gehGassiApp.Core.Services /// Id der öffentlichen Anfrage /// Id des akzeptierten Angebotes /// Id des Hundebesitzers + /// Zahlungsart für den Walk /// Aktuelles Accesstoken /// CancellationToken /// Erstellter Walk wenn erfolgreich, false sonst - public async Task AcceptResponseAsync(string publicWalkRequestId, string publicWalkResponseId, string appUserId, string accessToken, CancellationToken token) + public async Task AcceptResponseAsync(string publicWalkRequestId, string publicWalkResponseId, string appUserId, WalkPaymentType? paymentType, string accessToken, CancellationToken token) { var dto = new PublicWalkRequestAcceptDto() { PublicWalkRequestId = publicWalkRequestId, PublicWalkResponseId = publicWalkResponseId, DogOwnerId = appUserId, + PaymentType = paymentType.HasValue ? (WalkPaymentTypeDto)paymentType.Value : null, UpdatedAt = DateTimeOffset.UtcNow }; diff --git a/gehGassiApp/gehGassiApp/MauiProgram.cs b/gehGassiApp/gehGassiApp/MauiProgram.cs index a7a7cd2..fcde730 100644 --- a/gehGassiApp/gehGassiApp/MauiProgram.cs +++ b/gehGassiApp/gehGassiApp/MauiProgram.cs @@ -392,6 +392,7 @@ public static class MauiProgram builder.Services.AddTransientPopup(); builder.Services.AddTransientPopup(); builder.Services.AddTransientPopup(); + builder.Services.AddTransientPopup(); #endregion #region Views diff --git a/gehGassiApp/gehGassiApp/Resources/Images/cashonly.svg b/gehGassiApp/gehGassiApp/Resources/Images/cashonly.svg new file mode 100644 index 0000000..63d8404 --- /dev/null +++ b/gehGassiApp/gehGassiApp/Resources/Images/cashonly.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/gehGassiApp/gehGassiApp/ViewModels/Popups/PaymentInfoPopupViewModel.cs b/gehGassiApp/gehGassiApp/ViewModels/Popups/PaymentInfoPopupViewModel.cs new file mode 100644 index 0000000..ff32497 --- /dev/null +++ b/gehGassiApp/gehGassiApp/ViewModels/Popups/PaymentInfoPopupViewModel.cs @@ -0,0 +1,41 @@ +using CommunityToolkit.Maui; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace gehGassiApp.ViewModels.Popups; + +/// +/// ViewModel für das Zahlungshinweis-Popup +/// +public partial class PaymentInfoPopupViewModel : ObservableObject, IQueryAttributable +{ + #region Fields + private readonly IPopupService _popupService; + #endregion + + #region Constructor + public PaymentInfoPopupViewModel(IPopupService popupService) + { + _popupService = popupService; + } + #endregion + + #region IQueryAttributable Implementation + public void ApplyQueryAttributes(IDictionary query) + { + // Falls Parameter benötigt werden, hier implementieren + } + #endregion + + #region Commands + /// + /// User hat den Hinweis zur Kenntnis genommen + /// + [RelayCommand] + private async Task Acknowledge() + { + // Popup schließen mit true als Ergebnis + await _popupService.ClosePopupAsync(Shell.Current, true); + } + #endregion +} diff --git a/gehGassiApp/gehGassiApp/ViewModels/Walks/BookWalkViewModel.cs b/gehGassiApp/gehGassiApp/ViewModels/Walks/BookWalkViewModel.cs index 37747f9..04248a2 100644 --- a/gehGassiApp/gehGassiApp/ViewModels/Walks/BookWalkViewModel.cs +++ b/gehGassiApp/gehGassiApp/ViewModels/Walks/BookWalkViewModel.cs @@ -1,7 +1,9 @@ using System.Collections.ObjectModel; using System.Text.Json; using System.Web; +using CommunityToolkit.Maui; using CommunityToolkit.Maui.Core.Extensions; +using CommunityToolkit.Maui.Views; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; @@ -18,6 +20,7 @@ using gehGassiApp.Domain.Walkers; using gehGassiApp.Resources; using gehGassiApp.Services; using gehGassiApp.Views.Dogs; +using gehGassiApp.Views.Popups; using gehGassiApp.Views.More; using gehGassiApp.Views.Payments; using gehGassiApp.Views.Walks; @@ -38,6 +41,7 @@ namespace gehGassiApp.ViewModels.Walks private readonly IWalkingTimeService _walkingTimeService; private readonly IDialogService _dialogService; private readonly IDispatcher _dispatcher; + private readonly IPopupService _popupService; private string _walkerId; private bool _countryHandlerEnabled; @@ -55,8 +59,9 @@ namespace gehGassiApp.ViewModels.Walks /// Instanz eines IWalkingTimeService /// Instanz eines IDialogService /// Instanz eines IDispatcher - public BookWalkViewModel(IDogWalkerService dogWalkerService, IDogService dogService, ICountryService countryService, ILocationService locationService, IWalkService walkService, - IWalkingTimeService walkingTimeService, IDialogService dialogService, IDispatcher dispatcher) + /// Instanz eines IPopupService + public BookWalkViewModel(IDogWalkerService dogWalkerService, IDogService dogService, ICountryService countryService, ILocationService locationService, IWalkService walkService, + IWalkingTimeService walkingTimeService, IDialogService dialogService, IDispatcher dispatcher, IPopupService popupService) { _dogWalkerService = dogWalkerService; _dogService = dogService; @@ -66,6 +71,7 @@ namespace gehGassiApp.ViewModels.Walks _walkingTimeService = walkingTimeService; _dialogService = dialogService; _dispatcher = dispatcher; + _popupService = popupService; _bookNow = false; _addressChangedCounter = 0; @@ -615,6 +621,9 @@ namespace gehGassiApp.ViewModels.Walks var helpStartDate = new DateTimeOffset(StartDate.Year, StartDate.Month, StartDate.Day, StartTime.Hours, StartTime.Minutes, StartTime.Seconds, DateTimeOffset.Now.Offset); var helpEndDate = new DateTimeOffset(EndDate.Year, EndDate.Month, EndDate.Day, EndTime.Hours, EndTime.Minutes, EndTime.Seconds, DateTimeOffset.Now.Offset); + // PaymentType auswählen + var paymentResult = await _popupService.ShowPopupAsync(Shell.Current); + var walkCreateDto = new WalkCreateDto() { Type = WalkTypeDto.Scheduled, @@ -628,7 +637,8 @@ namespace gehGassiApp.ViewModels.Walks PickupAddress = PickupAddress.ToDto(), ReturnAddress = ReturnAddress.ToDto(), Price = Price, - Info = Info + Info = Info, + PaymentType = paymentResult.Result is true ? WalkPaymentTypeDto.Cash : null }; #region Pickupaddress @@ -723,6 +733,9 @@ namespace gehGassiApp.ViewModels.Walks var helpStartDate = new DateTimeOffset(StartDate.Year, StartDate.Month, StartDate.Day, StartTime.Hours, StartTime.Minutes, StartTime.Seconds, DateTimeOffset.Now.Offset); var helpEndDate = new DateTimeOffset(EndDate.Year, EndDate.Month, EndDate.Day, EndTime.Hours, EndTime.Minutes, EndTime.Seconds, DateTimeOffset.Now.Offset); + // PaymentType auswählen + var paymentResult = await _popupService.ShowPopupAsync(Shell.Current); + var walkCreateDto = new WalkCreateDto() { Type = WalkTypeDto.Direct, @@ -736,7 +749,8 @@ namespace gehGassiApp.ViewModels.Walks PickupAddress = PickupAddress.ToDto(), ReturnAddress = ReturnAddress.ToDto(), Price = Price, - Info = Info + Info = Info, + PaymentType = paymentResult.Result is true ? WalkPaymentTypeDto.Cash : null }; #region Pickupaddress diff --git a/gehGassiApp/gehGassiApp/ViewModels/Walks/PublicWalkResponseShowViewModel.cs b/gehGassiApp/gehGassiApp/ViewModels/Walks/PublicWalkResponseShowViewModel.cs index ebfae29..15e61cd 100644 --- a/gehGassiApp/gehGassiApp/ViewModels/Walks/PublicWalkResponseShowViewModel.cs +++ b/gehGassiApp/gehGassiApp/ViewModels/Walks/PublicWalkResponseShowViewModel.cs @@ -1,5 +1,7 @@ using System.Web; +using CommunityToolkit.Maui; using CommunityToolkit.Maui.Core; +using CommunityToolkit.Maui.Views; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; @@ -17,6 +19,7 @@ using gehGassiApp.Views.Payments; using gehGassiApp.Views.Ratings; using gehGassiApp.Views.Report; using gehGassiApp.Views.Walks; +using gehGassiApp.Views.Popups; namespace gehGassiApp.ViewModels.Walks { @@ -32,6 +35,7 @@ namespace gehGassiApp.ViewModels.Walks private readonly IConversationService _conversationService; private readonly IUserService _userService; private readonly IFavouriteService _favouriteService; + private readonly IPopupService _popupService; private string _responseId; private string _requestId; @@ -49,8 +53,9 @@ namespace gehGassiApp.ViewModels.Walks /// Instanz eines IConversationService /// Instanz eines IUserService /// Instanz eines IFavouriteService - public PublicWalkResponseShowViewModel(IPublicWalkRequestService publicWalkRequestService, IPublicWalkResponseService publicWalkResponseService, IDialogService dialogService, ILookupService lookupService, - ILocalPushNotificationService localPushNotificationService, IDispatcher dispatcher, IWalkService walkService, IConversationService conversationService, IUserService userService, IFavouriteService favouriteService) + /// Instanz eines IPopupService + public PublicWalkResponseShowViewModel(IPublicWalkRequestService publicWalkRequestService, IPublicWalkResponseService publicWalkResponseService, IDialogService dialogService, ILookupService lookupService, + ILocalPushNotificationService localPushNotificationService, IDispatcher dispatcher, IWalkService walkService, IConversationService conversationService, IUserService userService, IFavouriteService favouriteService, IPopupService popupService) { _publicWalkRequestService = publicWalkRequestService; _publicWalkResponseService = publicWalkResponseService; @@ -62,6 +67,7 @@ namespace gehGassiApp.ViewModels.Walks _conversationService = conversationService; _userService = userService; _favouriteService = favouriteService; + _popupService = popupService; Title = Text.View_Title_PublicWalkResponse; SelectedMenu = MenuSelected.Walks; IsLoading = true; @@ -120,9 +126,21 @@ namespace gehGassiApp.ViewModels.Walks return; } + // PaymentInfoPopup anzeigen um PaymentType zu bestimmen + var popupResult = await _popupService.ShowPopupAsync(Shell.Current); + + // Wenn das Popup geschlossen wurde ohne "Alles klar" zu klicken, abbrechen + if (popupResult?.Result is not true) + { + return; + } + + // Popup Result = true bedeutet Cash Payment + var paymentType = WalkPaymentType.Cash; + IsSaving = true; using var cts = new CancellationTokenSource(Core.Common.Constants.CreateTimeout); - var createdWalk = await _publicWalkRequestService.AcceptResponseAsync(_requestId, PublicWalkResponse.Id, App.CurrentAppUser.Id, App.CurrentUser.AccessToken, cts.Token); + var createdWalk = await _publicWalkRequestService.AcceptResponseAsync(_requestId, PublicWalkResponse.Id, App.CurrentAppUser.Id, paymentType, App.CurrentUser.AccessToken, cts.Token); if (createdWalk != null) { WeakReferenceMessenger.Default.Send(new RefreshViewModelMessage(Core.Messaging.Messages.Refresh_PublicWalkRequestDetails)); diff --git a/gehGassiApp/gehGassiApp/Views/Popups/PaymentInfoPopup.xaml b/gehGassiApp/gehGassiApp/Views/Popups/PaymentInfoPopup.xaml new file mode 100644 index 0000000..2b7b3a6 --- /dev/null +++ b/gehGassiApp/gehGassiApp/Views/Popups/PaymentInfoPopup.xaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + +