main
unknown 6 months ago
parent 47f3cf95c7
commit 6f28748796
  1. 19
      App.xaml.cs
  2. 30
      Helpers/DatabaseHelper.cs
  3. 49
      Helpers/GeolocationHelper.cs
  4. 23
      Helpers/SmsHelper.cs
  5. 32
      Helpers/SosService.cs
  6. 9
      Justice.csproj
  7. 13
      Justice.csproj.user
  8. 2
      Models/EmergencyContact.cs
  9. 58
      Views/AddContactPage.xaml
  10. 102
      Views/AddContactPage.xaml.cs
  11. 4
      Views/DashboardPage.xaml
  12. 16
      Views/DashboardPage.xaml.cs
  13. 25
      Views/SosPage.xaml
  14. 60
      Views/SosPage.xaml.cs

@ -1,10 +1,27 @@
namespace Justice
using Justice.Helpers;
using Justice.Models;
namespace Justice
{
public partial class App : Application
{
public App()
{
InitializeComponent();
InitializeDatabaseAsync(); // Call the async method to initialize the database
}
private async void InitializeDatabaseAsync()
{
try
{
var dbHelper = new DatabaseHelper();
await dbHelper.InitializeAsync<EmergencyContact>(); // Asynchronously create the EmergencyContact table
}
catch (Exception ex)
{
Console.WriteLine($"Database initialization failed: {ex.Message}");
}
}
protected override Window CreateWindow(IActivationState? activationState)

@ -1,42 +1,38 @@
using System;
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using SQLite;
namespace Justice.Helpers
{
public class DatabaseHelper
{
private readonly SQLiteAsyncConnection _database;
public DatabaseHelper(string databaseName = "Appdatabase.db")
public DatabaseHelper(string databaseName = "JusticeAppDatabase.db")
{
var dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), databaseName);
_database = new SQLiteAsyncConnection(dbPath);
}
public async Task InitializeAsync<T>() where T: new()
public async Task InitializeAsync<T>() where T : class, new()
{
await _database.CreateTableAsync<T>();
}
public async Task<int> InsertAsync<T>(T item) where T : new()
public async Task<int> InsertAsync<T>(T item) where T : class, new()
{
return await _database.InsertAsync(item);
}
public async Task<List<T>>GetAllAsync<T>()where T : new()
public async Task<List<T>> GetAllAsync<T>() where T : class, new()
{
return await _database.Table<T>().ToListAsync();
}
public async Task<List<T>> GetAsync<T>(Expression<Func<T, bool>> predicate) where T : new()
{
return await _database.Table<T>().Where(predicate).ToListAsync();
}
public async Task<int> UpdateAsync<T>(T item) where T : new()
{
return await _database.UpdateAsync(item);
}
public async Task<int> DeleteAsync<T>(T item) where T : new()
public async Task<int> DeleteAsync<T>(T item) where T : class, new()
{
return await _database.DeleteAsync(item);
}

@ -3,30 +3,59 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Maui.ApplicationModel;
namespace Justice.Helpers
{
public class GeolocationHelper
{
public static async Task<(double Latitude, double Longitude)> GetCurrentLocationAsync()
/// <summary>
/// Retrieves the user's current latitude, longitude, and a readable address.
/// </summary>
/// <returns>A tuple containing Latitude, Longitude, and Address.</returns>
public static async Task<(double Latitude, double Longitude, string Address)> GetCurrentLocationAsync()
{
var location = await Geolocation.Default.GetLocationAsync();
try
{
var location = await Geolocation.Default.GetLocationAsync(new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.High,
Timeout = TimeSpan.FromSeconds(10)
});
if (location != null)
{
return (location.Latitude, location.Longitude);
var placemarks = await Geocoding.Default.GetPlacemarksAsync(location);
var placemark = placemarks.FirstOrDefault();
string address = "Address not found";
if (placemark != null)
{
address = $"{placemark.SubLocality}, {placemark.Thoroughfare}, " +
$"{placemark.Locality}, {placemark.AdminArea}, {placemark.CountryName}";
}
throw new Exception("Unable to fetch location");
return (location.Latitude, location.Longitude, address);
}
public static async Task<string> GetReadableAddressAsync(double latitude, double longitude)
// Return a default value if location is null
return (0, 0, "Unable to fetch location.");
}
catch (FeatureNotSupportedException)
{
var placemarks = await Geocoding.Default.GetPlacemarksAsync(latitude, longitude);
var placemark = placemarks.FirstOrDefault();
if ( placemark !=null)
// Return a default value if location services are not supported
return (0, 0, "Location services are not supported on this device.");
}
catch (PermissionException)
{
// Return a default value if permissions are not granted
return (0, 0, "Location permissions are not granted.");
}
catch (Exception ex)
{
return $"{placemark.SubLocality}, {placemark.Thoroughfare}, {placemark.Locality}, {placemark.CountryName}";
// Return a default value for other errors
return (0, 0, $"An error occurred: {ex.Message}");
}
return "Address Not Found";
}
}
}

@ -0,0 +1,23 @@
using Microsoft.Maui.ApplicationModel.Communication;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Justice.Helpers
{
public static class SmsHelper
{
public static async Task SendSmsAsync(string message, List<string> recipients)
{
try
{
var smsMessage = new SmsMessage(message, recipients);
await Sms.ComposeAsync(smsMessage);
}
catch (Exception ex)
{
throw new Exception($"Unable to send SMS: {ex.Message}");
}
}
}
}

@ -0,0 +1,32 @@
using Justice.Helpers;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Justice.Services
{
public class SosService
{
public async Task SendSosAlertAsync(List<string> recipients)
{
try
{
// Fetch location and address
(double latitude, double longitude, string address) = await GeolocationHelper.GetCurrentLocationAsync();
// Prepare the SOS message
string message = $"SOS Alert! I need help. My location:\n" +
$"Latitude: {latitude}, Longitude: {longitude}\n" +
$"Address: {address}\n\n";
// Send the SMS
await SmsHelper.SendSmsAsync(message, recipients);
}
catch (Exception ex)
{
throw new Exception($"SOS Alert failed: {ex.Message}");
}
}
}
}

@ -71,9 +71,18 @@
</ItemGroup>
<ItemGroup>
<MauiXaml Update="Views\AddContactPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\DashboardPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\EmergencyContactsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\SosPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
</ItemGroup>
</Project>

@ -3,17 +3,26 @@
<PropertyGroup>
<IsFirstTimeProjectOpen>False</IsFirstTimeProjectOpen>
<ActiveDebugFramework>net9.0-android</ActiveDebugFramework>
<ActiveDebugProfile>Medium Phone API 35 (Android 15.0 - API 35)</ActiveDebugProfile>
<SelectedPlatformGroup>Emulator</SelectedPlatformGroup>
<ActiveDebugProfile>Samsung SM-M325F (Android 13.0 - API 33)</ActiveDebugProfile>
<SelectedPlatformGroup>PhysicalDevice</SelectedPlatformGroup>
<DefaultDevice>Medium_Phone_API_35</DefaultDevice>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-android|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
<ItemGroup>
<MauiXaml Update="Views\AddContactPage.xaml">
<SubType>Designer</SubType>
</MauiXaml>
<MauiXaml Update="Views\DashboardPage.xaml">
<SubType>Designer</SubType>
</MauiXaml>
<MauiXaml Update="Views\EmergencyContactsPage.xaml">
<SubType>Designer</SubType>
</MauiXaml>
<MauiXaml Update="Views\SosPage.xaml">
<SubType>Designer</SubType>
</MauiXaml>
</ItemGroup>
<ItemGroup>
<None Update="App.xaml">

@ -4,11 +4,13 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Justice.Models;
using SQLite;
namespace Justice.Models
{
public class EmergencyContact
{
[PrimaryKey,AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
public string MobileNumber { get; set; }

@ -1,9 +1,10 @@
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Justice.Views.AddContactPage"
Title="Add Emergency Contact">
Title="Emergency Contacts">
<StackLayout Padding="20">
<!-- Input Fields -->
<Label Text="Name" FontSize="Medium" />
<Entry x:Name="NameEntry" Placeholder="Enter name" />
@ -16,32 +17,55 @@
<x:Array Type="{x:Type x:String}">
<x:String>Parents</x:String>
<x:String>Friends</x:String>
<x:String>Colleagues</x:String>
<x:String>Others</x:String>
<x:String>Authority</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<!-- Save Button -->
<Button Text="Save Contact" Clicked="OnSaveContactClicked" />
<ScrollView>
<StackLayout>
<Grid RowSpacing="10" ColumnSpacing="10">
<!-- List of Saved Contacts -->
<Label Text="Saved Contacts" FontSize="Medium" Margin="0,20,0,10" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" HeightRequest="100" Margin="1">
</Border>
<Border Grid.Column="1" HeightRequest="100">
</Border>
<Border Grid.Column="2" HeightRequest="100">
</Border>
<Label Text="Name" FontSize="Medium" Grid.Column="0"/>
<Label Text="Number" FontSize="Medium" Grid.Column="1"/>
<Label Text="Group" FontSize="Medium" Grid.Column="2"/>
<Label FontSize="Medium" Grid.Column="3"/>
</Grid>
<ListView x:Name="ContactsListView"
VerticalOptions="FillAndExpand"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" FontSize="Medium" Grid.Column="0"/>
<Label Text="{Binding MobileNumber}" FontSize="Small" Grid.Column="1"/>
<Label Text="{Binding Group}" FontSize="Small" Grid.Column="2"/>
<Button Text="Delete"
Grid.Column="3"
TextColor="LightGray"
Clicked="OnDeleteContactClicked"
CommandParameter="{Binding .}" />
</Grid>
</StackLayout>
</ScrollView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

@ -1,39 +1,113 @@
using Justice.Models;
using Justice.Views;
using Justice.Helpers;
using Justice.Models;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace Justice.Views;
public partial class AddContactPage : ContentPage
namespace Justice.Views
{
public partial class AddContactPage : ContentPage
{
private readonly DatabaseHelper _databaseHelper;
public ObservableCollection<EmergencyContact> Contacts { get; set; }
public AddContactPage()
{
InitializeComponent();
_databaseHelper = new DatabaseHelper();
Contacts = new ObservableCollection<EmergencyContact>();
ContactsListView.ItemsSource = Contacts;
}
private async void OnSaveContactClicked(object sender, EventArgs e)
protected override async void OnAppearing()
{
base.OnAppearing();
await LoadContactsAsync();
}
private async Task LoadContactsAsync()
{
try
{
if (string.IsNullOrWhiteSpace(NameEntry.Text) || string.IsNullOrWhiteSpace(MobileNumberEntry.Text) || GroupPicker.SelectedItem == null)
Contacts.Clear();
var contactsFromDb = await _databaseHelper.GetAllAsync<EmergencyContact>();
foreach (var contact in contactsFromDb)
{
Contacts.Add(contact);
}
}
catch (Exception ex)
{
await DisplayAlert("Error", $"Failed to load contacts: {ex.Message}", "OK");
}
}
private async void OnSaveContactClicked(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(NameEntry.Text) ||
string.IsNullOrWhiteSpace(MobileNumberEntry.Text) ||
GroupPicker.SelectedItem == null)
{
await DisplayAlert("Error", "All Fields Required", "OK");
await DisplayAlert("Error", "All fields are required.", "OK");
return;
}
var contact = new EmergencyContact
{
Name = NameEntry.Text,
MobileNumber = MobileNumberEntry.Text,
Name = NameEntry.Text.Trim(),
MobileNumber = MobileNumberEntry.Text.Trim(),
Group = GroupPicker.SelectedItem.ToString()
};
try
{
await _databaseHelper.InsertAsync(contact);
await DisplayAlert("Success", "Contact Saved Successfully", "OK");
await Navigation.PushAsync(new EmergencyContactsPage());
await LoadContactsAsync();
NameEntry.Text = string.Empty;
MobileNumberEntry.Text = string.Empty;
GroupPicker.SelectedItem = null;
await DisplayAlert("Success", "Contact saved successfully!", "OK");
}
catch (Exception ex)
{
await DisplayAlert("Success", $"{ex}", "OK");
await DisplayAlert("Error", $"Failed to save contact: {ex.Message}", "OK");
}
}
private async void OnDeleteContactClicked(object sender, EventArgs e)
{
try
{
Console.WriteLine("Delete button clicked."); // Debug log
var button = sender as Button;
if (button?.CommandParameter is EmergencyContact contactToDelete)
{
Console.WriteLine($"Attempting to delete contact: {contactToDelete.Name}"); // Debug log
bool confirm = await DisplayAlert("Delete Contact",
$"Are you sure you want to delete {contactToDelete.Name}?", "Yes", "No");
if (!confirm)
return;
await _databaseHelper.DeleteAsync(contactToDelete);
Contacts.Remove(contactToDelete);
await DisplayAlert("Success", "Contact deleted successfully!", "OK");
}
else
{
Console.WriteLine("CommandParameter is null or invalid."); // Debug log
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in OnDeleteContactClicked: {ex.Message}"); // Debug log
await DisplayAlert("Error", $"Failed to delete contact: {ex.Message}", "OK");
}
}
}
}

@ -5,7 +5,7 @@
Title="DashboardPage" BackgroundColor="WhiteSmoke">
<ScrollView>
<VerticalStackLayout>
<Button Text="Get Current Location" Clicked="OnGetLocationClicked"
<Button Text="Get Current Location"
HorizontalOptions="Fill"
BackgroundColor="White"
@ -97,7 +97,7 @@
<VerticalStackLayout Spacing="10" HorizontalOptions="FillAndExpand" VerticalOptions="Start">
<Label Text="View Report" FontSize="17" FontAttributes="Bold" HorizontalOptions="Start"/>
<Label Text="Check your incident report" FontSize="12" FontAttributes="Bold" HorizontalOptions="Start" TextColor="Gray"/>
<Button Text="SEND SOS" TextColor="White" FontAttributes="Bold" CornerRadius="5" BackgroundColor="#8BC34A" Clicked="OnViewReportClicked"/>
<Button Text="View Reports" TextColor="White" FontAttributes="Bold" CornerRadius="5" BackgroundColor="#8BC34A" Clicked="OnViewReportClicked"/>
</VerticalStackLayout>
</Border>
</Grid>

@ -12,20 +12,6 @@ public partial class DashboardPage : ContentPage
}
public async void OnGetLocationClicked(object sender, EventArgs e)
{
try
{
var (latitude, longitude) = await GeolocationHelper.GetCurrentLocationAsync();
string address = await GeolocationHelper.GetReadableAddressAsync(latitude, longitude);
LocationLabel.Text = $"{address}";
}
catch (Exception ex)
{
await DisplayAlert("Error", ex.Message,"OK");
}
}
private void OnInformationCenterClicked(object sender, EventArgs e)
{
DisplayAlert("Information button clicked","Information","OK");
@ -46,7 +32,7 @@ public partial class DashboardPage : ContentPage
private void OnSOSButtonClicked(object sender, EventArgs e)
{
DisplayAlert("SOS button clicked", "Send SOS", "OK");
Navigation.PushAsync(new SosPage());
}
private void OnAddContactClicked(object sender, EventArgs e)

@ -0,0 +1,25 @@
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Justice.Views.SosPage"
Title="SOS Alert">
<StackLayout Padding="20" VerticalOptions="CenterAndExpand">
<Label Text="Select Group for SOS Alert" FontSize="Medium" />
<Picker x:Name="GroupPicker">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Parents</x:String>
<x:String>Friends</x:String>
<x:String>Authority</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<Button Text="Send SOS Alert"
Clicked="OnSendSosClicked"
BackgroundColor="Red"
TextColor="White"
FontSize="Large"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>

@ -0,0 +1,60 @@
using Justice.Helpers;
using Justice.Models;
using Justice.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Justice.Views
{
public partial class SosPage : ContentPage
{
private readonly DatabaseHelper _databaseHelper;
private readonly SosService _sosService;
public SosPage()
{
InitializeComponent();
_databaseHelper = new DatabaseHelper();
_sosService = new SosService();
}
private async void OnSendSosClicked(object sender, EventArgs e)
{
try
{
// Get the selected group
var selectedGroup = GroupPicker.SelectedItem as string;
if (string.IsNullOrEmpty(selectedGroup))
{
await DisplayAlert("Error", "Please select a group.", "OK");
return;
}
// Fetch emergency contacts from the selected group
var contacts = await _databaseHelper.GetAllAsync<EmergencyContact>();
var groupContacts = contacts
.Where(contact => contact.Group.Equals(selectedGroup, StringComparison.OrdinalIgnoreCase))
.ToList();
if (!groupContacts.Any())
{
await DisplayAlert("Error", $"No contacts found in the group '{selectedGroup}'.", "OK");
return;
}
// Extract mobile numbers for the SOS alert
var emergencyNumbers = groupContacts.Select(contact => contact.MobileNumber).ToList();
// Send the SOS alert
await _sosService.SendSosAlertAsync(emergencyNumbers);
}
catch (Exception ex)
{
await DisplayAlert("Error", $"Failed to send SOS Alert: {ex.Message}", "OK");
}
}
}
}
Loading…
Cancel
Save