UnifiedNotes.Maui (1.4.4)
Installation
dotnet nuget add source --name logikonline --username your_username --password your_token dotnet add package --source logikonline --version 1.4.4 UnifiedNotes.MauiAbout this package
A comprehensive note-taking and management control library for .NET MAUI applications with features including:
- Rich text note editing with Syncfusion RichTextEditor (WYSIWYG)
- Bold, italic, underline, strikethrough, and more formatting options
- Note organization with folders and tags
- Search and filtering capabilities
- Per-note dirty state tracking with undo support
- AI-powered note insights using UnifiedRag.Maui
- Data integration with UnifiedData.Maui
- Customizable UI with bindable properties
- Touch gesture support and responsive design
- Note data persistence and sync
UnifiedNotes.Maui
A professional note-taking control for .NET MAUI applications featuring rich text editing, intelligent organization, hierarchical document trees, and AI-powered summaries.
Version 1.3.4
Features
- Rich Text Editing - WYSIWYG editor powered by Syncfusion RichTextEditor with bold, italic, underline, lists, links, tables, and more
- Smart Grouping - Notes automatically grouped by date (Today, Yesterday, This Week, Last Week, Month/Year) with sticky headers
- Document Hierarchy - Tree view for parent-child document relationships with load-on-demand, node reordering, and pagination
- Tag Management - Add/remove tags as pills, filter by tags
- Search & Filter - Full-text search, filter by tags, dates, pinned status, or top-level only
- Pin Notes - Pin important notes to the top of the list
- Per-Note Dirty Tracking - Visual indicator (orange dot) shows unsaved changes, with Undo support
- AI Features - AI-generated summaries and document classification via UnifiedRag.Maui integration
- Responsive Layout - Three-pane layout (list, editor, details/tree) using MALayout
- Data Persistence - Stores notes via UnifiedData.Maui service
Installation
dotnet add package UnifiedNotes.Maui
Dependencies
This package requires:
Syncfusion.Maui.RichTextEditor- Rich text editingSyncfusion.Maui.ListView- Grouped list with sticky headersSyncfusion.Maui.TreeView- Hierarchical document tree displaySyncfusion.Maui.DataSource- Sorting and groupingMarketAlly.MALayout- Responsive pane layoutMarketAlly.MAToolbar- Toolbar controlsMarketAlly.Dialogs.Maui- Confirmation dialogsCommunityToolkit.Mvvm- MVVM source generatorsIndiko.Maui.Controls.Markdown- Markdown renderingUnifiedData.Maui- Data persistence (via UnifiedRag.Maui)UnifiedRag.Maui- AI integration (optional)
Note
: Syncfusion components require a valid license. See Syncfusion Licensing.
Setup
1. Register in MauiProgram.cs
There are two approaches to configure UnifiedNotes:
Option A: "Batteries-Included" Setup (Recommended)
Use the AddUnifiedNotes* methods that configure everything automatically:
using UnifiedNotes.Maui.Extensions;
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureSyncfusionCore()
// Add UnifiedNotes with OpenAI (configures data + RAG + notes)
.AddUnifiedNotes(
openAiApiKey: "sk-...",
modelName: "gpt-4o-mini",
configureNotes: options =>
{
options.SummaryMode = NoteSummaryMode.Both;
options.AutoSave = true;
}
);
return builder.Build();
}
Option B: Manual Setup with UseUnifiedNotes
Register services separately for more control:
using UnifiedNotes.Maui.Extensions;
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
// Register Syncfusion (required)
.ConfigureSyncfusionCore()
// Register UnifiedData for persistence
.UseUnifiedData(options => { /* configure */ })
// Register UnifiedRag for AI features (optional)
.UseUnifiedRag(options => { /* configure */ })
// Register UnifiedNotes
.UseUnifiedNotes(options =>
{
options.SummaryMode = NoteSummaryMode.Both; // AI generates, user can edit
options.AutoSave = true;
options.AutoSaveIntervalSeconds = 10;
});
return builder.Build();
}
2. Add to XAML
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:notes="clr-namespace:UnifiedNotes.Maui.Controls;assembly=UnifiedNotes.Maui">
<notes:UnifiedNotesControl x:Name="NotesControl" />
</ContentPage>
3. Initialize (Code-Behind)
public partial class NotesPage : ContentPage
{
public NotesPage(UnifiedNotesControl notesControl)
{
InitializeComponent();
// Use DI-provided control or get from service provider
Content = notesControl;
// Load notes on appearing
notesControl.LoadNotesAsync();
}
}
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
NoteType |
string |
"note" |
Document type used to identify and filter notes in the database |
ContentType |
DocumentContentType |
AllNone |
Content type used to filter documents (0-99 built-in, 100+ custom). Ignored if ContentTypeRegistry is set. |
ContentTypeRegistry |
Dictionary<int, string>? |
null |
Registry of content types to filter by, with display names. Takes precedence over ContentType. |
SummaryMode |
NoteSummaryMode |
Both |
How summaries are generated |
EnableRichTextEditing |
bool |
true |
Enable WYSIWYG editor |
AutoSave |
bool |
true |
Auto-save notes |
AutoSaveIntervalSeconds |
int |
10 |
Auto-save interval |
ShowFolders |
bool |
true |
Show folder organization |
EnableAIInsights |
bool |
true |
Enable AI features |
DefaultSortOrder |
string |
"modified" |
Default sort (created/modified/alphabetical) |
Content Types
You can use the ContentType option to filter documents by their content type. This leverages the DocumentContentType enum from UnifiedData.Maui:
// Show only Plan-type documents
builder.UseUnifiedNotes(options =>
{
options.NoteType = "execution";
options.ContentType = DocumentContentType.Plan;
});
// Use a custom content type (100+)
builder.UseUnifiedNotes(options =>
{
options.NoteType = "custom";
options.ContentType = (DocumentContentType)150; // Custom type
});
Built-in content types (0-99):
AllNone(0) - No filtering, shows all content typesPlan- Execution plansStepOutput- Step execution outputsTrace- Trace/debug information
Custom content types (100+) can be defined by your application.
Content Type Registry (Multiple Types)
When you need to filter by multiple content types and display friendly names in the UI, use ContentTypeRegistry:
builder.UseUnifiedNotes(options =>
{
options.NoteType = "conversation";
options.ContentTypeRegistry = new Dictionary<int, string>
{
{ 207, "Session" },
{ 208, "Exchange" },
{ 209, "Tool Call" }
};
});
This provides:
- Multi-type filtering - SQL
WHERE content_type IN (207, 208, 209)instead of a single type - Display names - Details pane shows "Session" instead of "Custom (207)"
Example with a complete qLynq-style setup:
// Define content types as constants
private const int SessionType = 207;
private const int ExchangeType = 208;
private const int ToolCallType = 209;
builder.UseUnifiedNotes(options =>
{
options.NoteType = "conversation";
options.ContentTypeRegistry = new Dictionary<int, string>
{
{ SessionType, "Session" },
{ ExchangeType, "Exchange" },
{ ToolCallType, "Tool Call" }
};
});
Helper Methods on NotesOptions:
GetContentTypeIds()- Returnsint[]of content type IDs to filter byGetContentTypeName(int id)- Returns display name for a content type IDHasMultipleContentTypes-trueif registry has 2+ entries
Content Type Split Button:
When ContentTypeRegistry has multiple entries, the "New Note" toolbar button automatically becomes a split button with a dropdown menu:
- Click the button - Creates a note with the first content type in the registry
- Click the dropdown arrow - Shows all registered content types, allowing the user to select which type to create
This ensures newly created notes are visible in the filtered list, solving the issue where notes created with ContentType = 0 (None) wouldn't appear when filtering by specific types.
For child notes created via the tree view's "Add Child" button, the content type is inherited from the parent note (if it's in the registry) or defaults to the first registered type.
Multiple Control Instances
When you need multiple UnifiedNotesControl instances with different configurations (e.g., one for Notes, one for Memory), use the Configure() method or constructor with options:
Option 1: Configure() method (recommended for XAML-defined controls)
<!-- In your XAML -->
<controls:UnifiedNotesControl x:Name="NotesControl" />
<controls:UnifiedNotesControl x:Name="MemoryControl" />
// In code-behind (e.g., OnAppearing or constructor)
protected override async void OnAppearing()
{
base.OnAppearing();
// Configure Notes control (standard notes)
await NotesControl.Configure(new NotesOptions
{
NoteType = "note",
ContentType = DocumentContentType.AllNone
});
// Configure Memory control (custom content types 200+)
await MemoryControl.Configure(new NotesOptions
{
NoteType = "memory",
ContentTypeRegistry = new Dictionary<int, string>
{
{ 200, "Conversation" },
{ 201, "User Fact" },
{ 202, "Execution Trace" },
{ 207, "Session" },
{ 208, "Exchange" },
{ 209, "Tool Call" }
}
});
}
Option 2: Constructor with options (for code-created controls)
public partial class MemoryPopup : ContentPage
{
public MemoryPopup(IUnifiedDataService dataService, IRAGService ragService)
{
InitializeComponent();
var memoryOptions = new NotesOptions
{
NoteType = "memory",
ContentTypeRegistry = new Dictionary<int, string>
{
{ 200, "Conversation" },
{ 201, "User Fact" },
{ 202, "Execution Trace" }
}
};
var memoryControl = new UnifiedNotesControl(
dataService, ragService, null, null, memoryOptions);
Content = memoryControl;
}
}
Important: When using multiple instances, do NOT use builder.UseUnifiedNotes() for DI registration since that registers a single shared NotesOptions. Instead, register only the services and configure each control instance individually.
Custom Note Types
You can use the NoteType option to create separate note collections within the same database of the SAME Content Type. This is useful for:
- Separating different types of content (e.g., "journal", "memo", "task", "meeting")
- Creating multiple note controls that manage different document types
- Integrating with other systems that use specific document types
YOU are NOT to use this in replacement of the ContentTypes - ContentTypes shuld be used for the larger grouping. The Custom Not Types are a sub category under the ContentType
// Journal entries - stored separately from regular notes
builder.UseUnifiedNotes(options =>
{
options.NoteType = "journal";
});
// Meeting notes in a different control
builder.UseUnifiedNotes(options =>
{
options.NoteType = "meeting";
});
Notes created with different NoteType values are stored in the same database but filtered separately, so each control only shows its own type.
Provider Setup Options
OpenAI (Default)
builder.AddUnifiedNotes(
openAiApiKey: "your-api-key",
modelName: "gpt-4o-mini"
);
Anthropic Claude
builder.AddUnifiedNotesWithClaude(
anthropicApiKey: "your-api-key",
modelName: "claude-3-5-sonnet-20241022"
);
Together.AI
builder.AddUnifiedNotesWithTogetherAI(
togetherApiKey: "your-api-key",
modelName: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo"
);
Local Model
builder.AddUnifiedNotesWithLocalModel(
modelPath: "/path/to/model.gguf",
maxTokens: 512
);
Basic (No AI)
builder.AddUnifiedNotesBasic();
Dynamic Settings (App-Provided IProviderSettings)
// Register your settings adapter first
builder.Services.AddSingleton<IProviderSettings, MySettingsAdapter>();
// Then add notes with dynamic settings
builder.AddUnifiedNotesWithDynamicSettings(
configureNotes: options =>
{
options.SummaryMode = NoteSummaryMode.Auto;
}
);
Runtime Settings (Built-in RuntimeProviderSettings)
// Initial setup with runtime-updateable settings
builder.AddUnifiedNotesWithRuntimeSettings(
provider: "openai",
apiKey: "sk-...",
modelName: "gpt-4o-mini",
configureNotes: options =>
{
options.SummaryMode = NoteSummaryMode.Both;
}
);
// Later, update settings at runtime:
var settings = serviceProvider.GetRequiredService<RuntimeProviderSettings>();
// Update just the API key
await settings.UpdateApiKeyAsync("sk-new-key");
// Or switch to a different provider entirely
await settings.SwitchProviderAsync(
provider: "anthropic",
apiKey: "sk-ant-...",
modelName: "claude-3-5-sonnet-20241022"
);
XAML Instantiation with Lazy Service Resolution
When you create the control in XAML (without DI), services are automatically resolved from the DI container when needed:
<!-- In XAML - services will be resolved lazily -->
<notes:UnifiedNotesControl x:Name="NotesControl" />
How it works:
- When the control is created in XAML, the default constructor is used
- Services (
IRAGService,IUnifiedDataService,IRAGServiceSettings) are resolved lazily fromApplication.Current.Handler.MauiContext.Services - Resolution happens automatically when notes are loaded or saved
- If you registered services via
AddUnifiedNotesWithDynamicSettings()or other methods, they will be available
This means you can:
- Configure services in
MauiProgram.csusing anyAddUnifiedNotes*method - Create the control in XAML without constructor parameters
- AI features will work automatically when the user interacts with notes
// In MauiProgram.cs
builder.Services.AddSingleton<IProviderSettings, MySettingsAdapter>();
builder.AddUnifiedNotesWithDynamicSettings();
// In XAML - just use the control, services resolve automatically
// <notes:UnifiedNotesControl x:Name="NotesControl" />
Summary Modes
public enum NoteSummaryMode
{
None, // No summary field shown
Auto, // AI generates summary automatically on save
Manual, // User enters summary manually
Both // AI generates, user can edit (default)
}
Architecture
UnifiedNotes.Maui/
├── Controls/
│ └── UnifiedNotesControl # Main control with 3-pane layout
├── Views/
│ ├── NoteListView # Left pane - searchable, grouped note list
│ └── NoteEditorView # Center pane - rich text editor
├── ViewModels/
│ └── NotesViewModel # MVVM ViewModel with commands and state
├── Models/
│ ├── NoteModel # Note data wrapper with dirty tracking
│ ├── TreeNodeModel # Tree view node for document hierarchy
│ └── SourceUrlClickedEventArgs # Event args for source URL clicks
├── Converters/ # XAML value converters
│ ├── BoolToPaneVisibilityStateConverter
│ ├── InvertedBoolConverter
│ ├── IsNotNullConverter
│ ├── UtcToLocalDateTimeConverter
│ └── ... (8 total)
└── Extensions/
├── MauiAppBuilderExtensions # AddUnifiedNotes* setup methods
├── ServiceCollectionExtensions # DI registration
└── SharedRegistrationTracker # Prevents duplicate registrations
Key Components
| Component | Description |
|---|---|
UnifiedNotesControl |
Main control with 3-pane layout (list, editor, details/tree) |
NoteListView |
Left pane - searchable, grouped note list with pagination |
NoteEditorView |
Center pane - Syncfusion rich text editor |
NotesViewModel |
MVVM ViewModel with commands, state, and tree management |
NoteModel |
Note data wrapper with dirty tracking and undo support |
TreeNodeModel |
Hierarchical tree node with load-on-demand and pagination |
Data Model
Notes are stored as UnifiedDocument with DocumentType = "note":
public class NoteModel
{
public string Id { get; }
public string Title { get; set; }
public string Content { get; set; } // HTML content
public string[] Tags { get; set; }
public string Summary { get; set; }
public bool IsPinned { get; set; }
public bool IsDirty { get; } // Has unsaved changes
public DateTime CreatedAt { get; }
public DateTime UpdatedAt { get; set; }
public string DateGroupName { get; } // "Today", "Yesterday", etc.
public string Preview { get; } // Plain text preview (HTML stripped)
// Document hierarchy
public string? ParentId { get; } // Parent document ID
public string? RootId { get; } // Root document ID
public int ChildCount { get; } // Number of child documents
// Source tracking
public string? SourceUrl { get; set; } // Source URL if imported
public bool HasSourceUrl { get; }
// Classification
public bool IsClassified { get; } // Has been AI-classified
public DateTime? ClassifiedAt { get; }
}
public class TreeNodeModel
{
public string Id { get; } // Document ID
public string? ParentId { get; } // Parent document ID
public string Title { get; set; }
public string DocumentType { get; set; }
public bool IsDirty { get; set; }
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
public bool IsPinned { get; set; }
public bool HasChildrenInDb { get; set; } // For load-on-demand expander
public bool IsLoadMoreNode { get; } // Pagination node
public int LoadMoreOffset { get; } // Pagination offset
public ObservableCollection<TreeNodeModel> Children { get; }
public NoteModel? NoteModel { get; set; } // Reference to underlying note
}
ViewModel Commands
// Note operations
LoadNotesCommand // Load all notes from data service
CreateNoteCommand // Create a new note
SaveNoteCommand // Save current note
DeleteNoteCommand // Delete note (with confirmation)
UndoChangesCommand // Revert unsaved changes
SaveAllDirtyNotesCommand // Save all notes with changes
// Filtering
SearchNotesCommand // Search by text
FilterByTagCommand // Filter by tag
FilterByDateCommand // Filter by date range
ClearFiltersCommand // Clear all filters
TogglePinnedCommand // Toggle pin status
ToggleTopLevelOnlyCommand // Toggle showing only top-level notes
// Tag management
AddTagCommand // Add tag to current note
RemoveTagCommand // Remove tag from current note
// Tree view operations
LoadTreeHierarchyCommand // Load document tree for selected note
MoveNodeUpCommand // Move node up in sibling order
MoveNodeDownCommand // Move node down in sibling order
IndentNodeCommand // Make node child of previous sibling
OutdentNodeCommand // Make node sibling of its parent
ToggleTreeExpandCommand // Expand/collapse all tree nodes
LoadChildrenOnDemandCommand // Load children for a tree node
// AI operations
ClassifyNoteCommand // Run AI classification on current note
ViewModel Properties
// Collections
ObservableCollection<NoteModel> Notes // All notes
ObservableCollection<NoteModel> FilteredNotes // Filtered/sorted notes
ObservableCollection<TagGroup> TagGroups // Available tags
ObservableCollection<DateGroup> DateGroups // Date filter options
ObservableCollection<TreeNodeModel> TreeNodes // Document hierarchy tree
// State
NoteModel? SelectedNote // Currently selected note
TreeNodeModel? SelectedTreeNode // Currently selected tree node
bool IsLoading // Loading indicator
bool IsSaving // Saving indicator
bool IsEmpty // No notes exist
bool HasActiveFilters // Any filter is active
string SearchQuery // Current search text
bool ShowPinnedOnly // Filter to pinned only
bool ShowTopLevelOnly // Filter to top-level notes only (default: true)
// View visibility
bool IsNoteListVisible // Left pane visible
bool IsEditorVisible // Center pane visible
bool IsDetailsViewVisible // Right pane showing details
bool IsTreeViewVisible // Right pane showing tree
bool IsRightPaneVisible // Right pane visible (details or tree)
// Tree view state
string? RootDocumentId // Root of current tree hierarchy
bool IsTreeExpanded // All nodes expanded
bool IsTreeReorderMode // Tree reordering enabled
bool CanMoveNodeUp // Selected node can move up
bool CanMoveNodeDown // Selected node can move down
bool CanIndentNode // Selected node can be indented
bool CanOutdentNode // Selected node can be outdented
// AI state
bool IsAIAvailable // RAG service configured with API key
bool IsClassifying // AI classification in progress
// Editor bindings
string EditorTitle // Bound to title entry
string EditorContent // Bound to rich text editor (HTML)
string EditorSummary // Bound to summary field
ObservableCollection<string> EditorTagsList // Tags as pills
Events
event EventHandler<NoteModel?> NoteSelected; // Note selection changed
event EventHandler NotesLoaded; // Notes finished loading
event EventHandler<NoteModel> NoteSaved; // Note was saved
event EventHandler<NoteModel> NoteDeleted; // Note was deleted
event EventHandler<SourceUrlClickedEventArgs> SourceUrlClicked; // Source URL clicked
event EventHandler<TreeNodeModel?> TreeNodeSelected; // Tree node selection changed
UI Features
List View
- Grouped by date with sticky headers (Today, Yesterday, This Week, etc.)
- Sorted by: Pinned first, then date group, then most recent
- Visual indicators: Orange dot for unsaved, pin icon for pinned
- Swipe/tap to delete with confirmation dialog
- Empty state when no notes exist
Editor View
- Title entry at top
- Syncfusion RichTextEditor with toolbar:
- Bold, Italic, Underline, Strikethrough
- Font size, Text color, Highlight
- Alignment, Bullet/Number lists, Indent
- Hyperlinks, Tables
- Undo/Redo
- Bottom toolbar: Save, Undo, Pin, Delete buttons
Details Pane (Right)
- Summary section - AI-generated or user-entered
- Tags section - Pills with add/remove
- Info section - Created/Modified dates, classification status
- Source URL - Clickable link if note was imported from web
Tree View (Right Pane - Toggle)
- Document hierarchy - Parent-child relationships displayed as expandable tree
- Load-on-demand - Children loaded when node expanded (for performance with large trees)
- Pagination - "Load More" nodes for branches with many children (30 per page)
- Node reordering - Move up/down, indent/outdent operations
- Visual indicators - Dirty state, pinned status on tree nodes
- Two-level preload - Root and first-level children preloaded for immediate expander visibility
Toolbar Filters
- Search - Full-text search
- Tags - Filter by tag
- Dates - Filter by date range
- Pinned - Show pinned only
- Top-level only - Show only root-level notes (default on)
- Clear Filters - Reset all (only visible when filters active)
AI Integration
When UnifiedRag.Maui is configured with an AI provider, summaries are automatically generated:
// Summary generation prompt (internal)
"Summarize the following note in 1-2 concise sentences (max 150 characters).
Only provide the summary, no introductions:\n\n{noteContent}"
If no AI provider is configured, the summary field remains empty (no error shown).
Customization
Pane Visibility
Toggle panes via the main toolbar or programmatically:
// Access via the MALayout
NotesControl.MainLayout.Left.VisibilityState = PaneVisibilityState.Visible;
NotesControl.MainLayout.Right.VisibilityState = PaneVisibilityState.Collapsed;
Theming
The control supports Light/Dark themes via AppThemeBinding. Colors automatically adapt to system theme.
Public API for Developers/AI
The control exposes a comprehensive API for programmatic access:
Properties
// Get the configured note type (document type used for storage)
string noteType = notesControl.NoteType; // e.g., "note", "journal", "meeting"
// Get the configured content type (from ViewModel)
DocumentContentType contentType = notesControl.ViewModel.ConfiguredContentType;
// Get selected note
NoteModel? note = notesControl.SelectedNote;
// Get all notes
IReadOnlyList<NoteModel> allNotes = notesControl.Notes;
// Get filtered notes (based on current search/filter)
IReadOnlyList<NoteModel> filtered = notesControl.FilteredNotes;
// Check for unsaved changes
bool hasChanges = notesControl.HasUnsavedChanges;
int unsavedCount = notesControl.UnsavedChangesCount;
Selection Methods
// Select by ID
bool found = notesControl.SelectNoteById("note-123");
// Select by title (case-insensitive)
bool found = notesControl.SelectNoteByTitle("Meeting Notes");
// Get note by ID without selecting
NoteModel? note = notesControl.GetNoteById("note-123");
Search/Find Methods
// Find notes by title (partial match)
var notes = notesControl.FindNotesByTitle("meeting");
// Find notes by tag
var notes = notesControl.FindNotesByTag("work");
// Find notes by content
var notes = notesControl.FindNotesByContent("quarterly report");
// Full-text search (updates UI filter)
await notesControl.SearchNotesAsync("important");
Create/Update/Delete
// Create a new note
var note = await notesControl.CreateNoteAsync(
title: "New Note",
content: "<p>Hello world</p>",
tags: new[] { "demo", "test" },
autoSave: true
);
// Update selected note
notesControl.UpdateSelectedNoteTitle("Updated Title");
notesControl.UpdateSelectedNoteContent("<p>New content</p>");
notesControl.UpdateSelectedNoteTags(new[] { "tag1", "tag2" });
notesControl.UpdateSelectedNoteSummary("Brief summary");
// Update any note by ID
await notesControl.UpdateNoteByIdAsync(
noteId: "note-123",
title: "New Title",
content: "<p>Updated</p>",
tags: new[] { "updated" },
autoSave: true
);
// Delete by ID
await notesControl.DeleteNoteByIdAsync("note-123", skipConfirmation: false);
// Pin/unpin
await notesControl.SetNotePinnedAsync("note-123", isPinned: true);
Save/Filter Operations
// Save current note
await notesControl.SaveCurrentNoteAsync();
// Save all dirty notes
await notesControl.SaveAllAsync();
// Refresh from database
await notesControl.RefreshNotesAsync();
// Filter to pinned only
notesControl.ShowPinnedOnly(true);
// Clear all filters
notesControl.ClearFilters();
Example: AI Integration
// Find and update a note based on user request
public async Task UpdateNoteFromAI(UnifiedNotesControl control, string searchTerm, string newContent)
{
var notes = control.FindNotesByTitle(searchTerm);
if (notes.Any())
{
var note = notes.First();
await control.UpdateNoteByIdAsync(
note.Id,
content: newContent,
autoSave: true
);
}
else
{
// Create new note if not found
await control.CreateNoteAsync(
title: searchTerm,
content: newContent,
autoSave: true
);
}
}
Sharing Services with Other Controls
If you're using UnifiedNotes with other controls (e.g., UnifiedBrowser, UnifiedChat) that share the same database, you can set or share the IUnifiedDataService:
// Get shared services from DI
var dataService = serviceProvider.GetRequiredService<IUnifiedDataService>();
var ragService = serviceProvider.GetService<IRAGService>();
// Set services on the notes control (recommended method)
await notesControl.SetServicesAsync(dataService, ragService, autoReload: true);
// Or set individually (will trigger reload for each)
notesControl.DataService = dataService;
notesControl.RAGService = ragService;
// Access current services
IUnifiedDataService? currentDataService = notesControl.DataService;
IRAGService? currentRagService = notesControl.RAGService;
Example: Sharing database across multiple controls
public class MainPage : ContentPage
{
private readonly IUnifiedDataService _sharedDataService;
public MainPage(IUnifiedDataService dataService)
{
_sharedDataService = dataService;
// All controls share the same database
var notesControl = new UnifiedNotesControl(_sharedDataService);
var browserControl = new UnifiedBrowserControl(_sharedDataService);
var chatControl = new UnifiedChatControl(_sharedDataService);
// Notes, browser history, and chat all stored in same database
}
}
License
MIT License - Copyright 2025 MarketAlly
Version 1.4.4: - Fixed split button menu items not populated when using ContentTypeRegistry in constructor
Version 1.2.0:
- Syncfusion Rich Text Editor for WYSIWYG note editing
- Bold, italic, underline, and other rich text formatting
- Per-note dirty state tracking with undo support
- Bottom toolbar with Save, Undo, Pin, Delete actions
Version 1.1.0:
- Complete implementation of Notes control
- Note creation, editing, and deletion
- Search and filter by text, tags, and date
- Pin notes for quick access
- MALayout responsive two-pane layout
- Integration with UnifiedData.Maui for persistence
- Cross-platform support (iOS, Android)
Dependencies
| ID | Version | Target Framework |
|---|---|---|
| MarketAlly.Dialogs.Maui | 1.6.0 | net9.0-android35.0 |
| MarketAlly.MAToolbar | 1.9.10 | net9.0-android35.0 |
| UnifiedRag.Maui | 1.1.3 | net9.0-android35.0 |
| CommunityToolkit.Maui | 12.3.0 | net9.0-android35.0 |
| CommunityToolkit.Mvvm | 8.4.0 | net9.0-android35.0 |
| Indiko.Maui.Controls.Markdown | 1.3.14 | net9.0-android35.0 |
| MarketAlly.MABottomSheet | 1.3.0 | net9.0-android35.0 |
| MarketAlly.MALayout | 3.6.4 | net9.0-android35.0 |
| MarketAlly.TouchEffect.Maui | 1.0.0 | net9.0-android35.0 |
| Microsoft.Extensions.Configuration.Binder | 9.0.10 | net9.0-android35.0 |
| Microsoft.Extensions.Configuration.Json | 9.0.10 | net9.0-android35.0 |
| Microsoft.Extensions.Logging.Abstractions | 9.0.10 | net9.0-android35.0 |
| Microsoft.Maui.Controls | 9.0.120 | net9.0-android35.0 |
| Microsoft.Maui.Controls.Compatibility | 9.0.120 | net9.0-android35.0 |
| Syncfusion.Maui.Core | 31.2.5 | net9.0-android35.0 |
| Syncfusion.Maui.DataSource | 31.2.5 | net9.0-android35.0 |
| Syncfusion.Maui.ListView | 31.2.5 | net9.0-android35.0 |
| Syncfusion.Maui.RichTextEditor | 31.2.5 | net9.0-android35.0 |
| Syncfusion.Maui.TreeView | 31.2.5 | net9.0-android35.0 |
| MarketAlly.Dialogs.Maui | 1.6.0 | net9.0-ios18.0 |
| MarketAlly.MAToolbar | 1.9.10 | net9.0-ios18.0 |
| UnifiedRag.Maui | 1.1.3 | net9.0-ios18.0 |
| CommunityToolkit.Maui | 12.3.0 | net9.0-ios18.0 |
| CommunityToolkit.Mvvm | 8.4.0 | net9.0-ios18.0 |
| Indiko.Maui.Controls.Markdown | 1.3.14 | net9.0-ios18.0 |
| MarketAlly.MABottomSheet | 1.3.0 | net9.0-ios18.0 |
| MarketAlly.MALayout | 3.6.4 | net9.0-ios18.0 |
| MarketAlly.TouchEffect.Maui | 1.0.0 | net9.0-ios18.0 |
| Microsoft.Extensions.Configuration.Binder | 9.0.10 | net9.0-ios18.0 |
| Microsoft.Extensions.Configuration.Json | 9.0.10 | net9.0-ios18.0 |
| Microsoft.Extensions.Logging.Abstractions | 9.0.10 | net9.0-ios18.0 |
| Microsoft.Maui.Controls | 9.0.120 | net9.0-ios18.0 |
| Microsoft.Maui.Controls.Compatibility | 9.0.120 | net9.0-ios18.0 |
| Syncfusion.Maui.Core | 31.2.5 | net9.0-ios18.0 |
| Syncfusion.Maui.DataSource | 31.2.5 | net9.0-ios18.0 |
| Syncfusion.Maui.ListView | 31.2.5 | net9.0-ios18.0 |
| Syncfusion.Maui.RichTextEditor | 31.2.5 | net9.0-ios18.0 |
| Syncfusion.Maui.TreeView | 31.2.5 | net9.0-ios18.0 |
| MarketAlly.Dialogs.Maui | 1.6.0 | net9.0-windows10.0.19041 |
| MarketAlly.MAToolbar | 1.9.10 | net9.0-windows10.0.19041 |
| UnifiedRag.Maui | 1.1.3 | net9.0-windows10.0.19041 |
| CommunityToolkit.Maui | 12.3.0 | net9.0-windows10.0.19041 |
| CommunityToolkit.Mvvm | 8.4.0 | net9.0-windows10.0.19041 |
| Indiko.Maui.Controls.Markdown | 1.3.14 | net9.0-windows10.0.19041 |
| MarketAlly.MABottomSheet | 1.3.0 | net9.0-windows10.0.19041 |
| MarketAlly.MALayout | 3.6.4 | net9.0-windows10.0.19041 |
| MarketAlly.TouchEffect.Maui | 1.0.0 | net9.0-windows10.0.19041 |
| Microsoft.Extensions.Configuration.Binder | 9.0.10 | net9.0-windows10.0.19041 |
| Microsoft.Extensions.Configuration.Json | 9.0.10 | net9.0-windows10.0.19041 |
| Microsoft.Extensions.Logging.Abstractions | 9.0.10 | net9.0-windows10.0.19041 |
| Microsoft.Maui.Controls | 9.0.120 | net9.0-windows10.0.19041 |
| Microsoft.Maui.Controls.Compatibility | 9.0.120 | net9.0-windows10.0.19041 |
| Syncfusion.Maui.Core | 31.2.5 | net9.0-windows10.0.19041 |
| Syncfusion.Maui.DataSource | 31.2.5 | net9.0-windows10.0.19041 |
| Syncfusion.Maui.ListView | 31.2.5 | net9.0-windows10.0.19041 |
| Syncfusion.Maui.RichTextEditor | 31.2.5 | net9.0-windows10.0.19041 |
| Syncfusion.Maui.TreeView | 31.2.5 | net9.0-windows10.0.19041 |