2
0

todo app fixes

This commit is contained in:
2026-01-17 15:06:30 +00:00
parent 26b39a403a
commit ca48355f8a
6 changed files with 97 additions and 33 deletions

View File

@@ -54,7 +54,7 @@
<!-- Card-style item --> <!-- Card-style item -->
<Grid Padding="0,6" BackgroundColor="Transparent"> <Grid Padding="0,6" BackgroundColor="Transparent">
<Border StrokeThickness="0" <Border StrokeThickness="0"
BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}" BackgroundColor="{AppThemeBinding Light=#FFFFFF, Dark=#1E1E1E}"
Padding="16,14" Padding="16,14"
Opacity="{Binding IsCompleted, Converter={StaticResource CompletedToOpacityConverter}}"> Opacity="{Binding IsCompleted, Converter={StaticResource CompletedToOpacityConverter}}">
<Border.StrokeShape> <Border.StrokeShape>
@@ -67,7 +67,7 @@
WidthRequest="8" WidthRequest="8"
HeightRequest="44" HeightRequest="44"
Margin="0" Margin="0"
BackgroundColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=indicator}" BackgroundColor="#26A69A"
VerticalOptions="Center"> VerticalOptions="Center">
<Border.StrokeShape> <Border.StrokeShape>
<RoundRectangle CornerRadius="4" /> <RoundRectangle CornerRadius="4" />
@@ -98,22 +98,30 @@
</CollectionView.ItemTemplate> </CollectionView.ItemTemplate>
</CollectionView> </CollectionView>
<!-- Footer Stats --> <!-- Footer Stats with Theme Toggle -->
<Border Grid.Row="1" <Border Grid.Row="1"
BackgroundColor="{StaticResource PrimaryColor}" BackgroundColor="{StaticResource PrimaryColor}"
StrokeThickness="0" StrokeThickness="0"
Padding="24,14" Padding="20,10"
Margin="0"> Margin="0">
<Grid ColumnDefinitions="Auto,Auto" ColumnSpacing="6" HorizontalOptions="Center" VerticalOptions="Center"> <Grid ColumnDefinitions="*,Auto" VerticalOptions="Center">
<!-- Stats on the left -->
<Label Grid.Column="0" <Label Grid.Column="0"
Text="Tasks:"
FontSize="15"
TextColor="White" />
<Label Grid.Column="1"
x:Name="StatsLabel" x:Name="StatsLabel"
FontSize="15" FontSize="15"
TextColor="White" TextColor="White"
Opacity="0.9" /> VerticalOptions="Center"
LineBreakMode="NoWrap" />
<!-- Theme Toggle on the right -->
<ImageButton Grid.Column="1"
x:Name="ThemeToggleButton"
Source="light_mode_white.svg"
WidthRequest="32"
HeightRequest="32"
BackgroundColor="Transparent"
Clicked="OnThemeToggleClicked"
VerticalOptions="Center" />
</Grid> </Grid>
</Border> </Border>

View File

@@ -9,6 +9,7 @@ namespace TodoApp;
public partial class TodoListPage : ContentPage public partial class TodoListPage : ContentPage
{ {
private readonly TodoService _service = TodoService.Instance; private readonly TodoService _service = TodoService.Instance;
private bool _isNavigating; // Guard against double navigation
public TodoListPage() public TodoListPage()
{ {
@@ -18,22 +19,33 @@ public partial class TodoListPage : ContentPage
TodoCollectionView.ItemsSource = _service.Todos; TodoCollectionView.ItemsSource = _service.Todos;
UpdateStats(); UpdateStats();
// Subscribe to theme changes to verify event is firing
if (Application.Current != null)
{
Application.Current.RequestedThemeChanged += (s, e) =>
{
Console.WriteLine($"[TodoListPage] RequestedThemeChanged event received! NewTheme={e.RequestedTheme}");
};
}
Console.WriteLine("[TodoListPage] Constructor finished"); Console.WriteLine("[TodoListPage] Constructor finished");
} }
protected override void OnAppearing() protected override void OnAppearing()
{ {
Console.WriteLine("[TodoListPage] OnAppearing called - refreshing CollectionView");
base.OnAppearing(); base.OnAppearing();
// Reset navigation guard when page reappears
_isNavigating = false;
// Refresh indexes for alternating row colors // Refresh indexes for alternating row colors
_service.RefreshIndexes(); _service.RefreshIndexes();
// Refresh the collection view // Refresh the collection view
TodoCollectionView.ItemsSource = null; TodoCollectionView.ItemsSource = null;
TodoCollectionView.ItemsSource = _service.Todos; TodoCollectionView.ItemsSource = _service.Todos;
Console.WriteLine($"[TodoListPage] ItemsSource set with {_service.Todos.Count} items");
UpdateStats(); UpdateStats();
UpdateThemeIcon();
} }
private async void OnAddClicked(object sender, EventArgs e) private async void OnAddClicked(object sender, EventArgs e)
@@ -43,23 +55,18 @@ public partial class TodoListPage : ContentPage
private async void OnSelectionChanged(object? sender, SelectionChangedEventArgs e) private async void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{ {
try // Guard against double navigation
if (_isNavigating)
{ {
Console.WriteLine($"[TodoListPage] OnSelectionChanged: {e.CurrentSelection.Count} items selected"); return;
}
if (e.CurrentSelection.FirstOrDefault() is TodoItem todo) if (e.CurrentSelection.FirstOrDefault() is TodoItem todo)
{ {
Console.WriteLine($"[TodoListPage] Navigating to TodoDetailPage for: {todo.Title}"); _isNavigating = true;
TodoCollectionView.SelectedItem = null; // Deselect TodoCollectionView.SelectedItem = null; // Deselect immediately
var detailPage = new TodoDetailPage(todo); await Navigation.PushAsync(new TodoDetailPage(todo));
Console.WriteLine($"[TodoListPage] Created TodoDetailPage, pushing..."); // Note: _isNavigating is reset in OnAppearing when we return
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}");
} }
} }
@@ -70,13 +77,42 @@ public partial class TodoListPage : ContentPage
if (total == 0) if (total == 0)
{ {
StatsLabel.Text = ""; StatsLabel.Text = "No tasks";
} }
else else
{ {
StatsLabel.Text = $"{completed} of {total} completed"; StatsLabel.Text = $"Tasks: {completed} of {total} completed";
} }
} }
private void UpdateThemeIcon()
{
// Check UserAppTheme first, fall back to RequestedTheme
var userTheme = Application.Current?.UserAppTheme ?? AppTheme.Unspecified;
var effectiveTheme = userTheme != AppTheme.Unspecified ? userTheme : Application.Current?.RequestedTheme ?? AppTheme.Light;
var isDarkMode = effectiveTheme == AppTheme.Dark;
// Show sun icon in dark mode (to switch to light), moon icon in light mode (to switch to dark)
ThemeToggleButton.Source = isDarkMode ? "light_mode_white.svg" : "dark_mode_white.svg";
Console.WriteLine($"[TodoListPage] UpdateThemeIcon: UserAppTheme={userTheme}, RequestedTheme={Application.Current?.RequestedTheme}, isDarkMode={isDarkMode}");
}
private void OnThemeToggleClicked(object? sender, EventArgs e)
{
if (Application.Current == null) return;
// Check current effective theme
var userTheme = Application.Current.UserAppTheme;
var effectiveTheme = userTheme != AppTheme.Unspecified ? userTheme : Application.Current.RequestedTheme;
var isDarkMode = effectiveTheme == AppTheme.Dark;
// Toggle to the opposite theme
var newTheme = isDarkMode ? AppTheme.Light : AppTheme.Dark;
Application.Current.UserAppTheme = newTheme;
Console.WriteLine($"[TodoListPage] Theme toggled from {effectiveTheme} to {newTheme}");
UpdateThemeIcon();
}
} }
/// <summary> /// <summary>
@@ -111,7 +147,8 @@ public class AlternatingRowColorConverter : IValueConverter
public class CompletedToColorConverter : IValueConverter public class CompletedToColorConverter : IValueConverter
{ {
// Light theme colors // Light theme colors
private static readonly Color AccentColor = Color.FromArgb("#26A69A"); private static readonly Color AccentColorLight = Color.FromArgb("#26A69A");
private static readonly Color AccentColorDark = Color.FromArgb("#4DB6AC");
private static readonly Color CompletedColor = Color.FromArgb("#9E9E9E"); private static readonly Color CompletedColor = Color.FromArgb("#9E9E9E");
private static readonly Color TextPrimaryLight = Color.FromArgb("#212121"); private static readonly Color TextPrimaryLight = Color.FromArgb("#212121");
private static readonly Color TextSecondaryLight = Color.FromArgb("#757575"); private static readonly Color TextSecondaryLight = Color.FromArgb("#757575");
@@ -126,10 +163,12 @@ public class CompletedToColorConverter : IValueConverter
string param = parameter as string ?? ""; string param = parameter as string ?? "";
bool isDarkMode = Application.Current?.RequestedTheme == AppTheme.Dark; bool isDarkMode = Application.Current?.RequestedTheme == AppTheme.Dark;
// Indicator bar color // Indicator bar color - theme-aware accent color
if (param == "indicator") if (param == "indicator")
{ {
return isCompleted ? CompletedColor : AccentColor; var color = isCompleted ? CompletedColor : (isDarkMode ? AccentColorDark : AccentColorLight);
Console.WriteLine($"[CompletedToColorConverter] indicator: isCompleted={isCompleted}, isDarkMode={isDarkMode}, color={color}");
return color;
} }
// Text colors with theme support // Text colors with theme support
@@ -186,3 +225,4 @@ public class CompletedToOpacityConverter : IValueConverter
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z"/></svg>

After

Width:  |  Height:  |  Size: 442 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M480-360q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Zm0 80q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM200-440H40v-80h160v80Zm720 0H760v-80h160v80ZM440-760v-160h80v160h-80Zm0 720v-160h80v160h-80ZM256-650l-101-97 57-59 96 100-52 56Zm492 496-97-101 53-55 101 97-57 59Zm-98-550 97-101 59 57-100 96-56-52ZM154-212l101-97 55 53-97 101-59-57Zm326-268Z"/></svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@@ -38,9 +38,12 @@
<MauiXaml Update="**/*.xaml" /> <MauiXaml Update="**/*.xaml" />
</ItemGroup> </ItemGroup>
<!-- Embedded Resources (icons) --> <!-- Images -->
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Resources\Images\*.svg" /> <MauiImage Include="Resources\Images\*.svg" />
</ItemGroup> </ItemGroup>
<!-- Import OpenMaui targets for Linux builds -->
<Import Project="../../maui-linux/build/OpenMaui.Controls.Linux.targets" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
</Project> </Project>

View File

@@ -78,4 +78,15 @@ public class TodoItem : INotifyPropertyChanged
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
/// <summary>
/// Forces all bindings to re-evaluate by notifying all properties changed.
/// Used when external factors (like app theme) change.
/// </summary>
public void RefreshBindings()
{
OnPropertyChanged(nameof(IsCompleted));
OnPropertyChanged(nameof(Title));
OnPropertyChanged(nameof(Notes));
}
} }