diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3176a59..8e33296 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,12 +19,8 @@ jobs: - name: Checkout uses: actions/checkout@v2 - #- name: Setup nuget - # uses: nuget/setup-nuget@v1 - name: Restore packages for solution - run: dotnet restore Matomo.Maui.csproj + run: dotnet restore - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.0.3 - name: Build - run: msbuild Matomo.Maui.csproj \ No newline at end of file + run: dotnet build \ No newline at end of file diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index c05a6d0..63e17d6 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -31,27 +31,19 @@ jobs: script: | return context.payload.ref.replace(/\/refs\/tags\//, ''); - - name: Setup nuget - uses: nuget/setup-nuget@v1 - name: Restore nuget packages for solution - run: dotnet restore Matomo.Maui.csproj - - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.0.3 - - name: Build - run: msbuild Matomo.Maui.csproj /property:Configuration=Release + run: dotnet restore + - name: Pack run: | $version=${{ steps.tag.outputs.result }} $version=$version.replace("refs/tags/v", "") - nuget pack Matomo.Maui.nuspec -Prop Configuration=Release -verbosity detailed -basepath ./ -OutputDirectory ./package -Version $version - - name: Setup Nuget Push - run: nuget setApiKey "$env:NUGET_ORG_TOKEN" - env: - NUGET_ORG_TOKEN: ${{ secrets.NUGET_ORG_TOKEN }} + dotnet pack --configuration Release --verbosity detailed --output ../package -p:PackageVersion=$version - name: Push run: | $version=${{ steps.tag.outputs.result }} $version=$version.replace("refs/tags/v", "") $package="package/Matomo.Maui." + $version + ".nupkg" - nuget push $package -Source https://api.nuget.org/v3/index.json \ No newline at end of file + dotnet nuget push $package -k "$env:NUGET_ORG_TOKEN" -Source https://api.nuget.org/v3/index.json + env: + NUGET_ORG_TOKEN: ${{ secrets.NUGET_ORG_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ed3cfc..7844499 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,28 +30,21 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | return context.payload.ref.replace(/\/refs\/tags\//, ''); - - - name: Setup nuget - uses: nuget/setup-nuget@v1 + - name: Restore nuget packages for solution - run: nuget restore Matomo.Maui.csproj - - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.0.3 - - name: Build - run: msbuild Matomo.Maui.csproj /property:Configuration=Release + run: nuget restore + - name: Pack run: | $version=${{ steps.tag.outputs.result }} $version=$version.replace("refs/tags/v", "") - nuget pack Matomo.Maui.nuspec -Prop Configuration=Release -verbosity detailed -basepath ./ -OutputDirectory ./package -Version $version - - name: Setup Nuget Push - run: nuget setApiKey "$env:NUGET_ORG_TOKEN" - env: - NUGET_ORG_TOKEN: ${{ secrets.NUGET_ORG_TOKEN }} + dotnet pack --configuration Release --verbosity detailed --output ../package -p:PackageVersion=$version + - name: Push run: | $version=${{ steps.tag.outputs.result }} $version=$version.replace("refs/tags/v", "") $package="package/Matomo.Maui." + $version + ".nupkg" - nuget push $package -Source https://api.nuget.org/v3/index.json \ No newline at end of file + dotnet nuget push $package -k "$env:NUGET_ORG_TOKEN" -Source https://api.nuget.org/v3/index.json + env: + NUGET_ORG_TOKEN: ${{ secrets.NUGET_ORG_TOKEN }} \ No newline at end of file diff --git a/Matomo.Maui.sln b/Matomo.Maui.sln index f841878..3c49f9c 100644 --- a/Matomo.Maui.sln +++ b/Matomo.Maui.sln @@ -36,9 +36,6 @@ Global SolutionGuid = {99E4CA5A-ABE0-4F3F-8376-D15689601C67} EndGlobalSection GlobalSection(NestedProjects) = preSolution - {F98A6817-2895-47D3-9FC3-E93BB5747555} = {199F81EB-3FE7-451F-BD77-41E680A31F0B} - {EBD1EEA9-0CB1-42E1-B01E-17FEB3B1E2C0} = {F98A6817-2895-47D3-9FC3-E93BB5747555} - {C7882A5C-EE2F-4B30-AE11-530F33F4CF2A} = {F98A6817-2895-47D3-9FC3-E93BB5747555} - {F33C9D2B-FDF0-469A-9704-0154B9B87E3B} = {84FA4430-CA33-4436-98E6-E421058A0973} - EndGlobalSection + {F33C9D2B-FDF0-469A-9704-0154B9B87E3B} = {84FA4430-CA33-4436-98E6-E421058A0973} + EndGlobalSection EndGlobal diff --git a/Matomo.Maui/ActionBuffer.cs b/Matomo.Maui/ActionBuffer.cs deleted file mode 100644 index 05c4899..0000000 --- a/Matomo.Maui/ActionBuffer.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Matomo.Maui; - -public class ActionBuffer -{ - string baseParameters; - List inbox = new List(); - List outbox = new List(); - SimpleStorage storage; - - public ActionBuffer(NameValueCollection baseParameters, SimpleStorage storage) - { - this.baseParameters = $"?rec=1&apiv=1&{baseParameters}&"; - this.storage = storage; - - inbox = storage.Get>("actions_inbox", new List()); - outbox = storage.Get>("actions_outbox", new List()); - } - - public int Count { get { return inbox.Count + outbox.Count; } } - - public void Add(NameValueCollection parameters) - { - if (OptOut) - return; - - lock (inbox) - { - inbox.Add(baseParameters + parameters); - storage.Put>("actions_inbox", inbox); - } - } - - public string CreateOutbox() - { - lock (outbox) lock (inbox) - { - outbox.AddRange(inbox); - if (outbox.Count == 0) - return ""; - - inbox.Clear(); - storage.Put>("actions_inbox", inbox); - storage.Put>("actions_outbox", outbox); - } - - var data = new Dictionary(); - data["requests"] = outbox; - return JsonConvert.SerializeObject(data); - } - - public void ClearOutbox() - { - lock (outbox) - { - outbox.Clear(); - storage.Put>("actions_outbox", outbox); - } - } - - public void Clear() - { - lock (outbox) // NOTE we atain a lock for both objects before clearing. - lock (inbox) - { - inbox.Clear(); - outbox.Clear(); - storage.Put>("actions_inbox", inbox); - storage.Put>("actions_outbox", outbox); - } - } - - public bool OptOut - { - get => storage.Get("opt_out", false); - set => storage.Put("opt_out", value); - } -} \ No newline at end of file diff --git a/Matomo.Maui/AppBuilderExtensions.cs b/Matomo.Maui/AppBuilderExtensions.cs new file mode 100644 index 0000000..b3db98b --- /dev/null +++ b/Matomo.Maui/AppBuilderExtensions.cs @@ -0,0 +1,27 @@ +using Matomo.Maui.Services.Core; +using Matomo.Maui.Services.Shell; +using Matomo.Maui.Services.Storage; + +namespace Matomo.Maui; + +/// +/// Extensions +/// +public static class AppBuilderExtensions +{ + /// + /// Initializes the Matomo Analytics Client + /// + /// generated by + /// initialized for + public static MauiAppBuilder UseMatomo(this MauiAppBuilder builder) + { + builder + .Services + .AddTransient() + .AddSingleton() + .AddSingleton(); + + return builder; + } +} diff --git a/Matomo.Maui/Buffers/ActionBuffer.cs b/Matomo.Maui/Buffers/ActionBuffer.cs new file mode 100644 index 0000000..9a79a71 --- /dev/null +++ b/Matomo.Maui/Buffers/ActionBuffer.cs @@ -0,0 +1,90 @@ +using System.Collections.Specialized; +using Newtonsoft.Json; +using Matomo.Maui.Services.Storage; + +namespace Matomo.Maui.Buffers; + +public class ActionBuffer +{ + private readonly string _baseParameters; + private readonly List _inbox; + private readonly List _outbox; + private readonly ISimpleStorage _storage; + + public ActionBuffer(NameValueCollection baseParameters, ISimpleStorage storage) + { + _baseParameters = $"?rec=1&apiv=1&{baseParameters}&"; + _inbox = []; + _outbox = []; + _storage = storage; + + _inbox = storage.Get("actions_inbox", new List()); + _outbox = storage.Get("actions_outbox", new List()); + } + + public int Count + { + get + { + if (_inbox != null && _outbox != null) return _inbox.Count + _outbox.Count; + return 0; + } + } + + public void Add(NameValueCollection parameters) + { + if (OptOut) + return; + + lock (_inbox) + { + _inbox.Add(_baseParameters + parameters); + _storage.Put("actions_inbox", _inbox); + } + } + + public string CreateOutbox() + { + lock (_outbox) lock (_inbox) + { + _outbox.AddRange(_inbox); + if (_outbox.Count == 0) + return ""; + + _inbox.Clear(); + _storage.Put("actions_inbox", _inbox); + _storage.Put("actions_outbox", _outbox); + } + + var data = new Dictionary(); + data["requests"] = _outbox; + return JsonConvert.SerializeObject(data); + } + + public void ClearOutbox() + { + lock (_outbox) + { + _outbox.Clear(); + _storage.Put("actions_outbox", _outbox); + } + } + + public void Clear() + { + lock (_outbox) // NOTE we atain a lock for both objects before clearing. + lock (_inbox) + { + _inbox.Clear(); + _outbox.Clear(); + _storage.Put("actions_inbox", _inbox); + _storage.Put("actions_outbox", _outbox); + } + } + + public bool OptOut + { + get => _storage.Get("opt_out", false); + set => _storage.Put("opt_out", value); + } +} \ No newline at end of file diff --git a/Matomo.Maui/Matomo.Maui.csproj b/Matomo.Maui/Matomo.Maui.csproj index 3674940..c12952a 100644 --- a/Matomo.Maui/Matomo.Maui.csproj +++ b/Matomo.Maui/Matomo.Maui.csproj @@ -1,20 +1,25 @@ - net7.0;net7.0-android;net7.0-ios16.2;net7.0-maccatalyst16.2 - $(TargetFrameworks);net6.0-windows10.0.19041.0 - - + net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst + $(TargetFrameworks);net8.0-windows + Matomo.Maui true true enable + 8.0.91 + + + Matomo.Maui + 8.0.0 + bnoffer + bnotech + BNO Technology Solutions e.K. + This library provides Matomo Tracking for .NET MAUI Apps + ©Copyright 2025, BNO Technology Solutions e.K. + Matomo Piwik MAUI Analytics + README.md - 14.2 - 14.0 - 21.0 - 10.0.17763.0 - 10.0.17763.0 - 6.5 @@ -24,6 +29,14 @@ - + + + + + + + + + diff --git a/Matomo.Maui/Matomo.Maui.nuspec b/Matomo.Maui/Matomo.Maui.nuspec deleted file mode 100644 index de93904..0000000 --- a/Matomo.Maui/Matomo.Maui.nuspec +++ /dev/null @@ -1,24 +0,0 @@ - - - - Matomo.Maui - 6.1.0.0 - This library provides Matomo Tracking for .NET MAUI Apps - bnoffer - bnotech - https://github.com/bfn-tech/Matomo.Maui - false - This library provides Matomo Tracking for .NET MAUI Apps. - Updated Nuget package - ©Copyright 2023, BNO Technology Solutions e.K. - Matomo Piwik MAUI - - - - - - - - - - \ No newline at end of file diff --git a/Matomo.Maui/Dimension.cs b/Matomo.Maui/Models/Dimension.cs similarity index 90% rename from Matomo.Maui/Dimension.cs rename to Matomo.Maui/Models/Dimension.cs index 69d1d7d..bbde0d9 100644 --- a/Matomo.Maui/Dimension.cs +++ b/Matomo.Maui/Models/Dimension.cs @@ -1,5 +1,4 @@ -using System; -namespace Matomo.Maui; +namespace Matomo.Maui.Models; public class Dimension { diff --git a/Matomo.Maui/Services/Core/IMatomoAnalytics.cs b/Matomo.Maui/Services/Core/IMatomoAnalytics.cs new file mode 100644 index 0000000..22e1201 --- /dev/null +++ b/Matomo.Maui/Services/Core/IMatomoAnalytics.cs @@ -0,0 +1,102 @@ +using Matomo.Maui.Models; + +namespace Matomo.Maui.Services.Core; + +public interface IMatomoAnalytics +{ + /// + /// Custom UserAgent based on Device and Platform + /// + string UserAgent { get; } + /// + /// Log Matomo tracking to Console + /// + bool Verbose { get; set; } + /// + /// Number of actions that were not yet dispatched to Matomo + /// + int UnsentActions { get; } + /// + /// Custom Dimensions you can assign any custom data to your visitors or actions + /// + List Dimensions { get; set; } + /// + /// The base url used by the app (piwi's url parameter). Default is https://app + /// + string AppUrl { get; set; } + /// + /// Indicates if the user has opted-out of tracking. + /// + bool OptOut { get; set; } + + /// + /// Update a existing Dimension + /// + /// Dimension + /// New value + void UpdateDimension(string name, string newValue); + + /// + /// Tracks a page visit. + /// + /// page name (eg. "Settings", "Users", etc) + /// path which led to the page (eg. "/settings/language"), default is "/" + void TrackPage(string name, string path = "/"); + + /// + /// Tracks an page related event. + /// + /// event category ("Music", "Video", etc) + /// event action ("Play", "Pause", etc) + /// optional event name (eg. song title, file name, etc) + /// optional event value (eg. position in song, count of manual updates, etc) + void TrackPageEvent(string category, string action, string name = null, int? value = null); + + /// + /// Tracks an non-page related event. + /// + /// event category ("Music", "Video", etc) + /// event action ("Play", "Pause", etc) + void TrackEvent(string category, string action); + + /// + /// Tracks an non-page related event. + /// + /// event category ("Music", "Video", etc) + /// event action ("Play", "Pause", etc) + /// event name (eg. song title, file name, etc) + void TrackEvent(string category, string action, string name); + + /// + /// Tracks an non-page related event. + /// + /// event category ("Music", "Video", etc) + /// event action ("Play", "Pause", etc) + /// event name (eg. song title, file name, etc) + /// event value (eg. position in song, count of manual updates, etc) + void TrackEvent(string category, string action, string name, int? value); + + /// + /// Tracks a search query. + /// + /// search query (eg. "cats") + /// number of search results displayed to the user + /// categroy (eg. "cats") + void TrackSearch(string query, int resultCount, string category = null); + + /// + /// Track an App exit. Needed to accurately time the visibility of last page and triggers an Dispatch(). + /// + Task LeavingTheApp(); + + /// + /// Dispatches all tracked actions to the Matomo instance + /// + /// true if successful, else false. + Task Dispatch(); + + /// + /// Resets the current action queue + /// + void ClearQueue(); +} \ No newline at end of file diff --git a/Matomo.Maui/MatomoAnalytics.cs b/Matomo.Maui/Services/Core/MatomoAnalytics.cs similarity index 64% rename from Matomo.Maui/MatomoAnalytics.cs rename to Matomo.Maui/Services/Core/MatomoAnalytics.cs index cdbd1a4..1311723 100644 --- a/Matomo.Maui/MatomoAnalytics.cs +++ b/Matomo.Maui/Services/Core/MatomoAnalytics.cs @@ -3,11 +3,17 @@ using System.Net; using System.Text; using System.Web; +using Microsoft.Extensions.Configuration; +using Matomo.Maui.Buffers; +using Matomo.Maui.Models; +using Matomo.Maui.Services.Storage; -namespace Matomo.Maui; +namespace Matomo.Maui.Services.Core; -public class MatomoAnalytics +public class MatomoAnalytics : IMatomoAnalytics { + #region Properties + private string _userAgent; public string UserAgent { @@ -21,18 +27,48 @@ public string UserAgent } } - string apiUrl; - ActionBuffer actions; - NameValueCollection baseParameters; - NameValueCollection pageParameters; - - HttpClient httpClient = new HttpClient(); - Random random = new Random(); - SimpleStorage storage = SimpleStorage.Instance; + public bool Verbose { get; set; } = false; - System.Timers.Timer timer = new System.Timers.Timer(); + public int UnsentActions { get { lock (_actions) return _actions.Count; } } - public MatomoAnalytics(string apiUrl, int siteId) + private List _dimensions; + public List Dimensions + { + get { return _dimensions ??= []; } + set => _dimensions = value; + } + + public bool OptOut + { + get => _actions is { OptOut: true }; + set + { + if (_actions != null) + _actions.OptOut = value; + } + } + + /// + /// The base url used by the app (piwi's url parameter). Default is https://app + /// + public string AppUrl { get; set; } = "https://app"; + + #endregion + + #region Attributes + + private readonly string _apiUrl; + private readonly ActionBuffer _actions; + private readonly NameValueCollection _pageParameters; + + private readonly HttpClient _httpClient = new HttpClient(); + private readonly Random _random = new Random(); + + private readonly System.Timers.Timer _timer = new System.Timers.Timer(); + + #endregion + + public MatomoAnalytics(IConfiguration configuration, ISimpleStorage storage) { var visitor = GenerateId(16); if (storage.HasKey("visitor_id")) @@ -41,40 +77,37 @@ public MatomoAnalytics(string apiUrl, int siteId) { storage.Put("visitor_id", visitor); } - - this.apiUrl = $"{apiUrl}/piwik.php"; - baseParameters = HttpUtility.ParseQueryString(string.Empty); - baseParameters["idsite"] = siteId.ToString(); + + _dimensions = []; + + this._apiUrl = $"{configuration["Matomo:ApiUrl"]}/piwik.php"; + var baseParameters = HttpUtility.ParseQueryString(string.Empty); + baseParameters["idsite"] = configuration["Matomo:SiteId"]; baseParameters["_id"] = visitor; baseParameters["cid"] = visitor; - pageParameters = HttpUtility.ParseQueryString(string.Empty); + AppUrl = configuration["Matomo:SiteUrl"]; + _pageParameters = HttpUtility.ParseQueryString(string.Empty); - actions = new ActionBuffer(baseParameters, storage); + _actions = new ActionBuffer(baseParameters, storage); - httpClient.Timeout = TimeSpan.FromSeconds(30); + _httpClient.Timeout = TimeSpan.FromSeconds(30); - timer.Interval = TimeSpan.FromMinutes(2).TotalMilliseconds; - timer.Elapsed += async (s, args) => await Dispatch(); - timer.Start(); + _timer.Interval = TimeSpan.FromMinutes(2).TotalMilliseconds; + _timer.Elapsed += async (s, args) => await Dispatch(); + _timer.Start(); } - public bool Verbose { get; set; } = false; - - public int UnsentActions { get { lock (actions) return actions.Count; } } - - public List Dimensions = new List(); - + /// + /// Update a existing Dimension + /// + /// Dimension + /// New value public void UpdateDimension(string name, string newValue) { Dimensions.First(d => d.Name == name).Value = newValue; } - /// - /// The base url used by the app (piwi's url parameter). Default is http://app - /// - public string AppUrl { get; set; } = "http://app"; - /// /// Tracks a page visit. /// @@ -84,16 +117,16 @@ public void TrackPage(string name, string path = "/") { Log($"[Page] {name}"); - pageParameters["pv_id"] = GenerateId(6); - pageParameters["url"] = $"{AppUrl}{path}"; + _pageParameters["pv_id"] = GenerateId(6); + _pageParameters["url"] = $"{AppUrl}{path}"; var parameters = CreateParameters(); parameters["action_name"] = name; - parameters.Add(pageParameters); + parameters.Add(_pageParameters); - lock (actions) - actions.Add(parameters); + lock (_actions) + _actions.Add(parameters); } /// @@ -106,10 +139,10 @@ public void TrackPage(string name, string path = "/") public void TrackPageEvent(string category, string action, string name = null, int? value = null) { var parameters = CreateEventParemeters(category, action, name, value); - parameters.Add(pageParameters); + parameters.Add(_pageParameters); - lock (actions) - actions.Add(parameters); + lock (_actions) + _actions.Add(parameters); } /// @@ -145,8 +178,8 @@ public void TrackEvent(string category, string action, string name, int? value) var parameters = CreateEventParemeters(category, action, name, value); parameters["url"] = AppUrl; // non-page events must at least have the base url - lock (actions) - actions.Add(parameters); + lock (_actions) + _actions.Add(parameters); } private NameValueCollection CreateEventParemeters(string category, string action, string name, int? value) @@ -177,31 +210,33 @@ public void TrackSearch(string query, int resultCount, string category = null) if (category != null) parameters["search_cat"] = category; - parameters.Add(pageParameters); + parameters.Add(_pageParameters); - lock (actions) - actions.Add(parameters); + lock (_actions) + _actions.Add(parameters); } /// /// Track an App exit. Needed to acuratly time the visibility of last page and triggers an Dispatch(). /// - public void LeavingTheApp() + public async Task LeavingTheApp() { TrackPage("Close"); -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Dispatch(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + await Dispatch(); } + /// + /// Dispatches all tracked actions to the Matomo instance + /// + /// true if successful, else false. public async Task Dispatch() // TODO run in background: http://arteksoftware.com/backgrounding-with-xamarin-forms/ { var actionsToDispatch = ""; - lock (actions) + lock (_actions) { - if (actions.Count == 0) + if (_actions.Count == 0) return false; - actionsToDispatch = actions.CreateOutbox(); // new action buffer to store tracking infos while we dispatch + actionsToDispatch = _actions.CreateOutbox(); // new action buffer to store tracking infos while we dispatch } Log($"[Dispatching] {actionsToDispatch}"); @@ -209,11 +244,11 @@ public async Task Dispatch() // TODO run in background: http://arteksoftwa try { - var response = await httpClient.PostAsync(apiUrl, content); + var response = await _httpClient.PostAsync(_apiUrl, content); if (response.StatusCode == HttpStatusCode.OK) { - lock (actions) - actions.ClearOutbox(); + lock (_actions) + _actions.ClearOutbox(); return true; } @@ -222,23 +257,20 @@ public async Task Dispatch() // TODO run in background: http://arteksoftwa catch (Exception e) { LogError(e); - httpClient.CancelPendingRequests(); + _httpClient.CancelPendingRequests(); } return false; } + + /// + /// Resets the current action queue + /// + public void ClearQueue() => _actions?.Clear(); - public bool OptOut - { - get => actions.OptOut; - set => actions.OptOut = value; - } - - public void ClearQueue() => actions.Clear(); - - NameValueCollection CreateParameters() + private NameValueCollection CreateParameters() { var parameters = HttpUtility.ParseQueryString(string.Empty); - parameters["rand"] = random.Next().ToString(); + parameters["rand"] = _random.Next().ToString(); parameters["cdt"] = (DateTimeOffset.UtcNow.ToUnixTimeSeconds()).ToString(); // TODO dispatching cdt older thant 24 h needs token_auth in bulk request parameters["lang"] = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; parameters["ua"] = UserAgent; @@ -249,18 +281,18 @@ NameValueCollection CreateParameters() return parameters; } - void Log(object msg) + private void Log(object msg) { - if (Verbose && !actions.OptOut) + if (Verbose && !_actions.OptOut) Console.WriteLine($"[Analytics] {msg}"); } - void LogError(object msg) + private void LogError(object msg) { Console.WriteLine($"[Analytics] [Error] {msg}"); } - static string GenerateId(int length) + private string GenerateId(int length) { return Guid.NewGuid().ToString().Replace("-", "").Substring(0, length).ToUpper(); } diff --git a/Matomo.Maui/Services/Shell/IShellHelper.cs b/Matomo.Maui/Services/Shell/IShellHelper.cs new file mode 100644 index 0000000..f6d53ac --- /dev/null +++ b/Matomo.Maui/Services/Shell/IShellHelper.cs @@ -0,0 +1,6 @@ +namespace Matomo.Maui.Services.Shell; + +public interface IShellHelper +{ + string CurrentPath { get; } +} \ No newline at end of file diff --git a/Matomo.Maui/ShellHelper.cs b/Matomo.Maui/Services/Shell/ShellHelper.cs similarity index 56% rename from Matomo.Maui/ShellHelper.cs rename to Matomo.Maui/Services/Shell/ShellHelper.cs index c052c83..acdd6de 100644 --- a/Matomo.Maui/ShellHelper.cs +++ b/Matomo.Maui/Services/Shell/ShellHelper.cs @@ -1,25 +1,10 @@ -using System; -namespace Matomo.Maui +namespace Matomo.Maui.Services.Shell { /// /// Utility class for use with Shell Navigation /// - public class ShellHelper + public class ShellHelper : IShellHelper { - private static object _syncRoot = new object(); - private static ShellHelper _instance; - - public static ShellHelper Instance - { - get - { - lock (_syncRoot) - if (_instance == null) - _instance = new ShellHelper(); - return _instance; - } - } - /// /// Get the current Shell Navigation Path /// @@ -28,13 +13,13 @@ public string CurrentPath get { var path = ""; - - foreach (var page in Shell.Current.Navigation.NavigationStack) + + foreach (var page in Microsoft.Maui.Controls.Shell.Current.Navigation.NavigationStack) { path += GetPageName($"{page}") + "/"; } if (path.Equals("/")) - path = GetPageName($"{Shell.Current.CurrentPage}") + "/"; + path = GetPageName($"{Microsoft.Maui.Controls.Shell.Current.CurrentPage}") + "/"; return path; } } diff --git a/Matomo.Maui/Services/Storage/ISimpleStorage.cs b/Matomo.Maui/Services/Storage/ISimpleStorage.cs new file mode 100644 index 0000000..c497f9c --- /dev/null +++ b/Matomo.Maui/Services/Storage/ISimpleStorage.cs @@ -0,0 +1,10 @@ +namespace Matomo.Maui.Services.Storage; + +public interface ISimpleStorage +{ + bool HasKey(string key); + string Get(string key); + T Get(string key); + T Get(string key, T defaultValue); + void Put(string key, T value); +} \ No newline at end of file diff --git a/Matomo.Maui/SimpleStorage.cs b/Matomo.Maui/Services/Storage/SimpleStorage.cs similarity index 73% rename from Matomo.Maui/SimpleStorage.cs rename to Matomo.Maui/Services/Storage/SimpleStorage.cs index 8e91c8a..b39374b 100644 --- a/Matomo.Maui/SimpleStorage.cs +++ b/Matomo.Maui/Services/Storage/SimpleStorage.cs @@ -1,31 +1,13 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Newtonsoft.Json; +using Newtonsoft.Json; -namespace Matomo.Maui +namespace Matomo.Maui.Services.Storage { - public class SimpleStorage + public class SimpleStorage : ISimpleStorage { - private static object _syncRoot = new object(); - private static SimpleStorage _instance; - - public static SimpleStorage Instance - { - get - { - lock (_syncRoot) - if (_instance == null) - _instance = new SimpleStorage(); - return _instance; - } - } - private string _filename; private Dictionary _data; - private SimpleStorage() + public SimpleStorage() { _filename = "matomodata"; _data = new Dictionary(); diff --git a/README.md b/README.md index b1d77be..bfc4b65 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This library provides [Matomo](https://matomo.org) Tracking for .NET MAUI Apps. ## Releases -This library is published for .NET 6.0 and .NET 7.0, please use the v. 6.x and v. 7.x versions accordingly. +This library is published for .NET 8.0. ## Documentation diff --git a/Sample/Matomo.Maui.Sample/App.xaml.cs b/Sample/Matomo.Maui.Sample/App.xaml.cs index 5b42885..cf7b3fc 100644 --- a/Sample/Matomo.Maui.Sample/App.xaml.cs +++ b/Sample/Matomo.Maui.Sample/App.xaml.cs @@ -1,12 +1,35 @@ -namespace Matomo.Maui.Sample; +using Matomo.Maui.Models; + +namespace Matomo.Maui.Sample; public partial class App : Application { - public App() + private IMatomoAnalytics _matomo; + + public App(IMatomoAnalytics matomo) { + _matomo = matomo; + _matomo.Verbose = true; + _matomo.OptOut = false; + _matomo.Dimensions.Add(new Dimension(id: 1, name: "AppVersion", currentValue: AppInfo.VersionString)); + InitializeComponent(); MainPage = new AppShell(); } + + protected override async void OnSleep() + { + base.OnSleep(); + + try + { + await _matomo.LeavingTheApp(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + } + } } diff --git a/Sample/Matomo.Maui.Sample/MainPage.xaml.cs b/Sample/Matomo.Maui.Sample/MainPage.xaml.cs index 1f322cd..4883717 100644 --- a/Sample/Matomo.Maui.Sample/MainPage.xaml.cs +++ b/Sample/Matomo.Maui.Sample/MainPage.xaml.cs @@ -2,23 +2,47 @@ public partial class MainPage : ContentPage { - int count = 0; + private IMatomoAnalytics _matomo; + private int _count = 0; public MainPage() { InitializeComponent(); + + HandlerChanged += OnHandlerChanged; } - + + void OnHandlerChanged(object sender, EventArgs e) + { + _matomo = Handler?.MauiContext?.Services.GetService(); + } + private void OnCounterClicked(object sender, EventArgs e) { - count++; + _count++; - if (count == 1) - CounterBtn.Text = $"Clicked {count} time"; + if (_count == 1) + CounterBtn.Text = $"Clicked {_count} time"; else - CounterBtn.Text = $"Clicked {count} times"; + CounterBtn.Text = $"Clicked {_count} times"; SemanticScreenReader.Announce(CounterBtn.Text); + _matomo?.TrackPageEvent(category: "Button", action: "Counter Clicked", value: _count); + } + + protected override void OnAppearing() + { + base.OnAppearing(); + + if (_matomo == null) return; + _matomo.TrackPage(name: "Home", path: "/"); + _matomo.TrackPageEvent(category: "Page", action: "Appearing"); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + _matomo?.TrackPageEvent(category: "Page", action: "Disappearing"); } } diff --git a/Sample/Matomo.Maui.Sample/Matomo.Maui.Sample.csproj b/Sample/Matomo.Maui.Sample/Matomo.Maui.Sample.csproj index e7d578f..b3fa158 100644 --- a/Sample/Matomo.Maui.Sample/Matomo.Maui.Sample.csproj +++ b/Sample/Matomo.Maui.Sample/Matomo.Maui.Sample.csproj @@ -1,8 +1,8 @@  - net7.0-android;net7.0-ios;net7.0-maccatalyst - $(TargetFrameworks);net7.0-windows10.0.19041.0 + net8.0-android;net8.0-ios;net8.0-maccatalyst + $(TargetFrameworks);net8.0-windows Exe @@ -10,7 +10,8 @@ true true enable - + 8.0.91 + Matomo.Maui.Sample @@ -49,7 +50,24 @@ + + + + + + + + + + + + + + + + + diff --git a/Sample/Matomo.Maui.Sample/MauiProgram.cs b/Sample/Matomo.Maui.Sample/MauiProgram.cs index 40020d5..c762836 100644 --- a/Sample/Matomo.Maui.Sample/MauiProgram.cs +++ b/Sample/Matomo.Maui.Sample/MauiProgram.cs @@ -1,4 +1,9 @@ -using Microsoft.Extensions.Logging; +global using Matomo.Maui.Services.Core; +global using Matomo.Maui.Services.Shell; + +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; namespace Matomo.Maui.Sample; @@ -6,14 +11,30 @@ public static class MauiProgram { public static MauiApp CreateMauiApp() { + var configFileName = "Matomo.Maui.Sample.appsettings.json"; +#if DEBUG + configFileName = "Matomo.Maui.Sample.appsettings.Development.json"; +#endif + + var a = Assembly.GetExecutingAssembly(); + using var stream = a.GetManifestResourceStream(configFileName); + + var config = new ConfigurationBuilder() + .AddJsonStream(stream) + .Build(); + var builder = MauiApp.CreateBuilder(); + + builder.Configuration.AddConfiguration(config); + builder .UseMauiApp() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); - }); + }) + .UseMatomo(); #if DEBUG builder.Logging.AddDebug(); diff --git a/Sample/Matomo.Maui.Sample/appsettings.Development.json b/Sample/Matomo.Maui.Sample/appsettings.Development.json new file mode 100644 index 0000000..dd042ce --- /dev/null +++ b/Sample/Matomo.Maui.Sample/appsettings.Development.json @@ -0,0 +1,7 @@ +{ + "Matomo": { + "ApiUrl": "https://matomo.org", + "SiteId": 1, + "SiteUrl": "https://app" + } +} \ No newline at end of file diff --git a/Sample/Matomo.Maui.Sample/appsettings.json b/Sample/Matomo.Maui.Sample/appsettings.json new file mode 100644 index 0000000..dd042ce --- /dev/null +++ b/Sample/Matomo.Maui.Sample/appsettings.json @@ -0,0 +1,7 @@ +{ + "Matomo": { + "ApiUrl": "https://matomo.org", + "SiteId": 1, + "SiteUrl": "https://app" + } +} \ No newline at end of file diff --git a/docs/shared b/docs/shared index f259a67..90990d3 160000 --- a/docs/shared +++ b/docs/shared @@ -1 +1 @@ -Subproject commit f259a672ca3fb7283d1d123ad278a33670718d6f +Subproject commit 90990d303042cc974e49ca924afdba171ab708c0