Default theme is back. Container selection functionality etc.

This commit is contained in:
Burak Kaan Köse
2025-10-18 22:16:28 +02:00
parent ad135c5e32
commit ecff97419b
18 changed files with 205 additions and 655 deletions
+2 -1
View File
@@ -47,6 +47,7 @@
<PackageVersion Include="SkiaSharp" Version="3.119.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="SqlKata" Version="4.0.1" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.10" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
@@ -67,4 +68,4 @@
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.250930001-experimental1" />
<PackageVersion Include="WinUIEx" Version="2.9.0" />
</ItemGroup>
</Project>
</Project>
@@ -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.",
@@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xaml="using:Microsoft.UI.Xaml">
<x:String x:Key="ThemeName">Mica</x:String>
<x:String x:Key="ThemeName">Default</x:String>
<SolidColorBrush x:Key="WinoApplicationBackgroundColor">Transparent</SolidColorBrush>
<SolidColorBrush x:Key="AppBarBackgroundColor">Transparent</SolidColorBrush>
-173
View File
@@ -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<IThemeService, ThemeService>();
services.AddSingleton<INewThemeService, NewThemeService>();
```
### 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.
@@ -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<IThemeService>();
themeService.RootTheme = ApplicationElementTheme.Dark;
themeService.AccentColor = "#FF5722";
```
**After (NewThemeService):**
```csharp
var newThemeService = Services.GetService<INewThemeService>();
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
<winuiex:WindowEx.SystemBackdrop>
<MicaBackdrop />
</winuiex:WindowEx.SystemBackdrop>
```
**After:**
```xaml
<!-- SystemBackdrop will be set by NewThemeService -->
```
### 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<INewThemeService>();
var configService = Services.GetService<IConfigurationService>();
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<IInitializeAsync> GetActivationServices()
{
yield return DatabaseService;
yield return TranslationService;
yield return ThemeService; // Old service
}
```
**After:**
```csharp
public IEnumerable<IInitializeAsync> 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<INewThemeService>();
// For existing code that needs compatibility - keep using ThemeService
var oldThemeService = Services.GetService<IThemeService>();
```
### 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<IInitializeAsync> 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
-169
View File
@@ -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<INewThemeService>();
```
### 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<INewThemeService, NewThemeService>();
```
## Initialization Order
The service is initialized during app startup:
```csharp
// In WinoApplication.cs
public IEnumerable<IInitializeAsync> 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 `<MicaBackdrop />` 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
@@ -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<AppThemeBase> preDefinedThemes { get; set; } = new List<AppThemeBase>()
{
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),
+2 -1
View File
@@ -23,7 +23,7 @@
<Content Include="AppThemes\Garden.xaml">
<Generator>MSBuild:Compile</Generator>
</Content>
<Content Include="AppThemes\Mica.xaml">
<Content Include="AppThemes\Default.xaml">
<Generator>MSBuild:Compile</Generator>
</Content>
<Content Include="AppThemes\Nighty.xaml">
@@ -78,6 +78,7 @@
<PackageReference Include="CommunityToolkit.Diagnostics" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="EmailValidation" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="WinUIEx" />
</ItemGroup>
@@ -636,6 +636,8 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
}
}
public int IndexOf(object item) => Items.IndexOf(item);
private void RefreshThreadInUI(ThreadMailItemViewModel expander)
{
// Remove thread completely from UI
@@ -646,6 +648,53 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
AddThreadToUI(expander, groupKey);
}
public MailItemContainer GetMailItemContainer(Guid uniqueId)
{
// First, search in standalone mail items (not displayed in threads)
var standaloneMailItem = _sourceItems.FirstOrDefault(item =>
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);
@@ -6,6 +6,25 @@ public class MailItemContainer
{
public MailItemViewModel ItemViewModel { get; set; }
public ThreadMailItemViewModel ThreadViewModel { get; set; }
/// <summary>
/// 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).
/// </summary>
public bool IsItemVisible { get; set; }
/// <summary>
/// Indicates whether the thread expander (if applicable) is currently visible in the UI's Items collection.
/// Only relevant when ThreadViewModel is not null.
/// </summary>
public bool IsThreadVisible { get; set; }
/// <summary>
/// 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).
/// </summary>
public bool CanNavigate => ThreadViewModel != null ? IsThreadVisible : IsItemVisible;
public MailItemContainer(MailItemViewModel itemViewModel, ThreadMailItemViewModel threadViewModel) : this(itemViewModel)
{
@@ -110,4 +110,9 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable
NotifyPropertyChanges();
}
}
/// <summary>
/// Checks if this thread contains an email with the specified unique ID
/// </summary>
public bool HasUniqueId(Guid uniqueId) => _threadEmails.Any(email => email.MailCopy.UniqueId == uniqueId);
}
+1 -1
View File
@@ -17,7 +17,7 @@
<ResourceDictionary Source="Styles/WebViewEditorControl.xaml" />
<styles:WinoExpanderStyle />
<ResourceDictionary Source="/Wino.Core.WinUI/AppThemes/Mica.xaml" />
<ResourceDictionary Source="/Wino.Core.WinUI/AppThemes/Default.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</uwp:WinoApplication.Resources>
@@ -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;
}
/// <summary>
/// Recursively clears all selections except the given mail.
/// </summary>
/// <param name="exceptViewModel">Exceptional mail item to be not unselected.</param>
/// <param name="preserveThreadExpanding">Whether expansion states of thread containers should stay as it is or not.</param>
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;
}
}
}
}
}
}
+3 -2
View File
@@ -12,16 +12,17 @@
<!-- SystemBackdrop will be set by NewThemeService -->
<Grid Background="{ThemeResource WinoApplicationBackgroundColor}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.RowSpan="2" Background="{ThemeResource WinoApplicationBackgroundColor}" />
<TitleBar
x:Name="ShellTitleBar"
Title="{x:Bind StatePersistanceService.CoreWindowTitle, Mode=OneWay}"
Margin="-3,-3,0,0"
MinHeight="48"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
BackRequested="BackButtonClicked"
+2 -4
View File
@@ -127,10 +127,7 @@
</DataTemplate>
<DataTemplate x:Key="DateGroupHeaderTemplate" x:DataType="data:DateGroupHeader">
<ItemContainer
CanUserSelect="UserCannotSelect"
IsEnabled="False"
IsHitTestVisible="False">
<ItemContainer CanUserSelect="UserCannotSelect" IsHitTestVisible="False">
<Grid Padding="12,8" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}">
<TextBlock
FontSize="14"
@@ -197,6 +194,7 @@
<AutoSuggestBox
x:Name="SearchBar"
Width="400"
Margin="2,0,-2,0"
VerticalAlignment="Center"
BorderBrush="Transparent"
+65 -43
View File
@@ -98,10 +98,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
SelectAllCheckbox.Unchecked += SelectAllCheckboxUnchecked;
}
private void SelectionModeToggleChecked(object sender, RoutedEventArgs e)
{
ChangeSelectionMode(ListViewSelectionMode.Multiple);
}
private void SelectionModeToggleChecked(object sender, RoutedEventArgs e) => 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);
/// <summary>
/// Animates the rotation using high-performance Composition APIs
/// </summary>
@@ -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)
@@ -151,8 +151,8 @@
<!-- Backdrop Selection -->
<controls:SettingsCard
Description="Choose the backdrop effect for your app window"
Header="Window Backdrop"
Description="{x:Bind domain:Translator.SettingsWindowBackdrop_Description}"
Header="{x:Bind domain:Translator.SettingsWindowBackdrop_Title}"
IsEnabled="{x:Bind ViewModel.CanSelectElementTheme, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<PathIcon Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z" />
@@ -172,6 +172,13 @@
</controls:SettingsCard.Content>
</controls:SettingsCard>
<TextBlock
x:Name="WindowBackdropSelectionDisabledTextBlock"
Margin="4,0,0,0"
x:Load="{x:Bind ViewModel.CanSelectElementTheme, Mode=OneWay, Converter={StaticResource ReverseBooleanConverter}}"
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
Text="{x:Bind domain:Translator.SettingsWindowBackdrop_Disabled}" />
<!-- Mail spacing. -->
<controls:SettingsExpander Description="{x:Bind domain:Translator.SettingsMailSpacing_Description}" Header="{x:Bind domain:Translator.SettingsMailSpacing_Title}">
<controls:SettingsExpander.HeaderIcon>
+1
View File
@@ -112,6 +112,7 @@
<PackageReference Include="Sentry.Serilog" />
<PackageReference Include="sqlite-net-pcl" />
<PackageReference Include="EmailValidation" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="WinUIEx" />
<PackageReference Include="H.NotifyIcon.WinUI" />
</ItemGroup>