aktueller Stand

This commit is contained in:
Meik
2026-01-28 12:08:39 +01:00
parent 1283750829
commit ee1f54675e
104 changed files with 6797 additions and 1867 deletions

View File

@@ -21,6 +21,7 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
@@ -32,9 +33,12 @@ namespace FasdDesktopUi.Pages.SearchPage
{
public partial class SearchPageView : Window, ISearchUiProvider
{
private static SearchPageView _instance = null;
private const int WM_NCHITTEST = 0x0084;
private const int HTTRANSPARENT = -1;
private static SearchPageView _instance = null;
private const int WM_NCHITTEST = 0x0084;
private const int HTTRANSPARENT = -1;
private readonly HashSet<TileScope> _ticketOverviewNotificationScopesPrimed = new HashSet<TileScope>();
private bool _ticketOverviewFirstEventHandled;
private readonly HashSet<TileScope> _ticketOverviewInitWasEmptyScopes = new HashSet<TileScope>();
public static SearchPageView Instance
{
get
@@ -62,72 +66,155 @@ namespace FasdDesktopUi.Pages.SearchPage
public SupportCaseSearchService SearchService { get; } = new SupportCaseSearchService(new RelationService());
private SearchPageView()
{
try
{
InitializeComponent();
Visibility = Visibility.Visible;
_instance = this;
// FilterToggleCheckBox-Events registrieren
FilterCheckbox.Checked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, true);
FilterCheckbox.Unchecked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, false);
Loaded += (s, e) =>
{
Hide();
SearchBarUc.ActivateManualSearch();
};
AddCustomEventHandlers();
UiSettingsChanged(null, null);
if (TicketOverviewUpdateService.Instance != null)
{
TicketOverviewUpdateService.Instance.Start();
TicketOverviewUpdateService.Instance.OverviewCountsChanged += TicketOverviewUpdateService_OverviewCountsChanged;
}
}
catch (Exception E)
{
LogException(E);
}
private SearchPageView()
{
try
{
InitializeComponent();
Visibility = Visibility.Visible;
_instance = this;
GetPrimaryScreenSize();
// FilterToggleCheckBox-Events registrieren
FilterCheckbox.Checked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, true);
FilterCheckbox.Unchecked += (s, e) => FilterToggleCheckedChanged?.Invoke(this, false);
Loaded += (s, e) =>
{
Hide();
SearchBarUc.ActivateManualSearch();
ScheduleSearchResultMaxHeightUpdate();
};
SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
SearchBarUc.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
BodyStack_TicketOverview.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
SearchHistoryBorder.SizeChanged += (s, e) => ScheduleSearchResultMaxHeightUpdate();
AddCustomEventHandlers();
if (TicketOverviewUpdateService.Instance != null)
{
TicketOverviewUpdateService.Instance.OverviewCountsChanged += TicketOverviewUpdateService_OverviewCountsChanged;
}
UiSettingsChanged(null, null);
}
catch (Exception E)
{
LogException(E);
}
}
private void SetSearchResultVisibility(bool isVisible)
{
SearchResultBorder.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
}
public void GetPrimaryScreenSize()
{
UpdateSearchResultMaxHeight();
}
private void ScheduleSearchResultMaxHeightUpdate()
{
if (Dispatcher == null || Dispatcher.HasShutdownStarted)
return;
Dispatcher.BeginInvoke((Action)UpdateSearchResultMaxHeight, DispatcherPriority.Loaded);
}
private void UpdateSearchResultMaxHeight()
{
if (SearchResultBorder == null)
return;
var workAreaHeight = ActualHeight > 0 ? ActualHeight : SystemParameters.WorkArea.Height;
var reservedHeight = 0.0;
reservedHeight += GetVisibleHeightWithMargin(SearchBarUc);
reservedHeight += GetVisibleHeightWithMargin(BodyStack_TicketOverview);
reservedHeight += GetVisibleHeightWithMargin(SearchHistoryBorder);
if (MainBorder != null)
{
reservedHeight += MainBorder.Padding.Top + MainBorder.Padding.Bottom;
}
var searchResultMargin = SearchResultBorder.Margin;
var availableHeight = workAreaHeight - reservedHeight - searchResultMargin.Top - searchResultMargin.Bottom;
availableHeight = Math.Max(0, availableHeight);
SearchResultBorder.MaxHeight = availableHeight;
}
private static double GetVisibleHeightWithMargin(FrameworkElement element)
{
if (element == null || element.Visibility != Visibility.Visible)
return 0;
var margin = element.Margin;
return element.ActualHeight + margin.Top + margin.Bottom;
}
private void SetSearchResultVisibility(bool isVisible)
{
SearchResultBorder.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed;
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
ScheduleSearchResultMaxHeightUpdate();
}
public void SetSearchHistoryVisibility(bool isVisible)
{
SearchHistoryBorder.Visibility = isVisible && !SearchHistory.IsEmpty() ? Visibility.Visible : Visibility.Collapsed;
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
ScheduleSearchResultMaxHeightUpdate();
}
public void SetSearchHistoryVisibility(bool isVisible)
{
SearchHistoryBorder.Visibility = isVisible && !SearchHistory.IsEmpty() ? Visibility.Visible : Visibility.Collapsed;
BodyStack_SearchResults.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed;
}
private bool CheckTicketOverviewAvailability()
{
return cFasdCockpitConfig.Instance?.Global?.TicketConfiguration?.ShowOverview == true;
}
private void UpdateTicketOverviewAvailability()
{
var enabled = CheckTicketOverviewAvailability();
var service = TicketOverviewUpdateService.Instance;
service?.UpdateAvailability(enabled);
if (enabled)
_ = service?.FetchAsync();
if (!enabled)
{
if (Dispatcher.CheckAccess())
{
ApplyTicketOverviewDisabledState();
}
else
{
Dispatcher.Invoke(ApplyTicketOverviewDisabledState);
}
}
}
private void ApplyTicketOverviewDisabledState()
{
_renderTicketOverviewUserNames = false;
_ticketOverviewNotificationScopesPrimed.Clear();
_ticketOverviewFirstEventHandled = false;
_ticketOverviewInitWasEmptyScopes.Clear();
SetTicketOverviewVisibility(false);
(Application.Current as App)?.ClearTicketOverviewTrayNotification();
}
private bool CheckTicketOverviewAvailability()
{
if (cFasdCockpitCommunicationBase.Instance.IsDemo())
return true;
return false;
}
private void SetTicketOverviewVisibility(bool isVisible)
{
var b = isVisible;
if (!CheckTicketOverviewAvailability())
b = false;
private void SetTicketOverviewVisibility(bool isVisible)
{
var b = isVisible;
if (!CheckTicketOverviewAvailability())
b = false;
BodyStack_TicketOverview.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
TicketOverviewBorder.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
FilterCheckbox.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
RoleLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
OwnTicketsLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
TicketOverviewLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
}
RoleLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
OwnTicketsLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
TicketOverviewLabel.Visibility = b ? Visibility.Visible : Visibility.Collapsed;
ScheduleSearchResultMaxHeightUpdate();
}
public void ShowLoadingTextItem(string itemText)
{
@@ -135,10 +222,10 @@ namespace FasdDesktopUi.Pages.SearchPage
ResultMenu.ShowLoadingTextItem(itemText);
}
public void ShowTicketOverviewPane()
{
Dispatcher.Invoke(() =>
{
public void ShowTicketOverviewPane()
{
Dispatcher.Invoke(() =>
{
bool overviewAlreadyVisible = TicketOverviewBorder.Visibility == Visibility.Visible;
SetTicketOverviewVisibility(true);
@@ -151,10 +238,28 @@ namespace FasdDesktopUi.Pages.SearchPage
}
TicketOverviewUc?.RefreshHighlightState(IsFilterChecked);
var app = Application.Current as FasdDesktopUi.App;
app?.ClearTicketOverviewTrayNotification();
});
}
var app = Application.Current as FasdDesktopUi.App;
app?.ClearTicketOverviewTrayNotification();
});
}
public void ShowTicketOverviewPaneForScope(TileScope? scope)
{
if (scope.HasValue)
{
Dispatcher.Invoke(() =>
{
if (TicketOverviewBorder.Visibility == Visibility.Visible)
return;
var useRoleScope = scope.Value == TileScope.Role;
if (FilterCheckbox != null && FilterCheckbox.IsChecked != useRoleScope)
FilterCheckbox.IsChecked = useRoleScope;
});
}
ShowTicketOverviewPane();
}
internal void CloseTicketOverviewResults()
{
@@ -192,7 +297,7 @@ namespace FasdDesktopUi.Pages.SearchPage
enumFasdInformationClass GetInformationClass(cF4sdApiSearchResultRelation relation) => cF4sdIdentityEntry.GetFromSearchResult(relation.Type);
cMenuDataBase GetMenuData(cF4sdApiSearchResultRelation relation, IRelationService trelationService)
cMenuDataBase GetMenuData(cF4sdApiSearchResultRelation relation, IRelationService subRelationService)
{
try
{
@@ -837,37 +942,178 @@ namespace FasdDesktopUi.Pages.SearchPage
}
}
private void TicketOverviewUpdateService_OverviewCountsChanged(object sender, TicketOverviewCountsChangedEventArgs e)
{
try
{
ApplyLatestCounts();
var positiveChanges = e.Changes?.Where(change => change.Delta > 0).ToList();
TicketOverviewUc?.SetHighlights(positiveChanges, IsFilterChecked);
var app = Application.Current as FasdDesktopUi.App;
if (positiveChanges == null || positiveChanges.Count == 0)
{
app?.ClearTicketOverviewTrayNotification();
return;
}
var message = BuildNotificationMessage(positiveChanges);
if (string.IsNullOrWhiteSpace(message))
{
app?.ClearTicketOverviewTrayNotification();
return;
}
app?.ShowTicketOverviewTrayNotification(message);
ShowTicketOverviewNotification(message);
}
catch (Exception ex)
{
LogException(ex);
}
}
private void TicketOverviewUpdateService_OverviewCountsChanged(object sender, TicketOverviewCountsChangedEventArgs e)
{
try
{
ApplyLatestCounts();
var positiveChanges = e.Changes?.Where(change => change.Delta > 0).ToList();
var app = Application.Current as FasdDesktopUi.App;
var service = TicketOverviewUpdateService.Instance;
if (!_ticketOverviewFirstEventHandled)
{
_ticketOverviewFirstEventHandled = true;
foreach (var scope in GetTicketOverviewEventScopes(e))
{
PrimeTicketOverviewScope(scope);
TrackEmptyInitScope(scope, e?.CurrentCounts);
}
app?.ClearTicketOverviewTrayNotification();
return;
}
if (e.InitializedScope.HasValue)
{
PrimeTicketOverviewScope(e.InitializedScope.Value);
TrackEmptyInitScope(e.InitializedScope.Value, e?.CurrentCounts);
app?.ClearTicketOverviewTrayNotification();
return;
}
if (positiveChanges == null || positiveChanges.Count == 0)
{
app?.ClearTicketOverviewTrayNotification();
return;
}
if (service != null && !service.AreAllScopesInitialized)
{
app?.ClearTicketOverviewTrayNotification();
return;
}
var filteredChanges = FilterChangesForPrimedScopes(positiveChanges);
if (filteredChanges.Count == 0)
{
app?.ClearTicketOverviewTrayNotification();
return;
}
TicketOverviewUc?.SetHighlights(filteredChanges, IsFilterChecked);
var pendingScope = ResolveSingleScope(filteredChanges);
app?.SetTicketOverviewNotificationScope(pendingScope);
var message = BuildNotificationMessage(filteredChanges);
if (string.IsNullOrWhiteSpace(message))
{
app?.ClearTicketOverviewTrayNotification();
return;
}
app?.ShowTicketOverviewTrayNotification(message);
ShowTicketOverviewNotification(message);
}
catch (Exception ex)
{
LogException(ex);
}
}
private void PrimeTicketOverviewScope(TileScope scope)
{
if (_ticketOverviewNotificationScopesPrimed.Add(scope))
{
TicketOverviewUc?.ClearHighlightsForScope(scope);
}
}
private IReadOnlyList<TileCountChange> FilterChangesForPrimedScopes(IReadOnlyList<TileCountChange> changes)
{
if (changes == null || changes.Count == 0)
return Array.Empty<TileCountChange>();
var filteredChanges = new List<TileCountChange>(changes.Count);
var unprimedScopes = new HashSet<TileScope>();
var silentInitScopes = new HashSet<TileScope>();
foreach (var change in changes)
{
if (_ticketOverviewInitWasEmptyScopes.Contains(change.Scope))
{
silentInitScopes.Add(change.Scope);
continue;
}
if (_ticketOverviewNotificationScopesPrimed.Contains(change.Scope))
{
filteredChanges.Add(change);
}
else
{
unprimedScopes.Add(change.Scope);
}
}
foreach (var scope in unprimedScopes)
{
PrimeTicketOverviewScope(scope);
}
if (silentInitScopes.Count > 0)
{
foreach (var scope in silentInitScopes)
{
_ticketOverviewInitWasEmptyScopes.Remove(scope);
PrimeTicketOverviewScope(scope);
}
}
return filteredChanges;
}
private void TrackEmptyInitScope(TileScope scope, IReadOnlyDictionary<string, TileCounts> counts)
{
if (IsScopeCountsEmpty(scope, counts))
{
_ticketOverviewInitWasEmptyScopes.Add(scope);
}
}
private static bool IsScopeCountsEmpty(TileScope scope, IReadOnlyDictionary<string, TileCounts> counts)
{
if (counts == null || counts.Count == 0)
return true;
foreach (var kvp in counts)
{
var value = scope == TileScope.Role ? kvp.Value.Role : kvp.Value.Personal;
if (value != 0)
return false;
}
return true;
}
private static IEnumerable<TileScope> GetTicketOverviewEventScopes(TicketOverviewCountsChangedEventArgs e)
{
if (e == null)
return Enumerable.Empty<TileScope>();
if (e.InitializedScope.HasValue)
return new[] { e.InitializedScope.Value };
if (e.Changes == null || e.Changes.Count == 0)
return Enumerable.Empty<TileScope>();
return e.Changes.Select(change => change.Scope).Distinct();
}
private static TileScope? ResolveSingleScope(IReadOnlyList<TileCountChange> changes)
{
if (changes == null || changes.Count == 0)
return null;
var scope = changes[0].Scope;
for (int i = 1; i < changes.Count; i++)
{
if (changes[i].Scope != scope)
return null;
}
return scope;
}
private void ApplyLatestCounts()
{
@@ -1082,11 +1328,12 @@ namespace FasdDesktopUi.Pages.SearchPage
SetPendingInformationClasses(new HashSet<enumFasdInformationClass> { enumFasdInformationClass.Ticket });
var relations = await LoadRelationsForTileAsync(e.Key, e.UseRoleScope, Math.Max(0, e.Count));
Debug.WriteLine($"[TicketOverview] Relations loaded: {relations?.Count ?? 0}");
var firstRelation = relations.FirstOrDefault();
string displayText = header;
if (firstRelation != null)
var relations = await LoadRelationsForTileAsync(e.Key, e.UseRoleScope, Math.Max(0, e.Count));
Debug.WriteLine($"[TicketOverview] Relations loaded: {relations?.Count ?? 0}");
var ticketOverviewRelationService = new RelationService();
var firstRelation = relations.FirstOrDefault();
string displayText = header;
if (firstRelation != null)
{
string firstSummary = null;
if (firstRelation.Infos != null && firstRelation.Infos.TryGetValue("Summary", out var summaryValue))
@@ -1130,16 +1377,16 @@ namespace FasdDesktopUi.Pages.SearchPage
{
trailingUser = userDisplayName;
}
return (cMenuDataBase)new cMenuDataSearchRelation(r)
{
MenuText = r.DisplayName,
TrailingText = trailingUser,
UiAction = new cUiProcessSearchRelationAction(entry, r, null, this)
{
DisplayType = isEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
Description = isEnabled ? string.Empty : disabledReason,
AlternativeDescription = isEnabled ? string.Empty : disabledReason
}
return (cMenuDataBase)new cMenuDataSearchRelation(r)
{
MenuText = r.DisplayName,
TrailingText = trailingUser,
UiAction = new cUiProcessSearchRelationAction(entry, r, ticketOverviewRelationService, this)
{
DisplayType = isEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled,
Description = isEnabled ? string.Empty : disabledReason,
AlternativeDescription = isEnabled ? string.Empty : disabledReason
}
};
}
);
@@ -1381,11 +1628,11 @@ namespace FasdDesktopUi.Pages.SearchPage
}
}
private void UiSettingsChanged(object sender, EventArgs e)
{
try
{
var positionAlignement = cFasdCockpitConfig.Instance.Global.SmallViewAlignment;
private void UiSettingsChanged(object sender, EventArgs e)
{
try
{
var positionAlignement = cFasdCockpitConfig.Instance.Global.SmallViewAlignment;
switch (positionAlignement)
{
@@ -1396,18 +1643,49 @@ namespace FasdDesktopUi.Pages.SearchPage
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Right);
break;
default:
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Left);
break;
}
}
catch (Exception E)
{
LogException(E);
}
Dispatcher.Invoke(() => MainBorder.HorizontalAlignment = HorizontalAlignment.Left);
break;
}
UpdateTicketOverviewAvailability();
}
catch (Exception E)
{
LogException(E);
}
}
public void SetPendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses) => Dispatcher.Invoke(() => ResultMenu.SetPendingInformationClasses(informationClasses));
public void UpdatePendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses) => Dispatcher.Invoke(() => ResultMenu.UpdatePendingInformationClasses(informationClasses));
private void ResultMenu_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollViewer = FindParent<ScrollViewer>((DependencyObject)sender);
if (scrollViewer != null)
{
if (e.Delta > 0)
scrollViewer.LineUp();
else
scrollViewer.LineDown();
e.Handled = true;
}
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject
{
DependencyObject parent = VisualTreeHelper.GetParent(child);
while (parent != null)
{
if (parent is T typed)
return typed;
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
}
}