diff --git a/samples/TodoApp/App.xaml b/samples/TodoApp/App.xaml new file mode 100644 index 0000000..8857dfd --- /dev/null +++ b/samples/TodoApp/App.xaml @@ -0,0 +1,40 @@ + + + + + + #5C6BC0 + #3949AB + #26A69A + #EF5350 + + + #F5F5F5 + #1E1E1E + + + #FFFFFF + #2D2D2D + + + #FFFFFF + #3D3D3D + + + #212121 + #FFFFFF + #757575 + #B0B0B0 + + + #E0E0E0 + #424242 + + + #5C6BC0 + #3949AB + + + diff --git a/samples/TodoApp/App.xaml.cs b/samples/TodoApp/App.xaml.cs new file mode 100644 index 0000000..fc6342f --- /dev/null +++ b/samples/TodoApp/App.xaml.cs @@ -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()); + } +} diff --git a/samples/TodoApp/MauiProgram.cs b/samples/TodoApp/MauiProgram.cs new file mode 100644 index 0000000..88f0243 --- /dev/null +++ b/samples/TodoApp/MauiProgram.cs @@ -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() + .UseLinuxPlatform() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + }); + + return builder.Build(); + } +} diff --git a/samples/TodoApp/NewTodoPage.xaml b/samples/TodoApp/NewTodoPage.xaml new file mode 100644 index 0000000..9ee88fa --- /dev/null +++ b/samples/TodoApp/NewTodoPage.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + diff --git a/samples/TodoApp/NewTodoPage.xaml.cs b/samples/TodoApp/NewTodoPage.xaml.cs new file mode 100644 index 0000000..5a6f80f --- /dev/null +++ b/samples/TodoApp/NewTodoPage.xaml.cs @@ -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(); + } +} diff --git a/samples/TodoApp/Program.cs b/samples/TodoApp/Program.cs new file mode 100644 index 0000000..318d9f7 --- /dev/null +++ b/samples/TodoApp/Program.cs @@ -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); + } +} diff --git a/samples/TodoApp/Resources/Images/icon_add_light.svg b/samples/TodoApp/Resources/Images/icon_add_light.svg new file mode 100644 index 0000000..09a77b9 --- /dev/null +++ b/samples/TodoApp/Resources/Images/icon_add_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/samples/TodoApp/Resources/Images/icon_delete_light.svg b/samples/TodoApp/Resources/Images/icon_delete_light.svg new file mode 100644 index 0000000..5d366f3 --- /dev/null +++ b/samples/TodoApp/Resources/Images/icon_delete_light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/TodoApp/Resources/Images/icon_save_light.svg b/samples/TodoApp/Resources/Images/icon_save_light.svg new file mode 100644 index 0000000..5df4c5d --- /dev/null +++ b/samples/TodoApp/Resources/Images/icon_save_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/samples/TodoApp/TodoApp.csproj b/samples/TodoApp/TodoApp.csproj new file mode 100644 index 0000000..d735339 --- /dev/null +++ b/samples/TodoApp/TodoApp.csproj @@ -0,0 +1,24 @@ + + + + net9.0 + Exe + TodoApp + enable + enable + true + Todo App + com.openmaui.todoapp + 1.0 + 9.0 + + + + + + + + + + + diff --git a/samples/TodoApp/TodoDetailPage.xaml b/samples/TodoApp/TodoDetailPage.xaml new file mode 100644 index 0000000..8f78b20 --- /dev/null +++ b/samples/TodoApp/TodoDetailPage.xaml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + diff --git a/samples/TodoApp/TodoDetailPage.xaml.cs b/samples/TodoApp/TodoDetailPage.xaml.cs new file mode 100644 index 0000000..65e22e0 --- /dev/null +++ b/samples/TodoApp/TodoDetailPage.xaml.cs @@ -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(); + } + } +} diff --git a/samples/TodoApp/TodoItem.cs b/samples/TodoApp/TodoItem.cs new file mode 100644 index 0000000..40a2a2a --- /dev/null +++ b/samples/TodoApp/TodoItem.cs @@ -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)); + } +} diff --git a/samples/TodoApp/TodoListPage.xaml b/samples/TodoApp/TodoListPage.xaml new file mode 100644 index 0000000..9ff79c5 --- /dev/null +++ b/samples/TodoApp/TodoListPage.xaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/TodoApp/TodoListPage.xaml.cs b/samples/TodoApp/TodoListPage.xaml.cs new file mode 100644 index 0000000..36cd72b --- /dev/null +++ b/samples/TodoApp/TodoListPage.xaml.cs @@ -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}"; + } +} diff --git a/samples/TodoApp/TodoService.cs b/samples/TodoApp/TodoService.cs new file mode 100644 index 0000000..9fa8830 --- /dev/null +++ b/samples/TodoApp/TodoService.cs @@ -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 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; +}