// ----------------------------------------------------------------------- // // Copyright (c) aliasvault. All rights reserved. // Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. // // ----------------------------------------------------------------------- namespace AliasVault.Client.Main.Utilities; using AliasClientDb.Models; using AliasVault.Client.Main.Models; /// /// Utility for calculating field layout widths dynamically. /// public static class LayoutUtils { /// /// Field keys that should always render at full width regardless of their FieldType. /// Used for fields where pairing with an adjacent half-width field would not match /// the layout users expect (e.g. cardholder name on a credit card). /// private static readonly HashSet AlwaysFullWidthFieldKeys = new(StringComparer.OrdinalIgnoreCase) { FieldKey.CardCardholderName, }; /// /// Pairs of field keys that should render side-by-side at half width when both are /// present in the same field set. Pinning takes precedence over the always-full-width /// rules (so e.g. CVV + PIN, both Hidden, can still pair). If only one of a pair is /// present, normal layout rules apply. /// private static readonly (string A, string B)[] PinnedHalfWidthPairs = { (FieldKey.CardExpiryMonth, FieldKey.CardExpiryYear), (FieldKey.CardCvv, FieldKey.CardPin), }; /// /// Determines which fields should be displayed at full width based on the field list. /// Rules: /// - Pinned half-width pairs (e.g. expiry month/year, CVV/PIN) stay half width when both /// members are present; this takes precedence over type-based rules. /// - Fields that are inherently full width (Password, Hidden, TextArea, URL) stay full width. /// - Field keys in always stay full width. /// - If there's only one remaining half-width-capable field, it becomes full width. /// - If there's an odd number of remaining half-width-capable fields, the last one becomes full width. /// /// The list of fields to analyze. /// A set of field keys that should be displayed at full width. public static HashSet GetFullWidthFields(IReadOnlyList fields) { var fullWidthFields = new HashSet(); if (fields == null || fields.Count == 0) { return fullWidthFields; } // First, identify fields that are always full width by their type var alwaysFullWidthTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { FieldType.Password, FieldType.Hidden, FieldType.TextArea, FieldType.URL, }; // Activate pinned-half-width pairs only when BOTH members are present in this field set var presentKeys = fields .Where(f => !string.IsNullOrEmpty(f.FieldKey)) .Select(f => f.FieldKey!) .ToHashSet(StringComparer.OrdinalIgnoreCase); var activePinnedHalfWidthKeys = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var (a, b) in PinnedHalfWidthPairs) { if (presentKeys.Contains(a) && presentKeys.Contains(b)) { activePinnedHalfWidthKeys.Add(a); activePinnedHalfWidthKeys.Add(b); } } var promotionCandidates = new List(); foreach (var field in fields) { var fieldKey = field.FieldKey ?? string.Empty; // Pinned half-width pair members stay half width regardless of FieldType if (activePinnedHalfWidthKeys.Contains(fieldKey)) { continue; } if (alwaysFullWidthTypes.Contains(field.FieldType) || AlwaysFullWidthFieldKeys.Contains(fieldKey)) { fullWidthFields.Add(GetFieldIdentifier(field)); } else { promotionCandidates.Add(field); } } if (promotionCandidates.Count == 1) { fullWidthFields.Add(GetFieldIdentifier(promotionCandidates[0])); } else if (promotionCandidates.Count > 1 && promotionCandidates.Count % 2 == 1) { fullWidthFields.Add(GetFieldIdentifier(promotionCandidates[^1])); } return fullWidthFields; } /// /// Determines if a specific field should be displayed at full width based on the field list. /// /// The field to check. /// The list of all fields in the section. /// True if the field should be full width, false otherwise. public static bool ShouldBeFullWidth(DisplayField field, IReadOnlyList fields) { var fullWidthFields = GetFullWidthFields(fields); return fullWidthFields.Contains(GetFieldIdentifier(field)); } /// /// Gets a unique identifier for a field (uses FieldKey for system fields, FieldDefinitionId for custom). /// private static string GetFieldIdentifier(DisplayField field) { return !string.IsNullOrEmpty(field.FieldKey) ? field.FieldKey : field.FieldDefinitionId ?? string.Empty; } }