Design: dashboard-style ConnectionsPage with rich connection cards

- Add header with ProjectDiagram icon, app title, and connection count
- Replace plain ListView with styled cards (Border + rounded corners)
- Each card shows: database icon, name, host, scheme badge, secure lock
- Inline edit/delete buttons on each card (pencil + trash icons)
- Hide nav bar title (dashboard header replaces it)
- Add computed properties: IsSecure, SchemeShortName, DisplayName
- Add ConnectionCountLabel to ViewModel
- Add OpenConnection command for card tap handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gerwin Kuijntjes
2026-04-02 11:52:01 +02:00
parent 7df3395c3e
commit eefb7fc0ea
4 changed files with 169 additions and 42 deletions

View File

@@ -27,6 +27,12 @@ namespace Xamarin.Neo4j.Models
public string Password { get; set; }
public bool IsSecure => Scheme?.Contains("+s") ?? false;
public string SchemeShortName => Scheme != null && Scheme.StartsWith("bolt") ? "bolt" : "neo4j";
public string DisplayName => string.IsNullOrWhiteSpace(Name) ? Host : Name;
public Tuple<string, bool, bool> ParseHost()
{
var fullHost = Scheme + Host;

View File

@@ -3,30 +3,130 @@
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
xmlns:controls="clr-namespace:Xamarin.Neo4j.Controls;assembly=Xamarin.Neo4j"
x:Class="Xamarin.Neo4j.Pages.ConnectionsPage"
x:Class="Xamarin.Neo4j.Pages.ConnectionsPage"
x:Name="connectionsPage"
Title="PocketGraph">
Title=" ">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Settings" Order="Secondary" Command="{Binding Commands[OpenSettings]}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<Grid>
<ListView ItemsSource="{Binding ConnectionStrings}" HasUnevenRows="True" SelectionMode="None" ItemTapped="OpenSession" IsVisible="{Binding HasItems}">
<ListView.ItemTemplate>
<DataTemplate>
<controls:ConnectionCell>
<controls:ConnectionCell.ContextActions>
<MenuItem Text="Edit" Command="{Binding BindingContext.Commands[EditConnectionString], Source={x:Reference connectionsPage}}" CommandParameter="{Binding .}" />
<MenuItem Text="Delete" Command="{Binding BindingContext.Commands[DeleteConnectionString], Source={x:Reference connectionsPage}}" CommandParameter="{Binding .}" IsDestructive="true" />
</controls:ConnectionCell.ContextActions>
</controls:ConnectionCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid RowDefinitions="Auto,*">
<!-- Row 0: Dashboard header -->
<StackLayout Grid.Row="0" Spacing="4" Padding="16,16,16,12" HorizontalOptions="Center">
<Label Text="{x:Static fonts:FontAwesomeSolid.ProjectDiagram}"
FontFamily="{StaticResource FontAwesomeSolid}"
FontSize="32"
TextColor="{DynamicResource PrimaryTextColor}"
HorizontalOptions="Center"
Margin="0,0,0,4" />
<Label Text="PocketGraph"
FontSize="22" FontAttributes="Bold"
TextColor="{DynamicResource PrimaryTextColor}"
HorizontalOptions="Center" />
<Label Text="{Binding ConnectionCountLabel}"
FontSize="13"
TextColor="{DynamicResource SecondaryTextColor}"
HorizontalOptions="Center" />
</StackLayout>
<!-- Row 1: Connection cards + empty state + FAB -->
<Grid Grid.Row="1">
<!-- Scrollable connection cards -->
<ScrollView IsVisible="{Binding HasItems}">
<StackLayout BindableLayout.ItemsSource="{Binding ConnectionStrings}"
Spacing="0" Padding="0,0,0,80">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Border Margin="16,0,16,8"
BackgroundColor="{DynamicResource Extreme}"
Stroke="{DynamicResource QueryTextBorder}"
StrokeThickness="1"
StrokeShape="RoundRectangle 10">
<Grid ColumnDefinitions="Auto,*,Auto" Padding="14,12">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.Commands[OpenConnection], Source={x:Reference connectionsPage}}"
CommandParameter="{Binding .}" />
</Grid.GestureRecognizers>
<!-- Database icon -->
<Border Grid.Column="0"
WidthRequest="40" HeightRequest="40"
StrokeThickness="0"
StrokeShape="RoundRectangle 20"
BackgroundColor="{DynamicResource QueryActionBar}"
VerticalOptions="Center">
<Label Text="{x:Static fonts:FontAwesomeSolid.Database}"
FontFamily="{StaticResource FontAwesomeSolid}"
FontSize="16"
TextColor="{DynamicResource SecondaryTextColor}"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Border>
<!-- Name + Host + scheme badge inline -->
<StackLayout Grid.Column="1" Spacing="3" Padding="12,0,8,0" VerticalOptions="Center">
<Label Text="{Binding DisplayName}"
FontSize="15" FontAttributes="Bold"
TextColor="{DynamicResource PrimaryTextColor}"
LineBreakMode="TailTruncation" MaxLines="1" />
<StackLayout Orientation="Horizontal" Spacing="6">
<Label Text="{Binding Host}"
FontSize="12"
TextColor="{DynamicResource SecondaryTextColor}"
LineBreakMode="TailTruncation"
VerticalOptions="Center" />
<Label IsVisible="{Binding IsSecure}"
Text="{x:Static fonts:FontAwesomeSolid.Lock}"
FontFamily="{StaticResource FontAwesomeSolid}"
FontSize="9"
TextColor="{DynamicResource SecondaryTextColor}"
VerticalOptions="Center" />
<Border BackgroundColor="{DynamicResource QueryActionBar}"
StrokeThickness="0"
StrokeShape="RoundRectangle 6"
Padding="6,2">
<Label Text="{Binding SchemeShortName}"
FontSize="9" FontAttributes="Bold"
TextColor="{DynamicResource SecondaryTextColor}"
VerticalOptions="Center" />
</Border>
</StackLayout>
</StackLayout>
<!-- Edit / Delete -->
<StackLayout Grid.Column="2" Orientation="Horizontal" Spacing="0" VerticalOptions="Center">
<Button Text="{x:Static fonts:FontAwesomeSolid.PencilAlt}"
FontFamily="{StaticResource FontAwesomeSolid}"
FontSize="14"
WidthRequest="38" HeightRequest="38"
BackgroundColor="Transparent"
TextColor="{DynamicResource SecondaryTextColor}"
Padding="0"
Command="{Binding BindingContext.Commands[EditConnectionString], Source={x:Reference connectionsPage}}"
CommandParameter="{Binding .}" />
<Button Text="{x:Static fonts:FontAwesomeSolid.TrashAlt}"
FontFamily="{StaticResource FontAwesomeSolid}"
FontSize="14"
WidthRequest="38" HeightRequest="38"
BackgroundColor="Transparent"
TextColor="{DynamicResource SecondaryTextColor}"
Padding="0"
Command="{Binding BindingContext.Commands[DeleteConnectionString], Source={x:Reference connectionsPage}}"
CommandParameter="{Binding .}" />
</StackLayout>
</Grid>
</Border>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<!-- Empty state -->
<StackLayout IsVisible="{Binding IsEmpty}" VerticalOptions="Center" HorizontalOptions="Center" Spacing="12">
<Label Text="{x:Static fonts:FontAwesomeSolid.Server}"
FontFamily="{StaticResource FontAwesomeSolid}"
@@ -50,11 +150,13 @@ x:Class="Xamarin.Neo4j.Pages.ConnectionsPage"
FontSize="20"
WidthRequest="56" HeightRequest="56"
CornerRadius="28"
BackgroundColor="{DynamicResource Accent}"
BackgroundColor="{DynamicResource NavigationBarColor}"
TextColor="White"
VerticalOptions="End" HorizontalOptions="End"
Margin="0,0,16,16"
Command="{Binding Commands[AddConnection]}" />
</Grid>
</Grid>
</ContentPage.Content>
</ContentPage>

View File

@@ -42,9 +42,7 @@ namespace Xamarin.Neo4j.Pages
ViewModel.LoadConnectionStrings();
}
private void OpenSession(object sender, ItemTappedEventArgs e)
{
ViewModel.OpenSession((Neo4jConnectionString)e.Item);
}
}
}

View File

@@ -50,6 +50,12 @@ namespace Xamarin.Neo4j.ViewModels
LoadConnectionStrings();
}));
Commands.Add("OpenConnection", new Command((o) =>
{
if (o is Neo4jConnectionString cs)
OpenSession(cs);
}));
Commands.Add("OpenSettings", new Command(async () =>
{
await Navigation.PushAsync(new SettingsPage());
@@ -87,6 +93,7 @@ namespace Xamarin.Neo4j.ViewModels
OnPropertyChanged(nameof(ConnectionStrings));
OnPropertyChanged(nameof(IsEmpty));
OnPropertyChanged(nameof(HasItems));
OnPropertyChanged(nameof(ConnectionCountLabel));
}
}
@@ -94,6 +101,20 @@ namespace Xamarin.Neo4j.ViewModels
public bool HasItems => !IsEmpty;
public string ConnectionCountLabel
{
get
{
var count = _connectionStrings?.Count() ?? 0;
return count switch
{
0 => "No connections yet",
1 => "1 connection",
_ => $"{count} connections"
};
}
}
#endregion
}
}