diff --git a/.gitignore b/.gitignore index 154e127..c65dbe8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore +# Project files + +SOS/Constants/AppwriteConstants.cs + # User-specific files *.rsuser *.suo diff --git a/SOS/App.xaml.cs b/SOS/App.xaml.cs index 0890257..29eacec 100644 --- a/SOS/App.xaml.cs +++ b/SOS/App.xaml.cs @@ -1,4 +1,4 @@ -using SOS.Data; +using SOS.Business; namespace SOS; @@ -6,12 +6,16 @@ public partial class App : Application { public static SettingsRepository SettingsRepo { get; private set; } - public App(SettingsRepository repo) + public static LocationService LocationService { get; private set; } + + public App(SettingsRepository repo, LocationService locationService) { InitializeComponent(); MainPage = new AppShell(); SettingsRepo = repo; + + LocationService = locationService; } } diff --git a/SOS/Business/LocationService.cs b/SOS/Business/LocationService.cs new file mode 100644 index 0000000..9512554 --- /dev/null +++ b/SOS/Business/LocationService.cs @@ -0,0 +1,53 @@ +namespace SOS.Business +{ + public class LocationService + { + private CancellationTokenSource _cancelTokenSource; + private bool _isCheckingLocation; + + public async Task> GetCurrentLocation() + { + try + { + _isCheckingLocation = true; + + GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Best); + + _cancelTokenSource = new CancellationTokenSource(); + + Location location = await Geolocation.Default.GetLocationAsync(request, _cancelTokenSource.Token); + + if (location != null) + { + _isCheckingLocation = false; + return new() + { + { "found", true }, + { "latitude", location.Latitude.ToString() }, + { "longitude", location.Longitude.ToString() } + }; + + } + } + catch (Exception ex) + { + return new() + { + { "found", false }, + { "error", ex.Message } + }; + } + + return new() + { + { "found", false } + }; + } + + public void CancelRequest() + { + if (_isCheckingLocation && _cancelTokenSource != null && _cancelTokenSource.IsCancellationRequested == false) + _cancelTokenSource.Cancel(); + } + } +} diff --git a/SOS/Data/SettingsRepository.cs b/SOS/Business/SettingsRepository.cs similarity index 93% rename from SOS/Data/SettingsRepository.cs rename to SOS/Business/SettingsRepository.cs index 024d700..98770f9 100644 --- a/SOS/Data/SettingsRepository.cs +++ b/SOS/Business/SettingsRepository.cs @@ -1,7 +1,8 @@ -using SOS.Models; +using SOS.Constants; +using SOS.Models; using SQLite; -namespace SOS.Data +namespace SOS.Business { public class SettingsRepository { @@ -14,7 +15,7 @@ public async Task Init() return; } - conn = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags); + conn = new SQLiteAsyncConnection(DbConstants.DatabasePath, DbConstants.Flags); await conn.CreateTableAsync(); } diff --git a/SOS/Data/Constants.cs b/SOS/Constants/DbConstants.cs similarity index 86% rename from SOS/Data/Constants.cs rename to SOS/Constants/DbConstants.cs index 20204c9..95af481 100644 --- a/SOS/Data/Constants.cs +++ b/SOS/Constants/DbConstants.cs @@ -1,6 +1,6 @@ -namespace SOS.Data +namespace SOS.Constants { - public static class Constants + public static class DbConstants { public const string DatabaseFilename = "SOSSettings.db3"; diff --git a/SOS/MainPage.xaml.cs b/SOS/MainPage.xaml.cs index fe91ab1..d304ee4 100644 --- a/SOS/MainPage.xaml.cs +++ b/SOS/MainPage.xaml.cs @@ -1,8 +1,12 @@ -namespace SOS; +using Newtonsoft.Json; +using SOS.Helpers; +using SOS.Models; + +namespace SOS; public partial class MainPage : ContentPage { - public MainPage() + public MainPage() { InitializeComponent(); @@ -27,9 +31,70 @@ private async void SOSButtonClicked(object sender, EventArgs e) return; } - var settings = settingsDbResponse.SettingsData; + SettingsData settings = settingsDbResponse.SettingsData; + string phoneNumber = settings.PhoneNumber; + SOSButton.BackgroundColor = Colors.Crimson; - await DisplayAlert("Alert", $"Saved number: {settings.PhoneNumber}", "Ok"); + + var coordinates = await App.LocationService.GetCurrentLocation(); + if (coordinates["found"] is false) + { + await DisplayAlert("Error", "Not able to get location", "Ok"); + return; + } + + string latitude = coordinates["latitude"].ToString(); + string longitude = coordinates["longitude"].ToString(); + + NetworkAccess accessType = Connectivity.Current.NetworkAccess; + + if (accessType != NetworkAccess.Internet) + { + if (Sms.Default.IsComposeSupported) + { + string[] recipients = new[] { phoneNumber }; + string text = $"SOS ALERT:\n\nPlease get help at \n\nCoordinates: {latitude},{longitude}"; + + var message = new SmsMessage(text, recipients); + + await Sms.Default.ComposeAsync(message); + } + } + else + { + var endpoint = $"/v1/functions/{AppwriteConstants.FunctionId}/executions"; + + HttpClient client = new HttpClient(); + client.BaseAddress = new Uri(AppwriteConstants.AppwriteUrl); + client.DefaultRequestHeaders.Add("X-Appwrite-Response-Format", "1.0.0"); + client.DefaultRequestHeaders.Add("X-Appwrite-Project", AppwriteConstants.ProjectId); + client.DefaultRequestHeaders.Add("X-Appwrite-Key", AppwriteConstants.ApiKey); + + Dictionary requestData = new Dictionary(); + requestData.Add("phoneNumber", settings.PhoneNumber); + requestData.Add("latitude", coordinates["latitude"].ToString()); + requestData.Add("longitude", coordinates["longitude"].ToString()); + + var appwriteRequestInput = new Dictionary() + { + { "data", JsonConvert.SerializeObject(requestData) } + }; + var jsonContent = new StringContent(JsonConvert.SerializeObject(appwriteRequestInput), System.Text.Encoding.UTF8, "application/json"); + + var sosResponse = await client.PostAsync(endpoint, jsonContent); + var sosResponseContent = await sosResponse.Content.ReadAsStringAsync(); + var sosResponseData = JsonConvert.DeserializeObject(sosResponseContent); + + var appwriteFunctionResponse = JsonConvert.DeserializeObject>(sosResponseData.Response); + if (Convert.ToBoolean(appwriteFunctionResponse["sos"])) + { + await DisplayAlert("Alert", $"Sent SOS Request to {settings.PhoneNumber}", "Ok"); + } + else + { + await DisplayAlert("Alert", "SOS Message Not Sent", "Ok"); + } + } SOSButton.BackgroundColor = Colors.Red; } } diff --git a/SOS/MauiProgram.cs b/SOS/MauiProgram.cs index 261624f..4e95b7d 100644 --- a/SOS/MauiProgram.cs +++ b/SOS/MauiProgram.cs @@ -1,4 +1,4 @@ -using SOS.Data; +using SOS.Business; namespace SOS; @@ -16,6 +16,7 @@ public static MauiApp CreateMauiApp() }); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); return builder.Build(); } diff --git a/SOS/Models/AppwriteApiResponse.cs b/SOS/Models/AppwriteApiResponse.cs new file mode 100644 index 0000000..7c2afb3 --- /dev/null +++ b/SOS/Models/AppwriteApiResponse.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace SOS.Models +{ + public class AppwriteApiResponse + { + [JsonProperty("$id")] + public string Id { get; set; } + + [JsonProperty("$createdAt")] + public DateTime CreatedAt { get; set; } + + [JsonProperty("$updatedAt")] + public DateTime UpdatedAt { get; set; } + + [JsonProperty("$permissions")] + public List Permissions { get; set; } + + [JsonProperty("functionId")] + public string FunctionId { get; set; } + + [JsonProperty("trigger")] + public string Trigger { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("statusCode")] + public int StatusCode { get; set; } + + [JsonProperty("response")] + public string Response { get; set; } + + [JsonProperty("stdout")] + public string Stdout { get; set; } + + [JsonProperty("stderr")] + public string Stderr { get; set; } + + [JsonProperty("duration")] + public double Duration { get; set; } + } +} diff --git a/SOS/Platforms/Android/AndroidManifest.xml b/SOS/Platforms/Android/AndroidManifest.xml index e9937ad..21b9551 100644 --- a/SOS/Platforms/Android/AndroidManifest.xml +++ b/SOS/Platforms/Android/AndroidManifest.xml @@ -3,4 +3,10 @@ + + + + + + \ No newline at end of file diff --git a/SOS/Platforms/Android/MainApplication.cs b/SOS/Platforms/Android/MainApplication.cs index a32f643..d05237e 100644 --- a/SOS/Platforms/Android/MainApplication.cs +++ b/SOS/Platforms/Android/MainApplication.cs @@ -1,6 +1,14 @@ using Android.App; using Android.Runtime; +[assembly: UsesPermission(Android.Manifest.Permission.AccessNetworkState)] +[assembly: UsesPermission(Android.Manifest.Permission.AccessCoarseLocation)] +[assembly: UsesPermission(Android.Manifest.Permission.AccessFineLocation)] +[assembly: UsesFeature("android.hardware.location", Required = false)] +[assembly: UsesFeature("android.hardware.location.gps", Required = false)] +[assembly: UsesFeature("android.hardware.location.network", Required = false)] +[assembly: UsesPermission(Android.Manifest.Permission.AccessBackgroundLocation)] + namespace SOS; [Application] diff --git a/SOS/Platforms/MacCatalyst/Info.plist b/SOS/Platforms/MacCatalyst/Info.plist index c96dd0a..5705909 100644 --- a/SOS/Platforms/MacCatalyst/Info.plist +++ b/SOS/Platforms/MacCatalyst/Info.plist @@ -26,5 +26,7 @@ XSAppIconAssets Assets.xcassets/appicon.appiconset + NSLocationWhenInUseUsageDescription + Fill in a reason why your app needs access to location. diff --git a/SOS/Platforms/iOS/Info.plist b/SOS/Platforms/iOS/Info.plist index 0004a4f..e65d0a8 100644 --- a/SOS/Platforms/iOS/Info.plist +++ b/SOS/Platforms/iOS/Info.plist @@ -28,5 +28,7 @@ XSAppIconAssets Assets.xcassets/appicon.appiconset + NSLocationWhenInUseUsageDescription + Fill in a reason why your app needs access to location. diff --git a/SOS/SOS.csproj b/SOS/SOS.csproj index d8996ea..ff8b7f1 100644 --- a/SOS/SOS.csproj +++ b/SOS/SOS.csproj @@ -48,6 +48,7 @@ +