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] [ObservableProperty]
private ObservableCollection<PublicWalkRequestWithNamesAndResponseStatus> _publicWalkRequests; private ObservableCollection<PublicWalkRequestWithNamesAndResponseStatus> _publicWalkRequests;
[ObservableProperty]
private ObservableCollection<PublicWalkRequestWithNamesAndResponseStatus> _first10PublicWalkRequests;
[ObservableProperty] [ObservableProperty]
private PublicWalkRequestWithNamesAndResponseStatus _selectedPublicWalkRequest; private PublicWalkRequestWithNamesAndResponseStatus _selectedPublicWalkRequest;
@ -124,9 +127,6 @@ namespace gehGassiApp.ViewModels
[ObservableProperty] [ObservableProperty]
private double _nextWalkWidth; private double _nextWalkWidth;
[ObservableProperty]
private double _refreshGridHeight;
[ObservableProperty] [ObservableProperty]
private Thickness _bannerMargin; private Thickness _bannerMargin;
@ -148,6 +148,7 @@ namespace gehGassiApp.ViewModels
_skip = 0; _skip = 0;
SelectedPublicWalkRequest = null; SelectedPublicWalkRequest = null;
PublicWalkRequests = null; PublicWalkRequests = null;
First10PublicWalkRequests = null;
await LoadPublicWalkRequestsAsync(); await LoadPublicWalkRequestsAsync();
IsBusy = false; IsBusy = false;
PublicWalkRequestsRefreshing = false; PublicWalkRequestsRefreshing = false;
@ -245,49 +246,6 @@ namespace gehGassiApp.ViewModels
await Shell.Current.GoToAsync($"{nameof(WalkWalkerView)}?id={id}&menu={menu}", Core.Common.Constants.AnimateNavigation); 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> /// <summary>
/// Command für das Blockieren eines Hundebesitzers über die öffentliche Anfrage /// Command für das Blockieren eines Hundebesitzers über die öffentliche Anfrage
/// </summary> /// </summary>
@ -513,6 +471,9 @@ namespace gehGassiApp.ViewModels
} }
PublicWalkRequests = publicWalkRequestsResult.Value.OrderBy(c => c.Distance).ToObservableCollection(); PublicWalkRequests = publicWalkRequestsResult.Value.OrderBy(c => c.Distance).ToObservableCollection();
// Erste 10 für die Home-Ansicht
First10PublicWalkRequests = PublicWalkRequests.Take(10).ToObservableCollection();
_total = publicWalkRequestsResult.Total; _total = publicWalkRequestsResult.Total;
_skip = publicWalkRequestsResult.Skip + publicWalkRequestsResult.Take; _skip = publicWalkRequestsResult.Skip + publicWalkRequestsResult.Take;
@ -522,6 +483,7 @@ namespace gehGassiApp.ViewModels
_total = 0; _total = 0;
_skip = 0; _skip = 0;
PublicWalkRequests = null; PublicWalkRequests = null;
First10PublicWalkRequests = null;
} }
} }
catch (Exception ex) catch (Exception ex)
@ -530,6 +492,7 @@ namespace gehGassiApp.ViewModels
_total = 0; _total = 0;
_skip = 0; _skip = 0;
PublicWalkRequests = null; PublicWalkRequests = null;
First10PublicWalkRequests = null;
} }
} }

View File

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

View File

@ -10,9 +10,6 @@ public partial class HomeWalkerView : BaseContentPage
IDeviceInstallationService _deviceInstallationService; IDeviceInstallationService _deviceInstallationService;
IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ??= ServiceHelper.GetService<IDeviceInstallationService>(); IDeviceInstallationService DeviceInstallationService => _deviceInstallationService ??= ServiceHelper.GetService<IDeviceInstallationService>();
private HomeWalkerViewModel ViewModel => BindingContext as HomeWalkerViewModel; private HomeWalkerViewModel ViewModel => BindingContext as HomeWalkerViewModel;
private bool _isRowHidden = false;
private double _initialRowHeight;
private bool _animationRunning = false;
/// <summary> /// <summary>
/// Erstellt eine Instanz /// Erstellt eine Instanz
@ -31,16 +28,12 @@ public partial class HomeWalkerView : BaseContentPage
await ViewModel.InitializeAsync(this); await ViewModel.InitializeAsync(this);
var width = DeviceDisplay.Current.MainDisplayInfo.Width / DeviceDisplay.Current.MainDisplayInfo.Density; var width = DeviceDisplay.Current.MainDisplayInfo.Width / DeviceDisplay.Current.MainDisplayInfo.Density;
ViewModel.NextWalkWidth = width * 0.8; ViewModel.NextWalkWidth = width * 0.8;
_isRowHidden = false;
_initialRowHeight = 0;
_animationRunning = false;
rootVerticalStackLayout.HeightRequest = -1;
#if IOS #if IOS
if (DeviceInstallationService.NotificationsSupported) if (DeviceInstallationService.NotificationsSupported)
{ {
AppDelegate.AskForPermissionForRemoteNotificationsAndRegisteriOS(); AppDelegate.AskForPermissionForRemoteNotificationsAndRegisteriOS();
} }
#endif #endif
} }
@ -50,74 +43,4 @@ public partial class HomeWalkerView : BaseContentPage
base.OnDisappearing(); base.OnDisappearing();
await ViewModel.DisappearingAsync(this); 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();
}
}
} }