From e1067f1b24e0c60f4143a1d4e73a0821ee640ae9 Mon Sep 17 00:00:00 2001 From: Aaron LaBeau <80424345+biozal@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:01:34 -0600 Subject: [PATCH 1/4] feature: updated maui, taui to latest SDK and dotnet 10 --- .../DittoMauiTasksApp.csproj | 19 +- dotnet-maui/DittoMauiTasksApp/MauiProgram.cs | 55 +-- .../Platforms/Android/AndroidManifest.xml | 12 +- .../Platforms/Android/MainActivity.cs | 4 +- .../ViewModels/TasksPageviewModel.cs | 5 +- dotnet-maui/README.md | 8 +- .../DittoDotNetTasksConsole.csproj | 5 +- dotnet-tui/DittoDotNetTasksConsole/Program.cs | 3 +- .../DittoDotNetTasksConsole/TasksPeer.cs | 90 ++--- dotnet-tui/README.md | 4 +- dotnet-winforms/README.md | 6 +- dotnet-winforms/TasksApp/DittoTasksApp.csproj | 4 +- dotnet-winforms/TasksApp/MainForm.cs | 1 + dotnet-winforms/TasksApp/Program.cs | 6 +- dotnet-winforms/TasksApp/TasksPeer.cs | 370 +++++++++--------- .../TasksApp/ToDoTaskEditorForm.Designer.cs | 81 ++-- 16 files changed, 352 insertions(+), 321 deletions(-) diff --git a/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj b/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj index 9863d28ca..454943ec3 100644 --- a/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj +++ b/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj @@ -1,17 +1,17 @@  - net9.0-android;net9.0-ios;net9.0-maccatalyst - net9.0-windows10.0.19041.0;net9.0-android;net9.0-ios;net9.0-maccatalyst - net9.0-android + net10.0-android;net10.0-ios;net10.0-maccatalyst + net10.0-windows10.0.19041.0;net10.0-android;net10.0-ios + net10.0-android Exe DittoMauiTasksApp true true enable - - QS Tasks + + DittoMauiTasksApp live.ditto.quickstart.mauitasksapp @@ -21,11 +21,16 @@ 1 15.0 - 23.0 + 24.0 15.0 10.0.17763.0 + + + QS Tasks + + partial @@ -47,7 +52,7 @@ - + diff --git a/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs b/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs index 6731f2d02..5a176015a 100644 --- a/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs +++ b/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs @@ -5,6 +5,7 @@ using DittoMauiTasksApp.Utils; using DittoMauiTasksApp.ViewModels; using DittoSDK; +using DittoSDK.Auth; namespace DittoMauiTasksApp; @@ -42,30 +43,38 @@ private static Ditto SetupDitto() AppId = envVars["DITTO_APP_ID"]; PlaygroundToken = envVars["DITTO_PLAYGROUND_TOKEN"]; var authUrl = envVars["DITTO_AUTH_URL"]; - var websocketUrl = envVars["DITTO_WEBSOCKET_URL"]; - - var ditto = new Ditto(DittoIdentity - .OnlinePlayground( - AppId, - PlaygroundToken, - false, // This is required to be set to false to use the correct URLs - authUrl), Path.Combine(FileSystem.Current.AppDataDirectory, "ditto")); - - // Set the transport configuration - // https://docs.ditto.live/sdk/latest/sync/customizing-transport-configurations#enabling-and-disabling-transports - ditto.UpdateTransportConfig(config => - { - // Add the websocket URL to the transport configuration. - config.Connect.WebsocketUrls.Add(websocketUrl); - }); - - // disable sync with v3 peers, required for DQL - ditto.DisableSyncWithV3(); - - // Disable DQL strict mode - // https://docs.ditto.live/dql/strict-mode - ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false").Wait(); + // New Initialization code - https://docs.ditto.live/sdk/latest/ditto-config + var dittoConfig = new DittoConfig( + AppId, + new DittoConfigConnect.Server( + new Uri(authUrl) + ), + Path.Combine(FileSystem.Current.AppDataDirectory, "ditto") + ); + + var ditto = Ditto.Open(dittoConfig); + + // Set up authentication expiration handler (required for server connections) + ditto.Auth.ExpirationHandler = async (dittoAuth, secondsRemaining) => + { + // Authenticate when token is expiring + try + { + await dittoAuth.Auth.LoginAsync( + // Your development token, replace with your actual token + PlaygroundToken, + // Use DittoAuthenticationProvider.Development for playground, or your actual provider + DittoAuthenticationProvider.Development + ); + Console.WriteLine("Authentication successful"); + } + catch (Exception error) + { + Console.WriteLine($"Authentication failed: {error}"); + } + }; + return ditto; } diff --git a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml index 6191320ad..1aa87db56 100644 --- a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml +++ b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml @@ -1,6 +1,9 @@  - + @@ -18,11 +21,14 @@ - + - + diff --git a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs index e6b9c391d..8f5b6f6dc 100644 --- a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs +++ b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs @@ -4,7 +4,9 @@ namespace DittoMauiTasksApp; -[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] +[Activity(Theme = "@style/Maui.SplashTheme", + MainLauncher = true, + ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { } diff --git a/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs b/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs index 763965787..decfae85e 100644 --- a/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs +++ b/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs @@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Input; using DittoMauiTasksApp.Utils; using DittoSDK; +using DittoSDK.Sync; using Microsoft.Extensions.Logging; namespace DittoMauiTasksApp.ViewModels @@ -329,7 +330,7 @@ private void StartSync() { try { - ditto.StartSync(); + ditto.Sync.Start(); // Register a subscription, which determines what data syncs to this peer // https://docs.ditto.live/sdk/latest/sync/syncing-data#creating-subscriptions @@ -358,7 +359,7 @@ private void StopSync() try { - ditto.StopSync(); + ditto.Sync.Stop(); } catch (Exception e) { diff --git a/dotnet-maui/README.md b/dotnet-maui/README.md index 329afefb3..d9f47e300 100644 --- a/dotnet-maui/README.md +++ b/dotnet-maui/README.md @@ -26,7 +26,7 @@ dotnet restore These commands will build and run the app on the default iOS target: ```sh -dotnet build -t:Run -f net9.0-ios +dotnet build -t:Run -f net10.0-ios ``` ### Building and Running the App on Android @@ -34,19 +34,19 @@ dotnet build -t:Run -f net9.0-ios These commands will build and run the app on the default Android target: ```sh -dotnet build -t:Run -f net9.0-android +dotnet build -t:Run -f net10.0-android ``` ### Building and Running the App on MacOS ```sh -dotnet build -t:Run -f net9.0-maccatalyst +dotnet build -t:Run -f net10.0-maccatalyst ``` ### Building and Running the App on Windows ```sh -dotnet build -t:Run -f net9.0-windows10.0.19041.0 +dotnet build -t:Run -f net10.0-windows10.0.19041.0 ``` ### Other MAUI Platforms diff --git a/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj b/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj index df5c6ff9b..9a3db2728 100644 --- a/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj +++ b/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj @@ -2,13 +2,14 @@ Exe - net9.0 + net10.0 disable disable + 14 - + diff --git a/dotnet-tui/DittoDotNetTasksConsole/Program.cs b/dotnet-tui/DittoDotNetTasksConsole/Program.cs index c9ad3d5a7..258fcd308 100644 --- a/dotnet-tui/DittoDotNetTasksConsole/Program.cs +++ b/dotnet-tui/DittoDotNetTasksConsole/Program.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using DittoSDK; +using DittoSDK.Logging; using Terminal.Gui; public static class Program @@ -24,7 +25,7 @@ public static async Task Main(string[] args) // Disable Ditto's standard-error logging, which would interfere // with the the Terminal.Gui UI. - DittoLogger.SetLoggingEnabled(false); + DittoLogger.IsEnabled = false; RunTerminalGui(peer); } catch (Exception ex) diff --git a/dotnet-tui/DittoDotNetTasksConsole/TasksPeer.cs b/dotnet-tui/DittoDotNetTasksConsole/TasksPeer.cs index c063e61d2..16599ee07 100644 --- a/dotnet-tui/DittoDotNetTasksConsole/TasksPeer.cs +++ b/dotnet-tui/DittoDotNetTasksConsole/TasksPeer.cs @@ -6,20 +6,24 @@ using System.Threading.Tasks; using DittoSDK; +using DittoSDK.Auth; +using DittoSDK.Store; /// /// Encapsulates use of the Ditto SDK and the 'tasks' collection. -/// +/// _ditto.IsSyncActive; + public bool IsSyncActive => _ditto.Sync.IsActive; - private Ditto _ditto; + private readonly Ditto _ditto; /// /// Creates a new synchronizing TasksPeer instance. @@ -31,7 +35,7 @@ public static async Task Create( string websocketUrl) { var peer = new TasksPeer(appId, playgroundToken, authUrl, websocketUrl); - await peer.DisableStrictMode(); + peer.Authenticate(); peer.RegisterSubscription(); await peer.InsertInitialTasks(); peer.StartSync(); @@ -39,6 +43,27 @@ public static async Task Create( return peer; } + private void Authenticate() + { + _ditto.Auth.ExpirationHandler = async (ditto, secondsRemaining) => + { + // Authenticate when token is expiring + try + { + await ditto.Auth.LoginAsync( + // Your development token, replace with your actual token + PlaygroundToken, + // Use DittoAuthenticationProvider.Development for playground, or your actual provider + DittoAuthenticationProvider.Development + ); + } + catch (Exception error) + { + Console.WriteLine($"Authentication failed: {error}"); + } + }; + } + /// /// Registers a subscription for the tasks collection to enable data synchronization /// with other peers and the Ditto cloud. This subscription determines what data @@ -72,56 +97,23 @@ private TasksPeer(string appId, string playgroundToken, string authUrl, string w { AppId = appId; PlaygroundToken = playgroundToken; + AuthUrl = authUrl; + WebsocketUrl = websocketUrl; - // We use a temporary directory to store Ditto's local database. This - // means that data will not be persistent between runs of the - // application, but it allows us to run multiple instances of the - // application concurrently on the same machine. For a production - // application, we would want to store the database in a more permanent - // location, and if multiple instances are needed, ensure that each - // instance has its own persistence directory. - var tempDir = Path.Combine( - Path.GetTempPath(), - "DittoDotNetTasksConsole-" + Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempDir); + var config = new DittoConfig( + AppId, + new DittoConfigConnect.Server(new Uri(authUrl)) + ); - var identity = DittoIdentity.OnlinePlayground( - appId, - playgroundToken, - false, // This is required to be set to false to use the correct URLs - authUrl); - - _ditto = new Ditto(identity, tempDir); - - _ditto.UpdateTransportConfig(config => - { - // Add the websocket URL to the transport configuration. - config.Connect.WebsocketUrls.Add(websocketUrl); - }); - - // disable sync with v3 peers, required for DQL - _ditto.DisableSyncWithV3(); + _ditto = Ditto.Open(config); } public void Dispose() { _ditto.Dispose(); - _ditto = null; GC.SuppressFinalize(this); } - /// - /// Disables DQL strict mode to allow flexible query operations without requiring - /// predefined collection schemas. This enables the application to work with - /// dynamic document structures and perform queries that return all fields by default. - /// - /// A task that represents the asynchronous operation. - /// - private async Task DisableStrictMode() - { - await _ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false"); - } - /// /// Inserts the initial tasks into the 'tasks' collection. /// @@ -246,7 +238,7 @@ public async Task UpdateTaskDone(string taskId, bool newDoneState) } /// - /// Specify a handler to be called asynchronously when the tasks collection changes. + /// Specify a handler to be called asynchronously when the task collection changes. /// public DittoStoreObserver ObserveTasksCollection(Func, Task> handler) { @@ -259,7 +251,7 @@ public DittoStoreObserver ObserveTasksCollection(Func, Task> han // Deserialize the JSON documents into ToDoTask objects var tasks = queryResult.Items.Select(d => JsonSerializer.Deserialize(d.JsonString()) - ).OrderBy(t => t.Id).ToList(); + ).OrderBy(t => t?.Id).ToList(); await handler(tasks); } @@ -275,7 +267,7 @@ public DittoStoreObserver ObserveTasksCollection(Func, Task> han /// public void StartSync() { - _ditto.StartSync(); + _ditto.Sync.Start(); } /// @@ -287,6 +279,6 @@ public void StopSync() { subscription.Cancel(); } - _ditto.StopSync(); + _ditto.Sync.Stop(); } -} +} \ No newline at end of file diff --git a/dotnet-tui/README.md b/dotnet-tui/README.md index 21191a907..57ce7c4e5 100644 --- a/dotnet-tui/README.md +++ b/dotnet-tui/README.md @@ -3,7 +3,7 @@ ## Prerequisites -1. Install the .NET 9 SDK from +1. Install the .NET 10 SDK from 2. Create an application at . Make note of the app ID and online playground token 3. Copy the `.env.sample` file at the top level of the quickstart repo to `.env` and add your app ID and online playground token. @@ -11,7 +11,7 @@ ## Documentation - [Ditto C# .NET SDK Install Guide](https://docs.ditto.live/install-guides/c-sharp) -- [Ditto C# .NET SDK API Reference](https://software.ditto.live/dotnet/Ditto/4.9.1/api-reference/) +- [Ditto C# .NET SDK API Reference](https://software.ditto.live/dotnet/Ditto/4.14.1/api-reference/) ## .NET Console App diff --git a/dotnet-winforms/README.md b/dotnet-winforms/README.md index f6b6d7ea3..e40c00ed7 100644 --- a/dotnet-winforms/README.md +++ b/dotnet-winforms/README.md @@ -2,7 +2,7 @@ ## Prerequisites -1. Install the .NET 8 SDK from +1. Install the .NET 10 SDK from 2. Create an application at . Make note of the app ID and online playground token 3. Copy the `.env.sample` file at the top level of the quickstart repo to `.env` and add your app ID and online playground token. @@ -10,12 +10,12 @@ ## Documentation - [Ditto C# .NET SDK Install Guide](https://docs.ditto.live/install-guides/c-sharp) -- [Ditto C# .NET SDK API Reference](https://software.ditto.live/dotnet/Ditto/4.11.1/api-reference/) +- [Ditto C# .NET SDK API Reference](https://software.ditto.live/dotnet/Ditto/4.14.1/api-reference/) ## .NET Windows Forms Application -This is a Windows Form Application is targeting [.NET 8.0](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/whats-new/net80?view=netdesktop-9.0) using the new open source version of [Windows Forms](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/overview/). +This is a Windows Form Application is targeting [.NET 10.0](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/whats-new/net100) using the new open source version of [Windows Forms](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/overview/). This is not compatabile with the Windows version of .NET Framework (4.x) - nor is it compatable with libraries that target .NET Standard 2.0. Ditto requires libraries target modern .NET Standard 2.1 versions. If you need support for .NET Standard 2.0, please [contact us](https://www.ditto.com/schedule-a-demo). diff --git a/dotnet-winforms/TasksApp/DittoTasksApp.csproj b/dotnet-winforms/TasksApp/DittoTasksApp.csproj index d345393c5..2559b083f 100644 --- a/dotnet-winforms/TasksApp/DittoTasksApp.csproj +++ b/dotnet-winforms/TasksApp/DittoTasksApp.csproj @@ -6,10 +6,12 @@ enable true enable + SystemAware + true - + diff --git a/dotnet-winforms/TasksApp/MainForm.cs b/dotnet-winforms/TasksApp/MainForm.cs index fe61be3b8..b8e86546d 100644 --- a/dotnet-winforms/TasksApp/MainForm.cs +++ b/dotnet-winforms/TasksApp/MainForm.cs @@ -1,4 +1,5 @@ using DittoSDK; +using DittoSDK.Store; using System.Threading.Tasks.Sources; namespace DittoTasksApp diff --git a/dotnet-winforms/TasksApp/Program.cs b/dotnet-winforms/TasksApp/Program.cs index ae1e03a90..6394c2e2e 100644 --- a/dotnet-winforms/TasksApp/Program.cs +++ b/dotnet-winforms/TasksApp/Program.cs @@ -1,5 +1,6 @@ using System.Reflection; using DittoSDK; +using DittoSDK.Logging; namespace DittoTasksApp { @@ -19,9 +20,8 @@ static async Task Main() using var peer = await TasksPeer.Create(appId, playgroundToken, authUrl, websocketUrl); - // Disable Ditto's standard-error logging, which would interfere - // with the the Terminal.Gui UI. - DittoLogger.SetLoggingEnabled(false); + // Disable Ditto's standard-error logging + DittoLogger.IsEnabled = true; // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); diff --git a/dotnet-winforms/TasksApp/TasksPeer.cs b/dotnet-winforms/TasksApp/TasksPeer.cs index b1789cca0..f61412a0e 100644 --- a/dotnet-winforms/TasksApp/TasksPeer.cs +++ b/dotnet-winforms/TasksApp/TasksPeer.cs @@ -6,120 +6,125 @@ using System.Threading.Tasks; using DittoSDK; +using DittoSDK.Auth; +using DittoSDK.Store; using DittoTasksApp; /// /// Encapsulates use of the Ditto SDK and the 'tasks' collection. /// -public class TasksPeer : IDisposable -{ - private const string Query = "SELECT * FROM tasks WHERE NOT deleted"; - - public string AppId { get; private set; } - public string PlaygroundToken { get; private set; } - public string AuthUrl { get; private set; } - public string WebsocketUrl { get; private set; } - public bool IsSyncActive => _ditto.IsSyncActive; +namespace DittoTasksApp +{ + public class TasksPeer : IDisposable + { + private const string Query = "SELECT * FROM tasks WHERE NOT deleted"; - private Ditto _ditto; + public string AppId { get; private set; } + public string PlaygroundToken { get; private set; } + public string AuthUrl { get; private set; } + public string WebsocketUrl { get; private set; } - /// - /// Creates a new synchronizing TasksPeer instance. - /// - public static async Task Create( - string appId, - string playgroundToken, - string authUrl, - string websocketUrl) - { - var peer = new TasksPeer(appId, playgroundToken, authUrl, websocketUrl); - await peer.DisableStrictMode(); - peer.RegisterSubscription(); - await peer.InsertInitialTasks(); - peer.StartSync(); + public bool IsSyncActive => _ditto.Sync.IsActive; - return peer; - } + private readonly Ditto _ditto; - /// - /// Registers a subscription for the tasks collection to enable data synchronization - /// with other peers and the Ditto cloud. This subscription determines what data - /// will be synced to this peer during the synchronization process. - /// - /// - /// The subscription is created using the same query that filters out deleted tasks - /// (SELECT * FROM tasks WHERE NOT deleted), ensuring that only active, - /// non-deleted tasks are synchronized across the network. - /// - /// This method should be called during peer initialization to establish the - /// subscription before starting the sync process. The subscription remains active - /// until explicitly cancelled or when the peer is disposed. - /// - /// - private void RegisterSubscription() - { - // Register a subscription, which determines what data syncs to this peer - // https://docs.ditto.live/sdk/latest/sync/syncing-data#creating-subscriptions - _ditto.Sync.RegisterSubscription(Query); - } + /// + /// Creates a new synchronizing TasksPeer instance. + /// + public static async Task Create( + string appId, + string playgroundToken, + string authUrl, + string websocketUrl) + { + var peer = new TasksPeer(appId, playgroundToken, authUrl, websocketUrl); + peer.Authenticate(); + peer.RegisterSubscription(); + await peer.InsertInitialTasks(); + peer.StartSync(); - /// - /// Constructor - /// - /// Ditto application ID - /// Ditto online playground token - /// Ditto Auth URL - /// Ditto Websocket URL - private TasksPeer(string appId, string playgroundToken, string authUrl, string websocketUrl) - { - AppId = appId; - PlaygroundToken = playgroundToken; - AuthUrl = authUrl; - WebsocketUrl = websocketUrl; + return peer; + } - var identity = DittoIdentity.OnlinePlayground( - appId, - playgroundToken, - false, // This is required to be set to false to use the correct URLs - authUrl); + private void Authenticate() + { + _ditto.Auth.ExpirationHandler = async (ditto, secondsRemaining) => + { + // Authenticate when token is expiring + try + { + await ditto.Auth.LoginAsync( + // Your development token, replace with your actual token + PlaygroundToken, + // Use DittoAuthenticationProvider.Development for playground, or your actual provider + DittoAuthenticationProvider.Development + ); + Console.WriteLine("Authentication successful"); + } + catch (Exception error) + { + Console.WriteLine($"Authentication failed: {error}"); + } + }; + } - _ditto = new Ditto(identity); + /// + /// Registers a subscription for the tasks collection to enable data synchronization + /// with other peers and the Ditto cloud. This subscription determines what data + /// will be synced to this peer during the synchronization process. + /// + /// + /// The subscription is created using the same query that filters out deleted tasks + /// (SELECT * FROM tasks WHERE NOT deleted), ensuring that only active, + /// non-deleted tasks are synchronized across the network. + /// + /// This method should be called during peer initialization to establish the + /// subscription before starting the sync process. The subscription remains active + /// until explicitly cancelled or when the peer is disposed. + /// + /// + private void RegisterSubscription() + { + // Register a subscription, which determines what data syncs to this peer + // https://docs.ditto.live/sdk/latest/sync/syncing-data#creating-subscriptions + _ditto.Sync.RegisterSubscription(Query); + } - _ditto.UpdateTransportConfig(config => + /// + /// Constructor + /// + /// Ditto application ID + /// Ditto online playground token + /// Ditto Auth URL + /// Ditto Websocket URL + private TasksPeer(string appId, string playgroundToken, string authUrl, string websocketUrl) { - // Add the websocket URL to the transport configuration. - config.Connect.WebsocketUrls.Add(websocketUrl); - }); + AppId = appId; + PlaygroundToken = playgroundToken; + AuthUrl = authUrl; + WebsocketUrl = websocketUrl; - // disable sync with v3 peers, required for DQL - _ditto.DisableSyncWithV3(); - } + var config = new DittoConfig ( + AppId, + new DittoConfigConnect.Server(new Uri(authUrl)) + ); - public void Dispose() - { - _ditto.Dispose(); - GC.SuppressFinalize(this); - } + _ditto = Ditto.Open(config); + } - /// - /// Disables DQL strict mode to allow flexible query operations without requiring - /// predefined collection schemas. This enables the application to work with - /// dynamic document structures and perform queries that return all fields by default. - /// - /// A task that represents the asynchronous operation. - /// - private async Task DisableStrictMode() - { - await _ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false"); - } + public void Dispose() + { + _ditto.Dispose(); + GC.SuppressFinalize(this); + } - /// - /// Inserts the initial tasks into the 'tasks' collection. - /// - private async Task InsertInitialTasks() - { - var initialTasks = new List> + /// + /// Inserts the initial tasks into the 'tasks' collection. + /// + private async Task InsertInitialTasks() + { + var initialTasks = new List> { new Dictionary { @@ -151,134 +156,135 @@ private async Task InsertInitialTasks() } }; - const string insertCommand = "INSERT INTO tasks INITIAL DOCUMENTS (:task)"; - foreach (var task in initialTasks) - { - await _ditto.Store.ExecuteAsync(insertCommand, new Dictionary() + const string insertCommand = "INSERT INTO tasks INITIAL DOCUMENTS (:task)"; + foreach (var task in initialTasks) + { + await _ditto.Store.ExecuteAsync(insertCommand, new Dictionary() { { "task", task } }); + } } - } - /// - /// Adds a new task to the 'tasks' collection. - /// - public async Task AddTask(string title) - { - if (string.IsNullOrWhiteSpace(title)) + /// + /// Adds a new task to the 'tasks' collection. + /// + public async Task AddTask(string title) { - throw new ArgumentException("title cannot be empty"); - } + if (string.IsNullOrWhiteSpace(title)) + { + throw new ArgumentException("title cannot be empty"); + } - var doc = new Dictionary + var doc = new Dictionary { {"title", title}, {"done", false}, {"deleted", false } }; - const string insertCommand = "INSERT INTO tasks DOCUMENTS (:doc)"; - await _ditto.Store.ExecuteAsync(insertCommand, new Dictionary() + const string insertCommand = "INSERT INTO tasks DOCUMENTS (:doc)"; + await _ditto.Store.ExecuteAsync(insertCommand, new Dictionary() { { "doc", doc } }); - } - - /// - /// Update the title of an existing task. - /// - public async Task UpdateTaskTitle(string taskId, string newTitle) - { - if (string.IsNullOrWhiteSpace(taskId)) - { - throw new ArgumentException("taskId cannot be empty"); } - if (string.IsNullOrWhiteSpace(newTitle)) + /// + /// Update the title of an existing task. + /// + public async Task UpdateTaskTitle(string taskId, string newTitle) { - throw new ArgumentException("title cannot be empty"); - } + if (string.IsNullOrWhiteSpace(taskId)) + { + throw new ArgumentException("taskId cannot be empty"); + } - const string updateQuery = "UPDATE tasks SET title = :title WHERE _id = :id"; - await _ditto.Store.ExecuteAsync(updateQuery, new Dictionary() + if (string.IsNullOrWhiteSpace(newTitle)) + { + throw new ArgumentException("title cannot be empty"); + } + + const string updateQuery = "UPDATE tasks SET title = :title WHERE _id = :id"; + await _ditto.Store.ExecuteAsync(updateQuery, new Dictionary() { {"title", newTitle}, {"id", taskId} }); - } + } - /// - /// Mark a task as deleted. - /// - public async Task DeleteTask(string taskId) - { - if (string.IsNullOrWhiteSpace(taskId)) + /// + /// Mark a task as deleted. + /// + public async Task DeleteTask(string taskId) { - throw new ArgumentException("taskId cannot be empty"); - } + if (string.IsNullOrWhiteSpace(taskId)) + { + throw new ArgumentException("taskId cannot be empty"); + } - const string updateQuery = "UPDATE tasks SET deleted = true WHERE _id = :id"; - await _ditto.Store.ExecuteAsync(updateQuery, new Dictionary() + const string updateQuery = "UPDATE tasks SET deleted = true WHERE _id = :id"; + await _ditto.Store.ExecuteAsync(updateQuery, new Dictionary() { { "id", taskId } }); - } + } - /// - /// Mark a task as complete or not complete. - /// - public async Task UpdateTaskDone(string taskId, bool newDoneState) - { - const string updateQuery = "UPDATE tasks SET done = :newDoneState WHERE _id = :id AND done != :newDoneState"; - await _ditto.Store.ExecuteAsync(updateQuery, new Dictionary + /// + /// Mark a task as complete or not complete. + /// + public async Task UpdateTaskDone(string taskId, bool newDoneState) + { + const string updateQuery = "UPDATE tasks SET done = :newDoneState WHERE _id = :id AND done != :newDoneState"; + await _ditto.Store.ExecuteAsync(updateQuery, new Dictionary { { "newDoneState", newDoneState }, { "id", taskId } }); - } + } - /// - /// Specify a handler to be called asynchronously when the task collection changes. - /// - public DittoStoreObserver ObserveTasksCollection(Func, Task> handler) - { - // Register an observer, which runs against the local database on this peer - // https://docs.ditto.live/sdk/latest/crud/observing-data-changes#setting-up-store-observers - return _ditto.Store.RegisterObserver(Query, async (queryResult) => + /// + /// Specify a handler to be called asynchronously when the task collection changes. + /// + public DittoStoreObserver ObserveTasksCollection(Func, Task> handler) { - try + // Register an observer, which runs against the local database on this peer + // https://docs.ditto.live/sdk/latest/crud/observing-data-changes#setting-up-store-observers + return _ditto.Store.RegisterObserver(Query, async (queryResult) => { - // Deserialize the JSON documents into ToDoTask objects - var tasks = queryResult.Items.Select(d => - JsonSerializer.Deserialize(d.JsonString()) - ).OrderBy(t => t.Id).ToList(); + try + { + // Deserialize the JSON documents into ToDoTask objects + var tasks = queryResult.Items.Select(d => + JsonSerializer.Deserialize(d.JsonString()) + ).OrderBy(t => t?.Id).ToList(); - await handler(tasks); - } - catch (Exception e) - { - Console.Error.WriteLine($"ERROR tasks observation handler failed: {e.Message}"); - } - }); - } + await handler(tasks); + } + catch (Exception e) + { + Console.Error.WriteLine($"ERROR tasks observation handler failed: {e.Message}"); + } + }); + } - /// - /// Start synchronizing the 'tasks' collection. - /// - public void StartSync() - { - _ditto.StartSync(); - } + /// + /// Start synchronizing the 'tasks' collection. + /// + public void StartSync() + { + _ditto.Sync.Start(); + } - /// - /// Stop synchronizing the 'tasks' collection. - /// - public void StopSync() - { - foreach (var subscription in _ditto.Sync.Subscriptions) + /// + /// Stop synchronizing the 'tasks' collection. + /// + public void StopSync() { - subscription.Cancel(); + foreach (var subscription in _ditto.Sync.Subscriptions) + { + subscription.Cancel(); + } + _ditto.Sync.Stop(); } - _ditto.StopSync(); } } diff --git a/dotnet-winforms/TasksApp/ToDoTaskEditorForm.Designer.cs b/dotnet-winforms/TasksApp/ToDoTaskEditorForm.Designer.cs index d9c302505..5d5cd9e60 100644 --- a/dotnet-winforms/TasksApp/ToDoTaskEditorForm.Designer.cs +++ b/dotnet-winforms/TasksApp/ToDoTaskEditorForm.Designer.cs @@ -28,84 +28,89 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - btnSave = new Button(); - btnCancel = new Button(); lblNameTxt = new Label(); tbName = new TextBox(); lblIsCompleteTxt = new Label(); cbIsCompleted = new CheckBox(); + btnSave2 = new Button(); + btnCancel2 = new Button(); SuspendLayout(); // - // btnSave - // - btnSave.Location = new Point(153, 176); - btnSave.Name = "btnSave"; - btnSave.Size = new Size(75, 23); - btnSave.TabIndex = 0; - btnSave.Text = "Save"; - btnSave.UseVisualStyleBackColor = true; - btnSave.Click += btnSave_Click; - // - // btnCancel - // - btnCancel.Location = new Point(245, 176); - btnCancel.Name = "btnCancel"; - btnCancel.Size = new Size(75, 23); - btnCancel.TabIndex = 1; - btnCancel.Text = "Cancel"; - btnCancel.UseVisualStyleBackColor = true; - btnCancel.Click += btnCancel_Click; - // // lblNameTxt // lblNameTxt.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; lblNameTxt.AutoSize = true; - lblNameTxt.Location = new Point(12, 46); + lblNameTxt.Location = new Point(29, 126); + lblNameTxt.Margin = new Padding(7, 0, 7, 0); lblNameTxt.Name = "lblNameTxt"; - lblNameTxt.Size = new Size(30, 15); + lblNameTxt.Size = new Size(74, 41); lblNameTxt.TabIndex = 2; lblNameTxt.Text = "Task"; // // tbName // tbName.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - tbName.Location = new Point(57, 43); + tbName.Location = new Point(138, 118); + tbName.Margin = new Padding(7, 8, 7, 8); tbName.Multiline = true; tbName.Name = "tbName"; - tbName.Size = new Size(405, 67); + tbName.Size = new Size(978, 176); tbName.TabIndex = 3; // // lblIsCompleteTxt // lblIsCompleteTxt.AutoSize = true; - lblIsCompleteTxt.Location = new Point(12, 132); + lblIsCompleteTxt.Location = new Point(29, 361); + lblIsCompleteTxt.Margin = new Padding(7, 0, 7, 0); lblIsCompleteTxt.Name = "lblIsCompleteTxt"; - lblIsCompleteTxt.Size = new Size(71, 15); + lblIsCompleteTxt.Size = new Size(179, 41); lblIsCompleteTxt.TabIndex = 4; lblIsCompleteTxt.Text = "Is Completd"; // // cbIsCompleted // cbIsCompleted.AutoSize = true; - cbIsCompleted.Location = new Point(89, 133); + cbIsCompleted.Location = new Point(216, 364); + cbIsCompleted.Margin = new Padding(7, 8, 7, 8); cbIsCompleted.Name = "cbIsCompleted"; - cbIsCompleted.Size = new Size(15, 14); + cbIsCompleted.Size = new Size(34, 33); cbIsCompleted.TabIndex = 5; cbIsCompleted.UseVisualStyleBackColor = true; // + // btnSave2 + // + btnSave2.Location = new Point(682, 352); + btnSave2.Name = "btnSave2"; + btnSave2.Size = new Size(188, 58); + btnSave2.TabIndex = 6; + btnSave2.Text = "Save"; + btnSave2.UseVisualStyleBackColor = true; + btnSave2.Click += btnSave_Click; + // + // btnCancel2 + // + btnCancel2.Location = new Point(904, 352); + btnCancel2.Name = "btnCancel2"; + btnCancel2.Size = new Size(188, 58); + btnCancel2.TabIndex = 7; + btnCancel2.Text = "Cancel"; + btnCancel2.UseVisualStyleBackColor = true; + btnCancel2.Click += btnCancel_Click; + // // ToDoTaskEditorForm // - AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleDimensions = new SizeF(17F, 41F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(484, 211); + ClientSize = new Size(1137, 443); + Controls.Add(btnCancel2); + Controls.Add(btnSave2); Controls.Add(cbIsCompleted); Controls.Add(lblIsCompleteTxt); Controls.Add(tbName); Controls.Add(lblNameTxt); - Controls.Add(btnCancel); - Controls.Add(btnSave); - MaximumSize = new Size(500, 250); - MinimumSize = new Size(500, 250); + Margin = new Padding(7, 8, 7, 8); + MaximumSize = new Size(1169, 531); + MinimumSize = new Size(1169, 531); Name = "ToDoTaskEditorForm"; StartPosition = FormStartPosition.CenterParent; Text = "ToDo Task Editor"; @@ -115,11 +120,11 @@ private void InitializeComponent() #endregion - private Button btnSave; - private Button btnCancel; private Label lblNameTxt; private TextBox tbName; private Label lblIsCompleteTxt; private CheckBox cbIsCompleted; + private Button btnSave2; + private Button btnCancel2; } } \ No newline at end of file From c789374fb42c968dc466e72e9fec74268a072c70 Mon Sep 17 00:00:00 2001 From: Aaron LaBeau <80424345+biozal@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:25:48 -0600 Subject: [PATCH 2/4] feature: get android compiling --- .../DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml | 4 ++-- .../DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml index 1aa87db56..586a69380 100644 --- a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml +++ b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/AndroidManifest.xml @@ -28,7 +28,7 @@ + android:targetSdkVersion="36" + android:compileSdkVersion="36"/> diff --git a/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs b/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs index decfae85e..066ff0113 100644 --- a/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs +++ b/dotnet-maui/DittoMauiTasksApp/ViewModels/TasksPageviewModel.cs @@ -12,7 +12,7 @@ namespace DittoMauiTasksApp.ViewModels { public partial class TasksPageviewModel : ObservableObject { - private const string SelectQuery = "SELECT * FROM tasks WHERE NOT deleted ORDER BY title ASC"; + private const string SelectQuery = "SELECT * FROM tasks WHERE NOT deleted"; private readonly Ditto ditto; private readonly IPopupService popupService; From 95b0adfa18352a6dd3c905f04670f203409b2195 Mon Sep 17 00:00:00 2001 From: Aaron LaBeau <80424345+biozal@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:39:33 -0600 Subject: [PATCH 3/4] fix: lint maui project --- dotnet-maui/DittoMauiTasksApp/MauiProgram.cs | 22 +++++++++---------- .../Platforms/Android/MainActivity.cs | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs b/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs index 5a176015a..e316382a6 100644 --- a/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs +++ b/dotnet-maui/DittoMauiTasksApp/MauiProgram.cs @@ -46,25 +46,25 @@ private static Ditto SetupDitto() // New Initialization code - https://docs.ditto.live/sdk/latest/ditto-config var dittoConfig = new DittoConfig( - AppId, + AppId, new DittoConfigConnect.Server( new Uri(authUrl) - ), + ), Path.Combine(FileSystem.Current.AppDataDirectory, "ditto") ); - var ditto = Ditto.Open(dittoConfig); - - // Set up authentication expiration handler (required for server connections) + var ditto = Ditto.Open(dittoConfig); + + // Set up authentication expiration handler (required for server connections) ditto.Auth.ExpirationHandler = async (dittoAuth, secondsRemaining) => { // Authenticate when token is expiring try { - await dittoAuth.Auth.LoginAsync( - // Your development token, replace with your actual token - PlaygroundToken, - // Use DittoAuthenticationProvider.Development for playground, or your actual provider + await dittoAuth.Auth.LoginAsync( + // Your development token, replace with your actual token + PlaygroundToken, + // Use DittoAuthenticationProvider.Development for playground, or your actual provider DittoAuthenticationProvider.Development ); Console.WriteLine("Authentication successful"); @@ -73,8 +73,8 @@ await dittoAuth.Auth.LoginAsync( { Console.WriteLine($"Authentication failed: {error}"); } - }; - + }; + return ditto; } diff --git a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs index 8f5b6f6dc..31191d571 100644 --- a/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs +++ b/dotnet-maui/DittoMauiTasksApp/Platforms/Android/MainActivity.cs @@ -4,8 +4,8 @@ namespace DittoMauiTasksApp; -[Activity(Theme = "@style/Maui.SplashTheme", - MainLauncher = true, +[Activity(Theme = "@style/Maui.SplashTheme", + MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { From 5ecc96f84e2182100a3f08f52f2924bc1a4e29a8 Mon Sep 17 00:00:00 2001 From: Aaron LaBeau <80424345+biozal@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:48:49 -0600 Subject: [PATCH 4/4] updated dotnet to preview 4 --- dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj | 2 +- .../DittoDotNetTasksConsole.Tests.csproj | 2 +- .../DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj | 2 +- dotnet-winforms/TasksApp/DittoTasksApp.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj b/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj index 454943ec3..256a33ec7 100644 --- a/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj +++ b/dotnet-maui/DittoMauiTasksApp/DittoMauiTasksApp.csproj @@ -52,7 +52,7 @@ - + diff --git a/dotnet-tui/DittoDotNetTasksConsole.Tests/DittoDotNetTasksConsole.Tests.csproj b/dotnet-tui/DittoDotNetTasksConsole.Tests/DittoDotNetTasksConsole.Tests.csproj index f732e5996..e09f3febe 100644 --- a/dotnet-tui/DittoDotNetTasksConsole.Tests/DittoDotNetTasksConsole.Tests.csproj +++ b/dotnet-tui/DittoDotNetTasksConsole.Tests/DittoDotNetTasksConsole.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj b/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj index 9a3db2728..5951cc49e 100644 --- a/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj +++ b/dotnet-tui/DittoDotNetTasksConsole/DittoDotNetTasksConsole.csproj @@ -9,7 +9,7 @@ - + diff --git a/dotnet-winforms/TasksApp/DittoTasksApp.csproj b/dotnet-winforms/TasksApp/DittoTasksApp.csproj index 2559b083f..3285dea49 100644 --- a/dotnet-winforms/TasksApp/DittoTasksApp.csproj +++ b/dotnet-winforms/TasksApp/DittoTasksApp.csproj @@ -11,7 +11,7 @@ - +