// Copyright (c) 2024 Proton Technologies AG // // This file is part of Proton Mail. // // Proton Mail is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Proton Mail is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Proton Mail. If not, see https://www.gnu.org/licenses/. import InboxCore import InboxDesignSystem import SwiftUI struct OneLineLabelsListView: View { let labels: [LabelUIModel] private let labelsSpacing: CGFloat = DS.Spacing.small private let horizontalPadding: CGFloat = DS.Spacing.compact private let minimalLabelWidth: CGFloat = DS.Spacing.extraLarge private let height: CGFloat = 20 private let font = UIFont.font(textStyle: .caption2, weight: .semibold) var body: some View { GeometryReader { geometry in HStack(spacing: labelsSpacing) { ForEach(labelsFittingSpace(labels: labels, availableWidth: geometry.size.width), id: \.self) { label in switch label { case .regular(let viewModel): Text(viewModel.text) .font(.caption2) .fontWeight(.semibold) .foregroundColor(DS.Color.Global.white) .lineLimit(1) .frame(height: height) .frame(minWidth: minimalLabelWidth) .padding(.horizontal, horizontalPadding) .background(viewModel.color) .clipShape(RoundedRectangle(cornerRadius: DS.Radius.medium)) case .count(let count): Text(Strings.plus(count: count)) .foregroundStyle(DS.Color.Text.weak) .fontWeight(.semibold) .font(.caption) } } } }.frame(height: height) } // MARK: - Private private enum Label: Hashable { case regular(model: LabelUIModel) case count(Int) } @MainActor private func labelsFittingSpace(labels: [LabelUIModel], availableWidth: CGFloat) -> [Label] { let padding = 2 * horizontalPadding var availableWidth: CGFloat = availableWidth var labelsToDisplay: [Label] = [] for label in labels { let labelWidth = calculateLabel(label.text, font: font) + padding let requiredSpace = labelWidth + labelsSpacing if requiredSpace < availableWidth || labelsToDisplay.isEmpty { availableWidth -= requiredSpace labelsToDisplay.append(.regular(model: label)) } else { labelsToDisplay.append(.count(labels.count - labelsToDisplay.count)) break } } return labelsToDisplay } @MainActor private func calculateLabel(_ text: String, font: UIFont) -> CGFloat { let label = UILabel() label.text = text label.font = font label.numberOfLines = 1 label.sizeToFit() return max(label.frame.width, minimalLabelWidth) } } #Preview { VStack { ForEach(OneLineLabelsListViewPreviewDataProvider.labels, id: \.self) { labels in OneLineLabelsListView(labels: labels) } Spacer() }.padding() }