connection string management + listing databases

This commit is contained in:
Trevi Awater
2021-11-13 16:58:44 +01:00
parent 97c4a8e8d5
commit dc8b43574f
27 changed files with 28704 additions and 4553 deletions

View File

@@ -1,4 +1,5 @@
using System;
using Acr.UserDialogs;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
@@ -8,8 +9,7 @@ using Android.OS;
namespace Xamarin.Neo4j.Android
{
[Activity(Label = "Xamarin.Neo4j", Theme = "@style/MainTheme", MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[Activity(Label = "Xamarin.Neo4j", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
@@ -17,9 +17,22 @@ namespace Xamarin.Neo4j.Android
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
UserDialogs.Init(this);
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
Essentials.Platform.Init(this, savedInstanceState);
Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.Xamarin.Neo4j">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
<application android:label="Xamarin.Neo4j.Android"></application>
</manifest>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
<application android:label="Xamarin.Neo4j.Android"></application>
</manifest>

View File

@@ -14,7 +14,6 @@
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
</PropertyGroup>
@@ -39,36 +38,36 @@
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android"/>
<Reference Include="System"/>
<Reference Include="System.Core"/>
<Reference Include="System.Xml.Linq"/>
<Reference Include="System.Xml"/>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2012"/>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs"/>
<Compile Include="Resources\Resource.Designer.cs"/>
<Compile Include="Properties\AssemblyInfo.cs"/>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt"/>
<None Include="Assets\AboutAssets.txt"/>
<None Include="Properties\AndroidManifest.xml"/>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Tabbar.axml"/>
<AndroidResource Include="Resources\layout\Toolbar.axml"/>
<AndroidResource Include="Resources\values\styles.xml"/>
<AndroidResource Include="Resources\values\colors.xml"/>
<AndroidResource Include="Resources\layout\Tabbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Xamarin.Neo4j\Xamarin.Neo4j.csproj">
<Project>{25571B13-BB88-40F9-8C68-E66B1F4320A9}</Project>
<Project>{756232D0-DBB0-4AEB-B1CF-1F5CA12CAC85}</Project>
<Name>Xamarin.Neo4j</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets"/>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>

View File

@@ -6,14 +6,14 @@ using UIKit;
namespace Xamarin.Neo4j.iOS
{
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
@@ -21,10 +21,11 @@ namespace Xamarin.Neo4j.iOS
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
Forms.Forms.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
}
}

View File

@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?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>
</dict>
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)nl.awater-enterprises.xamarin-neo4j</string>
</array>
</dict>
</plist>

View File

@@ -25,7 +25,7 @@
<key>CFBundleDisplayName</key>
<string>Xamarin.Neo4j</string>
<key>CFBundleIdentifier</key>
<string>com.companyname.Xamarin.Neo4j</string>
<string>nl.resoftware.xamarin-neo4j</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UILaunchStoryboardName</key>

View File

@@ -25,6 +25,8 @@
<MtouchArch>x86_64</MtouchArch>
<MtouchLink>None</MtouchLink>
<MtouchDebug>true</MtouchDebug>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<CodesignKey>iPhone Developer</CodesignKey>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
<DebugType>none</DebugType>
@@ -35,6 +37,7 @@
<MtouchLink>None</MtouchLink>
<MtouchArch>x86_64</MtouchArch>
<ConsolePause>false</ConsolePause>
<CodesignKey>iPhone Developer</CodesignKey>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
<DebugSymbols>true</DebugSymbols>
@@ -49,6 +52,7 @@
<CodesignKey>iPhone Developer</CodesignKey>
<MtouchDebug>true</MtouchDebug>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchLink>SdkOnly</MtouchLink>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
<DebugType>none</DebugType>
@@ -60,6 +64,7 @@
<ConsolePause>false</ConsolePause>
<CodesignKey>iPhone Developer</CodesignKey>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchLink>SdkOnly</MtouchLink>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Ad-Hoc|iPhone' ">
<DebugType>none</DebugType>
@@ -73,6 +78,7 @@
<CodesignProvision>Automatic:AdHoc</CodesignProvision>
<CodesignKey>iPhone Distribution</CodesignKey>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchLink>SdkOnly</MtouchLink>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'AppStore|iPhone' ">
<DebugType>none</DebugType>
@@ -85,6 +91,7 @@
<CodesignProvision>Automatic:AppStore</CodesignProvision>
<CodesignKey>iPhone Distribution</CodesignKey>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchLink>SdkOnly</MtouchLink>
</PropertyGroup>
<ItemGroup>
<Compile Include="Main.cs" />
@@ -106,11 +113,11 @@
<Reference Include="Xamarin.iOS" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2012" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Xamarin.Neo4j\Xamarin.Neo4j.csproj">
<Project>{25571B13-BB88-40F9-8C68-E66B1F4320A9}</Project>
<Project>{756232D0-DBB0-4AEB-B1CF-1F5CA12CAC85}</Project>
<Name>Xamarin.Neo4j</Name>
</ProjectReference>
</ItemGroup>

View File

@@ -3,6 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xamarin.Neo4j.App">
<Application.Resources>
<Color x:Key="Glitter">#e4ecf5</Color>
<Color x:Key="EbonyClay">#31333b</Color>
<OnPlatform x:TypeArguments="x:String" x:Key="FontAwesomeBrands">
<On Platform="Android" Value="FontAwesome5Brands.ttf#Regular" />
<On Platform="iOS" Value="FontAwesome5Brands-Regular" />
@@ -17,5 +20,18 @@
<On Platform="Android" Value="FontAwesome5Regular.ttf#Regular" />
<On Platform="iOS" Value="FontAwesome5Free-Regular" />
</OnPlatform>
<Style TargetType="ContentPage" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="{StaticResource Glitter}" />
</Style>
<Style TargetType="ListView">
<Setter Property="BackgroundColor" Value="Transparent" />
</Style>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{StaticResource EbonyClay}"/>
<Setter Property="BarTextColor" Value="White" />
</Style>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,61 @@
//
// ConnectionStringManager.cs
//
// Trevi Awater
// 13-11-2021
//
// © Xamarin.Neo4j
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Neo4j.Models;
namespace Xamarin.Neo4j.Managers
{
public class ConnectionStringManager
{
private const string ConnectionStringsKey = "connection_strings";
public static async Task<List<Neo4jConnectionString>> GetConnectionStrings()
{
var json = await SecureStorage.GetAsync(ConnectionStringsKey);
if (string.IsNullOrEmpty(json))
return new List<Neo4jConnectionString>();
return JsonConvert.DeserializeObject<List<Neo4jConnectionString>>(json);
}
public static async Task AddConnectionString(Neo4jConnectionString connectionString)
{
var connectionStrings = await GetConnectionStrings();
connectionStrings.Add(connectionString);
await SaveConnectionStrings(connectionStrings);
}
private static async Task SaveConnectionStrings(IEnumerable<Neo4jConnectionString> connectionStrings)
{
var json = JsonConvert.SerializeObject(connectionStrings);
await SecureStorage.SetAsync(ConnectionStringsKey, json);
}
public static async Task DeleteConnectionString(Neo4jConnectionString neo4JConnectionString)
{
var connectionStrings = await GetConnectionStrings();
connectionStrings = connectionStrings.Where(cs => cs.Id != neo4JConnectionString.Id)
.ToList();
await SaveConnectionStrings(connectionStrings);
}
}
}

View File

@@ -0,0 +1,22 @@
//
// Database.cs
//
// Trevi Awater
// 13-11-2021
//
// © Xamarin.Neo4j
//
namespace Xamarin.Neo4j.Models
{
public class Database
{
public string Name { get; set; }
public string Status { get; set; }
public bool Default { get; set; }
public string DisplayName => Name + (Default ? " 🏠" : string.Empty);
}
}

View File

@@ -0,0 +1,32 @@
//
// Neo4jConnectionString.cs
//
// Trevi Awater
// 13-11-2021
//
// © Xamarin.Neo4j
//
using System;
namespace Xamarin.Neo4j.Models
{
public class Neo4jConnectionString
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Database { get; set; }
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool Encrypted { get; set; }
}
}

View File

@@ -2,6 +2,7 @@
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="Add Connection"
x:Class="Xamarin.Neo4j.Pages.AddConnectionPage">
<ContentPage.Content>
<ScrollView>
@@ -15,6 +16,33 @@
<Label Text="Port:" FontAttributes="Bold" FontSize="Small" />
<Entry Text="{Binding Port}" Keyboard="Numeric" />
</StackLayout>
<StackLayout Spacing="4">
<Label Text="Encryption:" FontAttributes="Bold" FontSize="Small" />
<Switch IsToggled="{Binding Encryption}" />
</StackLayout>
<StackLayout Spacing="4">
<Label Text="Username:" FontAttributes="Bold" FontSize="Small" />
<Entry Text="{Binding Username}" Keyboard="Numeric" />
</StackLayout>
<StackLayout Spacing="4">
<Label Text="Password:" FontAttributes="Bold" FontSize="Small" />
<Entry Text="{Binding Password}" IsPassword="True" />
</StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Text="Connect" Command="{Binding Commands[Connect]}" />
<Button Grid.Column="1" Text="Test" Command="{Binding Commands[Test]}" />
<Button Grid.Column="2" Text="Save" Command="{Binding Commands[Save]}" />
</Grid>
</StackLayout>
</ScrollView>
</ContentPage.Content>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
x:Class="Xamarin.Neo4j.Pages.ConnectionsPage"
x:Name="connectionsPage"
Title="Connections">
<ContentPage.IconImageSource>
<FontImageSource
FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FontAwesomeSolid.Server}"
Size="20" />
</ContentPage.IconImageSource>
<ContentPage.ToolbarItems>
<ToolbarItem Command="{Binding Commands[AddConnection]}">
<ToolbarItem.IconImageSource>
<FontImageSource
FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FontAwesomeSolid.PlusCircle}" Size="20" />
</ToolbarItem.IconImageSource>
</ToolbarItem>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<ListView ItemsSource="{Binding ConnectionStrings}" ItemTapped="StartSession">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}" Detail="{Binding Host}">
<TextCell.ContextActions>
<MenuItem Text="Delete" Command="{Binding BindingContext.Commands[DeleteConnectionString], Source={x:Reference connectionsPage} }" CommandParameter="{Binding .}" IsDestructive="true" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.ViewModels;
namespace Xamarin.Neo4j.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ConnectionsPage : ContentPage
{
private ConnectionsViewModel ViewModel => (ConnectionsViewModel)BindingContext;
public ConnectionsPage()
{
InitializeComponent();
BindingContext = new ConnectionsViewModel(Navigation);
}
protected override void OnAppearing()
{
ViewModel.LoadConnectionStrings();
base.OnAppearing();
}
private async void StartSession(object sender, ItemTappedEventArgs e)
{
var connectionString = (Neo4jConnectionString)e.Item;
await Navigation.PushAsync(new SessionPage(connectionString));
}
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
Title="Queries"
x:Class="Xamarin.Neo4j.Pages.QueriesPage">
<ContentPage.IconImageSource>
<FontImageSource
FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FontAwesomeSolid.Code}"
Size="20" />
</ContentPage.IconImageSource>
<ContentPage.Content>
<StackLayout>
<Label Text="Hello, world!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Neo4j.ViewModels;
namespace Xamarin.Neo4j.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class QueriesPage : ContentPage
{
public QueriesPage()
{
InitializeComponent();
BindingContext = new QueriesViewModel(Navigation);
}
}
}

View File

@@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
xmlns:pages="clr-namespace:Xamarin.Neo4j.Pages;assembly=Xamarin.Neo4j"
x:Class="Xamarin.Neo4j.Pages.RootPage">
<ContentPage.ToolbarItems>
<ToolbarItem Command="{Binding Commands[AddConnection]}">
<ToolbarItem.IconImageSource>
<FontImageSource
FontFamily="{StaticResource FontAwesomeSolid}"
Glyph="{x:Static fonts:FontAwesomeSolid.PlusCircle}" Size="20" />
</ToolbarItem.IconImageSource>
</ToolbarItem>
</ContentPage.ToolbarItems>
</ContentPage>
<TabbedPage.Children>
<pages:ConnectionsPage />
<pages:QueriesPage />
</TabbedPage.Children>
</TabbedPage>

View File

@@ -6,18 +6,22 @@ using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Neo4j.ViewModels;
namespace Xamarin.Neo4j.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RootPage : ContentPage
public partial class RootPage : TabbedPage
{
public RootPage()
{
InitializeComponent();
}
BindingContext = new RootViewModel(Navigation);
protected override void OnCurrentPageChanged()
{
Title = CurrentPage.Title;
base.OnCurrentPageChanged();
}
}
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
x:Class="Xamarin.Neo4j.Pages.SessionPage">
<NavigationPage.TitleView>
<StackLayout Spacing="0">
<Label TextColor="White" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="FocusDatabasePicker" />
</Label.GestureRecognizers>
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding CurrentDatabase.DisplayName}" />
<Span Text=" " />
<Span Text="{x:Static fonts:FontAwesomeSolid.CaretDown}" FontFamily="{StaticResource FontAwesomeSolid}" />
</FormattedString>
</Label.FormattedText>
</Label>
<Picker x:Name="databasePicker" ItemsSource="{Binding AvailableDatabases}" ItemDisplayBinding="{Binding DisplayName}" SelectedItem="{Binding CurrentDatabase}" IsVisible="False" />
</StackLayout>
</NavigationPage.TitleView>
<ContentPage.Content>
<StackLayout>
<Editor Text="{Binding Query}" AutoSize="TextChanges" />
<Button Text="Run Query" Command="{Binding Commands[RunQuery]}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.ViewModels;
namespace Xamarin.Neo4j.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SessionPage : ContentPage
{
public SessionPage(Neo4jConnectionString connectionString)
{
InitializeComponent();
BindingContext = new SessionViewModel(Navigation, connectionString);
}
private void FocusDatabasePicker(object sender, EventArgs e)
{
databasePicker.Focus();
}
}
}

View File

@@ -0,0 +1,85 @@
//
// Neo4jService.cs
//
// Trevi Awater
// 13-11-2021
//
// © Xamarin.Neo4j
//
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Neo4j.Driver;
using Neo4jClient;
using Neo4jClient.Cypher;
using Xamarin.Forms;
using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.Services;
[assembly: Dependency(typeof(Neo4jService))]
namespace Xamarin.Neo4j.Services
{
public class Neo4jService
{
private BoltGraphClient GraphClient { get; set; }
public async Task<bool> EstablishConnection(Neo4jConnectionString connectionString)
{
var boltUri = $"bolt://{connectionString.Host}:{connectionString.Port}";
var driver = GraphDatabase.Driver(boltUri, AuthTokens.Basic(connectionString.Username, connectionString.Password), builder =>
{
builder.WithEncryptionLevel(connectionString.Encrypted ? EncryptionLevel.Encrypted : EncryptionLevel.None);
});
GraphClient = new BoltGraphClient(driver);
try
{
await GraphClient.ConnectAsync();
}
catch (AuthenticationException)
{
return false;
}
return true;
}
public async Task<List<Database>> LoadDatabases()
{
var session = GraphClient.Driver.AsyncSession(d => d.WithDatabase("system"));
var cursor = await session.RunAsync("SHOW DATABASES;");
var databases = new List<Database>();
while (await cursor.FetchAsync())
{
var name = cursor.Current.Values["name"].As<string>();
var status = cursor.Current.Values["currentStatus"].As<string>();
var @default = cursor.Current.Values["default"].As<bool>();
databases.Add(new Database()
{
Name = name,
Status = status,
Default = @default
});
}
return databases;
}
public async Task ExecuteQuery(string query, string database)
{
var session = GraphClient.Driver.AsyncSession(d => d.WithDatabase(database));
var cursor = await session.RunAsync(query);
while (await cursor.FetchAsync())
{
}
}
}
}

View File

@@ -7,10 +7,17 @@
// © Xamarin.Neo4j
//
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Acr.UserDialogs;
using Xamarin.Essentials;
using Xamarin.Forms;
using Xamarin.Neo4j.Annotations;
using Xamarin.Neo4j.Managers;
using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.Pages;
using Xamarin.Neo4j.Services;
namespace Xamarin.Neo4j.ViewModels
{
@@ -22,9 +29,53 @@ namespace Xamarin.Neo4j.ViewModels
private int _port;
private readonly Neo4jService _neo4jService;
public event PropertyChangedEventHandler PropertyChanged;
public AddConnectionViewModel(INavigation navigation) : base(navigation)
{
InitializeDefaultValues();
_neo4jService = DependencyService.Resolve<Neo4jService>();
Commands.Add("Test", new Command(async () =>
{
// TODO: Validate input.
var connectionString = BuildConnectionString();
var couldConnect = await _neo4jService.EstablishConnection(connectionString);
await UserDialogs.Instance.AlertAsync(couldConnect ? "Connection successful." : "Connection failed.");
}));
Commands.Add("Save", new Command(async () =>
{
// TODO: Validate input.
var connectionString = BuildConnectionString();
var namePromptResult = await UserDialogs.Instance.PromptAsync("How do you want to name this connection?", "Save Connection", "Save", "Cancel");
if (!namePromptResult.Ok || string.IsNullOrWhiteSpace(namePromptResult.Value))
return;
connectionString.Name = namePromptResult.Value;
await ConnectionStringManager.AddConnectionString(connectionString);
await Navigation.PopAsync();
}));
Commands.Add("Connect", new Command(async () =>
{
// TODO: Validate input.
var connectionString = BuildConnectionString();
await Navigation.PushAsync(new SessionPage(connectionString));
}));
}
private void InitializeDefaultValues()
@@ -33,14 +84,25 @@ namespace Xamarin.Neo4j.ViewModels
Port = 7687;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Neo4jConnectionString BuildConnectionString()
{
return new Neo4jConnectionString
{
Id = Guid.NewGuid(),
Host = Host,
Port = Port,
Username = Username,
Password = Password,
Encrypted = Encrypted
};
}
#region Bindable Properties
public bool Encrypted

View File

@@ -0,0 +1,72 @@
//
// ConnectionsViewModel.cs
//
// Trevi Awater
// 13-11-2021
//
// © Xamarin.Neo4j
//
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using Xamarin.Neo4j.Annotations;
using Xamarin.Neo4j.Managers;
using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.Pages;
namespace Xamarin.Neo4j.ViewModels
{
public class ConnectionsViewModel : ViewModelBase, INotifyPropertyChanged
{
private IEnumerable<Neo4jConnectionString> _connectionStrings;
public event PropertyChangedEventHandler PropertyChanged;
public ConnectionsViewModel(INavigation navigation) : base(navigation)
{
Commands.Add("AddConnection", new Command(() =>
{
Navigation.PushAsync(new AddConnectionPage());
}));
Commands.Add("DeleteConnectionString", new Command(async (o) =>
{
if (!(o is Neo4jConnectionString neo4jConnectionString))
return;
await ConnectionStringManager.DeleteConnectionString(neo4jConnectionString);
LoadConnectionStrings();
}));
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public async void LoadConnectionStrings()
{
ConnectionStrings = await ConnectionStringManager.GetConnectionStrings();
}
#region Bindable Properties
public IEnumerable<Neo4jConnectionString> ConnectionStrings
{
get => _connectionStrings;
set
{
_connectionStrings = value;
OnPropertyChanged();
}
}
#endregion
}
}

View File

@@ -1,5 +1,5 @@
//
// RootViewModel.cs
// QueriesViewModel.cs
//
// Trevi Awater
// 13-11-2021
@@ -11,22 +11,18 @@ using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using Xamarin.Neo4j.Annotations;
using Xamarin.Neo4j.Pages;
namespace Xamarin.Neo4j.ViewModels
{
public class RootViewModel : ViewModelBase, INotifyPropertyChanged
public class QueriesViewModel : ViewModelBase, INotifyPropertyChanged
{
public RootViewModel(INavigation navigation) : base(navigation)
{
Commands.Add("AddConnection", new Command(() =>
{
Navigation.PushAsync(new AddConnectionPage());
}));
}
public event PropertyChangedEventHandler PropertyChanged;
public QueriesViewModel(INavigation navigation) : base(navigation)
{
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{

View File

@@ -0,0 +1,105 @@
//
// SessionViewModel.cs
//
// Trevi Awater
// 13-11-2021
//
// © Xamarin.Neo4j
//
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Acr.UserDialogs;
using Xamarin.Forms;
using Xamarin.Neo4j.Annotations;
using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.Services;
namespace Xamarin.Neo4j.ViewModels
{
public class SessionViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly Neo4jService _neo4jService;
private Database _currentDatabase;
private List<Database> _availableDatabases;
private string _query;
public SessionViewModel(INavigation navigation, Neo4jConnectionString connectionString) : base(navigation)
{
_neo4jService = DependencyService.Resolve<Neo4jService>();
Commands.Add("RunQuery", new Command(async () =>
{
_neo4jService.ExecuteQuery(Query, CurrentDatabase.Name);
}));
InitializeConnection(connectionString);
}
private async void InitializeConnection(Neo4jConnectionString connectionString)
{
await _neo4jService.EstablishConnection(connectionString);
AvailableDatabases = await _neo4jService.LoadDatabases();
if (!string.IsNullOrWhiteSpace(connectionString.Database))
CurrentDatabase = AvailableDatabases.SingleOrDefault(ad => ad.Name == connectionString.Database);
if (CurrentDatabase == null)
CurrentDatabase = AvailableDatabases.Single(ad => ad.Default);
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region Bindable Properties
public Database CurrentDatabase
{
get => _currentDatabase;
set
{
_currentDatabase = value;
OnPropertyChanged();
}
}
public List<Database> AvailableDatabases
{
get => _availableDatabases;
set
{
_availableDatabases = value;
OnPropertyChanged();
}
}
public string Query
{
get => _query;
set
{
_query = value;
OnPropertyChanged();
}
}
#endregion
}
}

View File

@@ -10,7 +10,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Acr.UserDialogs" Version="7.2.0.534" />
<PackageReference Include="Neo4jClient" Version="4.1.18" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2012" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
</ItemGroup>
<ItemGroup>
<Folder Include="Converters" />
</ItemGroup>
</Project>