Initial samples: TodoApp and ShellDemo
Two sample applications demonstrating OpenMaui Linux: TodoApp: - Full task manager with NavigationPage - CollectionView with XAML data binding - DisplayAlert dialogs - Grid layouts with star sizing ShellDemo: - Comprehensive control showcase - Shell with flyout navigation - All core MAUI controls demonstrated - Real-time event logging Both samples reference OpenMaui.Controls.Linux via NuGet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
21
TodoApp/App.cs
Normal file
21
TodoApp/App.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// TodoApp - Main Application with NavigationPage
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
|
||||
namespace TodoApp;
|
||||
|
||||
public class App : Application
|
||||
{
|
||||
public static NavigationPage? NavigationPage { get; private set; }
|
||||
|
||||
public App()
|
||||
{
|
||||
NavigationPage = new NavigationPage(new TodoListPage())
|
||||
{
|
||||
Title = "OpenMaui Todo App",
|
||||
BarBackgroundColor = Color.FromArgb("#2196F3"),
|
||||
BarTextColor = Colors.White
|
||||
};
|
||||
MainPage = NavigationPage;
|
||||
}
|
||||
}
|
||||
22
TodoApp/MauiProgram.cs
Normal file
22
TodoApp/MauiProgram.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// MauiProgram.cs - MAUI app configuration
|
||||
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
namespace TodoApp;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
|
||||
// Configure the app
|
||||
builder.UseMauiApp<App>();
|
||||
|
||||
// Add Linux platform support with all handlers
|
||||
builder.UseLinux();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
79
TodoApp/NewTodoPage.xaml
Normal file
79
TodoApp/NewTodoPage.xaml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?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="#F5F7FA">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||
<Color x:Key="AccentColor">#26A69A</Color>
|
||||
<Color x:Key="TextPrimary">#212121</Color>
|
||||
<Color x:Key="TextSecondary">#757575</Color>
|
||||
<Color x:Key="CardBackground">#FFFFFF</Color>
|
||||
<Color x:Key="BorderColor">#E8EAF6</Color>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="Save" Clicked="OnSaveClicked" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,*" RowSpacing="16" Padding="20">
|
||||
|
||||
<!-- Header -->
|
||||
<VerticalStackLayout Grid.Row="0" Spacing="4">
|
||||
<Label Text="Create a new task"
|
||||
FontSize="24"
|
||||
TextColor="{StaticResource TextPrimary}" />
|
||||
<Label Text="Fill in the details below"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}" />
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Title Section -->
|
||||
<VerticalStackLayout Grid.Row="1" Spacing="8">
|
||||
<Label Text="TITLE"
|
||||
FontSize="13"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource PrimaryColor}" />
|
||||
<Border BackgroundColor="{StaticResource CardBackground}"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
StrokeThickness="1"
|
||||
Padding="16,12">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="10" />
|
||||
</Border.StrokeShape>
|
||||
<Entry x:Name="TitleEntry"
|
||||
Placeholder="What needs to be done?"
|
||||
FontSize="18"
|
||||
TextColor="{StaticResource TextPrimary}"
|
||||
PlaceholderColor="{StaticResource TextSecondary}" />
|
||||
</Border>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Notes Label -->
|
||||
<Label Grid.Row="2"
|
||||
Text="NOTES"
|
||||
FontSize="13"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource PrimaryColor}" />
|
||||
|
||||
<!-- Notes Section (fills remaining space) -->
|
||||
<Border Grid.Row="3"
|
||||
BackgroundColor="{StaticResource CardBackground}"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
StrokeThickness="1"
|
||||
Padding="16,12">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="10" />
|
||||
</Border.StrokeShape>
|
||||
<Editor x:Name="NotesEditor"
|
||||
Placeholder="Add notes (optional)..."
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextPrimary}"
|
||||
PlaceholderColor="{StaticResource TextSecondary}"
|
||||
VerticalOptions="Fill" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ContentPage>
|
||||
31
TodoApp/NewTodoPage.xaml.cs
Normal file
31
TodoApp/NewTodoPage.xaml.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// NewTodoPage - Create a new todo item
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
67
TodoApp/Program.cs
Normal file
67
TodoApp/Program.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Program.cs - Linux platform entry point
|
||||
|
||||
using Microsoft.Maui.Platform.Linux;
|
||||
|
||||
namespace TodoApp;
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Redirect console output to a log file for debugging
|
||||
var logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "todoapp.log");
|
||||
using var logWriter = new StreamWriter(logPath, append: false) { AutoFlush = true };
|
||||
var multiWriter = new MultiTextWriter(Console.Out, logWriter);
|
||||
Console.SetOut(multiWriter);
|
||||
Console.SetError(multiWriter);
|
||||
|
||||
// Global exception handler
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
||||
{
|
||||
var ex = e.ExceptionObject as Exception;
|
||||
Console.WriteLine($"[FATAL] Unhandled exception: {ex?.GetType().Name}: {ex?.Message}");
|
||||
Console.WriteLine($"[FATAL] Stack trace: {ex?.StackTrace}");
|
||||
if (ex?.InnerException != null)
|
||||
{
|
||||
Console.WriteLine($"[FATAL] Inner exception: {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
|
||||
Console.WriteLine($"[FATAL] Inner stack trace: {ex.InnerException.StackTrace}");
|
||||
}
|
||||
};
|
||||
|
||||
TaskScheduler.UnobservedTaskException += (sender, e) =>
|
||||
{
|
||||
Console.WriteLine($"[FATAL] Unobserved task exception: {e.Exception?.GetType().Name}: {e.Exception?.Message}");
|
||||
Console.WriteLine($"[FATAL] Stack trace: {e.Exception?.StackTrace}");
|
||||
e.SetObserved(); // Prevent crash
|
||||
};
|
||||
|
||||
Console.WriteLine($"[Program] Starting TodoApp at {DateTime.Now}");
|
||||
Console.WriteLine($"[Program] Log file: {logPath}");
|
||||
|
||||
try
|
||||
{
|
||||
// Create the MAUI app with all handlers registered
|
||||
var app = MauiProgram.CreateMauiApp();
|
||||
|
||||
// Run on Linux platform
|
||||
LinuxApplication.Run(app, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[FATAL] Exception in Main: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine($"[FATAL] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to write to both console and file
|
||||
class MultiTextWriter : TextWriter
|
||||
{
|
||||
private readonly TextWriter[] _writers;
|
||||
public MultiTextWriter(params TextWriter[] writers) => _writers = writers;
|
||||
public override System.Text.Encoding Encoding => System.Text.Encoding.UTF8;
|
||||
public override void Write(char value) { foreach (var w in _writers) w.Write(value); }
|
||||
public override void WriteLine(string? value) { foreach (var w in _writers) w.WriteLine(value); }
|
||||
public override void Flush() { foreach (var w in _writers) w.Flush(); }
|
||||
}
|
||||
111
TodoApp/README.md
Normal file
111
TodoApp/README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# TodoApp Sample
|
||||
|
||||
A complete task management application demonstrating OpenMaui Linux capabilities with real-world XAML patterns.
|
||||
|
||||
## Features
|
||||
|
||||
- **NavigationPage** - Full page navigation with back button support
|
||||
- **CollectionView** - Scrollable list with data binding and selection
|
||||
- **XAML Data Binding** - Value converters for dynamic styling
|
||||
- **DisplayAlert Dialogs** - Confirmation dialogs for delete actions
|
||||
- **Grid Layouts** - Complex layouts with star sizing for expanding content
|
||||
- **Entry & Editor** - Single and multi-line text input
|
||||
- **Border with RoundRectangle** - Modern card-style UI
|
||||
- **ToolbarItems** - Navigation bar actions
|
||||
|
||||
## Screenshots
|
||||
|
||||
The app consists of three pages:
|
||||
|
||||
1. **TodoListPage** - Shows all tasks with completion status indicators
|
||||
2. **NewTodoPage** - Create a new task with title and notes
|
||||
3. **TodoDetailPage** - View/edit task details, mark complete, or delete
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
TodoApp/
|
||||
├── App.cs # Application entry with NavigationPage
|
||||
├── Program.cs # Linux platform bootstrap
|
||||
├── MauiProgram.cs # MAUI app builder
|
||||
├── TodoItem.cs # Data model
|
||||
├── TodoService.cs # In-memory data store
|
||||
├── TodoListPage.xaml(.cs) # Main list view
|
||||
├── NewTodoPage.xaml(.cs) # Create task page
|
||||
└── TodoDetailPage.xaml(.cs) # Task detail/edit page
|
||||
```
|
||||
|
||||
## XAML Highlights
|
||||
|
||||
### Value Converters
|
||||
The app uses custom converters for dynamic styling based on completion status:
|
||||
- `CompletedToColorConverter` - Gray text for completed items
|
||||
- `CompletedToTextDecorationsConverter` - Strikethrough for completed items
|
||||
- `CompletedToOpacityConverter` - Fade completed items
|
||||
- `AlternatingRowColorConverter` - Alternating background colors
|
||||
|
||||
### ResourceDictionary
|
||||
```xml
|
||||
<ContentPage.Resources>
|
||||
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||
<Color x:Key="AccentColor">#26A69A</Color>
|
||||
<Color x:Key="TextPrimary">#212121</Color>
|
||||
</ContentPage.Resources>
|
||||
```
|
||||
|
||||
### CollectionView with DataTemplate
|
||||
```xml
|
||||
<CollectionView SelectionMode="Single" SelectionChanged="OnSelectionChanged">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:TodoItem">
|
||||
<Border BackgroundColor="{StaticResource CardBackground}">
|
||||
<Label Text="{Binding Title}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
```
|
||||
|
||||
### Grid with Star Rows (Expanding Editor)
|
||||
```xml
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto" Padding="20">
|
||||
<!-- Row 0: Title section (Auto) -->
|
||||
<!-- Row 1: Notes label (Auto) -->
|
||||
<!-- Row 2: Editor - expands to fill (*) -->
|
||||
<!-- Row 3: Status section (Auto) -->
|
||||
<!-- Row 4: Created date (Auto) -->
|
||||
</Grid>
|
||||
```
|
||||
|
||||
## Building and Running
|
||||
|
||||
```bash
|
||||
# From the maui-linux-push directory
|
||||
cd samples/TodoApp
|
||||
dotnet publish -c Release -r linux-arm64
|
||||
|
||||
# Run on Linux
|
||||
./bin/Release/net9.0/linux-arm64/publish/TodoApp
|
||||
```
|
||||
|
||||
## Controls Demonstrated
|
||||
|
||||
| Control | Usage |
|
||||
|---------|-------|
|
||||
| NavigationPage | App navigation container |
|
||||
| ContentPage | Individual screens |
|
||||
| CollectionView | Task list with selection |
|
||||
| Grid | Page layouts |
|
||||
| VerticalStackLayout | Vertical grouping |
|
||||
| HorizontalStackLayout | Horizontal grouping |
|
||||
| Label | Text display |
|
||||
| Entry | Single-line input |
|
||||
| Editor | Multi-line input |
|
||||
| Button | Toolbar actions |
|
||||
| Border | Card styling with rounded corners |
|
||||
| CheckBox | Completion toggle |
|
||||
| BoxView | Visual separators |
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See repository root for details.
|
||||
15
TodoApp/TodoApp.csproj
Normal file
15
TodoApp/TodoApp.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenMaui.Controls.Linux" Version="1.0.0-preview.*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
106
TodoApp/TodoDetailPage.xaml
Normal file
106
TodoApp/TodoDetailPage.xaml
Normal file
@@ -0,0 +1,106 @@
|
||||
<?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="#F5F7FA">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||
<Color x:Key="AccentColor">#26A69A</Color>
|
||||
<Color x:Key="DangerColor">#EF5350</Color>
|
||||
<Color x:Key="TextPrimary">#212121</Color>
|
||||
<Color x:Key="TextSecondary">#757575</Color>
|
||||
<Color x:Key="CardBackground">#FFFFFF</Color>
|
||||
<Color x:Key="BorderColor">#E8EAF6</Color>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="Delete" Clicked="OnDeleteClicked" />
|
||||
<ToolbarItem Text="Save" 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="{StaticResource CardBackground}"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
StrokeThickness="1"
|
||||
Padding="16,12">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="10" />
|
||||
</Border.StrokeShape>
|
||||
<Entry x:Name="TitleEntry"
|
||||
Placeholder="Task title"
|
||||
FontSize="18"
|
||||
TextColor="{StaticResource TextPrimary}"
|
||||
PlaceholderColor="{StaticResource TextSecondary}" />
|
||||
</Border>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Notes Label -->
|
||||
<Label Grid.Row="1"
|
||||
Text="NOTES"
|
||||
FontSize="13"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource PrimaryColor}" />
|
||||
|
||||
<!-- Notes Section (fills remaining space) -->
|
||||
<Border Grid.Row="2"
|
||||
BackgroundColor="{StaticResource CardBackground}"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
StrokeThickness="1"
|
||||
Padding="16,12">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="10" />
|
||||
</Border.StrokeShape>
|
||||
<Editor x:Name="NotesEditor"
|
||||
Placeholder="Add notes here..."
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextPrimary}"
|
||||
PlaceholderColor="{StaticResource TextSecondary}"
|
||||
VerticalOptions="Fill" />
|
||||
</Border>
|
||||
|
||||
<!-- Status Section -->
|
||||
<VerticalStackLayout Grid.Row="3" Spacing="8">
|
||||
<Label Text="STATUS"
|
||||
FontSize="13"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{StaticResource PrimaryColor}" />
|
||||
<Border BackgroundColor="{StaticResource CardBackground}"
|
||||
Stroke="{StaticResource BorderColor}"
|
||||
StrokeThickness="1"
|
||||
Padding="16,12">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="10" />
|
||||
</Border.StrokeShape>
|
||||
<HorizontalStackLayout Spacing="12">
|
||||
<CheckBox x:Name="CompletedCheckBox"
|
||||
Color="{StaticResource AccentColor}"
|
||||
VerticalOptions="Center"
|
||||
CheckedChanged="OnCompletedChanged" />
|
||||
<Label x:Name="StatusLabel"
|
||||
Text="In Progress"
|
||||
FontSize="16"
|
||||
TextColor="{StaticResource TextPrimary}"
|
||||
VerticalOptions="Center" />
|
||||
</HorizontalStackLayout>
|
||||
</Border>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<!-- Created Date -->
|
||||
<Label Grid.Row="4"
|
||||
x:Name="CreatedLabel"
|
||||
FontSize="13"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Center"
|
||||
HorizontalTextAlignment="Center" />
|
||||
|
||||
</Grid>
|
||||
</ContentPage>
|
||||
91
TodoApp/TodoDetailPage.xaml.cs
Normal file
91
TodoApp/TodoDetailPage.xaml.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
// TodoDetailPage - View and edit a todo item
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace TodoApp;
|
||||
|
||||
public partial class TodoDetailPage : ContentPage
|
||||
{
|
||||
private readonly TodoItem _todo;
|
||||
private readonly TodoService _service = TodoService.Instance;
|
||||
|
||||
// Colors for status label
|
||||
private static readonly Color AccentColor = Color.FromArgb("#26A69A");
|
||||
private static readonly Color TextPrimary = Color.FromArgb("#212121");
|
||||
|
||||
public TodoDetailPage(TodoItem todo)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[TodoDetailPage] Constructor starting for: {todo.Title}");
|
||||
InitializeComponent();
|
||||
Console.WriteLine($"[TodoDetailPage] InitializeComponent complete");
|
||||
|
||||
_todo = todo;
|
||||
|
||||
// Populate fields
|
||||
Console.WriteLine($"[TodoDetailPage] Setting TitleEntry.Text");
|
||||
TitleEntry.Text = _todo.Title;
|
||||
Console.WriteLine($"[TodoDetailPage] Setting NotesEditor.Text");
|
||||
NotesEditor.Text = _todo.Notes;
|
||||
Console.WriteLine($"[TodoDetailPage] Setting CompletedCheckBox.IsChecked");
|
||||
CompletedCheckBox.IsChecked = _todo.IsCompleted;
|
||||
Console.WriteLine($"[TodoDetailPage] Calling UpdateStatusLabel");
|
||||
UpdateStatusLabel(_todo.IsCompleted);
|
||||
Console.WriteLine($"[TodoDetailPage] Setting CreatedLabel.Text");
|
||||
CreatedLabel.Text = $"Created {_todo.CreatedAt:MMMM d, yyyy} at {_todo.CreatedAt:h:mm tt}";
|
||||
Console.WriteLine($"[TodoDetailPage] Constructor complete");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[TodoDetailPage] EXCEPTION in constructor: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine($"[TodoDetailPage] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCompletedChanged(object? sender, Microsoft.Maui.Controls.CheckedChangedEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[TodoDetailPage] OnCompletedChanged: {e.Value}");
|
||||
UpdateStatusLabel(e.Value);
|
||||
}
|
||||
|
||||
private void UpdateStatusLabel(bool isCompleted)
|
||||
{
|
||||
if (StatusLabel == null)
|
||||
{
|
||||
Console.WriteLine($"[TodoDetailPage] UpdateStatusLabel: StatusLabel is null, skipping");
|
||||
return;
|
||||
}
|
||||
Console.WriteLine($"[TodoDetailPage] UpdateStatusLabel: setting to {(isCompleted ? "Completed" : "In Progress")}");
|
||||
StatusLabel.Text = isCompleted ? "Completed" : "In Progress";
|
||||
StatusLabel.TextColor = isCompleted ? AccentColor : TextPrimary;
|
||||
}
|
||||
|
||||
private async void OnSaveClicked(object? sender, EventArgs e)
|
||||
{
|
||||
_todo.Title = TitleEntry.Text ?? "";
|
||||
_todo.Notes = NotesEditor.Text ?? "";
|
||||
_todo.IsCompleted = CompletedCheckBox.IsChecked;
|
||||
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
|
||||
private async void OnDeleteClicked(object? sender, EventArgs e)
|
||||
{
|
||||
// Show confirmation dialog
|
||||
var confirmed = await LinuxDialogService.ShowAlertAsync(
|
||||
"Delete Task",
|
||||
$"Are you sure you want to delete \"{_todo.Title}\"? This action cannot be undone.",
|
||||
"Delete",
|
||||
"Cancel");
|
||||
|
||||
if (confirmed)
|
||||
{
|
||||
_service.DeleteTodo(_todo);
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
81
TodoApp/TodoItem.cs
Normal file
81
TodoApp/TodoItem.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
// TodoItem - Data model for a todo item
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace TodoApp;
|
||||
|
||||
public class TodoItem : INotifyPropertyChanged
|
||||
{
|
||||
private string _title = "";
|
||||
private string _notes = "";
|
||||
private bool _isCompleted;
|
||||
private DateTime _dueDate;
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index in the collection for alternating row colors.
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
|
||||
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 DueDate
|
||||
{
|
||||
get => _dueDate;
|
||||
set
|
||||
{
|
||||
if (_dueDate != value)
|
||||
{
|
||||
_dueDate = value;
|
||||
OnPropertyChanged(nameof(DueDate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
129
TodoApp/TodoListPage.xaml
Normal file
129
TodoApp/TodoListPage.xaml
Normal file
@@ -0,0 +1,129 @@
|
||||
<?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="#F5F7FA">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<!-- Colors -->
|
||||
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||
<Color x:Key="PrimaryDark">#3949AB</Color>
|
||||
<Color x:Key="AccentColor">#26A69A</Color>
|
||||
<Color x:Key="TextPrimary">#212121</Color>
|
||||
<Color x:Key="TextSecondary">#757575</Color>
|
||||
<Color x:Key="CardBackground">#FFFFFF</Color>
|
||||
<Color x:Key="DividerColor">#E0E0E0</Color>
|
||||
<Color x:Key="CompletedColor">#9E9E9E</Color>
|
||||
|
||||
<!-- Converters -->
|
||||
<local:AlternatingRowColorConverter x:Key="AlternatingRowColorConverter" />
|
||||
<local:CompletedToColorConverter x:Key="CompletedToColorConverter" />
|
||||
<local:CompletedToTextDecorationsConverter x:Key="CompletedToTextDecorationsConverter" />
|
||||
<local:CompletedToOpacityConverter x:Key="CompletedToOpacityConverter" />
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="+ Add" Clicked="OnAddClicked" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<Grid RowDefinitions="*,Auto" Padding="0">
|
||||
|
||||
<!-- Task List -->
|
||||
<CollectionView Grid.Row="0"
|
||||
x:Name="TodoCollectionView"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="OnSelectionChanged"
|
||||
VerticalOptions="FillAndExpand"
|
||||
BackgroundColor="Transparent"
|
||||
Margin="16,16,16,0">
|
||||
|
||||
<CollectionView.EmptyView>
|
||||
<VerticalStackLayout VerticalOptions="Center"
|
||||
HorizontalOptions="Center"
|
||||
Padding="40">
|
||||
<Label Text="No tasks yet"
|
||||
FontSize="22"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Center" />
|
||||
<Label Text="Tap '+ Add' to create your first task"
|
||||
FontSize="14"
|
||||
TextColor="{StaticResource TextSecondary}"
|
||||
HorizontalOptions="Center"
|
||||
Margin="0,8,0,0" />
|
||||
</VerticalStackLayout>
|
||||
</CollectionView.EmptyView>
|
||||
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:TodoItem">
|
||||
<!-- Card-style item -->
|
||||
<Grid Padding="0,6" BackgroundColor="Transparent">
|
||||
<Border StrokeThickness="0"
|
||||
BackgroundColor="{StaticResource CardBackground}"
|
||||
Padding="16,14"
|
||||
Opacity="{Binding IsCompleted, Converter={StaticResource CompletedToOpacityConverter}}">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="12" />
|
||||
</Border.StrokeShape>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="16">
|
||||
<!-- Completion indicator -->
|
||||
<Border Grid.Column="0"
|
||||
WidthRequest="8"
|
||||
HeightRequest="44"
|
||||
Margin="0"
|
||||
BackgroundColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=indicator}"
|
||||
VerticalOptions="Center">
|
||||
<Border.StrokeShape>
|
||||
<RoundRectangle CornerRadius="4" />
|
||||
</Border.StrokeShape>
|
||||
</Border>
|
||||
|
||||
<!-- Content -->
|
||||
<VerticalStackLayout Grid.Column="1"
|
||||
Spacing="4"
|
||||
VerticalOptions="Center">
|
||||
<!-- Title -->
|
||||
<Label Text="{Binding Title}"
|
||||
FontSize="16"
|
||||
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}}"
|
||||
TextDecorations="{Binding IsCompleted, Converter={StaticResource CompletedToTextDecorationsConverter}}" />
|
||||
|
||||
<!-- Notes preview -->
|
||||
<Label Text="{Binding Notes}"
|
||||
FontSize="13"
|
||||
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=notes}"
|
||||
MaxLines="2"
|
||||
LineBreakMode="TailTruncation" />
|
||||
</VerticalStackLayout>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
|
||||
<!-- Footer Stats -->
|
||||
<Border Grid.Row="1"
|
||||
BackgroundColor="{StaticResource PrimaryColor}"
|
||||
StrokeThickness="0"
|
||||
Padding="24,14"
|
||||
Margin="0">
|
||||
<Grid ColumnDefinitions="Auto,Auto" ColumnSpacing="6" HorizontalOptions="Center" VerticalOptions="Center">
|
||||
<Label Grid.Column="0"
|
||||
Text="Tasks:"
|
||||
FontSize="15"
|
||||
TextColor="White" />
|
||||
<Label Grid.Column="1"
|
||||
x:Name="StatsLabel"
|
||||
FontSize="15"
|
||||
TextColor="White"
|
||||
Opacity="0.9" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ContentPage>
|
||||
174
TodoApp/TodoListPage.xaml.cs
Normal file
174
TodoApp/TodoListPage.xaml.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
// TodoListPage - Main page for viewing todos with XAML support
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace TodoApp;
|
||||
|
||||
public partial class TodoListPage : ContentPage
|
||||
{
|
||||
private readonly TodoService _service = TodoService.Instance;
|
||||
|
||||
public TodoListPage()
|
||||
{
|
||||
Console.WriteLine("[TodoListPage] Constructor starting");
|
||||
InitializeComponent();
|
||||
|
||||
TodoCollectionView.ItemsSource = _service.Todos;
|
||||
UpdateStats();
|
||||
|
||||
Console.WriteLine("[TodoListPage] Constructor finished");
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
Console.WriteLine("[TodoListPage] OnAppearing called - refreshing CollectionView");
|
||||
base.OnAppearing();
|
||||
|
||||
// Refresh indexes for alternating row colors
|
||||
_service.RefreshIndexes();
|
||||
|
||||
// Refresh the collection view
|
||||
TodoCollectionView.ItemsSource = null;
|
||||
TodoCollectionView.ItemsSource = _service.Todos;
|
||||
Console.WriteLine($"[TodoListPage] ItemsSource set with {_service.Todos.Count} items");
|
||||
UpdateStats();
|
||||
}
|
||||
|
||||
private async void OnAddClicked(object sender, EventArgs e)
|
||||
{
|
||||
await Navigation.PushAsync(new NewTodoPage());
|
||||
}
|
||||
|
||||
private async void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[TodoListPage] OnSelectionChanged: {e.CurrentSelection.Count} items selected");
|
||||
if (e.CurrentSelection.FirstOrDefault() is TodoItem todo)
|
||||
{
|
||||
Console.WriteLine($"[TodoListPage] Navigating to TodoDetailPage for: {todo.Title}");
|
||||
TodoCollectionView.SelectedItem = null; // Deselect
|
||||
var detailPage = new TodoDetailPage(todo);
|
||||
Console.WriteLine($"[TodoListPage] Created TodoDetailPage, pushing...");
|
||||
await Navigation.PushAsync(detailPage);
|
||||
Console.WriteLine($"[TodoListPage] Navigation complete");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[TodoListPage] EXCEPTION in OnSelectionChanged: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine($"[TodoListPage] Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStats()
|
||||
{
|
||||
var completed = _service.CompletedCount;
|
||||
var total = _service.TotalCount;
|
||||
|
||||
if (total == 0)
|
||||
{
|
||||
StatsLabel.Text = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatsLabel.Text = $"{completed} of {total} completed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converter for alternating row background colors.
|
||||
/// </summary>
|
||||
public class AlternatingRowColorConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is int index)
|
||||
{
|
||||
return index % 2 == 0 ? Colors.White : Color.FromArgb("#F5F5F5");
|
||||
}
|
||||
return Colors.White;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converter for completed task text color and indicator color.
|
||||
/// </summary>
|
||||
public class CompletedToColorConverter : IValueConverter
|
||||
{
|
||||
// Define colors
|
||||
private static readonly Color PrimaryColor = Color.FromArgb("#5C6BC0");
|
||||
private static readonly Color AccentColor = Color.FromArgb("#26A69A");
|
||||
private static readonly Color CompletedColor = Color.FromArgb("#9E9E9E");
|
||||
private static readonly Color TextPrimary = Color.FromArgb("#212121");
|
||||
private static readonly Color TextSecondary = Color.FromArgb("#757575");
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
bool isCompleted = value is bool b && b;
|
||||
string param = parameter as string ?? "";
|
||||
|
||||
// Indicator bar color
|
||||
if (param == "indicator")
|
||||
{
|
||||
return isCompleted ? CompletedColor : AccentColor;
|
||||
}
|
||||
|
||||
// Text colors
|
||||
if (isCompleted)
|
||||
{
|
||||
return CompletedColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
return param == "notes" ? TextSecondary : TextPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converter for completed task text decorations (strikethrough).
|
||||
/// </summary>
|
||||
public class CompletedToTextDecorationsConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
bool isCompleted = value is bool b && b;
|
||||
return isCompleted ? TextDecorations.Strikethrough : TextDecorations.None;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converter for completed task opacity (slightly faded when complete).
|
||||
/// </summary>
|
||||
public class CompletedToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
bool isCompleted = value is bool b && b;
|
||||
return isCompleted ? 0.7 : 1.0;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
61
TodoApp/TodoService.cs
Normal file
61
TodoApp/TodoService.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
// TodoService - Manages todo items
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace TodoApp;
|
||||
|
||||
public class TodoService
|
||||
{
|
||||
private static TodoService? _instance;
|
||||
public static TodoService Instance => _instance ??= new TodoService();
|
||||
|
||||
private int _nextId = 1;
|
||||
|
||||
public ObservableCollection<TodoItem> Todos { get; } = new();
|
||||
|
||||
private TodoService()
|
||||
{
|
||||
// Add sample todos with varying lengths to test MaxLines=2 with ellipsis
|
||||
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 TodoItem AddTodo(string title, string notes = "")
|
||||
{
|
||||
var todo = new TodoItem
|
||||
{
|
||||
Id = _nextId++,
|
||||
Index = Todos.Count, // Set index for alternating row colors
|
||||
Title = title,
|
||||
Notes = notes,
|
||||
DueDate = DateTime.Today.AddDays(7)
|
||||
};
|
||||
Todos.Add(todo);
|
||||
return todo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the Index property on all items for alternating row colors.
|
||||
/// </summary>
|
||||
public void RefreshIndexes()
|
||||
{
|
||||
for (int i = 0; i < Todos.Count; i++)
|
||||
{
|
||||
Todos[i].Index = i;
|
||||
}
|
||||
}
|
||||
|
||||
public TodoItem? GetTodo(int id)
|
||||
{
|
||||
return Todos.FirstOrDefault(t => t.Id == id);
|
||||
}
|
||||
|
||||
public void DeleteTodo(TodoItem todo)
|
||||
{
|
||||
Todos.Remove(todo);
|
||||
}
|
||||
|
||||
public int CompletedCount => Todos.Count(t => t.IsCompleted);
|
||||
public int TotalCount => Todos.Count;
|
||||
}
|
||||
Reference in New Issue
Block a user