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 partial class App : Application
{ {
public App() public App()
{ {
InitializeComponent(); 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) protected override Window CreateWindow(IActivationState? activationState)

@ -1,42 +1,38 @@
using System; using SQLite;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.IO;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using SQLite;
namespace Justice.Helpers namespace Justice.Helpers
{ {
public class DatabaseHelper public class DatabaseHelper
{ {
private readonly SQLiteAsyncConnection _database; 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); var dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), databaseName);
_database = new SQLiteAsyncConnection(dbPath); _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>(); 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); 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(); return await _database.Table<T>().ToListAsync();
} }
public async Task<List<T>> GetAsync<T>(Expression<Func<T, bool>> predicate) where T : new()
{ public async Task<int> DeleteAsync<T>(T item) where T : class, 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()
{ {
return await _database.DeleteAsync(item); return await _database.DeleteAsync(item);
} }

@ -3,30 +3,59 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Maui.ApplicationModel;
namespace Justice.Helpers namespace Justice.Helpers
{ {
public class GeolocationHelper 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) 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); // Return a default value if location services are not supported
var placemark = placemarks.FirstOrDefault(); return (0, 0, "Location services are not supported on this device.");
if ( placemark !=null) }
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>
<ItemGroup> <ItemGroup>
<MauiXaml Update="Views\AddContactPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\DashboardPage.xaml"> <MauiXaml Update="Views\DashboardPage.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</MauiXaml> </MauiXaml>
<MauiXaml Update="Views\EmergencyContactsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\SosPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
</ItemGroup> </ItemGroup>
</Project> </Project>

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

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

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

@ -1,39 +1,113 @@
using Justice.Models;
using Justice.Views;
using Justice.Helpers; using Justice.Helpers;
using Justice.Models;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace Justice.Views
namespace Justice.Views;
public partial class AddContactPage : ContentPage
{ {
public partial class AddContactPage : ContentPage
{
private readonly DatabaseHelper _databaseHelper; private readonly DatabaseHelper _databaseHelper;
public ObservableCollection<EmergencyContact> Contacts { get; set; }
public AddContactPage() public AddContactPage()
{ {
InitializeComponent(); InitializeComponent();
_databaseHelper = new DatabaseHelper(); _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 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 var contact = new EmergencyContact
{ {
Name = NameEntry.Text, Name = NameEntry.Text.Trim(),
MobileNumber = MobileNumberEntry.Text, MobileNumber = MobileNumberEntry.Text.Trim(),
Group = GroupPicker.SelectedItem.ToString() Group = GroupPicker.SelectedItem.ToString()
}; };
try
{
await _databaseHelper.InsertAsync(contact); await _databaseHelper.InsertAsync(contact);
await DisplayAlert("Success", "Contact Saved Successfully", "OK"); await LoadContactsAsync();
await Navigation.PushAsync(new EmergencyContactsPage());
NameEntry.Text = string.Empty;
MobileNumberEntry.Text = string.Empty;
GroupPicker.SelectedItem = null;
await DisplayAlert("Success", "Contact saved successfully!", "OK");
} }
catch (Exception ex) 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"> Title="DashboardPage" BackgroundColor="WhiteSmoke">
<ScrollView> <ScrollView>
<VerticalStackLayout> <VerticalStackLayout>
<Button Text="Get Current Location" Clicked="OnGetLocationClicked" <Button Text="Get Current Location"
HorizontalOptions="Fill" HorizontalOptions="Fill"
BackgroundColor="White" BackgroundColor="White"
@ -97,7 +97,7 @@
<VerticalStackLayout Spacing="10" HorizontalOptions="FillAndExpand" VerticalOptions="Start"> <VerticalStackLayout Spacing="10" HorizontalOptions="FillAndExpand" VerticalOptions="Start">
<Label Text="View Report" FontSize="17" FontAttributes="Bold" HorizontalOptions="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"/> <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> </VerticalStackLayout>
</Border> </Border>
</Grid> </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) private void OnInformationCenterClicked(object sender, EventArgs e)
{ {
DisplayAlert("Information button clicked","Information","OK"); DisplayAlert("Information button clicked","Information","OK");
@ -46,7 +32,7 @@ public partial class DashboardPage : ContentPage
private void OnSOSButtonClicked(object sender, EventArgs e) 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) 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