Refactor HomeWalkerViewModel and HomeWalkerView to manage and display the first 10 public walk requests; remove unused properties and commands for improved performance and clarity.

This commit is contained in:
Max Mannstein 2025-08-24 14:20:19 +02:00
parent f0ef21d7d7
commit 5c85ce545f
3 changed files with 149 additions and 314 deletions

View File

@ -112,6 +112,9 @@ namespace gehGassiApp.ViewModels
[ObservableProperty]
private ObservableCollection<PublicWalkRequestWithNamesAndResponseStatus> _publicWalkRequests;
[ObservableProperty]
private ObservableCollection<PublicWalkRequestWithNamesAndResponseStatus> _first10PublicWalkRequests;
[ObservableProperty]
private PublicWalkRequestWithNamesAndResponseStatus _selectedPublicWalkRequest;
@ -124,9 +127,6 @@ namespace gehGassiApp.ViewModels
[ObservableProperty]
private double _nextWalkWidth;
[ObservableProperty]
private double _refreshGridHeight;
[ObservableProperty]
private Thickness _bannerMargin;
@ -148,6 +148,7 @@ namespace gehGassiApp.ViewModels
_skip = 0;
SelectedPublicWalkRequest = null;
PublicWalkRequests = null;
First10PublicWalkRequests = null;
await LoadPublicWalkRequestsAsync();
IsBusy = false;
PublicWalkRequestsRefreshing = false;
@ -245,49 +246,6 @@ namespace gehGassiApp.ViewModels
await Shell.Current.GoToAsync($"{nameof(WalkWalkerView)}?id={id}&menu={menu}", Core.Common.Constants.AnimateNavigation);
}
/// <summary>
/// Command für das Laden weiterer Listungen
/// </summary>
[RelayCommand]
public async Task LoadMore()
{
if (PublicWalkRequests != null && PublicWalkRequests.Count < _total && !IsBusy)
{
try
{
IsBusy = true;
var sortOrders = new List<DynamicSortOrder>() { new() { Property = "UpdatedAt", SortDirection = SortDirection.Descending } };
using var cts = new CancellationTokenSource(Core.Common.Constants.ListTimeout);
var publicWalkRequestsResult = await _publicWalkRequestService.GetWithResponseStatusAsync(App.CurrentAppUser.Id, string.Empty, string.Empty, PublicWalkRequestStatus.Open, App.CurrentAppUser.Id, PublicWalkResponseStatus.Awaiting, false, sortOrders, CurrentLocation, false, _take, _skip, App.AppMode, SelectedLanguage, App.CurrentUser.AccessToken, cts.Token);
if (publicWalkRequestsResult.Success)
{
foreach (var request in publicWalkRequestsResult.Value)
{
if (CurrentLocation.Lat != null && CurrentLocation.Lng != null)
request.Distance = Location.CalculateDistance(CurrentLocation.Lat.Value, CurrentLocation.Lng.Value, request.Lat, request.Lng, DistanceUnits.Kilometers);
request.HasResponse = await _publicWalkResponseService.HasResponseAsync(App.CurrentAppUser.Id, request.Id);
PublicWalkRequests.Add(request);
}
}
_total = publicWalkRequestsResult.Total;
_skip = publicWalkRequestsResult.Skip + publicWalkRequestsResult.Take;
IsBusy = false;
}
catch (Exception ex)
{
await TrackErrorAsync(ex, true);
}
finally
{
IsBusy = false;
}
}
}
/// <summary>
/// Command für das Blockieren eines Hundebesitzers über die öffentliche Anfrage
/// </summary>
@ -513,6 +471,9 @@ namespace gehGassiApp.ViewModels
}
PublicWalkRequests = publicWalkRequestsResult.Value.OrderBy(c => c.Distance).ToObservableCollection();
// Erste 10 für die Home-Ansicht
First10PublicWalkRequests = PublicWalkRequests.Take(10).ToObservableCollection();
_total = publicWalkRequestsResult.Total;
_skip = publicWalkRequestsResult.Skip + publicWalkRequestsResult.Take;
@ -522,6 +483,7 @@ namespace gehGassiApp.ViewModels
_total = 0;
_skip = 0;
PublicWalkRequests = null;
First10PublicWalkRequests = null;
}
}
catch (Exception ex)
@ -530,6 +492,7 @@ namespace gehGassiApp.ViewModels
_total = 0;
_skip = 0;
PublicWalkRequests = null;
First10PublicWalkRequests = null;
}
}

View File

@ -19,201 +19,150 @@
</controls:BaseContentPage.TitleView>
<ContentPage.Content>
<Grid
Padding="20,20,20,30"
HorizontalOptions="FillAndExpand"
RowDefinitions="Auto,*"
VerticalOptions="FillAndExpand">
<VerticalStackLayout
x:Name="rootVerticalStackLayout"
Grid.Row="0"
HorizontalOptions="Fill"
SizeChanged="rootVerticalStackLayout_SizeChanged"
Spacing="0"
VerticalOptions="FillAndExpand">
<Border x:Name="permissionStatusBorder" Style="{StaticResource PermissionStatusBorder}">
<Label Style="{StaticResource PermissionStatusLabel}" Text="{x:Static res:Text.Permission_Location_Error}" />
<Border.IsVisible>
<MultiBinding Converter="{StaticResource AllTrueMultiConverter}">
<Binding Converter="{StaticResource PermissionsStatusToBoolConverter}" Path="LocationPermissionStatus" />
<Binding Path="ShowPermissionStatus" />
</MultiBinding>
</Border.IsVisible>
<Border.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowPermissionsCommand}" />
</Border.GestureRecognizers>
</Border>
<controls:ProfileNotFinishedControl
Margin="{StaticResource MarginMid}"
HorizontalOptions="FillAndExpand"
IsVisible="{Binding ProfileNotFinished}"
VerticalOptions="StartAndExpand"
ViewModel="{Binding .}" />
<walkerDashboardControls:NextWalkControlSmall
HorizontalOptions="FillAndExpand"
IsVisible="{Binding HasNextWalk}"
NextWalk="{Binding NextWalk}"
ShowCommand="{Binding WalkSelectedCommand}"
VerticalOptions="StartAndExpand" />
<Grid
HeightRequest="130"
HorizontalOptions="Fill"
VerticalOptions="StartAndExpand">
<CollectionView
HeightRequest="130"
ItemsSource="{Binding NextWalks}"
VerticalOptions="StartAndExpand">
<CollectionView.ItemsLayout>
<GridItemsLayout
HorizontalItemSpacing="10"
Orientation="Horizontal"
SnapPointsAlignment="Center"
SnapPointsType="MandatorySingle" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<walkerDashboardControls:NextWalkListControl
NextWalk="{Binding .}"
NextWalkWidth="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=NextWalkWidth}"
ShowCommand="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=NextWalkSelectedCommand}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Grid.IsVisible>
<MultiBinding Converter="{StaticResource AllTrueMultiConverter}">
<Binding Converter="{StaticResource InvertedBoolConverter}" Path="HasNextWalk" />
<Binding Converter="{StaticResource IsListNotNullOrEmptyConverter}" Path="NextWalks" />
<Binding Converter="{StaticResource ListContainsMoreThanOneItemConverter}" Path="NextWalks" />
</MultiBinding>
</Grid.IsVisible>
</Grid>
<Image
Margin="{Binding BannerMargin}"
HeightRequest="{Binding Banner.Height}"
IsVisible="{Binding HasBanner}"
WidthRequest="{Binding Banner.Width}">
<Image.Source>
<UriImageSource CachingEnabled="True" Uri="{Binding Banner.Image}" />
</Image.Source>
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowBannerCommand}" />
</Image.GestureRecognizers>
</Image>
<Label
Grid.Row="1"
Margin="{Binding ListMargin}"
Style="{StaticResource H6_Blue}"
Text="{x:Static res:Text.Common_PublicWalkRequests_NearYou}" />
</VerticalStackLayout>
<controls:EmptyListViewControl
Grid.Row="1"
ImageHeight="125"
IsBusy="{Binding IsBusy}"
IsNotBusy="{Binding IsNotBusy}"
IsVisible="{Binding PublicWalkRequests, Converter={StaticResource IsListNullOrEmptyConverter}}"
ListSource="{Binding PublicWalkRequests}"
NotFoundImage="public_offers"
NotFoundText="{x:Static res:Text.PublicWalkRequests_NotFound}"
RefreshCommand="{Binding RefreshPublicWalkRequestsCommand}"
SearchImage="public_offers"
SearchText="{x:Static res:Text.PublicWalkRequests_Searching}" />
<AbsoluteLayout>
<!-- Main scrollable content (full screen) -->
<RefreshView
x:Name="homeRefreshView"
Grid.Row="1"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="#ffffff"
Command="{Binding RefreshPublicWalkRequestsCommand}"
HorizontalOptions="FillAndExpand"
IsRefreshing="{Binding PublicWalkRequestsRefreshing}"
IsVisible="{Binding PublicWalkRequests, Converter={StaticResource IsListNotNullOrEmptyConverter}}"
Refreshing="RefreshView_Refreshing"
VerticalOptions="FillAndExpand">
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<CollectionView
x:Name="homeCollectionView"
Background="#ffffff"
IsRefreshing="{Binding PublicWalkRequestsRefreshing}">
<ScrollView>
<Grid
x:Name="rootHomeGrid"
Padding="20,20,20,30"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding PublicWalkRequests}"
RemainingItemsThreshold="5"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}"
Scrolled="homeCollectionView_Scrolled"
SelectedItem="{Binding SelectedPublicWalkRequest}"
SelectionMode="Single"
VerticalOptions="Fill"
VerticalScrollBarVisibility="Never">
<CollectionView.ItemsLayout>
<LinearItemsLayout ItemSpacing="12" Orientation="Vertical" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<!-- <SwipeView Padding="0,0,0,12" BackgroundColor="{StaticResource Primary_White}">
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItemView Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=BlockCommand}" CommandParameter="{Binding .}">
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Image
HeightRequest="24"
HorizontalOptions="Center"
Source="block_user"
VerticalOptions="Center"
WidthRequest="24" />
<Label
HorizontalOptions="Center"
Style="{StaticResource BodyS_GreyDark}"
Text="{x:Static res:Text.Common_Block}" />
</StackLayout>
</SwipeItemView>
</SwipeItems>
</SwipeView.LeftItems>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItemView Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=ReportCommand}" CommandParameter="{Binding .}">
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Image
HeightRequest="24"
HorizontalOptions="Center"
Source="report_user"
VerticalOptions="Center"
WidthRequest="24" />
<Label
HorizontalOptions="Center"
Style="{StaticResource BodyS_GreyDark}"
Text="{x:Static res:Text.Common_Report}" />
</StackLayout>
</SwipeItemView>
</SwipeItems>
</SwipeView.RightItems>-->
<controls:PublicWalkRequestWithResponseListControl PublicWalkRequest="{Binding .}">
<controls:PublicWalkRequestWithResponseListControl.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=PublicWalkRequestSelectedCommand}" CommandParameter="{Binding .}" />
</controls:PublicWalkRequestWithResponseListControl.GestureRecognizers>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{StaticResource Primary_White}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</controls:PublicWalkRequestWithResponseListControl>
<!-- </SwipeView> -->
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
RowDefinitions="Auto"
VerticalOptions="FillAndExpand">
<VerticalStackLayout
x:Name="rootVerticalStackLayout"
Grid.Row="0"
HorizontalOptions="Fill"
Spacing="0"
VerticalOptions="Start">
<Border x:Name="permissionStatusBorder" Style="{StaticResource PermissionStatusBorder}">
<Label Style="{StaticResource PermissionStatusLabel}" Text="{x:Static res:Text.Permission_Location_Error}" />
<Border.IsVisible>
<MultiBinding Converter="{StaticResource AllTrueMultiConverter}">
<Binding Converter="{StaticResource PermissionsStatusToBoolConverter}" Path="LocationPermissionStatus" />
<Binding Path="ShowPermissionStatus" />
</MultiBinding>
</Border.IsVisible>
<Border.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowPermissionsCommand}" />
</Border.GestureRecognizers>
</Border>
<controls:ProfileNotFinishedControl
Margin="{StaticResource MarginMid}"
HorizontalOptions="Fill"
IsVisible="{Binding ProfileNotFinished}"
VerticalOptions="StartAndExpand"
ViewModel="{Binding .}" />
<walkerDashboardControls:NextWalkControlSmall
HorizontalOptions="FillAndExpand"
IsVisible="{Binding HasNextWalk}"
NextWalk="{Binding NextWalk}"
ShowCommand="{Binding WalkSelectedCommand}"
VerticalOptions="StartAndExpand" />
<Grid
HeightRequest="130"
HorizontalOptions="Fill"
VerticalOptions="StartAndExpand">
<CollectionView ItemsSource="{Binding NextWalks}" VerticalOptions="StartAndExpand">
<CollectionView.ItemsLayout>
<GridItemsLayout
HorizontalItemSpacing="10"
Orientation="Horizontal"
SnapPointsAlignment="Center"
SnapPointsType="MandatorySingle" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<walkerDashboardControls:NextWalkListControl
NextWalk="{Binding .}"
NextWalkWidth="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=NextWalkWidth}"
ShowCommand="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=NextWalkSelectedCommand}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Grid.IsVisible>
<MultiBinding Converter="{StaticResource AllTrueMultiConverter}">
<Binding Converter="{StaticResource InvertedBoolConverter}" Path="HasNextWalk" />
<Binding Converter="{StaticResource IsListNotNullOrEmptyConverter}" Path="NextWalks" />
<Binding Converter="{StaticResource ListContainsMoreThanOneItemConverter}" Path="NextWalks" />
</MultiBinding>
</Grid.IsVisible>
</Grid>
<Image
Margin="{Binding BannerMargin}"
HeightRequest="{Binding Banner.Height}"
IsVisible="{Binding HasBanner}"
WidthRequest="{Binding Banner.Width}">
<Image.Source>
<UriImageSource CachingEnabled="True" Uri="{Binding Banner.Image}" />
</Image.Source>
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowBannerCommand}" />
</Image.GestureRecognizers>
</Image>
<Label
Margin="{Binding ListMargin}"
Style="{StaticResource H6_Blue}"
Text="{x:Static res:Text.Common_PublicWalkRequests_NearYou}" />
<!-- EmptyListViewControl -->
<controls:EmptyListViewControl
ImageHeight="125"
IsBusy="{Binding IsBusy}"
IsNotBusy="{Binding IsNotBusy}"
IsVisible="{Binding First10PublicWalkRequests, Converter={StaticResource IsListNullOrEmptyConverter}}"
ListSource="{Binding First10PublicWalkRequests}"
NotFoundImage="public_offers"
NotFoundText="{x:Static res:Text.PublicWalkRequests_NotFound}"
RefreshCommand="{Binding RefreshPublicWalkRequestsCommand}"
SearchImage="public_offers"
SearchText="{x:Static res:Text.PublicWalkRequests_Searching}" />
<!-- PublicWalkRequests StackLayout (first 10) -->
<StackLayout
x:Name="publicWalkRequestsStackLayout"
BindableLayout.ItemsSource="{Binding First10PublicWalkRequests}"
IsVisible="{Binding First10PublicWalkRequests, Converter={StaticResource IsListNotNullOrEmptyConverter}}"
Spacing="12">
<BindableLayout.ItemTemplate>
<DataTemplate>
<controls:PublicWalkRequestWithResponseListControl PublicWalkRequest="{Binding .}">
<controls:PublicWalkRequestWithResponseListControl.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:HomeWalkerViewModel}}, Path=PublicWalkRequestSelectedCommand}" CommandParameter="{Binding .}" />
</controls:PublicWalkRequestWithResponseListControl.GestureRecognizers>
</controls:PublicWalkRequestWithResponseListControl>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<!-- "Alle anzeigen" Link Label -->
<Label
Margin="0,16,0,0"
HorizontalOptions="Center"
IsVisible="{Binding First10PublicWalkRequests, Converter={StaticResource IsListNotNullOrEmptyConverter}}"
Style="{StaticResource LinkLabelStyle}"
Text="Alle anzeigen">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NavigateCommand}" CommandParameter="{x:Static viewmodels:MenuSelected.Jobs}" />
</Label.GestureRecognizers>
</Label>
</VerticalStackLayout>
</Grid>
</ScrollView>
</RefreshView>
</Grid>
</AbsoluteLayout>
</ContentPage.Content>
</controls:BaseContentPage>

View File

@ -10,9 +10,6 @@ public partial class HomeWalkerView : BaseContentPage
IDeviceInstallationService _deviceInstallationService;
IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ??= ServiceHelper.GetService<IDeviceInstallationService>();
private HomeWalkerViewModel ViewModel => BindingContext as HomeWalkerViewModel;
private bool _isRowHidden = false;
private double _initialRowHeight;
private bool _animationRunning = false;
/// <summary>
/// Erstellt eine Instanz
@ -31,16 +28,12 @@ public partial class HomeWalkerView : BaseContentPage
await ViewModel.InitializeAsync(this);
var width = DeviceDisplay.Current.MainDisplayInfo.Width / DeviceDisplay.Current.MainDisplayInfo.Density;
ViewModel.NextWalkWidth = width * 0.8;
_isRowHidden = false;
_initialRowHeight = 0;
_animationRunning = false;
rootVerticalStackLayout.HeightRequest = -1;
#if IOS
if (DeviceInstallationService.NotificationsSupported)
{
AppDelegate.AskForPermissionForRemoteNotificationsAndRegisteriOS();
}
#endif
}
@ -50,74 +43,4 @@ public partial class HomeWalkerView : BaseContentPage
base.OnDisappearing();
await ViewModel.DisappearingAsync(this);
}
//Workaround f<>r den IOS-Bug der H<>henberechnung! Siehe https://github.com/dotnet/maui/issues/8820
private void rootVerticalStackLayout_SizeChanged(object sender, EventArgs e)
{
if (!_isRowHidden && rootVerticalStackLayout.Height > _initialRowHeight)
_initialRowHeight = rootVerticalStackLayout.Height;
}
private async void homeCollectionView_Scrolled(object sender, ItemsViewScrolledEventArgs e)
{
if (_animationRunning)
return;
if (e.VerticalDelta > 0 && e.VerticalOffset >= 50 && !_isRowHidden)
{
await HideHeader();
}
else if (e.VerticalDelta < 0 && e.VerticalOffset <= 50 && _isRowHidden)
{
await ShowHeader();
}
}
private async Task ShowHeader()
{
homeRefreshView.IsEnabled = false;
homeCollectionView.IsEnabled = false;
_isRowHidden = false;
_animationRunning = true;
var animation = new Animation(v => rootVerticalStackLayout.HeightRequest = v, 0, _initialRowHeight);
MainThread.BeginInvokeOnMainThread(() =>
{
animation.Commit(this, "HideRowAnimation", length: 500, easing: Easing.SinIn);
});
await Task.Delay(500);
_animationRunning = false;
homeCollectionView.IsEnabled = true;
homeRefreshView.IsEnabled = true;
}
private async Task HideHeader()
{
homeRefreshView.IsEnabled = false;
homeCollectionView.IsEnabled = false;
_isRowHidden = true;
_animationRunning = true;
var animation = new Animation(v => rootVerticalStackLayout.HeightRequest = v, _initialRowHeight, 0);
MainThread.BeginInvokeOnMainThread(() =>
{
animation.Commit(this, "HideRowAnimation", length: 500, easing: Easing.SinOut);
});
await Task.Delay(500);
_animationRunning = false;
homeCollectionView.IsEnabled = true;
homeRefreshView.IsEnabled = true;
}
private async void RefreshView_Refreshing(object sender, EventArgs e)
{
if (_isRowHidden)
{
await ShowHeader();
}
}
}