From ecff97419b7777f4e8f09ef3684175a2a356f7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Sat, 18 Oct 2025 22:16:28 +0200 Subject: [PATCH] Default theme is back. Container selection functionality etc. --- Directory.Packages.props | 3 +- .../Translations/en_US/resources.json | 3 + .../AppThemes/{Mica.xaml => Default.xaml} | 2 +- Wino.Core.WinUI/IMPLEMENTATION_SUMMARY.md | 173 ------------ Wino.Core.WinUI/MIGRATION_NewThemeService.md | 258 ------------------ Wino.Core.WinUI/README_NewThemeService.md | 169 ------------ Wino.Core.WinUI/Services/NewThemeService.cs | 2 + Wino.Core.WinUI/Wino.Core.WinUI.csproj | 3 +- .../Collections/GroupedEmailCollection.cs | 49 ++++ .../Data/MailItemContainer.cs | 19 ++ .../Data/ThreadMailItemViewModel.cs | 5 + Wino.Mail.WinUI/App.xaml | 2 +- .../Controls/Advanced/WinoItemsView.cs | 41 +++ Wino.Mail.WinUI/ShellWindow.xaml | 5 +- Wino.Mail.WinUI/Views/MailListPage.xaml | 6 +- Wino.Mail.WinUI/Views/MailListPage.xaml.cs | 108 +++++--- .../Views/Settings/PersonalizationPage.xaml | 11 +- Wino.Mail.WinUI/Wino.Mail.WinUI.csproj | 1 + 18 files changed, 205 insertions(+), 655 deletions(-) rename Wino.Core.WinUI/AppThemes/{Mica.xaml => Default.xaml} (94%) delete mode 100644 Wino.Core.WinUI/IMPLEMENTATION_SUMMARY.md delete mode 100644 Wino.Core.WinUI/MIGRATION_NewThemeService.md delete mode 100644 Wino.Core.WinUI/README_NewThemeService.md diff --git a/Directory.Packages.props b/Directory.Packages.props index 388751b7..e824166e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -47,6 +47,7 @@ + @@ -67,4 +68,4 @@ - + \ No newline at end of file diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 8eccf7fa..c84f20f3 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -531,6 +531,9 @@ "SettingsDiscord_Title": "Discord Channel", "SettingsEditLinkedInbox_Description": "Add / remove accounts, rename or break the link between accounts.", "SettingsEditLinkedInbox_Title": "Edit Linked Inbox", + "SettingsWindowBackdrop_Title": "Window Backdrop", + "SettingsWindowBackdrop_Description": "Select a backdrop effect for Wino windows.", + "SettingsWindowBackdrop_Disabled": "Window backdrop selection is disabled when application theme is selected other than Default.", "SettingsElementTheme_Description": "Select a Windows theme for Wino", "SettingsElementTheme_Title": "Element Theme", "SettingsElementThemeSelectionDisabled": "Element theme selection is disabled when application theme is selected other than Default.", diff --git a/Wino.Core.WinUI/AppThemes/Mica.xaml b/Wino.Core.WinUI/AppThemes/Default.xaml similarity index 94% rename from Wino.Core.WinUI/AppThemes/Mica.xaml rename to Wino.Core.WinUI/AppThemes/Default.xaml index 88df78b2..d8adf74d 100644 --- a/Wino.Core.WinUI/AppThemes/Mica.xaml +++ b/Wino.Core.WinUI/AppThemes/Default.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xaml="using:Microsoft.UI.Xaml"> - Mica + Default Transparent Transparent diff --git a/Wino.Core.WinUI/IMPLEMENTATION_SUMMARY.md b/Wino.Core.WinUI/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index e4c7125e..00000000 --- a/Wino.Core.WinUI/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,173 +0,0 @@ -# NewThemeService Implementation Summary - -## What's Been Created - -### 🏗️ Core Implementation - -1. **WindowBackdropType Enum** (`Wino.Core.Domain\Enums\WindowBackdropType.cs`) - - Defines all supported backdrop types (Mica, MicaAlt, DesktopAcrylic, etc.) - -2. **INewThemeService Interface** (`Wino.Core.Domain\Interfaces\INewThemeService.cs`) - - Extended interface with backdrop management and enhanced accent color support - - Backward compatible with existing IThemeService functionality - -3. **NewThemeService Implementation** (`Wino.Core.WinUI\Services\NewThemeService.cs`) - - Full WinUI-optimized theme service - - Window backdrop management with WindowEx support - - Enhanced accent color management with theme preservation - - Proper initialization sequence for backdrop application - - Event system for backdrop, theme, and accent color changes - -### 🔧 Integration Updates - -4. **CoreUWPContainerSetup.cs** - Updated DI registration - - Registers both old and new theme services - - Maintains backward compatibility - -5. **WinoApplication.cs** - Enhanced base application class - - Added NewThemeService property and initialization - - Updated service initialization order - - Proper backdrop application timing - -6. **App.xaml.cs** - Updated application launch sequence - - Loads backdrop settings before window creation - - Applies backdrop immediately after window creation - - Maintains proper initialization flow - -7. **ShellWindow.xaml** - Removed hardcoded backdrop - - Allows NewThemeService to control backdrop dynamically - -### 📚 Documentation & Examples - -8. **Usage Example** (`Examples\NewThemeServiceExampleViewModel.cs`) - - Complete ViewModel showing all features - - Event handling patterns - - Error handling best practices - - UI binding examples - -9. **Comprehensive Documentation** (`README_NewThemeService.md`) - - Feature overview and benefits - - Usage examples and patterns - - Configuration and best practices - - Service registration details - -10. **Migration Guide** (`MIGRATION_NewThemeService.md`) - - Step-by-step migration instructions - - Before/after code comparisons - - Common issues and solutions - - Testing checklist - -## Key Features Delivered - -### ✨ Window Backdrop Management -- **Dynamic Backdrop Control**: Switch between Mica, Acrylic, and other backdrops at runtime -- **Persistent Settings**: Backdrop preferences saved and restored across app sessions -- **Initialization Support**: Backdrop applied before window is shown to user -- **Multiple Backdrop Types**: - - `Mica` - Standard translucent material - - `MicaAlt` - Alternative Mica variant - - `DesktopAcrylic` - Semi-transparent acrylic - - `AcrylicBase` & `AcrylicThin` - Acrylic variants - - `None` - No backdrop for solid backgrounds - -### 🎨 Enhanced Accent Color Management -- **Theme-Preserving Color Changes**: Change accent color without switching themes -- **System Integration**: Automatic system accent color detection -- **Improved API**: `SetAccentColorAsync()` with better control options -- **Backward Compatibility**: Works with existing accent color code - -### 🖼️ Custom Wallpaper Support -- **Existing Functionality**: Polished implementation from old service -- **Thumbnail Generation**: Automatic preview image creation -- **Persistent Storage**: Themes saved in local app data -- **Metadata Management**: JSON-based theme configuration - -### ⚡ Better Initialization -- **Startup Performance**: Backdrop settings loaded early in app lifecycle -- **Window Creation Flow**: Proper timing between window creation and backdrop application -- **Service Dependencies**: Correct initialization order with other services -- **Configuration Persistence**: All settings properly saved and restored - -### 🔄 Backward Compatibility -- **Dual Service Support**: Both old and new services available -- **Gradual Migration**: Can migrate features incrementally -- **API Compatibility**: All existing theme functionality preserved -- **Zero Breaking Changes**: Existing code continues to work - -## Technical Implementation Details - -### Service Registration Pattern -```csharp -// Both services registered for compatibility -services.AddSingleton(); -services.AddSingleton(); -``` - -### Initialization Sequence -1. App constructs services (including NewThemeService) -2. OnLaunched loads saved backdrop type from configuration -3. Window is created (ShellWindow) -4. Backdrop is applied immediately via ApplyBackdropAsync() -5. Service initialization completes (InitializeServicesAsync) -6. Window is activated and shown to user - -### Configuration Keys -- `WindowBackdropTypeKey`: Backdrop type preference -- `AccentColorKey`: Custom accent color -- `CurrentApplicationThemeKey`: Selected theme ID -- `SelectedAppThemeKey`: Element theme (Light/Dark/Default) - -### Event System -- `BackdropChanged`: Fired when backdrop type changes -- `ElementThemeChanged`: Existing theme change events -- `AccentColorChanged`: Existing accent color events - -## Benefits Over Original ThemeService - -### 🎯 WinUI Optimized -- **WindowEx Integration**: Proper SystemBackdrop property usage -- **Modern Backdrop Types**: Support for latest WinUI backdrop materials -- **Performance**: Optimized for WinUI rendering pipeline - -### 💡 Enhanced User Experience -- **Visual Polish**: Professional backdrop effects (Mica, Acrylic) -- **Smooth Transitions**: Proper backdrop switching without flicker -- **System Integration**: Respects system theme and accent preferences - -### 🔧 Developer Experience -- **Better APIs**: Async methods with proper error handling -- **Event System**: Rich event notifications for UI updates -- **Clear Separation**: Theme vs backdrop vs accent color management -- **Documentation**: Comprehensive guides and examples - -### 🚀 Future Ready -- **Extensible**: Easy to add new backdrop types -- **Modern Patterns**: Async/await, proper DI integration -- **Maintainable**: Clean separation of concerns -- **Testable**: Interface-based design with dependency injection - -## Deployment Notes - -### Required Steps for Integration -1. ✅ All code files created and properly structured -2. ✅ Service registration updated in DI container -3. ✅ WinoApplication updated with NewThemeService support -4. ✅ App launch sequence updated for proper backdrop initialization -5. ✅ Hardcoded backdrop removed from XAML -6. ✅ Backward compatibility maintained -7. ✅ Documentation and examples provided - -### Testing Recommendations -- Test all backdrop types on different system themes -- Verify backdrop persistence across app restarts -- Test accent color changes with different themes -- Verify custom theme creation still works -- Test on different Windows versions and hardware - -### Migration Path -- Services can coexist during transition period -- Teams can migrate features incrementally -- No breaking changes to existing functionality -- Full backward compatibility maintained - -This implementation provides a solid foundation for modern WinUI theming with professional backdrop effects while maintaining all existing functionality. \ No newline at end of file diff --git a/Wino.Core.WinUI/MIGRATION_NewThemeService.md b/Wino.Core.WinUI/MIGRATION_NewThemeService.md deleted file mode 100644 index 1f65c425..00000000 --- a/Wino.Core.WinUI/MIGRATION_NewThemeService.md +++ /dev/null @@ -1,258 +0,0 @@ -# NewThemeService Migration Guide - -## Overview - -This guide helps you migrate from the original `ThemeService` to the new `NewThemeService` with enhanced backdrop management and improved accent color handling. - -## Quick Migration Steps - -### 1. Update Service Usage - -**Before (Old ThemeService):** -```csharp -var themeService = Services.GetService(); -themeService.RootTheme = ApplicationElementTheme.Dark; -themeService.AccentColor = "#FF5722"; -``` - -**After (NewThemeService):** -```csharp -var newThemeService = Services.GetService(); -newThemeService.RootTheme = ApplicationElementTheme.Dark; -await newThemeService.SetAccentColorAsync("#FF5722", preserveTheme: true); - -// Plus new backdrop management -await newThemeService.ApplyBackdropAsync(WindowBackdropType.Mica); -``` - -### 2. Remove Hardcoded Backdrops - -**Before:** -```xaml - - - -``` - -**After:** -```xaml - -``` - -### 3. Update App Initialization - -**Before:** -```csharp -protected override async void OnLaunched(LaunchActivatedEventArgs args) -{ - MainWindow = new ShellWindow(); - await InitializeServicesAsync(); - MainWindow.Activate(); -} -``` - -**After:** -```csharp -protected override async void OnLaunched(LaunchActivatedEventArgs args) -{ - // Load backdrop settings before creating window - var newThemeService = Services.GetService(); - var configService = Services.GetService(); - var savedBackdropType = (WindowBackdropType)configService.Get("WindowBackdropTypeKey", (int)WindowBackdropType.Mica); - - MainWindow = new ShellWindow(); - - // Apply backdrop immediately after window creation - if (newThemeService != null) - { - await newThemeService.ApplyBackdropAsync(savedBackdropType); - } - - await InitializeServicesAsync(); - MainWindow.Activate(); -} -``` - -### 4. Update WinoApplication Services - -**Before:** -```csharp -public IEnumerable GetActivationServices() -{ - yield return DatabaseService; - yield return TranslationService; - yield return ThemeService; // Old service -} -``` - -**After:** -```csharp -public IEnumerable GetActivationServices() -{ - yield return DatabaseService; - yield return TranslationService; - yield return NewThemeService; // New service - // yield return ThemeService; // Keep for backward compatibility but don't initialize -} -``` - -## New Features Available After Migration - -### 1. Backdrop Management -```csharp -// All available backdrop types -await newThemeService.ApplyBackdropAsync(WindowBackdropType.Mica); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.MicaAlt); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.DesktopAcrylic); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.AcrylicBase); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.AcrylicThin); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.None); - -// Persistent backdrop setting -newThemeService.CurrentBackdropType = WindowBackdropType.MicaAlt; -``` - -### 2. Enhanced Accent Color Management -```csharp -// Set accent color without changing theme -await newThemeService.SetAccentColorAsync("#FF5722", preserveTheme: true); - -// Get system accent color -var systemColor = newThemeService.GetSystemAccentColorHex(); -await newThemeService.SetAccentColorAsync(systemColor); -``` - -### 3. Event Handling -```csharp -// New backdrop change events -newThemeService.BackdropChanged += (sender, backdropType) => { - // Handle backdrop changes - UpdateUI(backdropType); -}; - -// Existing events still work -newThemeService.ElementThemeChanged += (sender, theme) => { - // Handle theme changes -}; - -newThemeService.AccentColorChanged += (sender, color) => { - // Handle accent color changes -}; -``` - -## Backward Compatibility - -### Both Services Available -- `IThemeService` (original) - still registered and functional -- `INewThemeService` (new) - enhanced version with additional features - -### Choosing Which to Use -```csharp -// For new code - use NewThemeService -var newThemeService = Services.GetService(); - -// For existing code that needs compatibility - keep using ThemeService -var oldThemeService = Services.GetService(); -``` - -### Gradual Migration Strategy -1. **Phase 1**: Register both services, initialize NewThemeService -2. **Phase 2**: Update new features to use NewThemeService -3. **Phase 3**: Migrate existing code gradually -4. **Phase 4**: Eventually phase out old ThemeService (optional) - -## Common Migration Issues - -### Issue 1: Window Backdrop Not Applied -**Problem**: Backdrop doesn't appear after migration -**Solution**: Ensure backdrop is applied after window creation but before activation - -```csharp -MainWindow = new ShellWindow(); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.Mica); // Add this -await InitializeServicesAsync(); -MainWindow.Activate(); -``` - -### Issue 2: Accent Color Changes Don't Persist -**Problem**: Accent color resets after app restart -**Solution**: Use the enhanced SetAccentColorAsync method - -```csharp -// Old way - might not persist properly -newThemeService.AccentColor = "#FF5722"; - -// New way - properly persisted -await newThemeService.SetAccentColorAsync("#FF5722", preserveTheme: true); -``` - -### Issue 3: Multiple Service Initialization -**Problem**: Both services being initialized causing conflicts -**Solution**: Only initialize NewThemeService in GetActivationServices() - -```csharp -public IEnumerable GetActivationServices() -{ - yield return DatabaseService; - yield return TranslationService; - yield return NewThemeService; // Only this one - // Don't yield ThemeService here -} -``` - -## Testing Your Migration - -### 1. Backdrop Functionality -- [ ] App starts with saved backdrop type -- [ ] Backdrop changes are applied immediately -- [ ] Backdrop changes persist after app restart -- [ ] All backdrop types work correctly - -### 2. Theme Functionality -- [ ] Light/Dark theme changes work -- [ ] Custom themes still function -- [ ] Theme changes persist after restart - -### 3. Accent Color Management -- [ ] Custom accent colors apply correctly -- [ ] System accent color detection works -- [ ] Accent color changes persist -- [ ] Theme preservation works with accent changes - -### 4. Backward Compatibility -- [ ] Existing custom themes still work -- [ ] Old theme-related code continues to function -- [ ] No regression in existing functionality - -## Performance Considerations - -### Initialization Order -- NewThemeService initializes backdrop settings from saved configuration -- Window creation happens before service initialization for best performance -- Backdrop is applied immediately after window creation - -### Runtime Performance -- Backdrop changes are async operations -- Don't change backdrop frequently (e.g., during animations) -- Cache backdrop type to avoid unnecessary changes - -## Complete Migration Checklist - -- [ ] Update DI container registration -- [ ] Update WinoApplication service initialization -- [ ] Remove hardcoded SystemBackdrop from XAML -- [ ] Update app launch sequence -- [ ] Update settings/preferences UI for backdrop options -- [ ] Test all backdrop types -- [ ] Test theme and accent color functionality -- [ ] Verify persistence across app restarts -- [ ] Update documentation and comments -- [ ] Train team on new features - -## Need Help? - -If you encounter issues during migration: -1. Check the complete example in `NewThemeServiceExampleViewModel` -2. Review the full documentation in `README_NewThemeService.md` -3. Ensure proper initialization order in your app -4. Verify all required using statements are included \ No newline at end of file diff --git a/Wino.Core.WinUI/README_NewThemeService.md b/Wino.Core.WinUI/README_NewThemeService.md deleted file mode 100644 index 73385ae4..00000000 --- a/Wino.Core.WinUI/README_NewThemeService.md +++ /dev/null @@ -1,169 +0,0 @@ -# NewThemeService Documentation - -## Overview - -The `NewThemeService` is an enhanced theme management service designed specifically for WinUI applications. It extends the capabilities of the original `ThemeService` with advanced features like backdrop management, improved accent color handling, and better window initialization support. - -## Key Features - -### 🎨 Window Backdrop Management -- **Mica**: Modern translucent material with system-aware tinting -- **MicaAlt**: Alternative Mica variant with different tinting behavior -- **DesktopAcrylic**: Semi-transparent acrylic background -- **AcrylicBase & AcrylicThin**: Different acrylic variants -- **None**: No backdrop (solid background) - -### 🌈 Enhanced Accent Color Management -- Set custom accent colors without changing themes -- Preserve theme while changing accent colors -- Automatic system accent color detection -- Backward-compatible color management - -### 🖼️ Custom Wallpaper Support -- Create custom themes with user wallpapers -- Automatic thumbnail generation -- Persistent theme storage -- Reuses existing custom theme functionality - -### ⚡ Improved Initialization -- Backdrop settings applied before window creation -- Persistent settings restoration -- Proper service initialization order - -## Usage Examples - -### Basic Setup - -```csharp -// Service is automatically registered in DI container -var newThemeService = WinoApplication.Current.Services.GetService(); -``` - -### Changing Window Backdrop - -```csharp -// Apply different backdrop types -await newThemeService.ApplyBackdropAsync(WindowBackdropType.Mica); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.DesktopAcrylic); -await newThemeService.ApplyBackdropAsync(WindowBackdropType.None); - -// Persist the setting -newThemeService.CurrentBackdropType = WindowBackdropType.MicaAlt; -``` - -### Custom Accent Color - -```csharp -// Set accent color while preserving theme -await newThemeService.SetAccentColorAsync("#FF6B47", preserveTheme: true); - -// Reset to system accent color -var systemAccent = newThemeService.GetSystemAccentColorHex(); -await newThemeService.SetAccentColorAsync(systemAccent); -``` - -### Event Handling - -```csharp -newThemeService.BackdropChanged += (sender, backdropType) => { - Debug.WriteLine($"Backdrop changed to: {backdropType}"); -}; - -newThemeService.ElementThemeChanged += (sender, theme) => { - Debug.WriteLine($"Theme changed to: {theme}"); -}; - -newThemeService.AccentColorChanged += (sender, color) => { - Debug.WriteLine($"Accent color changed to: {color}"); -}; -``` - -### Creating Custom Themes - -```csharp -// Create custom theme with wallpaper -var wallpaperData = await GetWallpaperBytesAsync(); // Your implementation -var customTheme = await newThemeService.CreateNewCustomThemeAsync( - "My Theme", - "#FF5722", - wallpaperData); - -// Apply the custom theme -newThemeService.CurrentApplicationThemeId = customTheme.Id; -``` - -## Service Registration - -The service is automatically registered in the DI container: - -```csharp -// In CoreUWPContainerSetup.cs -services.AddSingleton(); -``` - -## Initialization Order - -The service is initialized during app startup: - -```csharp -// In WinoApplication.cs -public IEnumerable GetActivationServices() -{ - yield return DatabaseService; - yield return TranslationService; - yield return NewThemeService; // Initializes before window activation -} -``` - -## Backdrop Application Flow - -1. **App Launch**: Saved backdrop type is loaded from configuration -2. **Window Creation**: Window is created without hardcoded backdrop -3. **Service Initialization**: NewThemeService initializes and applies saved backdrop -4. **Runtime Changes**: User can change backdrop which is immediately applied and persisted - -## Backward Compatibility - -- The original `IThemeService` is still registered and functional -- All existing theme functionality is preserved -- Custom theme creation and management works as before -- Accent color management is enhanced but compatible - -## Configuration Keys - -The service uses these persistent configuration keys: - -- `WindowBackdropTypeKey`: Stores the selected backdrop type (int) -- `AccentColorKey`: Stores custom accent color (string) -- `CurrentApplicationThemeKey`: Stores selected theme ID (Guid) -- `SelectedAppThemeKey`: Stores element theme preference (ApplicationElementTheme) - -## Best Practices - -### ✅ Do -- Initialize the service early in app lifecycle -- Use async methods for backdrop changes -- Handle backdrop change errors gracefully -- Subscribe to events for UI updates -- Preserve themes when changing accent colors - -### ❌ Don't -- Set hardcoded SystemBackdrop in XAML -- Change backdrop on every frame/animation -- Ignore initialization errors -- Forget to unsubscribe from events - -## Migration from Old ThemeService - -1. **Update DI Registration**: Both services are registered, choose which to use -2. **Update Initialization**: Change `GetActivationServices()` to use `NewThemeService` -3. **Remove Hardcoded Backdrops**: Remove `` from XAML -4. **Add Backdrop Management**: Use `ApplyBackdropAsync()` for backdrop changes -5. **Enhanced Accent Colors**: Use `SetAccentColorAsync()` for better accent color management - -## See Also - -- `INewThemeService` interface documentation -- `WindowBackdropType` enumeration -- `NewThemeServiceExampleViewModel` for complete usage example -- Original `ThemeService` for backward compatibility \ No newline at end of file diff --git a/Wino.Core.WinUI/Services/NewThemeService.cs b/Wino.Core.WinUI/Services/NewThemeService.cs index b019b1db..bc4907e8 100644 --- a/Wino.Core.WinUI/Services/NewThemeService.cs +++ b/Wino.Core.WinUI/Services/NewThemeService.cs @@ -38,6 +38,7 @@ public class NewThemeService : INewThemeService { public const string CustomThemeFolderName = "CustomThemes"; + private static string _defaultThemeId = "00000000-0000-0000-0000-000000000000"; private static string _cloudsThemeId = "3b621cc2-e270-4a76-8477-737917cccda0"; private static string _forestThemeId = "8bc89b37-a7c5-4049-86e2-de1ae8858dbd"; private static string _nightyThemeId = "5b65e04e-fd7e-4c2d-8221-068d3e02d23a"; @@ -64,6 +65,7 @@ public class NewThemeService : INewThemeService private List preDefinedThemes { get; set; } = new List() { + new SystemAppTheme("Default", Guid.Parse(_defaultThemeId)), new PreDefinedAppTheme("Nighty", Guid.Parse(_nightyThemeId), "#e1b12c", ApplicationElementTheme.Dark), new PreDefinedAppTheme("Forest", Guid.Parse(_forestThemeId), "#16a085", ApplicationElementTheme.Dark), new PreDefinedAppTheme("Clouds", Guid.Parse(_cloudsThemeId), "#0984e3", ApplicationElementTheme.Light), diff --git a/Wino.Core.WinUI/Wino.Core.WinUI.csproj b/Wino.Core.WinUI/Wino.Core.WinUI.csproj index e1afcf2f..8398273b 100644 --- a/Wino.Core.WinUI/Wino.Core.WinUI.csproj +++ b/Wino.Core.WinUI/Wino.Core.WinUI.csproj @@ -23,7 +23,7 @@ MSBuild:Compile - + MSBuild:Compile @@ -78,6 +78,7 @@ + diff --git a/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs b/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs index 96076530..b67dba8e 100644 --- a/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs +++ b/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs @@ -636,6 +636,8 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient Items.IndexOf(item); + private void RefreshThreadInUI(ThreadMailItemViewModel expander) { // Remove thread completely from UI @@ -646,6 +648,53 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient + item.MailCopy.UniqueId == uniqueId && !item.IsDisplayedInThread); + + if (standaloneMailItem != null) + { + // Check if the standalone item is visible in the UI + var isItemVisible = Items.Contains(standaloneMailItem); + + return new MailItemContainer(standaloneMailItem) + { + IsItemVisible = isItemVisible, + IsThreadVisible = false // Not a threaded item + }; + } + + // Search in thread expanders for threaded mail items + foreach (var threadExpander in _threadExpanders.Values) + { + if (threadExpander.HasUniqueId(uniqueId)) + { + // Find the specific mail item within the thread + var threadMailItem = threadExpander.ThreadEmails.FirstOrDefault(email => + email.MailCopy.UniqueId == uniqueId); + + if (threadMailItem != null) + { + // Check visibility: thread expander must be visible, and for individual item visibility, + // the thread must be expanded and the item must be in the visible Items collection + var isThreadVisible = Items.Contains(threadExpander); + var isItemVisible = isThreadVisible && threadExpander.IsThreadExpanded && Items.Contains(threadMailItem); + + return new MailItemContainer(threadMailItem, threadExpander) + { + IsItemVisible = isItemVisible, + IsThreadVisible = isThreadVisible + }; + } + } + } + + // Item not found + return null; + } + private void AddThreadToUI(ThreadMailItemViewModel expander, string groupKey) { var groupHeader = GetOrCreateGroupHeader(groupKey); diff --git a/Wino.Mail.ViewModels/Data/MailItemContainer.cs b/Wino.Mail.ViewModels/Data/MailItemContainer.cs index 08920973..67d5f590 100644 --- a/Wino.Mail.ViewModels/Data/MailItemContainer.cs +++ b/Wino.Mail.ViewModels/Data/MailItemContainer.cs @@ -6,6 +6,25 @@ public class MailItemContainer { public MailItemViewModel ItemViewModel { get; set; } public ThreadMailItemViewModel ThreadViewModel { get; set; } + + /// + /// Indicates whether the mail item is currently visible in the UI's Items collection. + /// For threaded items, this indicates if the individual mail item is visible (thread must be expanded). + /// + public bool IsItemVisible { get; set; } + + /// + /// Indicates whether the thread expander (if applicable) is currently visible in the UI's Items collection. + /// Only relevant when ThreadViewModel is not null. + /// + public bool IsThreadVisible { get; set; } + + /// + /// Indicates whether the container can be successfully navigated to in the UI. + /// For standalone items: true if IsItemVisible is true. + /// For threaded items: true if IsThreadVisible is true (the thread expander can be navigated to). + /// + public bool CanNavigate => ThreadViewModel != null ? IsThreadVisible : IsItemVisible; public MailItemContainer(MailItemViewModel itemViewModel, ThreadMailItemViewModel threadViewModel) : this(itemViewModel) { diff --git a/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs b/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs index 980762b5..3ceb7f11 100644 --- a/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs +++ b/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs @@ -110,4 +110,9 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable NotifyPropertyChanges(); } } + + /// + /// Checks if this thread contains an email with the specified unique ID + /// + public bool HasUniqueId(Guid uniqueId) => _threadEmails.Any(email => email.MailCopy.UniqueId == uniqueId); } diff --git a/Wino.Mail.WinUI/App.xaml b/Wino.Mail.WinUI/App.xaml index 02bfd708..bcdfb7e6 100644 --- a/Wino.Mail.WinUI/App.xaml +++ b/Wino.Mail.WinUI/App.xaml @@ -17,7 +17,7 @@ - + diff --git a/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs b/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs index 8772925f..7e1c4af9 100644 --- a/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs +++ b/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs @@ -2,6 +2,7 @@ using System.Windows.Input; using CommunityToolkit.WinUI; using Microsoft.UI.Xaml.Controls; +using Wino.Mail.ViewModels.Data; namespace Wino.Mail.WinUI.Controls.Advanced; @@ -43,4 +44,44 @@ public partial class WinoItemsView : ItemsView // Trigger when scrolled past 90% of total height if (progress >= 0.9) LoadMoreCommand?.Execute(null); } + + public bool SelectMailItemContainer(MailItemViewModel mailItemViewModel) + { + return true; + } + + /// + /// Recursively clears all selections except the given mail. + /// + /// Exceptional mail item to be not unselected. + /// Whether expansion states of thread containers should stay as it is or not. + public void ClearSelections(MailItemViewModel? exceptViewModel = null, bool preserveThreadExpanding = false) + { + if (CastedItemsSource == null) return; + + foreach (var item in CastedItemsSource) + { + if (item is MailItemViewModel mailItemViewModel) + { + if (mailItemViewModel != exceptViewModel) + { + mailItemViewModel.IsSelected = false; + } + } + else if (item is ThreadMailItemViewModel threadMailItemViewModel) + { + threadMailItemViewModel.IsSelected = false; + + if (!preserveThreadExpanding) threadMailItemViewModel.IsThreadExpanded = false; + + foreach (var childMail in threadMailItemViewModel.ThreadEmails) + { + if (childMail != exceptViewModel) + { + childMail.IsSelected = false; + } + } + } + } + } } diff --git a/Wino.Mail.WinUI/ShellWindow.xaml b/Wino.Mail.WinUI/ShellWindow.xaml index 0eb46c36..c2f6c1ad 100644 --- a/Wino.Mail.WinUI/ShellWindow.xaml +++ b/Wino.Mail.WinUI/ShellWindow.xaml @@ -12,16 +12,17 @@ - + + - + ChangeSelectionMode(ItemsViewSelectionMode.Multiple); private void FolderPivotChanged(object sender, SelectionChangedEventArgs e) { @@ -130,20 +127,19 @@ public sealed partial class MailListPage : MailListPageAbstract, ViewModel.SelectedPivotChangedCommand.Execute(null); } - private void ChangeSelectionMode(ListViewSelectionMode mode) + private void ChangeSelectionMode(ItemsViewSelectionMode mode) { - // ItemsView doesn't have a ChangeSelectionMode method like ListView - // The selection mode is set in XAML and doesn't need to change dynamically for ItemsView + MailListView.SelectionMode = mode; if (ViewModel?.PivotFolders != null) { - ViewModel.PivotFolders.ForEach(a => a.IsExtendedMode = mode == ListViewSelectionMode.Extended); + ViewModel.PivotFolders.ForEach(a => a.IsExtendedMode = mode == ItemsViewSelectionMode.Extended); } } private void SelectionModeToggleUnchecked(object sender, RoutedEventArgs e) { - ChangeSelectionMode(ListViewSelectionMode.Extended); + ChangeSelectionMode(ItemsViewSelectionMode.Extended); } private void SelectAllCheckboxChecked(object sender, RoutedEventArgs e) @@ -306,46 +302,54 @@ public sealed partial class MailListPage : MailListPageAbstract, { if (message.SelectedMailViewModel == null) return; - //await ViewModel.ExecuteUIThread(async () => - //{ - // MailListView.ClearSelections(message.SelectedMailViewModel, true); + await ViewModel.ExecuteUIThread(async () => + { + MailListView.ClearSelections(message.SelectedMailViewModel, true); - // int retriedSelectionCount = 0; - //trySelection: + int retriedSelectionCount = 0; + trySelection: - // bool isSelected = MailListView.SelectMailItemContainer(message.SelectedMailViewModel); + bool isSelected = MailListView.SelectMailItemContainer(message.SelectedMailViewModel); - // if (!isSelected) - // { - // for (int i = retriedSelectionCount; i < 5;) - // { - // // Retry with delay until the container is realized. Max 1 second. - // await Task.Delay(200); + if (!isSelected) + { + for (int i = retriedSelectionCount; i < 5;) + { + // Retry with delay until the container is realized. Max 1 second. + await Task.Delay(200); - // retriedSelectionCount++; + retriedSelectionCount++; - // goto trySelection; - // } - // } + goto trySelection; + } + } - // // Automatically scroll to the selected item. - // // This is useful when creating draft. - // if (isSelected && message.ScrollToItem) - // { - // var collectionContainer = ViewModel.MailCollection.GetMailItemContainer(message.SelectedMailViewModel.UniqueId); + // Automatically scroll to the selected item. + // This is useful when creating draft. - // // Scroll to thread if available. - // if (collectionContainer.ThreadViewModel != null) - // { - // MailListView.StartBringItemIntoView(collectionContainer.ThreadViewModel, new BringIntoViewOptions()); - // } - // else if (collectionContainer.ItemViewModel != null) - // { - // MailListView.StartBringItemIntoView(collectionContainer.ItemViewModel, new BringIntoViewOptions()); - // } + if (isSelected && message.ScrollToItem) + { + var collectionContainer = ViewModel.MailCollection.GetMailItemContainer(message.SelectedMailViewModel.MailCopy.UniqueId); - // } - //}); + // Scroll to thread if available. + // Find the item index on the UI. This is different than ListView. + + int scrollIndex = -1; + if (collectionContainer.ThreadViewModel != null) + { + scrollIndex = ViewModel.MailCollection.IndexOf(collectionContainer.ThreadViewModel); + } + else if (collectionContainer.ItemViewModel != null) + { + scrollIndex = ViewModel.MailCollection.IndexOf(collectionContainer.ItemViewModel); + } + + if (scrollIndex >= 0) + { + MailListView.StartBringItemIntoView(scrollIndex, new BringIntoViewOptions() { AnimationDesired = true }); + } + } + }); } private void SearchBoxFocused(object sender, RoutedEventArgs e) @@ -501,8 +505,6 @@ public sealed partial class MailListPage : MailListPageAbstract, private void DeleteAllInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) => ViewModel.ExecuteMailOperationCommand.Execute(MailOperation.SoftDelete); - - /// /// Animates the rotation using high-performance Composition APIs /// @@ -539,6 +541,26 @@ public sealed partial class MailListPage : MailListPageAbstract, { UpdateSelectAllButtonStatus(); UpdateAdaptiveness(); + + SynchronizeSelectedItems(); + } + + private static object _selectedItemsLock = new object(); + private void SynchronizeSelectedItems() + { + lock (_selectedItemsLock) + { + ViewModel.SelectedItems.Clear(); + + foreach (var item in MailListView.SelectedItems) + { + if (item is MailItemViewModel mailItem) + { + if (!mailItem.IsSelected) mailItem.IsSelected = true; + if (!ViewModel.SelectedItems.Contains(mailItem)) ViewModel.SelectedItems.Add(mailItem); + } + } + } } private void ThreadContainerRightTapped(object sender, RightTappedRoutedEventArgs e) diff --git a/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml b/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml index 6c8076b3..776e6523 100644 --- a/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml +++ b/Wino.Mail.WinUI/Views/Settings/PersonalizationPage.xaml @@ -151,8 +151,8 @@ @@ -172,6 +172,13 @@ + + diff --git a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj index 666cec74..8f262f1d 100644 --- a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj +++ b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj @@ -112,6 +112,7 @@ +