Add TodoApp sample with reconstructed XAML

Complete TodoApp sample application with:
- App.xaml/cs: Colors and styles for light/dark themes
- TodoListPage: Task list with theme toggle switch
- NewTodoPage: Form to create new tasks
- TodoDetailPage: Edit task details with delete option
- TodoItem.cs/TodoService.cs: Data model and service
- SVG icons for save, delete, and add actions

Theme switching via toggle on main page applies app-wide.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 19:52:56 -05:00
parent 8a36a44341
commit 18ab0abe97
16 changed files with 613 additions and 0 deletions

40
samples/TodoApp/App.xaml Normal file
View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TodoApp.App">
<Application.Resources>
<ResourceDictionary>
<!-- Primary Colors -->
<Color x:Key="PrimaryColor">#5C6BC0</Color>
<Color x:Key="PrimaryDarkColor">#3949AB</Color>
<Color x:Key="AccentColor">#26A69A</Color>
<Color x:Key="DangerColor">#EF5350</Color>
<!-- Page Backgrounds -->
<Color x:Key="PageBackgroundLight">#F5F5F5</Color>
<Color x:Key="PageBackgroundDark">#1E1E1E</Color>
<!-- Card/Surface Backgrounds -->
<Color x:Key="CardBackgroundLight">#FFFFFF</Color>
<Color x:Key="CardBackgroundDark">#2D2D2D</Color>
<!-- Input Backgrounds -->
<Color x:Key="InputBackgroundLight">#FFFFFF</Color>
<Color x:Key="InputBackgroundDark">#3D3D3D</Color>
<!-- Text Colors -->
<Color x:Key="TextPrimaryLight">#212121</Color>
<Color x:Key="TextPrimaryDark">#FFFFFF</Color>
<Color x:Key="TextSecondaryLight">#757575</Color>
<Color x:Key="TextSecondaryDark">#B0B0B0</Color>
<!-- Border Colors -->
<Color x:Key="BorderLight">#E0E0E0</Color>
<Color x:Key="BorderDark">#424242</Color>
<!-- Footer/Status Bar -->
<Color x:Key="FooterBackgroundLight">#5C6BC0</Color>
<Color x:Key="FooterBackgroundDark">#3949AB</Color>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,14 @@
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
namespace TodoApp;
public partial class App : Application
{
public App()
{
InitializeComponent();
UserAppTheme = AppTheme.Light;
MainPage = new NavigationPage(new TodoListPage());
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.Maui;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace TodoApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseLinuxPlatform()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}
}

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TodoApp.NewTodoPage"
Title="New Task"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ContentPage.ToolbarItems>
<ToolbarItem IconImageSource="icon_save_light.svg" Clicked="OnSaveClicked" />
</ContentPage.ToolbarItems>
<Grid RowDefinitions="Auto,Auto,*"
RowSpacing="16"
Padding="20">
<!-- Title Section -->
<VerticalStackLayout Grid.Row="0" Spacing="8">
<Label Text="TITLE"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Entry x:Name="TitleEntry"
Placeholder="What needs to be done?"
FontSize="18"
BackgroundColor="{AppThemeBinding Light={StaticResource InputBackgroundLight}, Dark={StaticResource InputBackgroundDark}}"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
PlaceholderColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Border>
</VerticalStackLayout>
<!-- Notes Label -->
<Label Grid.Row="1"
Text="NOTES"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<!-- Notes Section -->
<Border Grid.Row="2"
BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Editor x:Name="NotesEditor"
Placeholder="Add notes (optional)..."
FontSize="14"
BackgroundColor="{AppThemeBinding Light={StaticResource InputBackgroundLight}, Dark={StaticResource InputBackgroundDark}}"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
PlaceholderColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
VerticalOptions="Fill" />
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,29 @@
using System;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace TodoApp;
public partial class NewTodoPage : ContentPage
{
private readonly TodoService _service = TodoService.Instance;
public NewTodoPage()
{
InitializeComponent();
}
private async void OnSaveClicked(object? sender, EventArgs e)
{
var title = TitleEntry.Text?.Trim();
if (string.IsNullOrEmpty(title))
{
TitleEntry.Placeholder = "Title is required!";
TitleEntry.PlaceholderColor = Colors.Red;
return;
}
_service.AddTodo(title, NotesEditor.Text ?? "");
await Navigation.PopAsync();
}
}

View File

@@ -0,0 +1,12 @@
using Microsoft.Maui.Platform.Linux.Hosting;
namespace TodoApp;
public class Program
{
[STAThread]
public static void Main(string[] args)
{
LinuxProgramHost.Run(args, MauiProgram.CreateMauiApp);
}
}

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#5C6BC0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>

After

Width:  |  Height:  |  Size: 278 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#E57373" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>

After

Width:  |  Height:  |  Size: 425 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#5C6BC0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 232 B

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<RootNamespace>TodoApp</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseMaui>true</UseMaui>
<ApplicationTitle>Todo App</ApplicationTitle>
<ApplicationId>com.openmaui.todoapp</ApplicationId>
<ApplicationVersion>1.0</ApplicationVersion>
<SupportedOSPlatformVersion>9.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\OpenMaui.Controls.Linux.csproj" />
</ItemGroup>
<ItemGroup>
<MauiImage Include="Resources\Images\*" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TodoApp.TodoDetailPage"
Title="Task Details"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ContentPage.ToolbarItems>
<ToolbarItem IconImageSource="icon_delete_light.svg" Clicked="OnDeleteClicked" />
<ToolbarItem IconImageSource="icon_save_light.svg" Clicked="OnSaveClicked" />
</ContentPage.ToolbarItems>
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto"
RowSpacing="16"
Padding="20">
<!-- Title Section -->
<VerticalStackLayout Grid.Row="0" Spacing="8">
<Label Text="TITLE"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Entry x:Name="TitleEntry"
FontSize="18"
BackgroundColor="{AppThemeBinding Light={StaticResource InputBackgroundLight}, Dark={StaticResource InputBackgroundDark}}"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</Border>
</VerticalStackLayout>
<!-- Notes Label -->
<Label Grid.Row="1"
Text="NOTES"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<!-- Notes Section -->
<Border Grid.Row="2"
BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Editor x:Name="NotesEditor"
FontSize="14"
BackgroundColor="{AppThemeBinding Light={StaticResource InputBackgroundLight}, Dark={StaticResource InputBackgroundDark}}"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
VerticalOptions="Fill" />
</Border>
<!-- Status Section -->
<VerticalStackLayout Grid.Row="3" Spacing="8">
<Label Text="STATUS"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<HorizontalStackLayout Spacing="12">
<CheckBox x:Name="CompletedCheckBox"
VerticalOptions="Center" />
<Label Text="In Progress"
FontSize="16"
VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
</Border>
</VerticalStackLayout>
<!-- Created Date -->
<Label x:Name="CreatedLabel"
Grid.Row="4"
HorizontalOptions="Center"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Grid>
</ContentPage>

View File

@@ -0,0 +1,42 @@
using System;
using Microsoft.Maui.Controls;
namespace TodoApp;
public partial class TodoDetailPage : ContentPage
{
private readonly TodoService _service = TodoService.Instance;
private readonly TodoItem _item;
public TodoDetailPage(TodoItem item)
{
InitializeComponent();
_item = item;
TitleEntry.Text = item.Title;
NotesEditor.Text = item.Notes;
CompletedCheckBox.IsChecked = item.IsCompleted;
CreatedLabel.Text = $"Created {item.CreatedAt:MMMM d, yyyy} at {item.CreatedAt:h:mm tt}";
}
private async void OnSaveClicked(object? sender, EventArgs e)
{
_item.Title = TitleEntry.Text ?? "";
_item.Notes = NotesEditor.Text ?? "";
_item.IsCompleted = CompletedCheckBox.IsChecked;
await Navigation.PopAsync();
}
private async void OnDeleteClicked(object? sender, EventArgs e)
{
bool confirm = await DisplayAlert("Delete Task",
"Are you sure you want to delete this task?",
"Delete", "Cancel");
if (confirm)
{
_service.RemoveTodo(_item);
await Navigation.PopAsync();
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.ComponentModel;
namespace TodoApp;
public class TodoItem : INotifyPropertyChanged
{
private string _title = "";
private string _notes = "";
private bool _isCompleted;
private int _index;
public int Id { get; set; }
public int Index
{
get => _index;
set { if (_index != value) { _index = value; OnPropertyChanged(nameof(Index)); } }
}
public string Title
{
get => _title;
set { if (_title != value) { _title = value; OnPropertyChanged(nameof(Title)); } }
}
public string Notes
{
get => _notes;
set { if (_notes != value) { _notes = value; OnPropertyChanged(nameof(Notes)); } }
}
public bool IsCompleted
{
get => _isCompleted;
set { if (_isCompleted != value) { _isCompleted = value; OnPropertyChanged(nameof(IsCompleted)); } }
}
public DateTime CreatedAt { get; set; } = DateTime.Now;
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TodoApp"
x:Class="TodoApp.TodoListPage"
Title="My Tasks"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ContentPage.ToolbarItems>
<ToolbarItem Text="+" Clicked="OnAddClicked" />
</ContentPage.ToolbarItems>
<Grid RowDefinitions="*,Auto">
<!-- Task List -->
<CollectionView x:Name="TodoCollectionView"
Grid.Row="0"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:TodoItem">
<Grid Padding="16,8">
<Border BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="0"
Padding="0">
<Border.StrokeShape>
<RoundRectangle CornerRadius="8" />
</Border.StrokeShape>
<Grid ColumnDefinitions="4,*">
<!-- Accent Border -->
<BoxView Grid.Column="0"
Color="{StaticResource PrimaryColor}"
WidthRequest="4" />
<!-- Content -->
<VerticalStackLayout Grid.Column="1"
Padding="16,12"
Spacing="4">
<Label Text="{Binding Title}"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="{Binding Notes}"
FontSize="14"
MaxLines="2"
LineBreakMode="TailTruncation"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</VerticalStackLayout>
</Grid>
<Border.GestureRecognizers>
<TapGestureRecognizer Tapped="OnItemTapped" CommandParameter="{Binding .}" />
</Border.GestureRecognizers>
</Border>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!-- Footer -->
<Grid Grid.Row="1"
ColumnDefinitions="*,Auto,Auto"
Padding="16,12"
ColumnSpacing="16"
BackgroundColor="{StaticResource PrimaryColor}">
<Label x:Name="StatsLabel"
Grid.Column="0"
Text="Tasks: 0 of 3"
TextColor="White"
FontSize="14"
VerticalOptions="Center" />
<Label Grid.Column="1"
Text="&#x1F4A1;"
FontSize="20"
VerticalOptions="Center" />
<Switch x:Name="ThemeSwitch"
Grid.Column="2"
Toggled="OnThemeToggled"
VerticalOptions="Center" />
</Grid>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,69 @@
using System;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
namespace TodoApp;
public partial class TodoListPage : ContentPage
{
private readonly TodoService _service = TodoService.Instance;
private bool _isNavigating;
public TodoListPage()
{
InitializeComponent();
TodoCollectionView.ItemsSource = _service.Todos;
UpdateStats();
ThemeSwitch.IsToggled = Application.Current?.UserAppTheme == AppTheme.Dark;
}
protected override void OnAppearing()
{
base.OnAppearing();
_isNavigating = false;
_service.RefreshIndexes();
TodoCollectionView.ItemsSource = null;
TodoCollectionView.ItemsSource = _service.Todos;
UpdateStats();
}
private void OnThemeToggled(object? sender, ToggledEventArgs e)
{
if (Application.Current != null)
{
Application.Current.UserAppTheme = e.Value ? AppTheme.Dark : AppTheme.Light;
// Refresh to apply theme
var items = TodoCollectionView.ItemsSource;
TodoCollectionView.ItemsSource = null;
TodoCollectionView.ItemsSource = items;
}
}
private async void OnAddClicked(object? sender, EventArgs e)
{
await Navigation.PushAsync(new NewTodoPage());
}
private async void OnItemTapped(object? sender, TappedEventArgs e)
{
if (_isNavigating || e.Parameter is not TodoItem todoItem)
return;
_isNavigating = true;
try
{
await Navigation.PushAsync(new TodoDetailPage(todoItem));
}
catch
{
_isNavigating = false;
}
}
private void UpdateStats()
{
int completed = _service.CompletedCount;
int total = _service.TotalCount;
StatsLabel.Text = total == 0 ? "" : $"Tasks: {completed} of {total}";
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace TodoApp;
public class TodoService
{
private static TodoService? _instance;
public static TodoService Instance => _instance ??= new TodoService();
public ObservableCollection<TodoItem> Todos { get; } = new();
private int _nextId = 1;
private TodoService()
{
// Add sample data
AddTodo("Learn OpenMaui Linux",
"Explore the SkiaSharp-based rendering engine for .NET MAUI on Linux desktop. " +
"This is a very long description that should wrap to multiple lines and demonstrate " +
"the ellipsis truncation feature when MaxLines is set to 2.");
AddTodo("Build amazing apps",
"Create cross-platform applications that run on Windows, macOS, iOS, Android, and Linux! " +
"With OpenMaui, you can write once and deploy everywhere.");
AddTodo("Share with the community",
"Contribute to the open-source project and help others build great Linux apps. " +
"Join our growing community of developers who are passionate about bringing .NET MAUI to Linux.");
}
public void AddTodo(string title, string notes)
{
var todo = new TodoItem
{
Id = _nextId++,
Title = title,
Notes = notes,
Index = Todos.Count
};
Todos.Add(todo);
}
public void RemoveTodo(TodoItem item)
{
Todos.Remove(item);
RefreshIndexes();
}
public void RefreshIndexes()
{
for (int i = 0; i < Todos.Count; i++)
{
Todos[i].Index = i;
}
}
public int CompletedCount => Todos.Count(t => t.IsCompleted);
public int TotalCount => Todos.Count;
}