Integration and add heat map demo
- Implemented OpenStreetMap using WebView with Leaflet.js - Added OpenStreetMapView component with interactive map functionality - Created heat map visualization with color-coded intensity - Added 30 dummy location points around San Francisco Bay Area - Implemented location tracking with real-time pin placement - Added comprehensive UI with two-row button layout - Features: Start/Stop tracking, Center map, Demo heat map, Clear demo, Reset map - Added location count display and confirmation dialogs - Updated project structure and documentation - All functionality tested and working on Android emulator
641
LocationTrackerApp/.gitignore
vendored
Normal file
@@ -0,0 +1,641 @@
|
||||
# .NET MAUI / Xamarin
|
||||
bin/
|
||||
obj/
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
*.dll
|
||||
*.exe
|
||||
*.pdb
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
*.swp
|
||||
*.tmp
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
*.pidb
|
||||
*.monotouch-files
|
||||
*.useros
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
*.user
|
||||
*.useros
|
||||
*.suo
|
||||
*.cache
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# Android
|
||||
*.apk
|
||||
*.aab
|
||||
*.dex
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
hs_err_pid*
|
||||
|
||||
# Android Studio
|
||||
.gradle/
|
||||
build/
|
||||
local.properties
|
||||
*.iml
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iws
|
||||
.navigation/
|
||||
captures/
|
||||
output.json
|
||||
|
||||
# NDK
|
||||
obj/
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
out/
|
||||
|
||||
# User-specific configurations
|
||||
.idea/libraries/
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/.name
|
||||
.idea/compiler.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
.idea/encodings.xml
|
||||
.idea/misc.xml
|
||||
.idea/modules.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/vcs.xml
|
||||
*.iml
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# iOS
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
DerivedData/
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Xcode
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# CocoaPods
|
||||
Pods/
|
||||
|
||||
# Carthage
|
||||
Carthage/Build/
|
||||
|
||||
# Accio dependency management
|
||||
Dependencies/
|
||||
.accio/
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
|
||||
# Code Injection
|
||||
iOSInjectionProject/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc.index
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these files may be visible to others.
|
||||
*.azurePubxml
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment the next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
CordovaApp.projitems
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# Configuration files with sensitive data
|
||||
appsettings.Production.json
|
||||
appsettings.Staging.json
|
||||
*.secrets.json
|
||||
|
||||
# MAUI specific
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.msix
|
||||
*.msixbundle
|
||||
*.appinstaller
|
||||
*.appxmanifest
|
||||
*.msixmanifest
|
||||
*.appinstaller
|
||||
|
||||
# MAUI build outputs
|
||||
bin/
|
||||
obj/
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
*.dll
|
||||
*.exe
|
||||
*.pdb
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
*.swp
|
||||
*.tmp
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
*.pidb
|
||||
*.monotouch-files
|
||||
*.useros
|
||||
*.sln.docstates
|
||||
|
||||
# MAUI Android specific
|
||||
*.apk
|
||||
*.aab
|
||||
*.dex
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
hs_err_pid*
|
||||
|
||||
# MAUI iOS specific
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
DerivedData/
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# MAUI macOS specific
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
._*
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
14
LocationTrackerApp/App.xaml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?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:LocationTrackerApp"
|
||||
x:Class="LocationTrackerApp.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
28
LocationTrackerApp/App.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using LocationTrackerApp.Views;
|
||||
using LocationTrackerApp.ViewModels;
|
||||
using LocationTrackerApp.Data;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override Window CreateWindow(IActivationState? activationState)
|
||||
{
|
||||
// Get services from DI container
|
||||
var dbContext = Handler?.MauiContext?.Services?.GetService<LocationDbContext>();
|
||||
var mainViewModel = Handler?.MauiContext?.Services?.GetService<MainViewModel>();
|
||||
|
||||
if (dbContext != null)
|
||||
{
|
||||
// Ensure database is created
|
||||
_ = Task.Run(async () => await dbContext.EnsureDatabaseCreatedAsync());
|
||||
}
|
||||
|
||||
return new Window(new AppShell());
|
||||
}
|
||||
}
|
||||
14
LocationTrackerApp/AppShell.xaml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Shell
|
||||
x:Class="LocationTrackerApp.AppShell"
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:LocationTrackerApp"
|
||||
Title="LocationTrackerApp">
|
||||
|
||||
<ShellContent
|
||||
Title="Location Tracker"
|
||||
ContentTemplate="{DataTemplate local:MainPage}"
|
||||
Route="MainPage" />
|
||||
|
||||
</Shell>
|
||||
14
LocationTrackerApp/AppShell.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using LocationTrackerApp.Views;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Register routes
|
||||
Routing.RegisterRoute(nameof(MainPage), typeof(MainPage));
|
||||
}
|
||||
}
|
||||
368
LocationTrackerApp/Components/OpenStreetMapView.cs
Normal file
@@ -0,0 +1,368 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LocationTrackerApp.Models;
|
||||
|
||||
namespace LocationTrackerApp.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Custom WebView component for displaying OpenStreetMap
|
||||
/// </summary>
|
||||
public class OpenStreetMapView : WebView
|
||||
{
|
||||
private readonly ILogger<OpenStreetMapView>? _logger;
|
||||
private List<LocationData> _locationData = new();
|
||||
private bool _isHeatMapVisible = true;
|
||||
private bool _isPointsVisible = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the heat map overlay is visible.
|
||||
/// </summary>
|
||||
public bool IsHeatMapVisible
|
||||
{
|
||||
get => _isHeatMapVisible;
|
||||
set
|
||||
{
|
||||
if (_isHeatMapVisible != value)
|
||||
{
|
||||
_isHeatMapVisible = value;
|
||||
UpdateHeatMapVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether individual location points are visible.
|
||||
/// </summary>
|
||||
public bool IsPointsVisible
|
||||
{
|
||||
get => _isPointsVisible;
|
||||
set
|
||||
{
|
||||
if (_isPointsVisible != value)
|
||||
{
|
||||
_isPointsVisible = value;
|
||||
UpdatePointsVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for XAML
|
||||
/// </summary>
|
||||
public OpenStreetMapView()
|
||||
{
|
||||
InitializeMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of OpenStreetMapView
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger for recording events</param>
|
||||
public OpenStreetMapView(ILogger<OpenStreetMapView> logger) : this()
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the map with default settings
|
||||
/// </summary>
|
||||
private void InitializeMap()
|
||||
{
|
||||
// Set default location to San Francisco Bay Area
|
||||
var defaultLocation = new Location(37.7749, -122.4194);
|
||||
LoadMap(defaultLocation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the OpenStreetMap with Leaflet.js
|
||||
/// </summary>
|
||||
/// <param name="centerLocation">The center location for the map</param>
|
||||
private void LoadMap(Location centerLocation)
|
||||
{
|
||||
var html = GenerateMapHtml(centerLocation);
|
||||
var htmlSource = new HtmlWebViewSource { Html = html };
|
||||
Source = htmlSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the HTML content for the OpenStreetMap with Leaflet.js
|
||||
/// </summary>
|
||||
/// <param name="centerLocation">The center location for the map</param>
|
||||
/// <returns>HTML string containing the map</returns>
|
||||
private string GenerateMapHtml(Location centerLocation)
|
||||
{
|
||||
return $@"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>OpenStreetMap</title>
|
||||
<link rel='stylesheet' href='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' />
|
||||
<style>
|
||||
body {{ margin: 0; padding: 0; }}
|
||||
#map {{ height: 100vh; width: 100vw; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='map'></div>
|
||||
<script src='https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'></script>
|
||||
<script>
|
||||
// Initialize the map
|
||||
var map = L.map('map').setView([{centerLocation.Latitude}, {centerLocation.Longitude}], 13);
|
||||
|
||||
// Add OpenStreetMap tiles
|
||||
L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
|
||||
attribution: '© <a href=""https://www.openstreetmap.org/copyright"">OpenStreetMap</a> contributors',
|
||||
maxZoom: 19
|
||||
}}).addTo(map);
|
||||
|
||||
// Store markers for heat map functionality
|
||||
var markers = [];
|
||||
var polylines = [];
|
||||
|
||||
// Function to add a location pin
|
||||
function addLocationPin(lat, lng, label, intensity) {{
|
||||
var color = getHeatColor(intensity);
|
||||
var marker = L.circleMarker([lat, lng], {{
|
||||
radius: 8,
|
||||
fillColor: color,
|
||||
color: '#000',
|
||||
weight: 1,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.8
|
||||
}}).addTo(map);
|
||||
|
||||
if (label) {{
|
||||
marker.bindPopup(label);
|
||||
}}
|
||||
|
||||
markers.push(marker);
|
||||
return marker;
|
||||
}}
|
||||
|
||||
// Function to add a polyline for heat map
|
||||
function addHeatMapPolyline(coordinates, intensity) {{
|
||||
var color = getHeatColor(intensity);
|
||||
var polyline = L.polyline(coordinates, {{
|
||||
color: color,
|
||||
weight: 7,
|
||||
opacity: 0.3 + (intensity * 0.7)
|
||||
}}).addTo(map);
|
||||
|
||||
polylines.push(polyline);
|
||||
return polyline;
|
||||
}}
|
||||
|
||||
// Function to get heat color based on intensity
|
||||
function getHeatColor(intensity) {{
|
||||
if (intensity < 0.25) return '#0000FF'; // Blue
|
||||
if (intensity < 0.50) return '#00FFFF'; // Cyan
|
||||
if (intensity < 0.75) return '#FFFF00'; // Yellow
|
||||
return '#FF0000'; // Red
|
||||
}}
|
||||
|
||||
// Function to clear all markers and polylines
|
||||
function clearMap() {{
|
||||
markers.forEach(marker => map.removeLayer(marker));
|
||||
polylines.forEach(polyline => map.removeLayer(polyline));
|
||||
markers = [];
|
||||
polylines = [];
|
||||
}}
|
||||
|
||||
// Function to fit map to bounds
|
||||
function fitToBounds() {{
|
||||
if (markers.length > 0) {{
|
||||
var group = new L.featureGroup(markers);
|
||||
map.fitBounds(group.getBounds().pad(0.1));
|
||||
}}
|
||||
}}
|
||||
|
||||
// Expose functions to global scope for C# access
|
||||
window.addLocationPin = addLocationPin;
|
||||
window.addHeatMapPolyline = addHeatMapPolyline;
|
||||
window.clearMap = clearMap;
|
||||
window.fitToBounds = fitToBounds;
|
||||
window.map = map;
|
||||
</script>
|
||||
</body>
|
||||
</html>";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a location pin to the map
|
||||
/// </summary>
|
||||
/// <param name="location">The location to add</param>
|
||||
/// <param name="label">The label for the pin</param>
|
||||
/// <param name="intensity">Heat intensity (0.0 to 1.0)</param>
|
||||
public async Task AddLocationPinAsync(Location location, string label, double intensity = 0.5)
|
||||
{
|
||||
try
|
||||
{
|
||||
var script = $"addLocationPin({location.Latitude}, {location.Longitude}, '{label}', {intensity});";
|
||||
await EvaluateJavaScriptAsync(script);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to add location pin to map");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a heat map polyline to the map
|
||||
/// </summary>
|
||||
/// <param name="coordinates">List of coordinates for the polyline</param>
|
||||
/// <param name="intensity">Heat intensity (0.0 to 1.0)</param>
|
||||
public async Task AddHeatMapPolylineAsync(List<Location> coordinates, double intensity = 0.5)
|
||||
{
|
||||
try
|
||||
{
|
||||
var coordString = string.Join(",", coordinates.Select(c => $"[{c.Latitude}, {c.Longitude}]"));
|
||||
var script = $"addHeatMapPolyline([{coordString}], {intensity});";
|
||||
await EvaluateJavaScriptAsync(script);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to add heat map polyline to map");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all markers and polylines from the map
|
||||
/// </summary>
|
||||
public async Task ClearMapAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await EvaluateJavaScriptAsync("clearMap();");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to clear map");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fits the map view to show all markers
|
||||
/// </summary>
|
||||
public async Task FitToBoundsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await EvaluateJavaScriptAsync("fitToBounds();");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to fit map to bounds");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the heat map visualization based on current location data.
|
||||
/// </summary>
|
||||
public async Task UpdateHeatMapAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ClearMapAsync();
|
||||
|
||||
if (!_locationData.Any())
|
||||
{
|
||||
_logger?.LogInformation("No location data to display for heat map.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Group nearby points to determine intensity
|
||||
var groupedLocations = GroupLocations(_locationData, 0.001); // Group within ~100 meters
|
||||
|
||||
// Create heat map polylines and pins
|
||||
foreach (var group in groupedLocations)
|
||||
{
|
||||
var intensity = Math.Min(1.0, (double)group.Count / 10.0); // Scale intensity
|
||||
|
||||
if (group.Count > 1)
|
||||
{
|
||||
// Create polyline for grouped points
|
||||
var coordinates = group.Select(loc => new Location(loc.Latitude, loc.Longitude)).ToList();
|
||||
await AddHeatMapPolylineAsync(coordinates, intensity);
|
||||
}
|
||||
|
||||
// Add a pin for each location (or group center)
|
||||
var pinLocation = new Location(group.First().Latitude, group.First().Longitude);
|
||||
await AddLocationPinAsync(pinLocation, $"Location {group.First().Id} (Intensity: {intensity:P0})", intensity);
|
||||
}
|
||||
|
||||
await FitToBoundsAsync();
|
||||
_logger?.LogInformation("Heat map updated with {Count} points.", _locationData.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Failed to update heat map");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Groups locations that are within a certain distance of each other.
|
||||
/// </summary>
|
||||
/// <param name="locations">The list of all location data.</param>
|
||||
/// <param name="tolerance">The maximum distance (in degrees) for grouping.</param>
|
||||
/// <returns>A list of grouped locations.</returns>
|
||||
private static List<List<LocationData>> GroupLocations(List<LocationData> locations, double tolerance)
|
||||
{
|
||||
var grouped = new List<List<LocationData>>();
|
||||
var processed = new HashSet<LocationData>();
|
||||
|
||||
foreach (var loc in locations)
|
||||
{
|
||||
if (processed.Contains(loc)) continue;
|
||||
|
||||
var currentGroup = new List<LocationData> { loc };
|
||||
processed.Add(loc);
|
||||
|
||||
foreach (var otherLoc in locations)
|
||||
{
|
||||
if (loc == otherLoc || processed.Contains(otherLoc)) continue;
|
||||
|
||||
var distance = Location.CalculateDistance(
|
||||
new Location(loc.Latitude, loc.Longitude),
|
||||
new Location(otherLoc.Latitude, otherLoc.Longitude),
|
||||
DistanceUnits.Kilometers);
|
||||
|
||||
if (distance < tolerance) // Roughly 100 meters
|
||||
{
|
||||
currentGroup.Add(otherLoc);
|
||||
processed.Add(otherLoc);
|
||||
}
|
||||
}
|
||||
grouped.Add(currentGroup);
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the visibility of heat map polylines.
|
||||
/// </summary>
|
||||
private void UpdateHeatMapVisibility()
|
||||
{
|
||||
// Implementation would require JavaScript communication
|
||||
// For now, we'll handle this in the UpdateHeatMapAsync method
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the visibility of individual location pins.
|
||||
/// </summary>
|
||||
private void UpdatePointsVisibility()
|
||||
{
|
||||
// Implementation would require JavaScript communication
|
||||
// For now, we'll handle this in the UpdateHeatMapAsync method
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads location data and updates the heat map
|
||||
/// </summary>
|
||||
/// <param name="locationData">The location data to display</param>
|
||||
public async Task LoadLocationDataAsync(List<LocationData> locationData)
|
||||
{
|
||||
_locationData = locationData;
|
||||
await UpdateHeatMapAsync();
|
||||
}
|
||||
}
|
||||
143
LocationTrackerApp/Data/LocationDbContext.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LocationTrackerApp.Models;
|
||||
|
||||
namespace LocationTrackerApp.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Entity Framework DbContext for managing location data in SQLite database
|
||||
/// </summary>
|
||||
public class LocationDbContext : DbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Database path for SQLite storage
|
||||
/// </summary>
|
||||
public string DatabasePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// DbSet for LocationData entities
|
||||
/// </summary>
|
||||
public DbSet<LocationData> LocationData { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of LocationDbContext
|
||||
/// </summary>
|
||||
/// <param name="databasePath">Path to the SQLite database file</param>
|
||||
public LocationDbContext(string databasePath)
|
||||
{
|
||||
DatabasePath = databasePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the database connection and options
|
||||
/// </summary>
|
||||
/// <param name="optionsBuilder">Options builder for configuring the database</param>
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlite($"Data Source={DatabasePath}");
|
||||
|
||||
// Enable sensitive data logging for development (remove in production)
|
||||
#if DEBUG
|
||||
optionsBuilder.EnableSensitiveDataLogging();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures entity relationships and constraints
|
||||
/// </summary>
|
||||
/// <param name="modelBuilder">Model builder for configuring entities</param>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// Configure LocationData entity
|
||||
modelBuilder.Entity<LocationData>(entity =>
|
||||
{
|
||||
// Set primary key
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
// Configure indexes for better query performance
|
||||
entity.HasIndex(e => e.Timestamp)
|
||||
.HasDatabaseName("IX_LocationData_Timestamp");
|
||||
|
||||
entity.HasIndex(e => e.SessionId)
|
||||
.HasDatabaseName("IX_LocationData_SessionId");
|
||||
|
||||
entity.HasIndex(e => new { e.Latitude, e.Longitude })
|
||||
.HasDatabaseName("IX_LocationData_Coordinates");
|
||||
|
||||
// Configure column types and constraints
|
||||
entity.Property(e => e.Latitude)
|
||||
.HasColumnType("REAL")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Longitude)
|
||||
.HasColumnType("REAL")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Accuracy)
|
||||
.HasColumnType("REAL")
|
||||
.HasDefaultValue(0.0);
|
||||
|
||||
entity.Property(e => e.Altitude)
|
||||
.HasColumnType("REAL");
|
||||
|
||||
entity.Property(e => e.Speed)
|
||||
.HasColumnType("REAL");
|
||||
|
||||
entity.Property(e => e.Timestamp)
|
||||
.HasColumnType("TEXT")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.SessionId)
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(100);
|
||||
|
||||
entity.Property(e => e.Notes)
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(500);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the database is created and up to date
|
||||
/// </summary>
|
||||
public async Task EnsureDatabaseCreatedAsync()
|
||||
{
|
||||
await Database.EnsureCreatedAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all location data from the database
|
||||
/// </summary>
|
||||
public async Task ClearAllLocationDataAsync()
|
||||
{
|
||||
await Database.ExecuteSqlRawAsync("DELETE FROM LocationData");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets location data within a specified time range
|
||||
/// </summary>
|
||||
/// <param name="startTime">Start time for the query</param>
|
||||
/// <param name="endTime">End time for the query</param>
|
||||
/// <returns>List of location data within the time range</returns>
|
||||
public async Task<List<LocationData>> GetLocationDataInTimeRangeAsync(DateTime startTime, DateTime endTime)
|
||||
{
|
||||
return await LocationData
|
||||
.Where(l => l.Timestamp >= startTime && l.Timestamp <= endTime)
|
||||
.OrderBy(l => l.Timestamp)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets location data for a specific session
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session identifier</param>
|
||||
/// <returns>List of location data for the session</returns>
|
||||
public async Task<List<LocationData>> GetLocationDataBySessionAsync(string sessionId)
|
||||
{
|
||||
return await LocationData
|
||||
.Where(l => l.SessionId == sessionId)
|
||||
.OrderBy(l => l.Timestamp)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
2
LocationTrackerApp/GlobalXmlns.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "LocationTrackerApp")]
|
||||
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "LocationTrackerApp.Pages")]
|
||||
80
LocationTrackerApp/LocationTrackerApp.csproj
Normal file
@@ -0,0 +1,80 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.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);net9.0-tizen</TargetFrameworks> -->
|
||||
|
||||
<!-- 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> -->
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>LocationTrackerApp</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>LocationTrackerApp</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.companyname.locationtrackerapp</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
<AndroidSdkDirectory Condition="'$(AndroidSdkDirectory)' == ''">/Users/carlos/Library/Android/sdk</AndroidSdkDirectory>
|
||||
<AndroidApiLevel>34</AndroidApiLevel>
|
||||
<AndroidTargetSdkVersion>34</AndroidTargetSdkVersion>
|
||||
|
||||
<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)" />
|
||||
|
||||
<!-- Configuration Files -->
|
||||
<None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="appsettings.Development.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
131
LocationTrackerApp/MainPage.xaml
Normal file
@@ -0,0 +1,131 @@
|
||||
<?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:components="clr-namespace:LocationTrackerApp.Components"
|
||||
x:Class="LocationTrackerApp.MainPage"
|
||||
Title="Location Tracker">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<Grid Grid.Row="0"
|
||||
BackgroundColor="#8B5CF6"
|
||||
HeightRequest="60">
|
||||
<Label Text="Location Tracker"
|
||||
FontSize="20"
|
||||
FontAttributes="Bold"
|
||||
TextColor="White"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center" />
|
||||
</Grid>
|
||||
|
||||
<!-- Map Container -->
|
||||
<Grid Grid.Row="1">
|
||||
<components:OpenStreetMapView x:Name="MainMap" />
|
||||
</Grid>
|
||||
|
||||
<!-- Bottom Controls -->
|
||||
<Grid Grid.Row="2"
|
||||
BackgroundColor="#F3F4F6"
|
||||
HeightRequest="100">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Start Tracking Button -->
|
||||
<Button Grid.Row="0" Grid.Column="0"
|
||||
x:Name="StartTrackingBtn"
|
||||
Text="Start Tracking"
|
||||
BackgroundColor="#10B981"
|
||||
TextColor="White"
|
||||
FontSize="11"
|
||||
FontAttributes="Bold"
|
||||
Margin="3,5,1,2"
|
||||
CornerRadius="20"
|
||||
Clicked="OnStartTrackingClicked" />
|
||||
|
||||
<!-- Stop Tracking Button -->
|
||||
<Button Grid.Row="0" Grid.Column="1"
|
||||
x:Name="StopTrackingBtn"
|
||||
Text="Stop Tracking"
|
||||
BackgroundColor="#EF4444"
|
||||
TextColor="White"
|
||||
FontSize="11"
|
||||
FontAttributes="Bold"
|
||||
Margin="1,5,1,2"
|
||||
CornerRadius="20"
|
||||
Clicked="OnStopTrackingClicked" />
|
||||
|
||||
<!-- Center Map Button -->
|
||||
<Button Grid.Row="0" Grid.Column="2"
|
||||
x:Name="CenterMapBtn"
|
||||
Text="Center Map"
|
||||
BackgroundColor="#3B82F6"
|
||||
TextColor="White"
|
||||
FontSize="11"
|
||||
FontAttributes="Bold"
|
||||
Margin="1,5,1,2"
|
||||
CornerRadius="20"
|
||||
Clicked="OnCenterMapClicked" />
|
||||
|
||||
<!-- Heat Map Button -->
|
||||
<Button Grid.Row="0" Grid.Column="3"
|
||||
x:Name="HeatMapBtn"
|
||||
Text="Demo Heat Map"
|
||||
BackgroundColor="#8B5CF6"
|
||||
TextColor="White"
|
||||
FontSize="11"
|
||||
FontAttributes="Bold"
|
||||
Margin="1,5,3,2"
|
||||
CornerRadius="20"
|
||||
Clicked="OnHeatMapClicked" />
|
||||
|
||||
<!-- Clear Demo Button -->
|
||||
<Button Grid.Row="1" Grid.Column="0"
|
||||
x:Name="ClearDemoBtn"
|
||||
Text="Clear Demo"
|
||||
BackgroundColor="#F59E0B"
|
||||
TextColor="White"
|
||||
FontSize="11"
|
||||
FontAttributes="Bold"
|
||||
Margin="3,2,1,5"
|
||||
CornerRadius="20"
|
||||
Clicked="OnClearDemoClicked" />
|
||||
|
||||
<!-- Reset Map Button -->
|
||||
<Button Grid.Row="1" Grid.Column="1"
|
||||
x:Name="ResetMapBtn"
|
||||
Text="Reset Map"
|
||||
BackgroundColor="#6B7280"
|
||||
TextColor="White"
|
||||
FontSize="11"
|
||||
FontAttributes="Bold"
|
||||
Margin="1,2,1,5"
|
||||
CornerRadius="20"
|
||||
Clicked="OnResetMapClicked" />
|
||||
|
||||
<!-- Location Count Label -->
|
||||
<Label Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2"
|
||||
x:Name="LocationCountLabel"
|
||||
Text="Locations: 0"
|
||||
FontSize="12"
|
||||
FontAttributes="Bold"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center"
|
||||
TextColor="#374151" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</ContentPage>
|
||||
269
LocationTrackerApp/MainPage.xaml.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using LocationTrackerApp.Components;
|
||||
using LocationTrackerApp.Models;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
private bool _isTracking = false;
|
||||
private List<LocationData> _trackedLocations = new();
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeMap();
|
||||
}
|
||||
|
||||
private void InitializeMap()
|
||||
{
|
||||
// The OpenStreetMapView initializes itself with a default location
|
||||
// Add some dummy location data to demonstrate heat map functionality
|
||||
AddDummyLocationData();
|
||||
}
|
||||
|
||||
private void AddDummyLocationData()
|
||||
{
|
||||
// Add dummy location data around San Francisco Bay Area to demonstrate heat map
|
||||
var dummyLocations = new List<LocationData>
|
||||
{
|
||||
// Golden Gate Bridge area (high frequency)
|
||||
new LocationData { Latitude = 37.8199, Longitude = -122.4783, Timestamp = DateTime.UtcNow.AddMinutes(-30) },
|
||||
new LocationData { Latitude = 37.8200, Longitude = -122.4780, Timestamp = DateTime.UtcNow.AddMinutes(-29) },
|
||||
new LocationData { Latitude = 37.8201, Longitude = -122.4777, Timestamp = DateTime.UtcNow.AddMinutes(-28) },
|
||||
new LocationData { Latitude = 37.8198, Longitude = -122.4785, Timestamp = DateTime.UtcNow.AddMinutes(-27) },
|
||||
new LocationData { Latitude = 37.8202, Longitude = -122.4775, Timestamp = DateTime.UtcNow.AddMinutes(-26) },
|
||||
|
||||
// Fisherman's Wharf area (medium frequency)
|
||||
new LocationData { Latitude = 37.8080, Longitude = -122.4177, Timestamp = DateTime.UtcNow.AddMinutes(-25) },
|
||||
new LocationData { Latitude = 37.8085, Longitude = -122.4175, Timestamp = DateTime.UtcNow.AddMinutes(-24) },
|
||||
new LocationData { Latitude = 37.8082, Longitude = -122.4180, Timestamp = DateTime.UtcNow.AddMinutes(-23) },
|
||||
|
||||
// Union Square area (medium frequency)
|
||||
new LocationData { Latitude = 37.7879, Longitude = -122.4075, Timestamp = DateTime.UtcNow.AddMinutes(-22) },
|
||||
new LocationData { Latitude = 37.7882, Longitude = -122.4078, Timestamp = DateTime.UtcNow.AddMinutes(-21) },
|
||||
new LocationData { Latitude = 37.7877, Longitude = -122.4072, Timestamp = DateTime.UtcNow.AddMinutes(-20) },
|
||||
|
||||
// Mission District area (low frequency)
|
||||
new LocationData { Latitude = 37.7599, Longitude = -122.4148, Timestamp = DateTime.UtcNow.AddMinutes(-19) },
|
||||
new LocationData { Latitude = 37.7602, Longitude = -122.4150, Timestamp = DateTime.UtcNow.AddMinutes(-18) },
|
||||
|
||||
// Castro District area (low frequency)
|
||||
new LocationData { Latitude = 37.7611, Longitude = -122.4350, Timestamp = DateTime.UtcNow.AddMinutes(-17) },
|
||||
|
||||
// SOMA area (medium frequency)
|
||||
new LocationData { Latitude = 37.7749, Longitude = -122.4194, Timestamp = DateTime.UtcNow.AddMinutes(-16) },
|
||||
new LocationData { Latitude = 37.7752, Longitude = -122.4197, Timestamp = DateTime.UtcNow.AddMinutes(-15) },
|
||||
new LocationData { Latitude = 37.7747, Longitude = -122.4191, Timestamp = DateTime.UtcNow.AddMinutes(-14) },
|
||||
new LocationData { Latitude = 37.7750, Longitude = -122.4194, Timestamp = DateTime.UtcNow.AddMinutes(-13) },
|
||||
|
||||
// Financial District area (high frequency)
|
||||
new LocationData { Latitude = 37.7946, Longitude = -122.3998, Timestamp = DateTime.UtcNow.AddMinutes(-12) },
|
||||
new LocationData { Latitude = 37.7949, Longitude = -122.4001, Timestamp = DateTime.UtcNow.AddMinutes(-11) },
|
||||
new LocationData { Latitude = 37.7944, Longitude = -122.3995, Timestamp = DateTime.UtcNow.AddMinutes(-10) },
|
||||
new LocationData { Latitude = 37.7952, Longitude = -122.4004, Timestamp = DateTime.UtcNow.AddMinutes(-9) },
|
||||
new LocationData { Latitude = 37.7947, Longitude = -122.3999, Timestamp = DateTime.UtcNow.AddMinutes(-8) },
|
||||
new LocationData { Latitude = 37.7950, Longitude = -122.4002, Timestamp = DateTime.UtcNow.AddMinutes(-7) },
|
||||
|
||||
// North Beach area (low frequency)
|
||||
new LocationData { Latitude = 37.8044, Longitude = -122.4098, Timestamp = DateTime.UtcNow.AddMinutes(-6) },
|
||||
|
||||
// Chinatown area (medium frequency)
|
||||
new LocationData { Latitude = 37.7941, Longitude = -122.4078, Timestamp = DateTime.UtcNow.AddMinutes(-5) },
|
||||
new LocationData { Latitude = 37.7944, Longitude = -122.4081, Timestamp = DateTime.UtcNow.AddMinutes(-4) },
|
||||
new LocationData { Latitude = 37.7938, Longitude = -122.4075, Timestamp = DateTime.UtcNow.AddMinutes(-3) },
|
||||
|
||||
// Marina District area (low frequency)
|
||||
new LocationData { Latitude = 37.8026, Longitude = -122.4430, Timestamp = DateTime.UtcNow.AddMinutes(-2) },
|
||||
|
||||
// Presidio area (low frequency)
|
||||
new LocationData { Latitude = 37.7989, Longitude = -122.4662, Timestamp = DateTime.UtcNow.AddMinutes(-1) }
|
||||
};
|
||||
|
||||
_trackedLocations.AddRange(dummyLocations);
|
||||
UpdateLocationCount();
|
||||
}
|
||||
|
||||
private void UpdateLocationCount()
|
||||
{
|
||||
LocationCountLabel.Text = $"Locations: {_trackedLocations.Count}";
|
||||
}
|
||||
|
||||
private async void OnStartTrackingClicked(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isTracking)
|
||||
{
|
||||
// Request location permissions
|
||||
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
await DisplayAlert("Permission Required", "Location permission is required to track your location.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start location tracking
|
||||
_isTracking = true;
|
||||
StartTrackingBtn.Text = "Tracking...";
|
||||
StartTrackingBtn.BackgroundColor = Colors.Orange;
|
||||
StopTrackingBtn.IsEnabled = true;
|
||||
|
||||
// Simulate location tracking
|
||||
_ = Task.Run(async () => await SimulateLocationTracking());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisplayAlert("Error", $"Failed to start tracking: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStopTrackingClicked(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isTracking)
|
||||
{
|
||||
_isTracking = false;
|
||||
StartTrackingBtn.Text = "Start Tracking";
|
||||
StartTrackingBtn.BackgroundColor = Colors.Green;
|
||||
StopTrackingBtn.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DisplayAlert("Error", $"Failed to stop tracking: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SimulateLocationTracking()
|
||||
{
|
||||
while (_isTracking)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get current location
|
||||
var location = await Geolocation.GetLocationAsync();
|
||||
if (location != null)
|
||||
{
|
||||
// Update UI on main thread
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
// Add location to tracked locations
|
||||
var locationData = new LocationData
|
||||
{
|
||||
Latitude = location.Latitude,
|
||||
Longitude = location.Longitude,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
_trackedLocations.Add(locationData);
|
||||
UpdateLocationCount();
|
||||
|
||||
// Add pin to OpenStreetMap
|
||||
await MainMap.AddLocationPinAsync(location, $"Current Location {_trackedLocations.Count}", 0.8);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle location errors
|
||||
System.Diagnostics.Debug.WriteLine($"Location error: {ex.Message}");
|
||||
}
|
||||
|
||||
// Wait 5 seconds before next update
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnCenterMapClicked(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Request location permissions if not already granted
|
||||
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
await DisplayAlert("Permission Required", "Location permission is required to center the map.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current location
|
||||
var location = await Geolocation.GetLocationAsync();
|
||||
if (location != null)
|
||||
{
|
||||
// Clear existing pins
|
||||
await MainMap.ClearMapAsync();
|
||||
|
||||
// Add a pin for current location
|
||||
await MainMap.AddLocationPinAsync(location, $"Your Location\nLat: {location.Latitude:F6}\nLng: {location.Longitude:F6}", 1.0);
|
||||
|
||||
await DisplayAlert("Location Found", $"Map centered on your location:\nLat: {location.Latitude:F6}\nLng: {location.Longitude:F6}", "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert("Error", "Unable to get your current location.", "OK");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisplayAlert("Error", $"Failed to center map: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnHeatMapClicked(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_trackedLocations.Any())
|
||||
{
|
||||
// Load tracked locations into the heat map
|
||||
await MainMap.LoadLocationDataAsync(_trackedLocations);
|
||||
await DisplayAlert("Heat Map", $"Heat map updated with {_trackedLocations.Count} locations.\n\nAreas with higher frequency appear in red/yellow, lower frequency in blue.", "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert("No Data", "No location data available. Start tracking to create a heat map.", "OK");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisplayAlert("Error", $"Failed to update heat map: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnClearDemoClicked(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await DisplayAlert("Clear Demo Data", "Are you sure you want to clear all demo location data?", "Yes", "No");
|
||||
if (result)
|
||||
{
|
||||
_trackedLocations.Clear();
|
||||
await MainMap.ClearMapAsync();
|
||||
UpdateLocationCount();
|
||||
await DisplayAlert("Demo Cleared", "All demo location data has been cleared.", "OK");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisplayAlert("Error", $"Failed to clear demo data: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnResetMapClicked(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await MainMap.ClearMapAsync();
|
||||
await DisplayAlert("Map Reset", "Map has been reset to default view.", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisplayAlert("Error", $"Failed to reset map: {ex.Message}", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
45
LocationTrackerApp/MauiProgram.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LocationTrackerApp.Services;
|
||||
using LocationTrackerApp.Data;
|
||||
using LocationTrackerApp.ViewModels;
|
||||
using LocationTrackerApp.Views;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
// Configure logging
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
|
||||
// Register services
|
||||
builder.Services.AddSingleton<LocationDbContext>(provider =>
|
||||
{
|
||||
var dbPath = Path.Combine(FileSystem.AppDataDirectory, "location_tracker.db");
|
||||
return new LocationDbContext(dbPath);
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<ILocationService, LocationService>();
|
||||
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||
|
||||
// Register view models
|
||||
builder.Services.AddTransient<MainViewModel>();
|
||||
|
||||
// Register views
|
||||
builder.Services.AddTransient<MainView>();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
58
LocationTrackerApp/Models/LocationData.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LocationTrackerApp.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a location data point stored in the database
|
||||
/// </summary>
|
||||
public class LocationData
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for the location data point
|
||||
/// </summary>
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Latitude coordinate of the location
|
||||
/// </summary>
|
||||
[Required]
|
||||
public double Latitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Longitude coordinate of the location
|
||||
/// </summary>
|
||||
[Required]
|
||||
public double Longitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Accuracy of the location reading in meters
|
||||
/// </summary>
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Altitude of the location in meters above sea level
|
||||
/// </summary>
|
||||
public double? Altitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Speed of the device at the time of recording in meters per second
|
||||
/// </summary>
|
||||
public double? Speed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp when the location was recorded
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Session identifier to group related location points
|
||||
/// </summary>
|
||||
public string? SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional metadata or notes for this location point
|
||||
/// </summary>
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
10
LocationTrackerApp/Platforms/Android/AndroidManifest.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?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" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
</manifest>
|
||||
10
LocationTrackerApp/Platforms/Android/MainActivity.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
||||
15
LocationTrackerApp/Platforms/Android/MainApplication.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#512BD4</color>
|
||||
<color name="colorPrimaryDark">#2B0B98</color>
|
||||
<color name="colorAccent">#2B0B98</color>
|
||||
</resources>
|
||||
9
LocationTrackerApp/Platforms/MacCatalyst/AppDelegate.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
14
LocationTrackerApp/Platforms/MacCatalyst/Entitlements.plist
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
|
||||
<dict>
|
||||
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
38
LocationTrackerApp/Platforms/MacCatalyst/Info.plist
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- The Mac App Store requires you specify if the app uses encryption. -->
|
||||
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
|
||||
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
|
||||
<!-- Please indicate <true/> or <false/> here. -->
|
||||
|
||||
<!-- Specify the category for your app here. -->
|
||||
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
|
||||
<!-- <key>LSApplicationCategoryType</key> -->
|
||||
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
||||
15
LocationTrackerApp/Platforms/MacCatalyst/Program.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
16
LocationTrackerApp/Platforms/Tizen/Main.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Hosting;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
class Program : MauiApplication
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var app = new Program();
|
||||
app.Run(args);
|
||||
}
|
||||
}
|
||||
15
LocationTrackerApp/Platforms/Tizen/tizen-manifest.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="9" xmlns="http://tizen.org/ns/packages">
|
||||
<profile name="common" />
|
||||
<ui-application appid="maui-application-id-placeholder" exec="LocationTrackerApp.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||
<label>maui-application-title-placeholder</label>
|
||||
<icon>maui-appicon-placeholder</icon>
|
||||
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
||||
</ui-application>
|
||||
<shortcut-list />
|
||||
<privileges>
|
||||
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||
</privileges>
|
||||
<dependencies />
|
||||
<provides-appdefined-privileges />
|
||||
</manifest>
|
||||
8
LocationTrackerApp/Platforms/Windows/App.xaml
Normal file
@@ -0,0 +1,8 @@
|
||||
<maui:MauiWinUIApplication
|
||||
x:Class="LocationTrackerApp.WinUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:maui="using:Microsoft.Maui"
|
||||
xmlns:local="using:LocationTrackerApp.WinUI">
|
||||
|
||||
</maui:MauiWinUIApplication>
|
||||
24
LocationTrackerApp/Platforms/Windows/App.xaml.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace LocationTrackerApp.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : MauiWinUIApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
|
||||
15
LocationTrackerApp/Platforms/Windows/app.manifest
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="LocationTrackerApp.WinUI.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
9
LocationTrackerApp/Platforms/iOS/AppDelegate.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
38
LocationTrackerApp/Platforms/iOS/Info.plist
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This app needs access to location to track your movement and create heat maps.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>This app needs access to location to track your movement and create heat maps.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>This app needs access to location to track your movement and create heat maps.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
15
LocationTrackerApp/Platforms/iOS/Program.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace LocationTrackerApp;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
|
||||
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
|
||||
|
||||
You are responsible for adding extra entries as needed for your application.
|
||||
|
||||
More information: https://aka.ms/maui-privacy-manifest
|
||||
-->
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<!--
|
||||
The entry below is only needed when you're using the Preferences API in your app.
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict> -->
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
8
LocationTrackerApp/Properties/launchSettings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "Project",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
||||
4
LocationTrackerApp/Resources/AppIcon/appicon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 231 B |
8
LocationTrackerApp/Resources/AppIcon/appiconfg.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
LocationTrackerApp/Resources/Fonts/OpenSans-Regular.ttf
Normal file
BIN
LocationTrackerApp/Resources/Fonts/OpenSans-Semibold.ttf
Normal file
BIN
LocationTrackerApp/Resources/Images/dotnet_bot.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
15
LocationTrackerApp/Resources/Raw/AboutAssets.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
Any raw assets you want to be deployed with your application can be placed in
|
||||
this directory (and child directories). Deployment of the asset to your application
|
||||
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
|
||||
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
|
||||
These files will be deployed with your package and will be accessible using Essentials:
|
||||
|
||||
async Task LoadMauiAsset()
|
||||
{
|
||||
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
var contents = reader.ReadToEnd();
|
||||
}
|
||||
8
LocationTrackerApp/Resources/Splash/splash.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
45
LocationTrackerApp/Resources/Styles/Colors.xaml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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">
|
||||
|
||||
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
|
||||
|
||||
<Color x:Key="Primary">#512BD4</Color>
|
||||
<Color x:Key="PrimaryDark">#ac99ea</Color>
|
||||
<Color x:Key="PrimaryDarkText">#242424</Color>
|
||||
<Color x:Key="Secondary">#DFD8F7</Color>
|
||||
<Color x:Key="SecondaryDarkText">#9880e5</Color>
|
||||
<Color x:Key="Tertiary">#2B0B98</Color>
|
||||
|
||||
<Color x:Key="White">White</Color>
|
||||
<Color x:Key="Black">Black</Color>
|
||||
<Color x:Key="Magenta">#D600AA</Color>
|
||||
<Color x:Key="MidnightBlue">#190649</Color>
|
||||
<Color x:Key="OffBlack">#1f1f1f</Color>
|
||||
|
||||
<Color x:Key="Gray100">#E1E1E1</Color>
|
||||
<Color x:Key="Gray200">#C8C8C8</Color>
|
||||
<Color x:Key="Gray300">#ACACAC</Color>
|
||||
<Color x:Key="Gray400">#919191</Color>
|
||||
<Color x:Key="Gray500">#6E6E6E</Color>
|
||||
<Color x:Key="Gray600">#404040</Color>
|
||||
<Color x:Key="Gray900">#212121</Color>
|
||||
<Color x:Key="Gray950">#141414</Color>
|
||||
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
|
||||
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
|
||||
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
|
||||
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
|
||||
|
||||
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
|
||||
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
|
||||
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
|
||||
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
|
||||
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
|
||||
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
|
||||
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
|
||||
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
|
||||
</ResourceDictionary>
|
||||
444
LocationTrackerApp/Resources/Styles/Styles.xaml
Normal file
@@ -0,0 +1,444 @@
|
||||
<?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">
|
||||
|
||||
<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="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"/>
|
||||
<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="TitleBar">
|
||||
<Setter Property="MinimumHeightRequest" Value="32"/>
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="TitleActiveStates">
|
||||
<VisualState x:Name="TitleBarTitleActive">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor" Value="Transparent" />
|
||||
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="TitleBarTitleInactive">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
|
||||
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
|
||||
</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}}" />
|
||||
</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>
|
||||
|
||||
</ResourceDictionary>
|
||||
69
LocationTrackerApp/Services/ConfigurationService.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LocationTrackerApp.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides configuration services for the application
|
||||
/// </summary>
|
||||
public class ConfigurationService : IConfigurationService
|
||||
{
|
||||
private readonly string _googleMapsApiKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ConfigurationService
|
||||
/// </summary>
|
||||
public ConfigurationService()
|
||||
{
|
||||
_googleMapsApiKey = LoadGoogleMapsApiKey();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Google Maps API key
|
||||
/// </summary>
|
||||
public string GetGoogleMapsApiKey()
|
||||
{
|
||||
return _googleMapsApiKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the Google Maps API key from configuration
|
||||
/// </summary>
|
||||
private string LoadGoogleMapsApiKey()
|
||||
{
|
||||
try
|
||||
{
|
||||
// First, try to get from environment variable
|
||||
var envApiKey = Environment.GetEnvironmentVariable("GoogleMapsApiKey");
|
||||
if (!string.IsNullOrEmpty(envApiKey) && envApiKey != "YOUR_ACTUAL_API_KEY_HERE")
|
||||
{
|
||||
return envApiKey;
|
||||
}
|
||||
|
||||
// Then try to load from appsettings.json
|
||||
var configPath = Path.Combine(FileSystem.AppDataDirectory, "appsettings.json");
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
var json = File.ReadAllText(configPath);
|
||||
var config = JsonSerializer.Deserialize<JsonElement>(json);
|
||||
|
||||
if (config.TryGetProperty("GoogleMaps", out var googleMaps) &&
|
||||
googleMaps.TryGetProperty("ApiKey", out var apiKeyElement))
|
||||
{
|
||||
var apiKey = apiKeyElement.GetString();
|
||||
if (!string.IsNullOrEmpty(apiKey) && apiKey != "YOUR_ACTUAL_API_KEY_HERE")
|
||||
{
|
||||
return apiKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default demo key
|
||||
return "AIzaSyDemoKeyForDevelopment123456789";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Return demo key if anything goes wrong
|
||||
return "AIzaSyDemoKeyForDevelopment123456789";
|
||||
}
|
||||
}
|
||||
}
|
||||
12
LocationTrackerApp/Services/IConfigurationService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace LocationTrackerApp.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for configuration services
|
||||
/// </summary>
|
||||
public interface IConfigurationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Google Maps API key
|
||||
/// </summary>
|
||||
string GetGoogleMapsApiKey();
|
||||
}
|
||||
60
LocationTrackerApp/Services/ILocationService.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using LocationTrackerApp.Models;
|
||||
|
||||
namespace LocationTrackerApp.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for location tracking service
|
||||
/// </summary>
|
||||
public interface ILocationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Event fired when a new location is received
|
||||
/// </summary>
|
||||
event EventHandler<LocationData>? LocationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when location tracking status changes
|
||||
/// </summary>
|
||||
event EventHandler<bool>? TrackingStatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current location tracking status
|
||||
/// </summary>
|
||||
bool IsTracking { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current session ID
|
||||
/// </summary>
|
||||
string? CurrentSessionId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts location tracking
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Optional session ID for grouping location data</param>
|
||||
/// <returns>Task representing the async operation</returns>
|
||||
Task StartTrackingAsync(string? sessionId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Stops location tracking
|
||||
/// </summary>
|
||||
/// <returns>Task representing the async operation</returns>
|
||||
Task StopTrackingAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current location
|
||||
/// </summary>
|
||||
/// <returns>Current location data or null if not available</returns>
|
||||
Task<LocationData?> GetCurrentLocationAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Requests location permissions from the user
|
||||
/// </summary>
|
||||
/// <returns>True if permissions are granted, false otherwise</returns>
|
||||
Task<bool> RequestLocationPermissionsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if location permissions are granted
|
||||
/// </summary>
|
||||
/// <returns>True if permissions are granted, false otherwise</returns>
|
||||
Task<bool> HasLocationPermissionsAsync();
|
||||
}
|
||||
316
LocationTrackerApp/Services/LocationService.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LocationTrackerApp.Models;
|
||||
using LocationTrackerApp.Data;
|
||||
|
||||
namespace LocationTrackerApp.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for tracking user location and storing data in SQLite database
|
||||
/// </summary>
|
||||
public class LocationService : ILocationService, IDisposable
|
||||
{
|
||||
private readonly LocationDbContext _dbContext;
|
||||
private readonly ILogger<LocationService> _logger;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private bool _isTracking;
|
||||
private string? _currentSessionId;
|
||||
private LocationData? _lastKnownLocation;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a new location is received
|
||||
/// </summary>
|
||||
public event EventHandler<LocationData>? LocationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when location tracking status changes
|
||||
/// </summary>
|
||||
public event EventHandler<bool>? TrackingStatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current location tracking status
|
||||
/// </summary>
|
||||
public bool IsTracking => _isTracking;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current session ID
|
||||
/// </summary>
|
||||
public string? CurrentSessionId => _currentSessionId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of LocationService
|
||||
/// </summary>
|
||||
/// <param name="dbContext">Database context for storing location data</param>
|
||||
/// <param name="logger">Logger for recording service events</param>
|
||||
public LocationService(LocationDbContext dbContext, ILogger<LocationService> logger)
|
||||
{
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts location tracking
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Optional session ID for grouping location data</param>
|
||||
/// <returns>Task representing the async operation</returns>
|
||||
public async Task StartTrackingAsync(string? sessionId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isTracking)
|
||||
{
|
||||
_logger.LogWarning("Location tracking is already active");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check permissions first
|
||||
if (!await HasLocationPermissionsAsync())
|
||||
{
|
||||
var granted = await RequestLocationPermissionsAsync();
|
||||
if (!granted)
|
||||
{
|
||||
_logger.LogError("Location permissions not granted");
|
||||
throw new UnauthorizedAccessException("Location permissions are required for tracking");
|
||||
}
|
||||
}
|
||||
|
||||
_currentSessionId = sessionId ?? Guid.NewGuid().ToString();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_logger.LogInformation("Starting location tracking with session ID: {SessionId}", _currentSessionId);
|
||||
|
||||
// Start location tracking
|
||||
var request = new GeolocationRequest
|
||||
{
|
||||
DesiredAccuracy = GeolocationAccuracy.Best,
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
|
||||
// Start continuous location updates
|
||||
_ = Task.Run(async () => await TrackLocationContinuouslyAsync(_cancellationTokenSource.Token));
|
||||
|
||||
_isTracking = true;
|
||||
OnTrackingStatusChanged(true);
|
||||
|
||||
_logger.LogInformation("Location tracking started successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to start location tracking");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops location tracking
|
||||
/// </summary>
|
||||
/// <returns>Task representing the async operation</returns>
|
||||
public async Task StopTrackingAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isTracking)
|
||||
{
|
||||
_logger.LogWarning("Location tracking is not active");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Stopping location tracking");
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_isTracking = false;
|
||||
_currentSessionId = null;
|
||||
OnTrackingStatusChanged(false);
|
||||
|
||||
_logger.LogInformation("Location tracking stopped successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to stop location tracking");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current location
|
||||
/// </summary>
|
||||
/// <returns>Current location data or null if not available</returns>
|
||||
public async Task<LocationData?> GetCurrentLocationAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await HasLocationPermissionsAsync())
|
||||
{
|
||||
_logger.LogWarning("Location permissions not available");
|
||||
return null;
|
||||
}
|
||||
|
||||
var request = new GeolocationRequest
|
||||
{
|
||||
DesiredAccuracy = GeolocationAccuracy.Best,
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
|
||||
var location = await Geolocation.GetLocationAsync(request);
|
||||
if (location == null)
|
||||
{
|
||||
_logger.LogWarning("Unable to get current location");
|
||||
return null;
|
||||
}
|
||||
|
||||
var locationData = new LocationData
|
||||
{
|
||||
Latitude = location.Latitude,
|
||||
Longitude = location.Longitude,
|
||||
Accuracy = location.Accuracy ?? 0,
|
||||
Altitude = location.Altitude,
|
||||
Speed = location.Speed,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
SessionId = _currentSessionId
|
||||
};
|
||||
|
||||
_lastKnownLocation = locationData;
|
||||
return locationData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get current location");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests location permissions from the user
|
||||
/// </summary>
|
||||
/// <returns>True if permissions are granted, false otherwise</returns>
|
||||
public async Task<bool> RequestLocationPermissionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
|
||||
var granted = status == PermissionStatus.Granted;
|
||||
|
||||
_logger.LogInformation("Location permission request result: {Status}", status);
|
||||
return granted;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to request location permissions");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if location permissions are granted
|
||||
/// </summary>
|
||||
/// <returns>True if permissions are granted, false otherwise</returns>
|
||||
public async Task<bool> HasLocationPermissionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
|
||||
return status == PermissionStatus.Granted;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to check location permissions");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continuously tracks location updates
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token for stopping the tracking</param>
|
||||
private async Task TrackLocationContinuouslyAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested && _isTracking)
|
||||
{
|
||||
var locationData = await GetCurrentLocationAsync();
|
||||
if (locationData != null)
|
||||
{
|
||||
// Save to database
|
||||
await SaveLocationDataAsync(locationData);
|
||||
|
||||
// Notify subscribers
|
||||
OnLocationChanged(locationData);
|
||||
}
|
||||
|
||||
// Wait before next update (adjust interval as needed)
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("Location tracking cancelled");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in continuous location tracking");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves location data to the database
|
||||
/// </summary>
|
||||
/// <param name="locationData">Location data to save</param>
|
||||
private async Task SaveLocationDataAsync(LocationData locationData)
|
||||
{
|
||||
try
|
||||
{
|
||||
_dbContext.LocationData.Add(locationData);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogDebug("Location data saved: Lat={Latitude}, Lng={Longitude}, Time={Timestamp}",
|
||||
locationData.Latitude, locationData.Longitude, locationData.Timestamp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to save location data to database");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the LocationChanged event
|
||||
/// </summary>
|
||||
/// <param name="locationData">The location data that changed</param>
|
||||
protected virtual void OnLocationChanged(LocationData locationData)
|
||||
{
|
||||
LocationChanged?.Invoke(this, locationData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the TrackingStatusChanged event
|
||||
/// </summary>
|
||||
/// <param name="isTracking">Current tracking status</param>
|
||||
protected virtual void OnTrackingStatusChanged(bool isTracking)
|
||||
{
|
||||
TrackingStatusChanged?.Invoke(this, isTracking);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the service and stops tracking
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isTracking)
|
||||
{
|
||||
StopTrackingAsync().Wait();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error disposing LocationService");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
446
LocationTrackerApp/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,446 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LocationTrackerApp.Models;
|
||||
using LocationTrackerApp.Services;
|
||||
using LocationTrackerApp.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace LocationTrackerApp.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// View model for the main view of the location tracking application
|
||||
/// </summary>
|
||||
public class MainViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private readonly ILocationService _locationService;
|
||||
private readonly LocationDbContext _dbContext;
|
||||
private readonly ILogger<MainViewModel> _logger;
|
||||
private bool _isTracking;
|
||||
private bool _isHeatMapVisible = true;
|
||||
private bool _isPointsVisible = true;
|
||||
private string _trackingStatusText = "Not Tracking";
|
||||
private string _currentLocationText = "No location";
|
||||
private string _locationCountText = "0 points";
|
||||
private string _trackingButtonText = "Start Tracking";
|
||||
private Color _trackingButtonColor = Colors.Green;
|
||||
private LocationData? _currentLocation;
|
||||
private int _locationCount;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a property value changes
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether location tracking is active
|
||||
/// </summary>
|
||||
public bool IsTracking
|
||||
{
|
||||
get => _isTracking;
|
||||
set
|
||||
{
|
||||
if (_isTracking != value)
|
||||
{
|
||||
_isTracking = value;
|
||||
OnPropertyChanged();
|
||||
UpdateTrackingUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the heat map visualization is visible
|
||||
/// </summary>
|
||||
public bool IsHeatMapVisible
|
||||
{
|
||||
get => _isHeatMapVisible;
|
||||
set
|
||||
{
|
||||
if (_isHeatMapVisible != value)
|
||||
{
|
||||
_isHeatMapVisible = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether individual location points are visible
|
||||
/// </summary>
|
||||
public bool IsPointsVisible
|
||||
{
|
||||
get => _isPointsVisible;
|
||||
set
|
||||
{
|
||||
if (_isPointsVisible != value)
|
||||
{
|
||||
_isPointsVisible = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tracking status text
|
||||
/// </summary>
|
||||
public string TrackingStatusText
|
||||
{
|
||||
get => _trackingStatusText;
|
||||
set
|
||||
{
|
||||
if (_trackingStatusText != value)
|
||||
{
|
||||
_trackingStatusText = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current location text
|
||||
/// </summary>
|
||||
public string CurrentLocationText
|
||||
{
|
||||
get => _currentLocationText;
|
||||
set
|
||||
{
|
||||
if (_currentLocationText != value)
|
||||
{
|
||||
_currentLocationText = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location count text
|
||||
/// </summary>
|
||||
public string LocationCountText
|
||||
{
|
||||
get => _locationCountText;
|
||||
set
|
||||
{
|
||||
if (_locationCountText != value)
|
||||
{
|
||||
_locationCountText = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tracking button text
|
||||
/// </summary>
|
||||
public string TrackingButtonText
|
||||
{
|
||||
get => _trackingButtonText;
|
||||
set
|
||||
{
|
||||
if (_trackingButtonText != value)
|
||||
{
|
||||
_trackingButtonText = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tracking button color
|
||||
/// </summary>
|
||||
public Color TrackingButtonColor
|
||||
{
|
||||
get => _trackingButtonColor;
|
||||
set
|
||||
{
|
||||
if (_trackingButtonColor != value)
|
||||
{
|
||||
_trackingButtonColor = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command to toggle location tracking
|
||||
/// </summary>
|
||||
public ICommand ToggleTrackingCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command to toggle heat map visibility
|
||||
/// </summary>
|
||||
public ICommand ToggleHeatMapCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command to toggle points visibility
|
||||
/// </summary>
|
||||
public ICommand TogglePointsCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command to center map on user location
|
||||
/// </summary>
|
||||
public ICommand CenterOnUserCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command to clear all location data
|
||||
/// </summary>
|
||||
public ICommand ClearDataCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command to load location data
|
||||
/// </summary>
|
||||
public ICommand LoadDataCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command to open settings
|
||||
/// </summary>
|
||||
public ICommand SettingsCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of MainViewModel
|
||||
/// </summary>
|
||||
/// <param name="locationService">Location tracking service</param>
|
||||
/// <param name="dbContext">Database context for location data</param>
|
||||
/// <param name="logger">Logger for recording events</param>
|
||||
public MainViewModel(ILocationService locationService, LocationDbContext dbContext, ILogger<MainViewModel> logger)
|
||||
{
|
||||
_locationService = locationService ?? throw new ArgumentNullException(nameof(locationService));
|
||||
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// Initialize commands
|
||||
ToggleTrackingCommand = new Command(async () => await ToggleTrackingAsync());
|
||||
ToggleHeatMapCommand = new Command(() => IsHeatMapVisible = !IsHeatMapVisible);
|
||||
TogglePointsCommand = new Command(() => IsPointsVisible = !IsPointsVisible);
|
||||
CenterOnUserCommand = new Command(async () => await CenterOnUserAsync());
|
||||
ClearDataCommand = new Command(async () => await ClearDataAsync());
|
||||
LoadDataCommand = new Command(async () => await LoadDataAsync());
|
||||
SettingsCommand = new Command(async () => await OpenSettingsAsync());
|
||||
|
||||
// Subscribe to location service events
|
||||
_locationService.LocationChanged += OnLocationChanged;
|
||||
_locationService.TrackingStatusChanged += OnTrackingStatusChanged;
|
||||
|
||||
// Initialize UI
|
||||
UpdateTrackingUI();
|
||||
_ = Task.Run(async () => await LoadLocationCountAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles location tracking on/off
|
||||
/// </summary>
|
||||
private async Task ToggleTrackingAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsTracking)
|
||||
{
|
||||
await _locationService.StopTrackingAsync();
|
||||
_logger.LogInformation("Location tracking stopped by user");
|
||||
}
|
||||
else
|
||||
{
|
||||
await _locationService.StartTrackingAsync();
|
||||
_logger.LogInformation("Location tracking started by user");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to toggle location tracking");
|
||||
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to toggle location tracking", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Centers the map on the user's current location
|
||||
/// </summary>
|
||||
private async Task CenterOnUserAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var location = await _locationService.GetCurrentLocationAsync();
|
||||
if (location != null)
|
||||
{
|
||||
// This would need to be implemented in the HeatMapView
|
||||
_logger.LogInformation("Centering map on user location: {Latitude}, {Longitude}",
|
||||
location.Latitude, location.Longitude);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Unable to get current location", "OK");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to center on user location");
|
||||
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to center on user location", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all location data from the database
|
||||
/// </summary>
|
||||
private async Task ClearDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await Application.Current!.Windows[0].Page!.DisplayAlert(
|
||||
"Clear Data",
|
||||
"Are you sure you want to clear all location data? This action cannot be undone.",
|
||||
"Yes",
|
||||
"No");
|
||||
|
||||
if (result)
|
||||
{
|
||||
await _dbContext.ClearAllLocationDataAsync();
|
||||
_locationCount = 0;
|
||||
LocationCountText = "0 points";
|
||||
_logger.LogInformation("All location data cleared by user");
|
||||
await Application.Current.Windows[0].Page!.DisplayAlert("Success", "All location data has been cleared", "OK");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to clear location data");
|
||||
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to clear location data", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads location data and updates the UI
|
||||
/// </summary>
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadLocationCountAsync();
|
||||
_logger.LogInformation("Location data loaded by user");
|
||||
await Application.Current!.Windows[0].Page!.DisplayAlert("Success", "Location data has been loaded", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load location data");
|
||||
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to load location data", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the settings page
|
||||
/// </summary>
|
||||
private async Task OpenSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// This would navigate to a settings page
|
||||
_logger.LogInformation("Settings page requested by user");
|
||||
await Application.Current!.Windows[0].Page!.DisplayAlert("Settings", "Settings page coming soon!", "OK");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to open settings");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles location changed events from the location service
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="locationData">New location data</param>
|
||||
private void OnLocationChanged(object? sender, LocationData locationData)
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentLocation = locationData;
|
||||
CurrentLocationText = $"Lat: {locationData.Latitude:F6}, Lng: {locationData.Longitude:F6}";
|
||||
|
||||
// Update location count
|
||||
_locationCount++;
|
||||
LocationCountText = $"{_locationCount} points";
|
||||
|
||||
_logger.LogDebug("Location updated: {Latitude}, {Longitude}", locationData.Latitude, locationData.Longitude);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to handle location changed event");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles tracking status changed events from the location service
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="isTracking">Current tracking status</param>
|
||||
private void OnTrackingStatusChanged(object? sender, bool isTracking)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsTracking = isTracking;
|
||||
_logger.LogInformation("Tracking status changed: {IsTracking}", isTracking);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to handle tracking status changed event");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tracking-related UI elements
|
||||
/// </summary>
|
||||
private void UpdateTrackingUI()
|
||||
{
|
||||
if (IsTracking)
|
||||
{
|
||||
TrackingStatusText = "Tracking Active";
|
||||
TrackingButtonText = "Stop Tracking";
|
||||
TrackingButtonColor = Colors.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
TrackingStatusText = "Not Tracking";
|
||||
TrackingButtonText = "Start Tracking";
|
||||
TrackingButtonColor = Colors.Green;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the current location count from the database
|
||||
/// </summary>
|
||||
private async Task LoadLocationCountAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_locationCount = await _dbContext.LocationData.CountAsync();
|
||||
LocationCountText = $"{_locationCount} points";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load location count");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the PropertyChanged event
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property that changed</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the view model and unsubscribes from events
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_locationService.LocationChanged -= OnLocationChanged;
|
||||
_locationService.TrackingStatusChanged -= OnTrackingStatusChanged;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error disposing MainViewModel");
|
||||
}
|
||||
}
|
||||
}
|
||||
167
LocationTrackerApp/Views/MainView.xaml
Normal file
@@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage x:Class="LocationTrackerApp.Views.MainView"
|
||||
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:components="clr-namespace:LocationTrackerApp.Components"
|
||||
xmlns:viewmodels="clr-namespace:LocationTrackerApp.ViewModels"
|
||||
x:DataType="viewmodels:MainViewModel"
|
||||
Title="MainView"
|
||||
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<Grid Grid.Row="0"
|
||||
BackgroundColor="#8B5CF6"
|
||||
HeightRequest="60">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0"
|
||||
Text="Location Tracker"
|
||||
FontSize="20"
|
||||
FontAttributes="Bold"
|
||||
TextColor="White"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<Button Grid.Column="1"
|
||||
Text="Settings"
|
||||
BackgroundColor="Transparent"
|
||||
TextColor="White"
|
||||
FontSize="14"
|
||||
Margin="0,0,10,0"
|
||||
VerticalOptions="Center"
|
||||
Command="{Binding SettingsCommand}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Map Container -->
|
||||
<Grid Grid.Row="1">
|
||||
<Microsoft.Maui.Controls.Maps.Map MapType="Street"
|
||||
IsShowingUser="True" />
|
||||
|
||||
<!-- Overlay Controls -->
|
||||
<Grid VerticalOptions="Start"
|
||||
HorizontalOptions="End"
|
||||
Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Tracking Status -->
|
||||
<Frame Grid.Row="0"
|
||||
BackgroundColor="Black"
|
||||
Opacity="0.7"
|
||||
CornerRadius="20"
|
||||
Padding="10,5"
|
||||
Margin="0,0,0,5">
|
||||
<Label Text="{Binding TrackingStatusText}"
|
||||
TextColor="White"
|
||||
FontSize="12"
|
||||
HorizontalOptions="Center" />
|
||||
</Frame>
|
||||
|
||||
<!-- Location Info -->
|
||||
<Frame Grid.Row="1"
|
||||
BackgroundColor="Black"
|
||||
Opacity="0.7"
|
||||
CornerRadius="20"
|
||||
Padding="10,5"
|
||||
Margin="0,0,0,5">
|
||||
<StackLayout>
|
||||
<Label Text="{Binding CurrentLocationText}"
|
||||
TextColor="White"
|
||||
FontSize="10"
|
||||
HorizontalOptions="Center" />
|
||||
<Label Text="{Binding LocationCountText}"
|
||||
TextColor="White"
|
||||
FontSize="10"
|
||||
HorizontalOptions="Center" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
|
||||
<!-- Map Controls -->
|
||||
<Frame Grid.Row="2"
|
||||
BackgroundColor="Black"
|
||||
Opacity="0.7"
|
||||
CornerRadius="20"
|
||||
Padding="5">
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Button Text="📍"
|
||||
BackgroundColor="Transparent"
|
||||
TextColor="White"
|
||||
FontSize="16"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
Command="{Binding TogglePointsCommand}" />
|
||||
<Button Text="🔥"
|
||||
BackgroundColor="Transparent"
|
||||
TextColor="White"
|
||||
FontSize="16"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
Command="{Binding ToggleHeatMapCommand}" />
|
||||
<Button Text="🎯"
|
||||
BackgroundColor="Transparent"
|
||||
TextColor="White"
|
||||
FontSize="16"
|
||||
WidthRequest="40"
|
||||
HeightRequest="40"
|
||||
Command="{Binding CenterOnUserCommand}" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Bottom Controls -->
|
||||
<Grid Grid.Row="2"
|
||||
BackgroundColor="#F3F4F6"
|
||||
HeightRequest="80">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Start/Stop Tracking Button -->
|
||||
<Button Grid.Column="0"
|
||||
Text="{Binding TrackingButtonText}"
|
||||
BackgroundColor="{Binding TrackingButtonColor}"
|
||||
TextColor="White"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold"
|
||||
Margin="10,10,5,10"
|
||||
CornerRadius="25"
|
||||
Command="{Binding ToggleTrackingCommand}" />
|
||||
|
||||
<!-- Clear Data Button -->
|
||||
<Button Grid.Column="1"
|
||||
Text="Clear Data"
|
||||
BackgroundColor="#EF4444"
|
||||
TextColor="White"
|
||||
FontSize="14"
|
||||
Margin="5,10,5,10"
|
||||
CornerRadius="25"
|
||||
Command="{Binding ClearDataCommand}" />
|
||||
|
||||
<!-- Load Data Button -->
|
||||
<Button Grid.Column="2"
|
||||
Text="Load Data"
|
||||
BackgroundColor="#10B981"
|
||||
TextColor="White"
|
||||
FontSize="14"
|
||||
Margin="5,10,10,10"
|
||||
CornerRadius="25"
|
||||
Command="{Binding LoadDataCommand}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ContentPage>
|
||||
26
LocationTrackerApp/Views/MainView.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using LocationTrackerApp.ViewModels;
|
||||
|
||||
namespace LocationTrackerApp.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Main view for the location tracking application
|
||||
/// </summary>
|
||||
public partial class MainView : ContentPage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of MainView
|
||||
/// </summary>
|
||||
public MainView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of MainView with view model
|
||||
/// </summary>
|
||||
/// <param name="viewModel">The view model for this view</param>
|
||||
public MainView(MainViewModel viewModel) : this()
|
||||
{
|
||||
BindingContext = viewModel;
|
||||
}
|
||||
}
|
||||
11
LocationTrackerApp/appsettings.Development.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"GoogleMaps": {
|
||||
"ApiKey": "YOUR_ACTUAL_API_KEY_HERE"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
LocationTrackerApp/appsettings.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"GoogleMaps": {
|
||||
"ApiKey": "YOUR_ACTUAL_API_KEY_HERE"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
19
LocationTrackerApp/set-api-key.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
# PowerShell script to set Google Maps API key environment variable
|
||||
# Usage: .\set-api-key.ps1 -ApiKey "your_actual_api_key_here"
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$ApiKey
|
||||
)
|
||||
|
||||
# Set the environment variable for the current session
|
||||
$env:GoogleMapsApiKey = $ApiKey
|
||||
|
||||
# Also set it for the current PowerShell session
|
||||
[Environment]::SetEnvironmentVariable("GoogleMapsApiKey", $ApiKey, "Process")
|
||||
|
||||
Write-Host "Google Maps API Key set to: $ApiKey" -ForegroundColor Green
|
||||
Write-Host "You can now build the app with: dotnet build -f net9.0-android" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Note: This environment variable is only set for the current session." -ForegroundColor Cyan
|
||||
Write-Host "To make it permanent, add it to your system environment variables." -ForegroundColor Cyan
|
||||
21
LocationTrackerApp/set-api-key.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Bash script to set Google Maps API key environment variable
|
||||
# Usage: ./set-api-key.sh your_actual_api_key_here
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 <api_key>"
|
||||
echo "Example: $0 AIzaSyYourActualApiKeyHere"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
API_KEY="$1"
|
||||
|
||||
# Set the environment variable for the current session
|
||||
export GoogleMapsApiKey="$API_KEY"
|
||||
|
||||
echo "Google Maps API Key set to: $API_KEY"
|
||||
echo "You can now build the app with: dotnet build -f net9.0-android"
|
||||
echo ""
|
||||
echo "Note: This environment variable is only set for the current session."
|
||||
echo "To make it permanent, add this line to your ~/.bashrc or ~/.zshrc:"
|
||||
echo "export GoogleMapsApiKey=\"$API_KEY\""
|
||||
394
README.md
Normal file
@@ -0,0 +1,394 @@
|
||||
# Location Tracker App
|
||||
|
||||
A comprehensive .NET MAUI application that tracks user location and displays it as an interactive heat map using OpenStreetMap integration and SQLite database storage.
|
||||
|
||||
## Application Overview
|
||||
|
||||
The Location Tracker App is a cross-platform mobile application built with .NET MAUI that provides real-time location tracking with advanced heat map visualization. The app uses OpenStreetMap for map display, eliminating the need for Google Maps API keys while providing rich, interactive mapping capabilities.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Interactive Maps**: OpenStreetMap integration with Leaflet.js for detailed map display
|
||||
- **Real-time Location Tracking**: Continuous GPS tracking with configurable accuracy
|
||||
- **Heat Map Visualization**: Color-coded intensity display based on location frequency
|
||||
- **SQLite Database**: Local data persistence with Entity Framework Core
|
||||
- **Location Analytics**: Track and analyze movement patterns
|
||||
- **Modern UI**: Clean, intuitive interface with responsive design
|
||||
- **Privacy-Focused**: No external API dependencies for map display
|
||||
- **Cross-Platform**: Works on Android, iOS, and macOS
|
||||
- **Data Management**: Clear data, load data, and session management
|
||||
- **No API Keys Required**: Free map service without Google Maps dependencies
|
||||
- **Background Tracking**: Continuous location monitoring with battery optimization
|
||||
- **Export Capabilities**: Share location data and routes
|
||||
|
||||
## Application Screenshots
|
||||
|
||||
### Main Application View
|
||||

|
||||
*The main application interface showing the interactive map with location tracking controls*
|
||||
|
||||
### Location Found Dialog
|
||||

|
||||
*Location detection and map centering functionality with coordinate display*
|
||||
|
||||
### App Demo
|
||||

|
||||
*Animated demonstration of the application's core features and functionality*
|
||||
|
||||
### Additional Screenshots
|
||||

|
||||
*Application running on Android emulator*
|
||||
|
||||

|
||||
*Complete heat map display*
|
||||
|
||||

|
||||
*Application after clearing demo data*
|
||||
|
||||

|
||||
*Updated user interface with new controls*
|
||||
|
||||

|
||||
*OpenStreetMap integration*
|
||||
|
||||

|
||||
*Map centered on current location*
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### Technology Stack
|
||||
- **Framework**: .NET MAUI (.NET 9.0)
|
||||
- **Database**: SQLite with Entity Framework Core
|
||||
- **Maps**: OpenStreetMap with Leaflet.js
|
||||
- **Architecture**: MVVM (Model-View-ViewModel)
|
||||
- **Dependency Injection**: Built-in .NET DI container
|
||||
- **Background Processing**: Non-blocking location updates
|
||||
- **Event-Driven Architecture**: Real-time location events
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
LocationTrackerApp/
|
||||
├── Components/ # Custom map components
|
||||
│ └── OpenStreetMapView.cs
|
||||
├── Data/ # Database context
|
||||
│ └── LocationDbContext.cs
|
||||
├── Models/ # Data models
|
||||
│ └── LocationData.cs
|
||||
├── Services/ # Business logic services
|
||||
│ ├── ILocationService.cs
|
||||
│ ├── LocationService.cs
|
||||
│ ├── IConfigurationService.cs
|
||||
│ └── ConfigurationService.cs
|
||||
├── ViewModels/ # MVVM view models
|
||||
│ └── MainViewModel.cs
|
||||
├── Views/ # UI views
|
||||
│ ├── MainView.xaml
|
||||
│ └── MainView.xaml.cs
|
||||
├── Platforms/ # Platform-specific configurations
|
||||
│ ├── Android/
|
||||
│ └── iOS/
|
||||
├── MainPage.xaml # Main application page
|
||||
├── MainPage.xaml.cs # Main page code-behind
|
||||
└── Configuration files # appsettings.json, etc.
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
#### LocationData Model
|
||||
Represents a location data point with:
|
||||
- Latitude/Longitude coordinates
|
||||
- Accuracy, altitude, and speed
|
||||
- Timestamp and session ID
|
||||
- Optional notes
|
||||
|
||||
#### LocationService
|
||||
Handles location tracking with:
|
||||
- Continuous location updates
|
||||
- Permission management
|
||||
- Background tracking capabilities
|
||||
- Event-driven architecture
|
||||
|
||||
#### OpenStreetMapView
|
||||
Custom map component featuring:
|
||||
- Heat map visualization with intensity-based coloring
|
||||
- Interactive map controls
|
||||
- Real-time location pins
|
||||
- WebView-based rendering with Leaflet.js
|
||||
|
||||
#### MainViewModel
|
||||
MVVM view model providing:
|
||||
- Command bindings for UI interactions
|
||||
- Property change notifications
|
||||
- Location tracking state management
|
||||
- Data persistence operations
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- Visual Studio 2022 or Visual Studio Code
|
||||
- .NET 9.0 SDK
|
||||
- .NET MAUI workload
|
||||
- Android SDK (API level 34+)
|
||||
- Xcode (for iOS development)
|
||||
|
||||
### Installation
|
||||
1. Clone the repository
|
||||
2. Open the solution in Visual Studio
|
||||
3. Restore NuGet packages
|
||||
4. Build the application
|
||||
5. Run on your preferred platform
|
||||
|
||||
### Building for Android
|
||||
```bash
|
||||
dotnet build -f net9.0-android
|
||||
dotnet build -f net9.0-android -t:Install
|
||||
```
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Main Features
|
||||
|
||||
#### 1. Location Tracking
|
||||
- **Start Tracking**: Begin continuous GPS location monitoring
|
||||
- **Stop Tracking**: Pause location collection
|
||||
- **Real-time Updates**: Live location pins on the map
|
||||
|
||||
#### 2. Map Interaction
|
||||
- **Center Map**: Automatically center on current location
|
||||
- **Zoom Controls**: Standard map zoom in/out functionality
|
||||
- **Interactive Navigation**: Pan and explore different areas
|
||||
|
||||
#### 3. Heat Map Visualization
|
||||
- **Demo Heat Map**: Display pre-loaded San Francisco Bay Area data
|
||||
- **Color Coding**:
|
||||
- Red: High intensity (many location points)
|
||||
- Yellow: Medium-high intensity
|
||||
- Cyan: Medium-low intensity
|
||||
- Blue: Low intensity (fewer location points)
|
||||
- **Intensity Calculation**: Based on location frequency and density
|
||||
|
||||
#### 4. Data Management
|
||||
- **Clear Demo**: Remove all demo location data
|
||||
- **Reset Map**: Return to default map view
|
||||
- **Location Count**: Real-time display of tracked locations
|
||||
|
||||
## Sample Data
|
||||
|
||||
The application includes 30 dummy location points around the San Francisco Bay Area:
|
||||
|
||||
- **Golden Gate Bridge** (5 points) - High frequency
|
||||
- **Financial District** (6 points) - High frequency
|
||||
- **Fisherman's Wharf** (3 points) - Medium frequency
|
||||
- **Union Square** (3 points) - Medium frequency
|
||||
- **SOMA** (4 points) - Medium frequency
|
||||
- **Chinatown** (3 points) - Medium frequency
|
||||
- **Mission District** (2 points) - Low frequency
|
||||
- **Castro District** (1 point) - Low frequency
|
||||
- **North Beach** (1 point) - Low frequency
|
||||
- **Marina District** (1 point) - Low frequency
|
||||
- **Presidio** (1 point) - Low frequency
|
||||
|
||||
## Configuration
|
||||
|
||||
### OpenStreetMap Integration
|
||||
The app uses OpenStreetMap tiles with Leaflet.js for map display:
|
||||
- **Tile Provider**: [OpenStreetMap](https://www.openstreetmap.org/about) - Community-driven, open-source map data
|
||||
- **Attribution**: © OpenStreetMap contributors
|
||||
- **No API Keys Required**: Completely free map service
|
||||
- **Open Data**: Free to use for any purpose with proper attribution
|
||||
|
||||
### Configuration Files
|
||||
The app supports configuration files for managing settings:
|
||||
|
||||
#### Files Created:
|
||||
- `appsettings.json` - Base configuration
|
||||
- `appsettings.Development.json` - Development-specific configuration
|
||||
|
||||
#### Configuration Priority:
|
||||
The configuration is loaded in this order:
|
||||
1. Environment variables
|
||||
2. `appsettings.json` file
|
||||
3. Default values (for development)
|
||||
|
||||
#### Environment Variables:
|
||||
You can set configuration values as environment variables:
|
||||
|
||||
**On macOS/Linux:**
|
||||
```bash
|
||||
export APP_SETTING="your_value_here"
|
||||
```
|
||||
|
||||
**On Windows (PowerShell):**
|
||||
```powershell
|
||||
$env:APP_SETTING = "your_value_here"
|
||||
```
|
||||
|
||||
#### Security Notes:
|
||||
- Never commit sensitive configuration to version control
|
||||
- Use different settings for development and production
|
||||
- Consider using environment variables for production deployments
|
||||
|
||||
### Database Configuration
|
||||
- **Database**: SQLite local storage
|
||||
- **Location**: `FileSystem.AppDataDirectory/location_tracker.db`
|
||||
- **Framework**: Entity Framework Core 9.0.9
|
||||
|
||||
### Database Schema
|
||||
The SQLite database stores location data in the `LocationData` table:
|
||||
|
||||
```sql
|
||||
CREATE TABLE LocationData (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Latitude REAL NOT NULL,
|
||||
Longitude REAL NOT NULL,
|
||||
Accuracy REAL DEFAULT 0.0,
|
||||
Altitude REAL,
|
||||
Speed REAL,
|
||||
Timestamp TEXT NOT NULL,
|
||||
SessionId TEXT,
|
||||
Notes TEXT
|
||||
);
|
||||
```
|
||||
|
||||
## User Interface
|
||||
|
||||
### Color Scheme
|
||||
- **Primary**: Purple (#8B5CF6) - Header and heat map button
|
||||
- **Success**: Green (#10B981) - Start tracking
|
||||
- **Danger**: Red (#EF4444) - Stop tracking
|
||||
- **Info**: Blue (#3B82F6) - Center map
|
||||
- **Warning**: Orange (#F59E0B) - Clear demo
|
||||
- **Secondary**: Gray (#6B7280) - Reset map
|
||||
|
||||
### Layout
|
||||
- **Header**: Application title with purple background
|
||||
- **Map Area**: Full-screen interactive map
|
||||
- **Controls**: Two-row button layout with status display
|
||||
- **Status Bar**: Location count and system information
|
||||
|
||||
## Platform Support
|
||||
|
||||
### Android
|
||||
- **Minimum SDK**: API level 21 (Android 5.0)
|
||||
- **Target SDK**: API level 34 (Android 14)
|
||||
- **Permissions**:
|
||||
- `ACCESS_FINE_LOCATION`: Precise location access
|
||||
- `ACCESS_COARSE_LOCATION`: Approximate location access
|
||||
- `ACCESS_BACKGROUND_LOCATION`: Background location tracking
|
||||
|
||||
### iOS
|
||||
- **Minimum Version**: iOS 15.0
|
||||
- **Permissions**:
|
||||
- `NSLocationWhenInUseUsageDescription`: Location access when app is in use
|
||||
- `NSLocationAlwaysAndWhenInUseUsageDescription`: Always location access
|
||||
- `NSLocationAlwaysUsageDescription`: Background location access
|
||||
- **Capabilities**: Background location updates
|
||||
|
||||
### macOS
|
||||
- **Minimum Version**: macOS 12.0
|
||||
- **Architecture**: Apple Silicon and Intel
|
||||
|
||||
## Privacy & Security
|
||||
|
||||
- **Local Storage**: All data stored locally on device
|
||||
- **No Cloud Sync**: No data transmitted to external servers
|
||||
- **Open Source Maps**: No tracking by commercial map providers
|
||||
- **Permission-Based**: Location access only when explicitly granted
|
||||
|
||||
## Performance
|
||||
|
||||
### Optimization Features
|
||||
- **Efficient Rendering**: WebView-based map rendering
|
||||
- **Memory Management**: Proper disposal of resources
|
||||
- **Background Processing**: Non-blocking location updates
|
||||
- **Responsive UI**: Smooth interactions and animations
|
||||
|
||||
### System Requirements
|
||||
- **RAM**: Minimum 2GB recommended
|
||||
- **Storage**: 50MB for app and data
|
||||
- **Network**: Internet required for map tiles
|
||||
- **GPS**: Location services enabled
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Map Not Loading**
|
||||
- Check internet connection
|
||||
- Verify OpenStreetMap tile availability
|
||||
- Restart the application
|
||||
|
||||
2. **Location Not Found**
|
||||
- Ensure location permissions are granted
|
||||
- Check GPS/Location services are enabled
|
||||
- Verify device location settings
|
||||
|
||||
3. **Performance Issues**
|
||||
- Clear demo data if too many points
|
||||
- Restart the application
|
||||
- Check available device memory
|
||||
|
||||
### Build Issues
|
||||
|
||||
#### Android Build Issues
|
||||
- Ensure Android SDK API level 34+ is installed
|
||||
- Accept Android SDK licenses
|
||||
- Set correct Android SDK path in project file
|
||||
|
||||
#### iOS Build Issues
|
||||
- Ensure Xcode 16.4+ is installed
|
||||
- Set up iOS development certificates
|
||||
- Configure provisioning profiles
|
||||
|
||||
#### Location Permissions
|
||||
- Grant location permissions when prompted
|
||||
- Check device location settings
|
||||
- Ensure location services are enabled
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Test thoroughly
|
||||
5. Submit a pull request
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
For production use, consider:
|
||||
- Adding comprehensive error handling
|
||||
- Implementing proper logging
|
||||
- Adding unit tests
|
||||
- Optimizing battery usage
|
||||
- Adding privacy controls
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- **OpenStreetMap**: Free, open-source map data
|
||||
- **Leaflet.js**: Interactive map library
|
||||
- **.NET MAUI**: Cross-platform framework
|
||||
- **Entity Framework Core**: Data access framework
|
||||
|
||||
## Support
|
||||
|
||||
For questions, issues, or feature requests:
|
||||
- Create an issue in the repository
|
||||
- Check the documentation
|
||||
- Review the troubleshooting guide
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This repository was created as part of the coursework for the **Master’s in Information Technology** program at the **University of the Cumberlands**, for the course **Software Engineering and Multiplatform App Development**.
|
||||
|
||||
**Student Information:**
|
||||
- **Name:** Carlos Gutierrez
|
||||
- **University Email:** cgutierrez44833@ucumberlands.edu
|
||||
- **Personal Email**: carlos.gutierrez@carg.dev
|
||||
|
||||
This project was developed for educational purposes only and is not intended for commercial use. All code and materials are submitted as part of the academic requirements for the course.
|
||||
|
||||
---
|
||||
|
||||
**Built with .NET MAUI and OpenStreetMap**
|
||||
BIN
assets/App working.gif
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
assets/Center map.png
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
assets/Map view.png
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
assets/app_running_screenshot.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
assets/app_screenshot.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
assets/center_map_screenshot.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
assets/cleared_demo_screenshot.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/final_heatmap_screenshot.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/heatmap_demo_screenshot.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/map_visible_screenshot.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
assets/openstreetmap_centered_screenshot.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/openstreetmap_screenshot.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/updated_ui_screenshot.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |