Merge pull request ' Navigation entre les pages, interactions et persistance' (#100) from navigation into dev
continuous-integration/drone/push Build is passing Details

Reviewed-on: #100
Reviewed-by: Remi NEVEU <remi.neveu@etu.uca.fr>
pull/108/head
Rémi LAVERGNE 11 months ago
commit a34c5cc715

@ -1,14 +1,18 @@
namespace Models.Game
using System.Runtime.Serialization;
namespace Models.Game
{
/// <summary>
/// The Cell class represents a cell in the application.
/// </summary>
[DataContract]
public class Cell : Position, IEquatable<Cell>
{
/// <summary>
/// The value of the cell.
/// </summary>
private int? _value;
[DataMember]
public int? Value {
get => _value;
set
@ -24,14 +28,17 @@
/// <summary>
/// The fact that the cell is dangerous or not.
/// </summary>
[DataMember]
public bool IsDangerous { get; set; }
[DataMember]
public bool Valid { get; set; }
/// <summary>
/// Atribute to know if the cell is a penalty cell.
/// </summary>
[DataMember]
private bool Penalty { get; set; }
/// <summary>

@ -5,6 +5,7 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
@ -19,6 +20,7 @@ namespace Models.Game
/// The Game class represents a game session in the application.
/// It contains all the necessary properties and methods to manage a game, including the game loop, dice rolling, and use of the game rules.
/// </summary>
[DataContract]
public class Game : INotifyPropertyChanged
{
/* Persistence */
@ -74,19 +76,24 @@ namespace Models.Game
}
private bool _isRunning;
[DataMember]
public bool IsRunning
{
get => _isRunning;
private set => _isRunning = value;
}
public Player CurrentPlayer { get; private set; }
[DataMember]
public Player? CurrentPlayer { get; private set; }
public Map UsedMap { get; private set; }
[DataMember]
public Map? UsedMap { get; private set; }
public Dice Dice1 { get; private set;}
public Dice Dice2 { get; private set; }
[DataMember]
public int Turn { get; private set; }
public Rules.Rules GameRules { get; }

@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
namespace Models.Game
{
@ -6,33 +7,39 @@ namespace Models.Game
/// <summary>
/// The Map class is the representation of the game map with the board and the operations table.
/// </summary>
[DataContract]
public class Map
{
/// <summary>
/// It is the list of cells on the map.
/// </summary>
[DataMember]
public ReadOnlyObservableCollection<Cell> Boards { get; private set; }
ObservableCollection<Cell> board = new ObservableCollection<Cell>();
/// <summary>
/// It is the backgrond image of the map
/// </summary>
[DataMember]
public string Background { get; private set; }
/// <summary>
/// It is the grid of the possible operation in the game
/// </summary>
[DataMember]
public ReadOnlyObservableCollection<OperationCell> OperationGrid { get; private set; }
ObservableCollection<OperationCell> operationGrid = new ObservableCollection<OperationCell>();
/// <summary>
/// It is a list of a list containing user's rope paths in the current game
/// </summary>
[DataMember]
public List<List<Cell>> RopePaths { get; private set; }
/// <summary>
/// It is a list of a list containing user's zones in the current game
/// </summary>
[DataMember]
public List<List<Cell>> Zones { get; private set; }
/// <summary>

@ -1,15 +1,18 @@
using System.ComponentModel;
using System.Runtime.Serialization;
namespace Models.Game
{
/// <summary>
/// Represents a cell in the operation grid of the game.
/// </summary>
[DataContract]
public class OperationCell : Position
{
/// <summary>
/// It tells if the operation is checked or not in the operation grid of the game.
/// </summary>
[DataMember]
public bool IsChecked { get; private set; }
/// <summary>

@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
namespace Models.Game
{
@ -7,16 +8,19 @@ namespace Models.Game
/// <summary>
/// The Position (x,y) of a cell in the game.
/// </summary>
[DataContract]
public class Position
{
/// <summary>
/// The X coordinate.
/// </summary>
[DataMember]
public int X { get; set; }
/// <summary>
/// The Y coordinate.
/// </summary>
[DataMember]
public int Y { get; set; }
/// <summary>

@ -1,11 +1,12 @@
using System.Diagnostics;
using System.Runtime.Serialization.DataContracts;
using DataContractPersistence;
using Models.Game;
using Models.Interfaces;
namespace Trek_12
{
using Models.Game;
using Models.Interfaces;
using DataContractPersistence;
public partial class App : Application
{
public string FileName { get; set; } = "data.json";
@ -33,6 +34,13 @@ namespace Trek_12
Debug.WriteLine("Data loaded from " + fullPath);
Manager.LoadData();
}
/* Add the permanent maps if they are not already in the game */
if (Manager.Maps.Count == 0)
{
Manager.AddMap(new Map("profile.jpg"));
Manager.AddMap(new Map("montagne1.png"));
Manager.AddMap(new Map("tmp1.jpeg"));
}
MainPage = new AppShell();

@ -5,38 +5,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Trek_12"
xmlns:views="clr-namespace:Trek_12.Views"
Shell.FlyoutBehavior="Flyout"
Shell.FlyoutBehavior="Disabled"
Title="Trek_12"
Shell.NavBarIsVisible="False">
<ShellContent
Title="Home"
ContentTemplate="{DataTemplate views:PageMenuPrincipal}"
Route="MenuPrincipal" />
<ShellContent
Title="LeaderBoard"
ContentTemplate="{DataTemplate views:PageLeaderBoard}"
Route="LeaderBoard"/>
<ShellContent
Title="Profils"
ContentTemplate="{DataTemplate views:PageProfiles}"
Route="Profils"/>
<ShellContent
Title="Règles"
ContentTemplate="{DataTemplate views:PageRegles}"
Route="Regles"/>
<ShellContent
Title="Map Select"
ContentTemplate="{DataTemplate views:PageSelectMap}"
Route="PageSelectMap"/>
<ShellContent
Title="Board"
ContentTemplate="{DataTemplate views:PageBoard}"
Route="Board"/>
Shell.NavBarIsVisible="False"
Shell.FlyoutItemIsVisible="False"
Shell.TabBarIsVisible="False">
<ShellContent ContentTemplate="{DataTemplate views:PageMenuPrincipal}" />
</Shell>

@ -16,6 +16,7 @@ namespace Trek_12
Routing.RegisterRoute(nameof(PageSelectMap), typeof(PageSelectMap));
Routing.RegisterRoute(nameof(PageRegles), typeof(PageRegles));
Routing.RegisterRoute(nameof(PageLeaderBoard), typeof(PageLeaderBoard));
Routing.RegisterRoute(nameof(PageBoard), typeof(PageBoard));
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>

Before

Width:  |  Height:  |  Size: 228 B

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@ -40,7 +40,7 @@
<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
<MauiIcon Include="Resources\AppIcon\app_icon.png" />
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
@ -57,8 +57,11 @@
</ItemGroup>
<ItemGroup>
<None Remove="Resources\Images\app_icon.png" />
<None Remove="Resources\Images\back_arrow.png" />
<None Remove="Resources\Images\checked.png" />
<None Remove="Resources\Images\maptest.png" />
<None Remove="Resources\Images\user.png" />
</ItemGroup>
<ItemGroup>

@ -14,7 +14,7 @@
HorizontalOptions="Center"
VerticalOptions="Center"
Padding="0">
<Label Text="{Binding ThePartie.Dice1.Value}"
<Label Text="{Binding Dice1.Value}"
FontSize="16"
VerticalOptions="Center"
HorizontalOptions="Center"
@ -28,7 +28,7 @@
HorizontalOptions="Center"
VerticalOptions="Center"
Padding="0">
<Label Text="{Binding ThePartie.Dice1.Value}"
<Label Text="{Binding Dice2.Value}"
FontSize="16"
VerticalOptions="Center"
HorizontalOptions="Center"
@ -38,11 +38,11 @@
<Button Text="Roll"
HeightRequest="200"
WidthRequest="200"
Clicked="Button_Clicked"/>
Clicked="DiceButton_Clicked"/>
</HorizontalStackLayout>
<Image Source="maptest.png" Aspect="AspectFit" Grid.ColumnSpan="2" Grid.RowSpan="3"/>
<CollectionView ItemsSource="{Binding ListMap[0].Boards}"
<CollectionView ItemsSource="{Binding UsedMap.Boards}"
Grid.Row="1"
Grid.Column="1"
SelectionMode="Single"
@ -81,7 +81,7 @@
<!-- Operation Grid -->
<CollectionView Grid.Row="0" Grid.Column="2"
ItemsSource="{Binding ListMap[0].OperationGrid}"
ItemsSource="{Binding UsedMap.OperationGrid}"
ItemsLayout="VerticalGrid,4"
WidthRequest="200"
HeightRequest="250"

@ -1,24 +1,27 @@
using System.ComponentModel;
using System.Diagnostics;
namespace Trek_12.Views;
using Models.Game;
using Stub;
public partial class PageBoard : ContentPage
{
public Stub MyStub { get; set; } = new Stub();
public Game GameManager => (App.Current as App).Manager;
public PageBoard()
{
InitializeComponent();
MyStub.ThePartie.DiceRolled += ThePartie_DiceRolled;
BindingContext = MyStub;
MyStub.ThePartie.InitializeGame(MyStub.ThePartie.UsedMap, MyStub.ListPlayer[0]);
GameManager.DiceRolled += TheGame_DiceRolled;
BindingContext = GameManager;
// We add this game to the list of games
GameManager.AddGame(GameManager);
GameManager.OnPropertyChanged(nameof(GameManager.Games));
GameManager.SaveData();
}
private void ThePartie_DiceRolled(object? sender, Models.Events.DiceRolledEventArgs e)
private void TheGame_DiceRolled(object? sender, Models.Events.DiceRolledEventArgs e)
{
Dice1.Text = $"{e.Dice1Value}";
Dice2.Text = $"{e.Dice2Value}";
@ -39,8 +42,8 @@ public partial class PageBoard : ContentPage
}
}
private void Button_Clicked(object sender, EventArgs e)
private void DiceButton_Clicked(object sender, EventArgs e)
{
MyStub.ThePartie.RollAllDice();
GameManager.RollAllDice();
}
}

@ -6,9 +6,23 @@
Title="PageLeaderBoard">
<Grid BackgroundColor="BlanchedAlmond"
RowDefinitions="auto,6*,*">
<VerticalStackLayout>
<Frame Grid.Row="0" BackgroundColor="Transparent" BorderColor="Transparent" Padding="0" Margin="15">
<Image Source="back_arrow.png"
Margin="0"
HeightRequest="50"
WidthRequest="50"
VerticalOptions="Center"
HorizontalOptions="Start"/>
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="OnBackArrow_Tapped"/>
</Frame.GestureRecognizers>
</Frame>
<VerticalStackLayout VerticalOptions="Center" HorizontalOptions="Center">
<Label
Text="Leader board"
Text="Leaderboard"
VerticalOptions="Center"
HorizontalOptions="Center"
FontSize="Title"/>
@ -21,7 +35,7 @@
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
Margin="0,10">
<VerticalStackLayout>
<VerticalStackLayout HorizontalOptions="Center">
<views:ContentLeaderBoard/>
<views:ContentLeaderBoard/>
<views:ContentLeaderBoard/>
@ -30,14 +44,5 @@
<views:ContentLeaderBoard/>
</VerticalStackLayout>
</ScrollView>
<Button Text="Back"
BackgroundColor="OliveDrab"
FontSize="Title"
Grid.Row="2"
HorizontalOptions="Start"
CornerRadius="20"
WidthRequest="150"
HeightRequest="75"
Margin="10"/>
</Grid>
</ContentPage>

@ -6,4 +6,9 @@ public partial class PageLeaderBoard : ContentPage
{
InitializeComponent();
}
private async void OnBackArrow_Tapped(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
}

@ -17,11 +17,19 @@
HorizontalOptions="Center"
MinimumHeightRequest="50"
MaximumHeightRequest="150"/>
<Frame Grid.Row="0" BackgroundColor="Transparent" BorderColor="Transparent" Padding="0" Margin="20" HorizontalOptions="End" VerticalOptions="Start">
<Image Source="user.png" HeightRequest="50" WidthRequest="50" Aspect="AspectFit" VerticalOptions="Center" HorizontalOptions="Center" Margin="0" />
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="OnProfilesButton_Tapped"/>
</Frame.GestureRecognizers>
</Frame>
<Grid Grid.Row="1" ColumnDefinitions="*,2*,*" RowDefinitions="*,*" HorizontalOptions="Fill" VerticalOptions="Center" ColumnSpacing="50" RowSpacing="25" Margin="50,0">
<Button Grid.Column="0" Grid.Row="1" Text="Leaderboard" BackgroundColor="#936f49" Opacity="0.9" CornerRadius="6"/>
<Button Grid.Column="1" Grid.RowSpan="2" Text="JOUER" BackgroundColor="#936f49" Opacity="0.9" CornerRadius="6"/>
<Button Grid.Column="2" Grid.Row="1" Text="Règles" BackgroundColor="#936f49" Opacity="0.9" CornerRadius="6"/>
<Button Grid.Column="0" Grid.Row="1" Text="Leaderboard" BackgroundColor="#936f49" Opacity="0.9" CornerRadius="6" Clicked="OnLeaderBoardButton_Clicked"/>
<Button Grid.Column="1" Grid.RowSpan="2" Text="JOUER" BackgroundColor="#936f49" Opacity="0.9" CornerRadius="6" Clicked="OnPlayButton_Clicked"/>
<Button Grid.Column="2" Grid.Row="1" Text="Règles" BackgroundColor="#936f49" Opacity="0.9" CornerRadius="6" Clicked="OnRulesButton_Clicked"/>
</Grid>
</Grid>
</ContentPage.Content>

@ -1,8 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Maui.Core;
using Font = Microsoft.Maui.Font;
namespace Trek_12.Views;
@ -12,4 +17,24 @@ public partial class PageMenuPrincipal : ContentPage
{
InitializeComponent();
}
private async void OnRulesButton_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(PageRegles));
}
private async void OnLeaderBoardButton_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(PageLeaderBoard));
}
private async void OnPlayButton_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(PageSelectMap));
}
private async void OnProfilesButton_Tapped(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(PageProfiles));
}
}

@ -7,12 +7,13 @@
<Grid RowDefinitions="auto,auto,auto">
<Label Grid.Row="0" Text="Sélection de la Carte" HorizontalOptions="Center" FontSize="Header"/>
<CollectionView Grid.Row="1"
ItemsSource="{Binding ListMap}"
ItemsSource="{Binding Maps}"
ItemsLayout="HorizontalList"
HorizontalOptions="Center"
SelectionMode="Single">
SelectionMode="Single"
SelectionChanged="OnSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="True"
@ -46,12 +47,14 @@
FontSize="Large"
Grid.Row="2"
HorizontalOptions="Start"
Margin="100"/>
Margin="100"
Clicked="BackButton_Clicked"/>
<Button Text="Jouer"
FontAttributes="Bold"
FontSize="Large"
Grid.Row="2"
HorizontalOptions="End"
Margin="100"/>
Margin="100"
Clicked="PlayButton_Clicked"/>
</Grid>
</ContentPage>

@ -1,16 +1,82 @@
using System.Diagnostics;
namespace Trek_12.Views;
using Stub;
using Models.Game;
public partial class PageSelectMap : ContentPage
{
public Game SelectMapManager => (App.Current as App).Manager;
private Map? _selectedMap;
protected override async void OnAppearing()
{
base.OnAppearing();
if (SelectMapManager.Games.Any(g => g.IsRunning))
{
await DisplayAlert("Warning", "You've previously quit in the middle of a game.\nIf you start a new game, this one will be permanently lost.", "I understand");
public Stub MyStub { get; set; } = new Stub();
}
}
public PageSelectMap()
public PageSelectMap()
{
InitializeComponent();
BindingContext = MyStub;
BindingContext = SelectMapManager;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
_selectedMap = e.CurrentSelection.FirstOrDefault() as Map;
}
private async void BackButton_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
private async void PlayButton_Clicked(object sender, EventArgs e)
{
if (_selectedMap == null)
{
await DisplayAlert("Selection Required", "Please select a map you want to play to continue.", "OK");
return;
}
if (SelectMapManager.Players.Count == 0)
{
await DisplayAlert("No player found", "Please add a player in the profile page.", "OK");
return;
}
string[] profiles = GetProfiles().ToArray();
string choosenPlayerName = await DisplayActionSheet("Choose a player", "Cancel", null, profiles);
if (choosenPlayerName == null || choosenPlayerName == "Cancel") return;
Player chosenPlayer = GetProfileByName(choosenPlayerName);
SelectMapManager.InitializeGame(_selectedMap, chosenPlayer);
if (SelectMapManager.UsedMap == _selectedMap && Equals(SelectMapManager.CurrentPlayer, chosenPlayer))
{
await Shell.Current.GoToAsync(nameof(PageBoard));
}
else
{
await DisplayAlert("Error", "An error occured while initializing the game. Please try again.", "OK");
}
}
private List<string> GetProfiles()
{
return SelectMapManager.Players.Select(p => p.Pseudo).ToList();
}
private Player GetProfileByName(string pseudo)
{
return SelectMapManager.Players.FirstOrDefault(p => p.Pseudo == pseudo);
}
}

@ -10,6 +10,18 @@
<Grid RowDefinitions="auto,3*,*">
<Image Source="bg_profils.jpg" Grid.RowSpan="3" Aspect="AspectFill"/>
<Frame Grid.Row="0" BackgroundColor="Transparent" BorderColor="Transparent" Padding="0" Margin="15">
<Image Source="back_arrow.png"
Margin="0"
HeightRequest="50"
WidthRequest="50"
VerticalOptions="Center"
HorizontalOptions="Start"/>
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="OnBackArrow_Tapped"/>
</Frame.GestureRecognizers>
</Frame>
<Frame BackgroundColor="WhiteSmoke" Opacity="0.5" Grid.Row="1" />
<Label Grid.Row="0" Text="Profils" TextColor="black" HorizontalTextAlignment="Center" FontSize="Header" Margin="30"/>

@ -17,11 +17,20 @@ public partial class PageProfiles : ContentPage
BindingContext = ProfileManager;
}
private async void OnBackArrow_Tapped(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
async void Button_ClickedAdd(System.Object sender, System.EventArgs e)
{
string pseudo = await DisplayPromptAsync("Info", $"Choose a name : ", "Ok");
if (pseudo == null) return;
if (ProfileManager.Players.Any(p => p.Pseudo == pseudo))
{
await DisplayAlert("Info", "This name is already taken", "Ok");
return;
}
var profilePicture = await MediaPicker.PickPhotoAsync();
if (profilePicture == null) return;

@ -74,7 +74,7 @@
</ScrollView>
<HorizontalStackLayout Grid.Row="2" HorizontalOptions="Center" Spacing="50">
<Button Text="Retour" WidthRequest="300" HeightRequest="60" CornerRadius="4"/>
<Button Text="Retour" WidthRequest="300" HeightRequest="60" CornerRadius="4" Clicked="BackButton_Clicked"/>
<Button Text="Acheter" WidthRequest="300" HeightRequest="60" CornerRadius="4" Clicked="BrowserOpen_Clicked"/>
</HorizontalStackLayout>
</Grid>

@ -9,6 +9,10 @@ public partial class PageRegles : ContentPage
InitializeComponent();
}
private async void BackButton_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("..");
}
private async void BrowserOpen_Clicked(System.Object sender, System.EventArgs e)
{

Loading…
Cancel
Save