diff --git a/.gitignore b/.gitignore index fbcede46c56779a004f1aa56b151811d28bd0017..f0d5dd17a20234590c84414b04b0ed17b62ad80b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /App/RobobinApp/bin/ /App/RobobinApp/obj/ .DS_Store +/App/RobobinApp/<AndroidSdkPath>/ +/App/RobobinApp/<JavaSdkPath>/ \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000000000000000000000000000000000000..70fefc060eed4610e13a5433a6fa7369c63556df --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,7 @@ +{ + "ExpandedNodes": [ + "" + ], + "SelectedNode": "\\App.sln", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..7768cbea50d0c8b6d0079797bfd1b57a875121e4 Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/App/App.sln b/App/App.sln index c07d0708f6a0f93246a9ef784e9fd7008f6a87df..238f2bff60b9adc23ea74db2d099db02717ac417 100644 --- a/App/App.sln +++ b/App/App.sln @@ -16,6 +16,7 @@ Global {6D394E71-6C39-4FAC-A1D4-054609783EFF}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {6D394E71-6C39-4FAC-A1D4-054609783EFF}.Release|Any CPU.ActiveCfg = Release|Any CPU {6D394E71-6C39-4FAC-A1D4-054609783EFF}.Release|Any CPU.Build.0 = Release|Any CPU + {6D394E71-6C39-4FAC-A1D4-054609783EFF}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/App/RobobinApp/App.xaml b/App/RobobinApp/App.xaml index 56a59004184efd4f771bc4d1b774d26aa4a5f42c..d16152ef3d93c455be9278fb402f10f99b4d270a 100644 --- a/App/RobobinApp/App.xaml +++ b/App/RobobinApp/App.xaml @@ -1,4 +1,4 @@ -<?xml version = "1.0" encoding = "UTF-8" ?> +<?xml version="1.0" encoding="UTF-8" ?> <Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:RobobinApp" @@ -9,6 +9,8 @@ <ResourceDictionary Source="Resources/Styles/Colors.xaml" /> <ResourceDictionary Source="Resources/Styles/Styles.xaml" /> </ResourceDictionary.MergedDictionaries> + + <StyleSheet Source="/Resources/Styles/appstyle.css" /> </ResourceDictionary> </Application.Resources> </Application> diff --git a/App/RobobinApp/App.xaml.cs b/App/RobobinApp/App.xaml.cs index 54855d76c62e51159d415adbd5fe94943c7a9a54..0feb8e721587139b919707a528d8a3459cdc0913 100644 --- a/App/RobobinApp/App.xaml.cs +++ b/App/RobobinApp/App.xaml.cs @@ -1,11 +1,29 @@ -namespace RobobinApp; +using Microsoft.Extensions.Logging; +namespace RobobinApp; public partial class App : Application { - public App() - { - InitializeComponent(); + public App() + { + InitializeComponent(); + ConfigureLogging(); + MainPage = new AppShell(); + } + private void ConfigureLogging() + { + // Create a LoggerFactory + var loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddConsole() + .AddDebug(); + }); - MainPage = new AppShell(); - } + // Store the logger factory for later use + Logger = loggerFactory.CreateLogger<App>(); + } + + public ILogger<App> Logger { get; private set; } } + + diff --git a/App/RobobinApp/AppShell.xaml b/App/RobobinApp/AppShell.xaml index d2f45e8f3e84e172bb17dd8dc5e59d0f6c88770a..270d8c2c43d06395bee14da8e8b033b79bb315ac 100644 --- a/App/RobobinApp/AppShell.xaml +++ b/App/RobobinApp/AppShell.xaml @@ -1,15 +1,8 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<Shell - x:Class="RobobinApp.AppShell" - xmlns="http://schemas.microsoft.com/dotnet/2021/maui" - xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" - xmlns:local="clr-namespace:RobobinApp" - Shell.FlyoutBehavior="Disabled" - Title="RobobinApp"> +<?xml version="1.0" encoding="utf-8" ?> +<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + xmlns:views="clr-namespace:RobobinApp.Views" + x:Class="RobobinApp.AppShell"> - <ShellContent - Title="Home" - ContentTemplate="{DataTemplate local:MainPage}" - Route="MainPage" /> - -</Shell> + <ShellContent ContentTemplate="{DataTemplate views:MainPage}" /> +</Shell> \ No newline at end of file diff --git a/App/RobobinApp/Interfaces/BLEClasses.cs b/App/RobobinApp/Interfaces/BLEClasses.cs new file mode 100644 index 0000000000000000000000000000000000000000..d0823784717806cbec943d833db30c863536806b --- /dev/null +++ b/App/RobobinApp/Interfaces/BLEClasses.cs @@ -0,0 +1,21 @@ +using Plugin.BLE.Abstractions.Contracts; +using Plugin.BLE; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Robobin.Interfaces +{ + public class BluetoothLEService : IBluetoothLEService + { + public IBluetoothLE Current => CrossBluetoothLE.Current; + } + + public class AdapterService : IAdapterService + { + public IAdapter Adapter => CrossBluetoothLE.Current.Adapter; + } + +} diff --git a/App/RobobinApp/Interfaces/BLEInterfaces.cs b/App/RobobinApp/Interfaces/BLEInterfaces.cs new file mode 100644 index 0000000000000000000000000000000000000000..181124d93a427c740e25b5838a6df1cbfe326c91 --- /dev/null +++ b/App/RobobinApp/Interfaces/BLEInterfaces.cs @@ -0,0 +1,20 @@ +using Plugin.BLE.Abstractions.Contracts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Robobin.Interfaces +{ + public interface IBluetoothLEService + { + IBluetoothLE Current { get; } + } + + public interface IAdapterService + { + IAdapter Adapter { get; } + } + +} diff --git a/App/RobobinApp/MainPage.xaml b/App/RobobinApp/MainPage.xaml deleted file mode 100644 index 551d650342b6ecad878a4a3dc3116e136200777b..0000000000000000000000000000000000000000 --- a/App/RobobinApp/MainPage.xaml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" - xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" - x:Class="RobobinApp.MainPage"> - - <ScrollView> - <VerticalStackLayout - Padding="30,0" - Spacing="25"> - <Image - Source="dotnet_bot.png" - HeightRequest="185" - Aspect="AspectFit" - SemanticProperties.Description="dot net bot in a race car number eight" /> - - <Label - Text="Hello, World!" - Style="{StaticResource Headline}" - SemanticProperties.HeadingLevel="Level1" /> - - <Label - Text="Welcome to .NET Multi-platform App UI" - Style="{StaticResource SubHeadline}" - SemanticProperties.HeadingLevel="Level2" - SemanticProperties.Description="Welcome to dot net Multi platform App U I" /> - - <Button - x:Name="CounterBtn" - Text="Click me" - SemanticProperties.Hint="Counts the number of times you click" - Clicked="OnCounterClicked" - HorizontalOptions="Fill" /> - </VerticalStackLayout> - </ScrollView> - -</ContentPage> diff --git a/App/RobobinApp/MainPage.xaml.cs b/App/RobobinApp/MainPage.xaml.cs deleted file mode 100644 index a2af1fc4ac20e701e03b86ebef01cc946b178268..0000000000000000000000000000000000000000 --- a/App/RobobinApp/MainPage.xaml.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace RobobinApp; - -public partial class MainPage : ContentPage -{ - int count = 0; - - public MainPage() - { - InitializeComponent(); - } - - private void OnCounterClicked(object sender, EventArgs e) - { - count++; - - if (count == 1) - CounterBtn.Text = $"Clicked {count} time"; - else - CounterBtn.Text = $"Clicked {count} times"; - - SemanticScreenReader.Announce(CounterBtn.Text); - } -} - diff --git a/App/RobobinApp/MauiProgram.cs b/App/RobobinApp/MauiProgram.cs index 90697e0e34c6feb93ca99d19460e62a7fd1d623f..b66f82981c8bdc5d55ccb433a06170cb82b54883 100644 --- a/App/RobobinApp/MauiProgram.cs +++ b/App/RobobinApp/MauiProgram.cs @@ -1,24 +1,26 @@ -using Microsoft.Extensions.Logging; + namespace RobobinApp; + public static class MauiProgram { - public static MauiApp CreateMauiApp() - { - var builder = MauiApp.CreateBuilder(); - builder - .UseMauiApp<App>() - .ConfigureFonts(fonts => - { - fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); - fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); - }); - -#if DEBUG - builder.Logging.AddDebug(); -#endif - - return builder.Build(); - } -} + public static MauiApp CreateMauiApp() + { + var builder = MauiApp + .CreateBuilder() + .UseMauiApp<App>() + + + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }); + + + + + return builder.Build(); + } +} \ No newline at end of file diff --git a/App/RobobinApp/Models/BluetoothDevice.cs b/App/RobobinApp/Models/BluetoothDevice.cs new file mode 100644 index 0000000000000000000000000000000000000000..c4a552e5f1170e31916f6e9cc25f41a1eab01f80 --- /dev/null +++ b/App/RobobinApp/Models/BluetoothDevice.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RobobinApp.Models +{ + public class BluetoothDevice + { + public string Name { get; set; } + public string MacAddress { get; set; } + + public BluetoothDevice(string name, string macAddress) + { + Name = name; + MacAddress = macAddress; + } + } +} + diff --git a/App/RobobinApp/Models/WifiNetwork.cs b/App/RobobinApp/Models/WifiNetwork.cs new file mode 100644 index 0000000000000000000000000000000000000000..0a688fbe80fe6270a2343a4b10f655a83918f841 --- /dev/null +++ b/App/RobobinApp/Models/WifiNetwork.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RobobinApp.Models +{ + public class WifiNetwork + { + public string SSID { get; set; } + public double SignalStrength { get; set; } + } +} diff --git a/App/RobobinApp/Platforms/Android/AndroidManifest.xml b/App/RobobinApp/Platforms/Android/AndroidManifest.xml index bdec9b5900dda15071a0a17a6027978e22c74f0b..4dcde8feb0b07e8059b2135c73d79e32de1133fa 100644 --- a/App/RobobinApp/Platforms/Android/AndroidManifest.xml +++ b/App/RobobinApp/Platforms/Android/AndroidManifest.xml @@ -1,6 +1,15 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.INTERNET" /> +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1"> + <application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true" android:label="RoboBin"></application> + <!-- Required permissions --> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> + + </manifest> \ No newline at end of file diff --git a/App/RobobinApp/Platforms/Windows/app.manifest b/App/RobobinApp/Platforms/Windows/app.manifest index 250c7ebd3b04cef6c28b11c01a1eb26cf672ab0f..d682a4c1f1ec6c1a80a2e4760d262c6d50d22b6d 100644 --- a/App/RobobinApp/Platforms/Windows/app.manifest +++ b/App/RobobinApp/Platforms/Windows/app.manifest @@ -12,4 +12,5 @@ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness> </windowsSettings> </application> + </assembly> diff --git a/App/RobobinApp/Resources/Styles/DarkTheme.xaml b/App/RobobinApp/Resources/Styles/DarkTheme.xaml deleted file mode 100644 index 90cbaea2c22e5bc34a60e299c8de92f5223c2e68..0000000000000000000000000000000000000000 --- a/App/RobobinApp/Resources/Styles/DarkTheme.xaml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui" - xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> - <Color x:Key="PrimaryColor">#4CAF50</Color> - <Color x:Key="SecondaryColor">#2196F3</Color> - <Color x:Key="BackgroundColor">#121212</Color> - <Color x:Key="TextColor">#FFFFFF</Color> - - <Style TargetType="Label"> - <Setter Property="TextColor" Value="{DynamicResource TextColor}"/> - </Style> - <Style TargetType="Button"> - <Setter Property="BackgroundColor" Value="{DynamicResource PrimaryColor}"/> - <Setter Property="TextColor" Value="#FFFFFF"/> - </Style> -</ResourceDictionary> diff --git a/App/RobobinApp/Resources/Styles/LightTheme.xaml b/App/RobobinApp/Resources/Styles/LightTheme.xaml deleted file mode 100644 index 5460de6ceb20fe04da1b6540258304f3f913fad6..0000000000000000000000000000000000000000 --- a/App/RobobinApp/Resources/Styles/LightTheme.xaml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui" - xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> - <!-- Light Theme Colors --> - <Color x:Key="PrimaryColor">#4CAF50</Color> - <!-- Green --> - <Color x:Key="SecondaryColor">#2196F3</Color> - <!-- Blue --> - <Color x:Key="BackgroundColor">#FFFFFF</Color> - <!-- White --> - <Color x:Key="TextColor">#000000</Color> - <!-- Black --> - - <!-- Default Styles --> - <Style TargetType="Label"> - <Setter Property="TextColor" Value="{DynamicResource TextColor}"/> - </Style> - <Style TargetType="Button"> - <Setter Property="BackgroundColor" Value="{DynamicResource PrimaryColor}"/> - <Setter Property="TextColor" Value="#FFFFFF"/> - </Style> -</ResourceDictionary> diff --git a/App/RobobinApp/Resources/Styles/Styles.xaml b/App/RobobinApp/Resources/Styles/Styles.xaml index d600a7f7167a93452322d196e93e2b63a3a6d0b1..e36989d610ef5890fb013dbb59847436ea28ac59 100644 --- a/App/RobobinApp/Resources/Styles/Styles.xaml +++ b/App/RobobinApp/Resources/Styles/Styles.xaml @@ -1,427 +1,21 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<?xaml-comp compile="true" ?> -<ResourceDictionary - xmlns="http://schemas.microsoft.com/dotnet/2021/maui" - xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> +<?xml version="1.0" encoding="utf-8" ?> +<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> - <Style TargetType="ActivityIndicator"> - <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" /> - </Style> - - <Style TargetType="IndicatorView"> - <Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/> - <Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/> - </Style> - - <Style TargetType="Border"> - <Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" /> - <Setter Property="StrokeShape" Value="Rectangle"/> - <Setter Property="StrokeThickness" Value="1"/> - </Style> - - <Style TargetType="BoxView"> - <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" /> - </Style> - - <Style TargetType="Button"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" /> - <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" /> - <Setter Property="FontFamily" Value="OpenSansRegular"/> - <Setter Property="FontSize" Value="14"/> - <Setter Property="BorderWidth" Value="0"/> - <Setter Property="CornerRadius" Value="8"/> - <Setter Property="Padding" Value="14,10"/> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" /> - <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - <VisualState x:Name="PointerOver" /> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="CheckBox"> - <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" /> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="DatePicker"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" /> - <Setter Property="BackgroundColor" Value="Transparent" /> - <Setter Property="FontFamily" Value="OpenSansRegular"/> - <Setter Property="FontSize" Value="14"/> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="Editor"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" /> - <Setter Property="BackgroundColor" Value="Transparent" /> - <Setter Property="FontFamily" Value="OpenSansRegular"/> - <Setter Property="FontSize" Value="14" /> - <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" /> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="Entry"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" /> - <Setter Property="BackgroundColor" Value="Transparent" /> - <Setter Property="FontFamily" Value="OpenSansRegular"/> - <Setter Property="FontSize" Value="14" /> - <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" /> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="Frame"> - <Setter Property="HasShadow" Value="False" /> - <Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" /> - <Setter Property="CornerRadius" Value="8" /> - <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" /> - </Style> - - <Style TargetType="ImageButton"> - <Setter Property="Opacity" Value="1" /> - <Setter Property="BorderColor" Value="Transparent"/> - <Setter Property="BorderWidth" Value="0"/> - <Setter Property="CornerRadius" Value="0"/> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="Opacity" Value="0.5" /> - </VisualState.Setters> - </VisualState> - <VisualState x:Name="PointerOver" /> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="Label"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" /> - <Setter Property="BackgroundColor" Value="Transparent" /> - <Setter Property="FontFamily" Value="OpenSansRegular" /> - <Setter Property="FontSize" Value="14" /> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="Span"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" /> - </Style> - - <Style TargetType="Label" x:Key="Headline"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" /> - <Setter Property="FontSize" Value="32" /> - <Setter Property="HorizontalOptions" Value="Center" /> - <Setter Property="HorizontalTextAlignment" Value="Center" /> - </Style> - - <Style TargetType="Label" x:Key="SubHeadline"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" /> - <Setter Property="FontSize" Value="24" /> - <Setter Property="HorizontalOptions" Value="Center" /> - <Setter Property="HorizontalTextAlignment" Value="Center" /> - </Style> - - <Style TargetType="ListView"> - <Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" /> - <Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" /> - </Style> - - <Style TargetType="Picker"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" /> - <Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" /> - <Setter Property="BackgroundColor" Value="Transparent" /> - <Setter Property="FontFamily" Value="OpenSansRegular"/> - <Setter Property="FontSize" Value="14"/> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - <Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="ProgressBar"> - <Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" /> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="RadioButton"> - <Setter Property="BackgroundColor" Value="Transparent"/> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" /> - <Setter Property="FontFamily" Value="OpenSansRegular"/> + <Style x:Key="TopBarButtonStyle" TargetType="Button"> + <Setter Property="BackgroundColor" Value="#E4E4E4"/> + <Setter Property="TextColor" Value="#333333"/> <Setter Property="FontSize" Value="14"/> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="RefreshView"> - <Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" /> - </Style> - - <Style TargetType="SearchBar"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" /> - <Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" /> - <Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" /> - <Setter Property="BackgroundColor" Value="Transparent" /> - <Setter Property="FontFamily" Value="OpenSansRegular" /> - <Setter Property="FontSize" Value="14" /> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="SearchHandler"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" /> - <Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" /> - <Setter Property="BackgroundColor" Value="Transparent" /> - <Setter Property="FontFamily" Value="OpenSansRegular" /> - <Setter Property="FontSize" Value="14" /> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="Shadow"> - <Setter Property="Radius" Value="15" /> - <Setter Property="Opacity" Value="0.5" /> - <Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" /> - <Setter Property="Offset" Value="10,10" /> - </Style> - - <Style TargetType="Slider"> - <Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" /> - <Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" /> - <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" /> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/> - <Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/> - <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="SwipeItem"> - <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" /> - </Style> - - <Style TargetType="Switch"> - <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" /> - <Setter Property="ThumbColor" Value="{StaticResource White}" /> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - <VisualState x:Name="On"> - <VisualState.Setters> - <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" /> - <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" /> - </VisualState.Setters> - </VisualState> - <VisualState x:Name="Off"> - <VisualState.Setters> - <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="TimePicker"> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" /> - <Setter Property="BackgroundColor" Value="Transparent"/> - <Setter Property="FontFamily" Value="OpenSansRegular"/> - <Setter Property="FontSize" Value="14"/> - <Setter Property="MinimumHeightRequest" Value="44"/> - <Setter Property="MinimumWidthRequest" Value="44"/> - <Setter Property="VisualStateManager.VisualStateGroups"> - <VisualStateGroupList> - <VisualStateGroup x:Name="CommonStates"> - <VisualState x:Name="Normal" /> - <VisualState x:Name="Disabled"> - <VisualState.Setters> - <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" /> - </VisualState.Setters> - </VisualState> - </VisualStateGroup> - </VisualStateGroupList> - </Setter> - </Style> - - <Style TargetType="Page" ApplyToDerivedTypes="True"> - <Setter Property="Padding" Value="0"/> - <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" /> - </Style> - - <Style TargetType="Shell" ApplyToDerivedTypes="True"> - <Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" /> - <Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" /> - <Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" /> - <Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" /> - <Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" /> - <Setter Property="Shell.NavBarHasShadow" Value="False" /> - <Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" /> - <Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" /> - <Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" /> - <Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" /> - </Style> - - <Style TargetType="NavigationPage"> - <Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" /> - <Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" /> - <Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" /> + <Setter Property="Padding" Value="10,5"/> + <Setter Property="CornerRadius" Value="5"/> + <Setter Property="BorderWidth" Value="0.5"/> + <Setter Property="BorderColor" Value="#CEC0C0"/> + <Setter Property="Margin" Value="5"/> </Style> - <Style TargetType="TabbedPage"> - <Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" /> - <Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" /> - <Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" /> - <Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" /> + <Style x:Key="TopBarStyle" TargetType="StackLayout"> + <Setter Property="BackgroundColor" Value="#F0F0F0"/> + <Setter Property="Padding" Value="10"/> </Style> </ResourceDictionary> diff --git a/App/RobobinApp/Resources/Styles/appstyle.css b/App/RobobinApp/Resources/Styles/appstyle.css new file mode 100644 index 0000000000000000000000000000000000000000..a9bc772656825194cb59212fa56c349c693d78b6 --- /dev/null +++ b/App/RobobinApp/Resources/Styles/appstyle.css @@ -0,0 +1,100 @@ +navigationpage { + -maui-bar-background-color: lightgray; +} + +^contentpage { + background-color: lightgray; +} + +#listView { + background-color: lightgray; +} + +stacklayout { + margin: 20; + -maui-spacing: 6; +} + +grid { + row-gap: 6; + column-gap: 6; +} + +.mainPageTitle { + font-style: bold; + font-size: 14; +} + +.mainPageSubtitle { + margin-top: 15; +} + +.detailPageTitle { + font-style: bold; + font-size: 14; + text-align: center; +} + +.detailPageSubtitle { + text-align: center; + font-style: italic; +} + +listview image { + height: 60; + width: 60; +} + +stacklayout > image { + height: 200; + width: 200; +} +.mainFrame { + background-color: White; + border-color: Black; + corner-radius: 5; +} + +.sideFrame { + background-color: #2B333E; + border-color: Black; + corner-radius: 5; +} + +.sideBox { + margin: 0; + padding: 0; +} + +.side-box-frame { + margin: 10; + padding: 0; + background-color: transparent; + border-color: transparent; + horizontal-options: fill-and-expand; +} + +.side-box-header { + background-color: #647687; + padding: 10 5; +} + +.side-box-header-text { + color: #FFFFFF; + font-size: 16; +} + +.side-box-content { + background-color: #E8EDF1; + padding: 10; + height: 200; +} + + .side-box-content Label { + margin: 0; + padding: 0; + } + +.side-box-vertical-stack { + spacing: 0; +} \ No newline at end of file diff --git a/App/RobobinApp/RobobinApp.csproj b/App/RobobinApp/RobobinApp.csproj index 248127a4d9f9356a835534ec2e4c8d02b22a5393..68992df409300d334a0a8a7f12bf5d38ec02d003 100644 --- a/App/RobobinApp/RobobinApp.csproj +++ b/App/RobobinApp/RobobinApp.csproj @@ -1,74 +1,64 @@ <Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <!-->TargetFrameworks>net8.0-ios;net8.0-maccatalyst</TargetFrameworks--> + <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks> + <OutputType>Exe</OutputType> + <RootNamespace>Robobin</RootNamespace> + <UseMaui>true</UseMaui> + <SingleProject>true</SingleProject> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <ApplicationTitle>Robobin</ApplicationTitle> + <ApplicationId>com.companyname.robobin</ApplicationId> + <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> + <ApplicationVersion>1</ApplicationVersion> + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion> + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion> + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion> + <TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion> + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion> + </PropertyGroup> - <PropertyGroup> - <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> - <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks> - <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> - <!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> --> + <ItemGroup> + <MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" /> + <MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" /> + <MauiImage Include="Resources\Images\*" /> + <MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" /> + <MauiFont Include="Resources\Fonts\*" /> + <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" /> + </ItemGroup> - <!-- Note for MacCatalyst: - The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64. - When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>. - The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated; - either BOTH runtimes must be indicated or ONLY macatalyst-x64. --> - <!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> --> + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" /> + <PackageReference Include="Microsoft.Maui.Controls" Version="8.0.91" /> + <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.91" /> + <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" /> + <PackageReference Include="Microsoft.Maui.Essentials" Version="8.0.91" /> + <PackageReference Include="Microsoft.Maui.Resizetizer" Version="8.0.91" /> + <PackageReference Include="Plugin.BLE" Version="3.1.0" /> + <PackageReference Include="Shiny" Version="2.7.3" /> + <PackageReference Include="Shiny.BluetoothLE" Version="3.3.3" /> + <PackageReference Include="Shiny.Hosting.Maui" Version="3.3.3" /> + </ItemGroup> - <OutputType>Exe</OutputType> - <RootNamespace>RobobinApp</RootNamespace> - <UseMaui>true</UseMaui> - <SingleProject>true</SingleProject> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>enable</Nullable> - - <!-- Display name --> - <ApplicationTitle>RobobinApp</ApplicationTitle> - - <!-- App Identifier --> - <ApplicationId>com.companyname.robobinapp</ApplicationId> - - <!-- Versions --> - <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> - <ApplicationVersion>1</ApplicationVersion> - - <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion> - <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion> - <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion> - <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion> - <TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion> - <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion> - </PropertyGroup> - - <ItemGroup> - <!-- App Icon --> - <MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" /> - - <!-- Splash Screen --> - <MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" /> - - <!-- Images --> - <MauiImage Include="Resources\Images\*" /> - <MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" /> - - <!-- Custom Fonts --> - <MauiFont Include="Resources\Fonts\*" /> - - <!-- Raw Assets (also remove the "Resources\Raw" prefix) --> - <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" /> - </ItemGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" /> - <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" /> - <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" /> - </ItemGroup> - - <ItemGroup> - <MauiXaml Update="Resources\Styles\DarkTheme.xaml"> - <Generator>MSBuild:Compile</Generator> - </MauiXaml> - <MauiXaml Update="Resources\Styles\LightTheme.xaml"> - <Generator>MSBuild:Compile</Generator> - </MauiXaml> - </ItemGroup> + <ItemGroup> + <Compile Update="LeftBox.xaml.cs"> + <DependentUpon>LeftBox.xaml</DependentUpon> + </Compile> + <Compile Update="Views\ConnectionPage.xaml.cs"> + <DependentUpon>ConnectionPage.xaml</DependentUpon> + </Compile> + </ItemGroup> + <ItemGroup> + <MauiXaml Update="LeftBox.xaml"> + <Generator>MSBuild:Compile</Generator> + </MauiXaml> + <MauiXaml Update="Views\ConnectionPage.xaml"> + <Generator>MSBuild:Compile</Generator> + </MauiXaml> + <MauiXaml Update="Views\SideBox.xaml"> + <Generator>MSBuild:Compile</Generator> + </MauiXaml> + </ItemGroup> </Project> diff --git a/App/RobobinApp/RobobinApp.csproj.user b/App/RobobinApp/RobobinApp.csproj.user index 7deb51d577e04b6949d0db77fd5901228e078c56..ed22012985172189459fe0632fc78a258b70b2b4 100644 --- a/App/RobobinApp/RobobinApp.csproj.user +++ b/App/RobobinApp/RobobinApp.csproj.user @@ -4,5 +4,27 @@ <IsFirstTimeProjectOpen>False</IsFirstTimeProjectOpen> <ActiveDebugFramework>net8.0-windows10.0.19041.0</ActiveDebugFramework> <ActiveDebugProfile>Windows Machine</ActiveDebugProfile> + <SelectedPlatformGroup>Emulator</SelectedPlatformGroup> + <DefaultDevice>pixel_5_-_api_34</DefaultDevice> </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android|AnyCPU'"> + <DebuggerFlavor>ProjectDebugger</DebuggerFlavor> + </PropertyGroup> + <PropertyGroup Condition="'$(TargetPlatformIdentifier)'=='iOS'"> + <RuntimeIdentifier>ios-arm64</RuntimeIdentifier> + <PlatformTarget>arm64</PlatformTarget> + </PropertyGroup> + <ItemGroup> + <MauiXaml Update="Views\ConnectionPage.xaml"> + <SubType>Designer</SubType> + </MauiXaml> + <MauiXaml Update="Views\SideBox.xaml"> + <SubType>Designer</SubType> + </MauiXaml> + </ItemGroup> + <ItemGroup> + <None Update="Platforms\Windows\Package.appxmanifest"> + <SubType>Designer</SubType> + </None> + </ItemGroup> </Project> \ No newline at end of file diff --git a/App/RobobinApp/ViewModels/BaseViewModel.cs b/App/RobobinApp/ViewModels/BaseViewModel.cs new file mode 100644 index 0000000000000000000000000000000000000000..7d99c718b35afb474a4089bdf94f72b66f2974c0 --- /dev/null +++ b/App/RobobinApp/ViewModels/BaseViewModel.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace RobobinApp.ViewModels +{ + public class BaseViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs b/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs new file mode 100644 index 0000000000000000000000000000000000000000..265a9f81b44ed05e04e114f63fd2b9b16dc129d4 --- /dev/null +++ b/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs @@ -0,0 +1,546 @@ +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Windows.Input; +using Microsoft.Maui.Controls; +using RobobinApp.Views; +using Plugin.BLE; +using Plugin.BLE.Abstractions.Contracts; +using Plugin.BLE.Abstractions.Exceptions; +using Plugin.BLE.Abstractions.EventArgs; +using System.Threading.Tasks; +using Plugin.BLE.Abstractions; +using RobobinApp.Models; +using System.Net.NetworkInformation; +using System.Collections.Generic; + +using Windows.Networking.Connectivity; +using Windows.Devices.WiFi; +using System.Reflection.PortableExecutable; +using Plugin.BLE.Windows; +using System.Text; +using System.Windows.Documents; +using Robobin.Interfaces; + +namespace RobobinApp.ViewModels +{ + public class ConnectionPageViewModel : BaseViewModel + { + private BluetoothDevice _selectedDevice; + private IDevice _connectedDevice; + public ICommand GoHomeCommand { get; } + public ObservableCollection<BluetoothDevice> BluetoothDevices { get; } + public ObservableCollection<WifiNetwork> WifiNetworks { get; } + + public ICommand ConnectCommand { get; } + public ICommand SendWifiInfoCommand { get; } + public ICommand DisconnectCommand { get; } + public ICommand TestReadOperation { get; } + public ICommand TestWriteOperation { get; } + + private bool _isConnected; + private string ssid; + private string password; + public bool IsBluetoothDeviceSelectionVisible => !IsWifiNetworkSelectionVisible; + public const string SendReceiveServiceUUID = "00000001-710e-4a5b-8d75-3e5b444bc3cf"; + public const string rxUUID = "00000002-710e-4a5b-8d75-3e5b444bc3cf"; + public const string wxUUID = "00000003-710e-4a5b-8d75-3e5b444bc3cf"; + public const string readCharacteristicName = "SSID List"; + public const string writeCharacteristicName = "SSID name and password"; + + + public string tempUnit = "C"; + + public bool IsConnected + { + get => _isConnected; + set + { + _isConnected = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ConnectButtonText)); + } + } + private ICharacteristic _readCharacteristic; + private ICharacteristic _writeCharacteristic; + + public ICharacteristic ReadCharacteristic + { + get => _readCharacteristic; + set + { + _readCharacteristic = value; + OnPropertyChanged(); + } + } + + public ICharacteristic WriteCharacteristic + { + get => _writeCharacteristic; + set + { + _writeCharacteristic = value; + OnPropertyChanged(); + } + } + + + + public string Password + { + get => password; + set + { + password = value; + OnPropertyChanged(nameof(Password)); + } + } + + private readonly IAdapter _adapter; + private readonly IBluetoothLE _bluetoothLE; + public string ConnectButtonText => IsConnected ? "Disconnect" : "Connect"; + private bool _isWifiNetworkSelectionVisible; + private WifiNetwork _selectedWifiNetwork; + + public bool IsWifiNetworkSelectionVisible + { + get => _isWifiNetworkSelectionVisible; + set + { + _isWifiNetworkSelectionVisible = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsBluetoothDeviceSelectionVisible)); + } + } + + public ConnectionPageViewModel() + { + + _bluetoothLE = CrossBluetoothLE.Current; + _adapter = CrossBluetoothLE.Current.Adapter; + + + BluetoothDevices = new ObservableCollection<BluetoothDevice>(); + WifiNetworks = new ObservableCollection<WifiNetwork>(); + ConnectCommand = new Command(OnConnect); + DisconnectCommand = new Command(OnDisconnect); + GoHomeCommand = new Command(async () => await OnGoHome()); + ConnectCommand = new Command(OnToggleConnection); + TestReadOperation = new Command(async () => await ReadOperationAsync()); + SendWifiInfoCommand = new Command(OnSendWifiInfo); + + TestWriteOperation = new Command(async() => await PingPiASync()); + + + _adapter.DeviceDiscovered += OnDeviceDiscovered; + IsWifiNetworkSelectionVisible = false; + + Debug.WriteLine("Checking and requesting Bluetooth permissions."); + CheckAndRequestBluetoothPermissions(); + } + + private void OnSendWifiInfo(object obj) + { + var SSID = SelectedWifiNetwork.SSID; + var result = WriteOperationAsync("CONNECT", SSID + "," + password); + Debug.WriteLine($"Result: {result}"); + } + + private async Task PingPiASync() + { + if (tempUnit.Equals("C")) + { + tempUnit = "F"; + } else + { + tempUnit = "C"; + } + WriteOperationAsync(tempUnit); + + } + + private async void ScanForWifiNetworks() + { + WifiNetworks.Clear(); + Debug.WriteLine("Retrieving Wifi networks from readCharacteristic"); + + //Delay 5 seconds + await Task.Delay(2500); + + var networks = await ReadOperationAsync(); + //var networks = "Network1\nNetwork2\nNetwork3"; // For testing + Debug.WriteLine(networks); + var delimiter = '\n'; //Test to see if actual SSID has \n, see if it breaks things + var networkList = networks.Split(delimiter); + var addedSsids = new HashSet<string>(); + + foreach (var network in networkList) + { + Debug.Write("This network exists: " + network); + + if (addedSsids.Add(network)) + { + var wifiNetwork = new WifiNetwork + { + SSID = network, + SignalStrength = 0.0 + }; + + Microsoft.Maui.Controls.Device.BeginInvokeOnMainThread(() => WifiNetworks.Add(wifiNetwork)); + Debug.WriteLine($"Found Wi-Fi network: {wifiNetwork.SSID}, Signal Strength: {wifiNetwork.SignalStrength} dBm"); + } + } + } + + + public BluetoothDevice SelectedDevice + { + get => _selectedDevice; + set + { + _selectedDevice = value; + OnPropertyChanged(); + } + } + public WifiNetwork SelectedWifiNetwork + { + get => _selectedWifiNetwork; + set + { + _selectedWifiNetwork = value; + OnPropertyChanged(); + } + } + private async void OnToggleConnection() + { + if (IsConnected) + { + OnDisconnect(); + } + else + { + OnConnect(); + } + } + private async void CheckAndRequestBluetoothPermissions() + { + Debug.WriteLine("Checking Bluetooth permissions."); + var status = await Permissions.CheckStatusAsync<Permissions.Bluetooth>(); + if (status != PermissionStatus.Granted) + { + Debug.WriteLine("Bluetooth permission not granted, requesting permission."); + status = await Permissions.RequestAsync<Permissions.Bluetooth>(); + } + + if (status == PermissionStatus.Granted) + { + Debug.WriteLine("Bluetooth permission granted, proceeding to scan devices."); + ScanDevices(); + } + else + { + Debug.WriteLine("Bluetooth permission denied."); + await Application.Current.MainPage.DisplayAlert("Permissions", "Bluetooth scan permission is required to discover devices.", "OK"); + } + } + + public async Task OnGoHome() + { + Debug.WriteLine("Navigating to home page."); + await Application.Current.MainPage.Navigation.PushAsync(new MainPage()); + } + + private async void ScanDevices() + { + BluetoothDevices.Clear(); + Debug.WriteLine("Cleared existing Bluetooth devices. Starting scan..."); + + try + { + if (!_bluetoothLE.IsAvailable) + { + Debug.WriteLine("Bluetooth is not available."); + await Application.Current.MainPage.DisplayAlert("Bluetooth", "Bluetooth is not available.", "OK"); + return; + } + + if (!_bluetoothLE.IsOn) + { + Debug.WriteLine("Bluetooth is turned off."); + await Application.Current.MainPage.DisplayAlert("Bluetooth", "Please turn on Bluetooth.", "OK"); + return; + } + + Debug.WriteLine("Starting scanning for devices..."); + await _adapter.StartScanningForDevicesAsync(); + + } + catch (Exception ex) + { + Debug.WriteLine($"Error during scanning: {ex.Message}"); + await Application.Current.MainPage.DisplayAlert("Error", $"Failed to scan for devices: {ex.Message}", "OK"); + } + } + + private void OnDeviceDiscovered(object sender, DeviceEventArgs e) + { + Debug.WriteLine($"Discovered device: {e.Device.Name ?? e.Device.Id.ToString()}"); + + var bluetoothDevice = new BluetoothDevice( + string.IsNullOrWhiteSpace(e.Device.Name) ? e.Device.Id.ToString() : e.Device.Name, + e.Device.Id.ToString() + ); + + // Check if the device is already in the list + if (!BluetoothDevices.Any(d => d.MacAddress == bluetoothDevice.MacAddress)) + { + // Device is new, add it + Microsoft.Maui.Controls.Device.BeginInvokeOnMainThread(() => BluetoothDevices.Add(bluetoothDevice)); + } + } + + private void RemoveUnavailableDevices(IEnumerable<IDevice> availableDevices) + { + var currentDevices = BluetoothDevices.ToList(); // Copy current list + + foreach (var device in currentDevices) + { + if (!availableDevices.Any(d => d.Id.ToString() == device.MacAddress)) + { + // Device is no longer available, remove it + Microsoft.Maui.Controls.Device.BeginInvokeOnMainThread(() => BluetoothDevices.Remove(device)); + Debug.WriteLine($"Removed unavailable device: {device.MacAddress}"); + } + } + } + + + + private async Task<IDevice> TryConnectAsync(Guid deviceId, int retryCount = 3) + { + IDevice device = null; + for (int attempt = 1; attempt <= retryCount; attempt++) + { + try + { + Debug.WriteLine($"Attempt {attempt}: Connecting to device {deviceId}"); + var cancellationTokenSource = new CancellationTokenSource(); // No timeout specified + device = await _adapter.ConnectToKnownDeviceAsync(deviceId, new ConnectParameters(false, true), cancellationTokenSource.Token); + return device; + } + catch (Exception ex) + { + Debug.WriteLine($"Attempt {attempt} failed: {ex.Message}"); + if (attempt == retryCount) + { + throw; + } + } + } + return device; + } + private async void OnConnect() + { + if (SelectedDevice == null) + { + Debug.WriteLine("No device selected for connection."); + return; + } + + Debug.WriteLine($"Attempting to connect to {SelectedDevice.Name}, MAC Address {SelectedDevice.MacAddress}"); + + try + { + await _adapter.StopScanningForDevicesAsync(); + IsConnected = true; + + _connectedDevice = await TryConnectAsync(Guid.Parse(SelectedDevice.MacAddress)); + + if (_connectedDevice != null) + { + Debug.WriteLine($"Successfully connected to {SelectedDevice.Name}."); + // + var services = await _connectedDevice.GetServicesAsync(); + Debug.WriteLine("Services gotten"); + IsWifiNetworkSelectionVisible = true; + // Iterate through the discovered services + //var service = await _connectedDevice.GetServiceAsync(Guid.Parse("00000001-710e-4a5b-8d75-3e5b444bc3cf")); + foreach (var service in services) + + { + Debug.WriteLine($"Service: ID={service.Id}, Name={service.Name}"); + + if (service.Id.Equals(Guid.Parse("00000001-710e-4a5b-8d75-3e5b444bc3cf")) ) + { + Debug.WriteLine("Found RW service"); + var characteristics = await service.GetCharacteristicsAsync(); + + foreach (var characteristic in characteristics) + { + var descriptors = await characteristic.GetDescriptorsAsync(); + foreach (var descriptor in descriptors) + { + var descriptorValue = await descriptor.ReadAsync(); + var descriptorValueString = Encoding.UTF8.GetString(descriptorValue); + Debug.WriteLine($"Descriptor: {descriptor.Id} | Value: {descriptorValueString}"); + } + + Debug.WriteLine($"{characteristic.Uuid} |{characteristic.Name}"); + if (characteristic.Name.Equals(readCharacteristicName)) + + { + ReadCharacteristic = characteristic; + var result = await ReadOperationAsync(); + + } + else if (characteristic.Name.Equals(writeCharacteristicName)) + { + WriteCharacteristic = characteristic; + const string command = "SCAN"; + //await WriteOperationAsync(command); + ScanForWifiNetworks(); + + } + } + } + } + var transmissionService = services.FirstOrDefault(s => s.Id == Guid.Parse(SendReceiveServiceUUID)); + var readCharacteristic = await transmissionService.GetCharacteristicAsync(Guid.Parse(rxUUID)); + + var result2 = await ReadOperationAsync(); + + } + else + { + Debug.WriteLine("Failed to connect to the device."); + IsConnected = false; + } + } + catch (DeviceConnectionException ex) + { + Debug.WriteLine($"Connection error: {ex.Message}"); + await Application.Current.MainPage.DisplayAlert("Connection Error", $"Failed to connect: {ex.Message}", "OK"); + } + catch (Exception ex) + { + Debug.WriteLine($"General connection error: {ex.Message}"); + await Application.Current.MainPage.DisplayAlert("Connection Error", $"An error occurred: {ex.Message}", "OK"); + } + finally + { + // Ensure we reset connection state + if (_connectedDevice == null) + { + IsConnected = false; + // Consider resetting any other related UI state + } + } + } + + + + + private async Task<string> ReadOperationAsync(int retryCount = 3) + { + if (ReadCharacteristic == null) + { + Debug.WriteLine("Read characteristic is not set."); + return "Failure"; + } + Debug.WriteLine($"READ COMMAND : {ReadCharacteristic.Uuid}"); + for (int attempt = 1; attempt <= retryCount; attempt++) + { + try + { + Debug.WriteLine($"Attempt {attempt}: Reading from characteristic {ReadCharacteristic.Uuid}"); + var temperatureData = await ReadCharacteristic.ReadAsync(); + var temperatureValue = System.Text.Encoding.UTF8.GetString(temperatureData.data); + Debug.WriteLine($"Temperature Value: {temperatureValue}"); + return temperatureValue; + } + catch (Exception ex) + { + Debug.WriteLine($"Read attempt {attempt} failed: {ex.Message}"); + if (attempt == retryCount) + { + Debug.WriteLine("Max retry attempts reached for reading."); + throw; + } else + { + await Task.Delay(1000); + } + } + } + return "Failure"; + } + + private async Task<string> WriteOperationAsync(string command, string arguments="", int retryCount = 3) + { + if (WriteCharacteristic == null) + { + Debug.WriteLine("Write characteristic is not set."); + return "Failure"; + } + string joinedData = command + ":" + arguments; + Debug.WriteLine($"WRITE COMMAND {joinedData}, UUID: {WriteCharacteristic}"); + byte[] data = System.Text.Encoding.UTF8.GetBytes(joinedData); + + for (int attempt = 1; attempt <= retryCount; attempt++) + { + try + { + await WriteCharacteristic.WriteAsync(data); + Debug.WriteLine("Write operation succeeded."); + return "Success"; + } + catch (Exception ex) + { + Debug.WriteLine($"Write attempt {attempt} failed: {ex.Message}"); + if (attempt == retryCount) + { + Debug.WriteLine("Max retry attempts reached for writing."); + throw; + } else + { + await Task.Delay(1000); + } + } + } + return "Failure"; + + } + + + private async void OnDisconnect() + { + if (_connectedDevice != null) + { + try + { + Debug.WriteLine($"Attempting to disconnect from {_connectedDevice.Name}"); + await _adapter.DisconnectDeviceAsync(_connectedDevice); + Debug.WriteLine("Disconnected successfully."); + IsWifiNetworkSelectionVisible = false; + // Dispose of the device reference + if (_connectedDevice != null) + // _connectedDevice.Dispose(); + _connectedDevice = null; + IsConnected = false; + ScanDevices(); + await Application.Current.MainPage.DisplayAlert("Disconnected", "Device disconnected successfully.", "OK"); + } + catch (Exception ex) + { + Debug.WriteLine($"Error during disconnection: {ex.Message}"); + await Application.Current.MainPage.DisplayAlert("Disconnection Error", $"Failed to disconnect: {ex.Message}", "OK"); + } + } + else + { + Debug.WriteLine("No device to disconnect."); + } + } + + + } + +} diff --git a/App/RobobinApp/ViewModels/MainPageViewModel.cs b/App/RobobinApp/ViewModels/MainPageViewModel.cs new file mode 100644 index 0000000000000000000000000000000000000000..784b95043026d824087567f31286057b2edd890c --- /dev/null +++ b/App/RobobinApp/ViewModels/MainPageViewModel.cs @@ -0,0 +1,76 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Input; +using Microsoft.Maui.Controls; +using RobobinApp.Views; +using Shiny.BluetoothLE; + +namespace RobobinApp.ViewModels + +{ + //https://docs.ros.org/en/jazzy/Concepts/Intermediate/About-Domain-ID.html <- Should just work once wifi connection + public class MainPageViewModel : INotifyPropertyChanged + { + private bool _isBusy; + private string _statusMessage; + + public ICommand ConnectToRobobinCommand { get; } + + public MainPageViewModel() + { + + ConnectToRobobinCommand = new Command(async () => await OnConnectToRobobin()); + } + + public bool IsBusy + { + get => _isBusy; + set + { + _isBusy = value; + OnPropertyChanged(); + } + } + + public string StatusMessage + { + get => _statusMessage; + set + { + _statusMessage = value; + OnPropertyChanged(); + } + } + + private async Task OnConnectToRobobin() + { + try + { + IsBusy = true; + + + var connectionPage = new ConnectionPage + { + BindingContext = new ConnectionPageViewModel() + }; + await Application.Current.MainPage.Navigation.PushAsync(connectionPage); + } + catch (Exception ex) + { + StatusMessage = $"Failed to connect: {ex.Message}"; + await Application.Current.MainPage.DisplayAlert("Error", StatusMessage, "OK"); + } + finally + { + IsBusy = false; + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/App/RobobinApp/Views/ConnectionPage.xaml b/App/RobobinApp/Views/ConnectionPage.xaml new file mode 100644 index 0000000000000000000000000000000000000000..42521cba7d7f8b4eb79dc65f00242ec742021430 --- /dev/null +++ b/App/RobobinApp/Views/ConnectionPage.xaml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="utf-8" ?> +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + x:Class="RobobinApp.Views.ConnectionPage" + Title="Connect to Robobin"> + <ContentPage.Resources> + <ResourceDictionary> + <Style x:Key="HeaderLabelStyle" TargetType="Label"> + <Setter Property="FontSize" Value="Medium" /> + <Setter Property="TextColor" Value="DarkSlateBlue" /> + <Setter Property="HorizontalOptions" Value="Center" /> + <Setter Property="VerticalOptions" Value="Start" /> + </Style> + + <Style x:Key="ButtonStyle" TargetType="Button"> + <Setter Property="BackgroundColor" Value="LightBlue" /> + <Setter Property="TextColor" Value="White" /> + <Setter Property="Padding" Value="10,5" /> + <Setter Property="CornerRadius" Value="5" /> + <Setter Property="FontAttributes" Value="Bold" /> + </Style> + + + + <Style x:Key="ListViewStyle" TargetType="ListView"> + <Setter Property="BackgroundColor" Value="White" /> + <Setter Property="SeparatorVisibility" Value="Default" /> + <Setter Property="SeparatorColor" Value="LightGray" /> + <Setter Property="SelectedItem" Value="Blue" /> + <Setter Property="VerticalScrollBarVisibility" Value="Always"/> + + </Style> + </ResourceDictionary> + </ContentPage.Resources> + + <ContentPage.MenuBarItems> + <MenuBarItem Text="Menu"> + <MenuFlyoutItem Text="Back" Command="{Binding GoHomeCommand}" /> + </MenuBarItem> + </ContentPage.MenuBarItems> + + <StackLayout Padding="20"> + <StackLayout IsVisible="{Binding IsBluetoothDeviceSelectionVisible}"> + <Label Text="Select a Bluetooth Device" + FontSize="Medium" + HorizontalOptions="Center" + VerticalOptions="Start" /> + + <ListView x:Name="DeviceListView" + ItemsSource="{Binding BluetoothDevices}" + SelectedItem="{Binding SelectedDevice}" + VerticalOptions="FillAndExpand" + ItemTapped="DeviceListView_ItemTapped"> + + <ListView.ItemTemplate> + <DataTemplate> + <TextCell Text="{Binding Name}" /> + </DataTemplate> + </ListView.ItemTemplate> + </ListView> + + <Button Text="{Binding ConnectButtonText}" + Command="{Binding ConnectCommand}" + IsEnabled="{Binding SelectedDevice, Converter={StaticResource NullToBooleanConverter}}" + HorizontalOptions="Center" + VerticalOptions="End" /> + </StackLayout> + + <StackLayout IsVisible="{Binding IsWifiNetworkSelectionVisible}"> + <Grid Padding="10" HorizontalOptions="FillAndExpand"> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*" /> + <ColumnDefinition Width="Auto" /> + </Grid.ColumnDefinitions> + + <Label Text="{Binding SelectedDevice.Name}" + FontSize="Medium" + HorizontalOptions="Start" + VerticalOptions="Center" + Grid.Column="0" /> + + <Button Text="Disconnect" + Command="{Binding DisconnectCommand}" + HorizontalOptions="End" + VerticalOptions="Center" + Grid.Column="1" /> + </Grid> + + <Label Text="Select a WiFi Network" + FontSize="Medium" + HorizontalOptions="Center" + VerticalOptions="Start" /> + + <ListView x:Name="WifiListView" + ItemsSource="{Binding WifiNetworks}" + SelectedItem="{Binding SelectedWifiNetwork}" + VerticalOptions="FillAndExpand"> + <ListView.ItemTemplate> + <DataTemplate> + <TextCell Text="{Binding SSID}" /> + </DataTemplate> + </ListView.ItemTemplate> + </ListView> + + <StackLayout Padding="10" + BackgroundColor="LightGray" + HorizontalOptions="FillAndExpand" + VerticalOptions="Start"> + <Label Text="Password:" + FontSize="Medium" + HorizontalOptions="Start" /> + <Entry x:Name="PasswordEntry" + Placeholder="Enter Password" + Text="{Binding Password}" + IsPassword="True" + HorizontalOptions="Fill" /> + + <Button Text="Send WiFi Information" + Command="{Binding SendWifiInfoCommand}" + HorizontalOptions="Center" + VerticalOptions="End" /> + </StackLayout> + </StackLayout> + <StackLayout Padding="10" + BackgroundColor="Black" + HorizontalOptions="FillAndExpand" + VerticalOptions="Start"> + <Label Text="Admin Panel:" + FontSize="Medium" + HorizontalOptions="Start" /> + + <Button Text= "Test Read Operation" + Command="{Binding TestReadOperation}" + HorizontalOptions="Center" + VerticalOptions="End" /> + <Button Text="Test Write Operation" + Command="{Binding TestWriteOperation}" + HorizontalOptions="Center" + VerticalOptions="End" /> + </StackLayout> + </StackLayout> +</ContentPage> diff --git a/App/RobobinApp/Views/ConnectionPage.xaml.cs b/App/RobobinApp/Views/ConnectionPage.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..0d4674816737d8d2594ea102472bc83ea16ee250 --- /dev/null +++ b/App/RobobinApp/Views/ConnectionPage.xaml.cs @@ -0,0 +1,14 @@ +namespace RobobinApp.Views; + +public partial class ConnectionPage : ContentPage +{ + public ConnectionPage() + { + InitializeComponent(); + } + + private void DeviceListView_ItemTapped(object sender, ItemTappedEventArgs e) + { + + } +} \ No newline at end of file diff --git a/App/RobobinApp/Views/LeftBox.xaml b/App/RobobinApp/Views/LeftBox.xaml new file mode 100644 index 0000000000000000000000000000000000000000..6b5fff0d0d3177e238940953023d162dfc8afca3 --- /dev/null +++ b/App/RobobinApp/Views/LeftBox.xaml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8" ?> +<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + x:Class="RobobinApp.Views.LeftBox" + BackgroundColor="Lavender"> + <VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center"> + <Label BackgroundColor="Black">Test</Label> + + <Label BackgroundColor="Black">Test</Label> + + </VerticalStackLayout> +</ContentView> diff --git a/App/RobobinApp/Views/LeftBox.xaml.cs b/App/RobobinApp/Views/LeftBox.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..6a829747ea9ad2b2689a75204e926cbf0f237edd --- /dev/null +++ b/App/RobobinApp/Views/LeftBox.xaml.cs @@ -0,0 +1,12 @@ +using Microsoft.Maui.Controls; + +namespace RobobinApp.Views +{ + public partial class LeftBox : ContentView + { + public LeftBox() + { + InitializeComponent(); + } + } +} diff --git a/App/RobobinApp/Views/MainPage.xaml b/App/RobobinApp/Views/MainPage.xaml new file mode 100644 index 0000000000000000000000000000000000000000..f5007a27db554aa1841729f8881964036b47d25b --- /dev/null +++ b/App/RobobinApp/Views/MainPage.xaml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8" ?> +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + xmlns:local="clr-namespace:RobobinApp.Views" + + + xmlns:viewModels="clr-namespace:RobobinApp.ViewModels" + x:Class="RobobinApp.Views.MainPage" + Title=""> + <ContentPage.BindingContext> + <viewModels:MainPageViewModel /> + </ContentPage.BindingContext> + + <ContentPage.Resources> + <ResourceDictionary> + <ResourceDictionary Source="../Resources/Styles/Styles.xaml" /> + + <StyleSheet Source="/Resources/Styles/appstyle.css" /> + </ResourceDictionary> + </ContentPage.Resources> + + + <Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"> + <Grid.RowDefinitions> + <RowDefinition Height="*"/> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="1*" /> + <ColumnDefinition Width="2*" /> + <ColumnDefinition Width="1*" /> + </Grid.ColumnDefinitions> + + <VerticalStackLayout StyleClass="sideFrame" + HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" + Grid.Column="0" Grid.Row="0"> + <HorizontalStackLayout HorizontalOptions="Start" VerticalOptions="End"> + <Button Text="Admin" HorizontalOptions="Start" VerticalOptions="Start" Command="{Binding ConnectToRobobinCommand}"/> + <Button Text="Setup" HorizontalOptions="Start" VerticalOptions="Start" Command="{Binding ConnectToRobobinCommand}"/> + + </HorizontalStackLayout> + + + + <local:SideBox Grid.Column="0" Grid.Row="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" /> + <local:SideBox Grid.Column="0" Grid.Row="1" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" HeaderTitle= "Status:" /> + + + </VerticalStackLayout> + <Frame StyleClass="mainFrame" + HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" + Grid.Column="1" Grid.Row="0"> + <Label Text="{Binding StatusMessage}" HorizontalOptions="Center" VerticalOptions="Center"/> + </Frame> + + <VerticalStackLayout StyleClass="sideFrame" + HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" + Grid.Column="2" Grid.Row="0"> + <HorizontalStackLayout HorizontalOptions="Start" VerticalOptions="End"> + <Button Text="Info" HorizontalOptions="Start" VerticalOptions="Start" /> + + </HorizontalStackLayout> + + <local:SideBox Grid.Column="0" Grid.Row="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" HeaderTitle= "Mode:" /> + <local:SideBox Grid.Column="0" Grid.Row="1" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" HeaderTitle= "Admin:" /> + </VerticalStackLayout> + </Grid> +</ContentPage> diff --git a/App/RobobinApp/Views/MainPage.xaml.cs b/App/RobobinApp/Views/MainPage.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..849c542164db32ba8b3f48a21b93939e6f01bb29 --- /dev/null +++ b/App/RobobinApp/Views/MainPage.xaml.cs @@ -0,0 +1,13 @@ +using Microsoft.Maui.Controls; +using Microsoft.Extensions.DependencyInjection; + +namespace RobobinApp.Views +{ + public partial class MainPage : ContentPage + { + public MainPage() + { + InitializeComponent(); + } + } +} diff --git a/App/RobobinApp/Views/SideBox.xaml b/App/RobobinApp/Views/SideBox.xaml new file mode 100644 index 0000000000000000000000000000000000000000..799f3e5c115d1ef4c079cfc99386df429208370f --- /dev/null +++ b/App/RobobinApp/Views/SideBox.xaml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8" ?> +<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + x:Class="RobobinApp.Views.SideBox" + StyleClass="SideBox"> + <Frame StyleClass="side-box-frame"> + <VerticalStackLayout> + <Grid StyleClass="side-box-header"> + <Label x:Name="HeaderText" + Text="Queue Position:" + StyleClass="side-box-header-text"/> + </Grid> + <Grid StyleClass="side-box-content"> + <ContentPresenter /> + </Grid> + </VerticalStackLayout> + </Frame> +</ContentView> \ No newline at end of file diff --git a/App/RobobinApp/Views/SideBox.xaml.cs b/App/RobobinApp/Views/SideBox.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..e115f034164160ac29fb063ba88c9f01fe3a0a4a --- /dev/null +++ b/App/RobobinApp/Views/SideBox.xaml.cs @@ -0,0 +1,32 @@ +namespace RobobinApp.Views +{ + public partial class SideBox : ContentView + { + public static readonly BindableProperty HeaderTitleProperty = + BindableProperty.Create(nameof(HeaderTitle), + typeof(string), + typeof(SideBox), + defaultValue: "Queue Position:", + propertyChanged: OnHeaderTitleChanged); + + public string HeaderTitle + { + get => (string)GetValue(HeaderTitleProperty); + set => SetValue(HeaderTitleProperty, value); + } + + public SideBox() + { + InitializeComponent(); + } + + protected static void OnHeaderTitleChanged(BindableObject bindable, object oldValue, object newValue) + { + var control = (SideBox)bindable; + if (control.HeaderText != null) + { + control.HeaderText.Text = newValue?.ToString(); + } + } + } +} \ No newline at end of file diff --git a/Connectivity/Pico/Workstations.py b/Connectivity/Pico/Workstations.py new file mode 100644 index 0000000000000000000000000000000000000000..73a88b6674fde37e2667aefeb07e7ddfbcdd8f1e --- /dev/null +++ b/Connectivity/Pico/Workstations.py @@ -0,0 +1,68 @@ +import time +from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY +from machine import Pin +from pimoroni import Button + +pico_display = PicoGraphics(display=DISPLAY_PICO_DISPLAY, rotate=180) +width, height = pico_display.get_bounds() + +background_color = pico_display.create_pen(0, 0, 0) +text_color = pico_display.create_pen(255, 255, 255) + +text_options = [ + ("Home", (0, 0)), + ("Station 1", (10, 10)), + ("Station 2", (20, 20)), + ("Station 3", (30, 30)) +] + +current_index = 0 +scroll_x = width +pico_display.set_font("sans") + +button_pins = [12, 13, 14, 15] +buttons = [Button(pin) for pin in button_pins] + +while True: + for index, button in enumerate(buttons): + if button.read() == True: + if index == 0: + current_index = (current_index + 1) % len(text_options) + print(f"Cycled to: {text_options[current_index][0]}") + elif index == 2: + current_index = (current_index - 1) % len(text_options) + print(f"Cycled to: {text_options[current_index][0]}") + elif index == 1: + selected_text, position = text_options[current_index] + print(f"Sending to {selected_text}, {position}") + + text, position = text_options[current_index] + + max_width = width - 20 + scale = 2 + + while True: + text_width = pico_display.measure_text(text, scale) + if text_width <= max_width: + break + scale -= 1 + if scale < 1: + scale = 1 + break + + pico_display.set_pen(background_color) + pico_display.clear() + pico_display.set_pen(text_color) + + text_width = pico_display.measure_text(text, scale) + text_x = (width - text_width) // 2 + text_y = (height // 2) - (scale * 4) + + pico_display.text(text, text_x, text_y, width, scale) + pico_display.update() + + scroll_x -= 2 + if scroll_x < -text_width: + scroll_x = width + + time.sleep(0.05) diff --git a/Connectivity/Pico/bluetoothTest.py b/Connectivity/Pico/bluetoothTest.py new file mode 100644 index 0000000000000000000000000000000000000000..febbc745821b8dc3342c4023556920f1a0c5907d --- /dev/null +++ b/Connectivity/Pico/bluetoothTest.py @@ -0,0 +1,47 @@ +import ubluetooth +import struct +import time + +class BLEAdvertising: + def __init__(self): + self.ble = ubluetooth.BLE() + self.ble.active(True) + self.ble.irq(self.ble_irq) + self.register_services() + self.advertising_payload = self.generate_advertising_payload("RPi Pico") + self.ble.gap_advertise(100_000, self.advertising_payload) + + def ble_irq(self, event, data): + if event == 1: + print("Device connected") + elif event == 2: + print("Device disconnected") + self.ble.gap_advertise(100_000, self.advertising_payload) + elif event == 3: + conn_handle, attr_handle = data + value = self.ble.gatts_read(self.rx_handle) + print("Received message:", value.decode('utf-8')) + + def generate_advertising_payload(self, name): + name_bytes = bytes(name, 'utf-8') + payload = bytearray() + payload.extend(struct.pack('BB', 0x02, 0x01)) + payload.append(0x06) + payload.extend(struct.pack('BB', len(name_bytes) + 1, 0x09)) + payload.extend(name_bytes) + return payload + + def register_services(self): + UART_SERVICE_UUID = ubluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") + UART_TX_UUID = ubluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") + UART_RX_UUID = ubluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") + self.rx_char = (UART_RX_UUID, ubluetooth.FLAG_WRITE) + self.tx_char = (UART_TX_UUID, ubluetooth.FLAG_NOTIFY) + UART_SERVICE = (UART_SERVICE_UUID, (self.tx_char, self.rx_char)) + self.services = (UART_SERVICE,) + ((self.tx_handle, self.rx_handle),) = self.ble.gatts_register_services(self.services) + +ble_adv = BLEAdvertising() + +while True: + time.sleep(1) diff --git a/Connectivity/RPiBluetooth.py b/Connectivity/RPiBluetooth.py new file mode 100644 index 0000000000000000000000000000000000000000..bfed730ebbde59b35aca446bb405d0f78c6a79c9 --- /dev/null +++ b/Connectivity/RPiBluetooth.py @@ -0,0 +1,65 @@ +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib + +# Define UUIDs for your service and characteristic +SERVICE_UUID = '00001801-0000-1000-8000-00805f9b34fb' +PAIRING_UUID = '00002a25-0000-1000-8000-00805f9b34fb' + +class PairingCharacteristic(dbus.service.Object): + def __init__(self, service, index): + self.path = f"{service.path}/char{index}" + self.service = service + self.props = { + 'UUID': dbus.String(PAIRING_UUID), + 'Service': dbus.ObjectPath(service.path), + 'Flags': dbus.Array(['read', 'write'], signature='s') + } + super().__init__(self.service.bus, self.path) # Use self.service.bus here + + @dbus.service.method('org.bluez.GattCharacteristic1', in_signature='', out_signature='a{sv}') + def GetProperties(self): + return self.props + + @dbus.service.method('org.bluez.GattCharacteristic1', in_signature='ay', out_signature='ay') + def ReadValue(self, options): + return dbus.Array([dbus.Byte(0x00)], signature='y') + + @dbus.service.method('org.bluez.GattCharacteristic1', in_signature='aya{sv}', out_signature='') + def WriteValue(self, value, options): + print(f"Pairing request received: {value}") + # Handle pairing logic here + +class BLEService(dbus.service.Object): + def __init__(self, bus, index): + self.bus = bus # Store the bus object as an attribute of the BLEService + self.path = f"/org/bluez/example/service{index}" + self.props = { + 'UUID': dbus.String(SERVICE_UUID), + 'Primary': dbus.Boolean(True), + } + super().__init__(bus, self.path) + self.characteristics = [PairingCharacteristic(self, 0)] + + @dbus.service.method('org.bluez.GattService1', in_signature='', out_signature='a{sv}') + def GetProperties(self): + return self.props + + @dbus.service.method('org.bluez.GattService1', in_signature='', out_signature='') + def Release(self): + print(f"{self.path}: Released") + +def main(): + DBusGMainLoop(set_as_default=True) + bus = dbus.SystemBus() + + # Create the BLE service + service = BLEService(bus, 0) + + print("BLE Service running. Press Ctrl+C to stop.") + mainloop = GLib.MainLoop() + mainloop.run() + +if __name__ == '__main__': + main() diff --git a/README.md b/README.md index 72fb4b5c55adc224374b58821ce1a183110df963..91c3b33f417219e2f2438017204f0496bc2129fb 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,33 @@ # Robobin +Welcome to the repository for Group 27's Robobin -## Getting started -To make it easy for you to get started with GitLab, here's a list of recommended next steps. -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! -## Add your files +# Application: -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +//Section for explaining the MVVM setup in MAUI +for the application, we are following MVVM +Model: Handles data and business logic (e.g., represents a Robobin object). +View: Displays the UI and interacts with the user (e.g., buttons, labels). +ViewModel: Connects the Model to the View, providing data and commands (e.g., logic to connect to Robobin and update the status). -``` -cd existing_repo -git remote add origin https://git.soton.ac.uk/plw1g21/robobin.git -git branch -M main -git push -uf origin main -``` +| Command | Parameters | Use | Comms Used | +| ------ | ------ | ------ | ------ | +| PING |-| Test to ensure comms between App and RPi exist | BLE | +| | | | | +| | | | | +| | | | | -## Integrate with your tools -- [ ] [Set up project integrations](https://git.soton.ac.uk/plw1g21/robobin/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README - -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. ## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +Paul Winpenny: Owner and maintainer of the gitlab and app developer. + ## License -For open source projects, say how it is licensed. +University Of Southampton... + -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.