chromium/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_button.swift

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import SwiftUI
import ios_chrome_common_ui_colors_swift

/// The button displaying info about the current inactive tabs.
struct InactiveTabsButton: View {
  private enum Dimensions {
    static let verticalPadding: CGFloat = 10
    static let horizontalPadding: CGFloat = 16
    static let spacing: CGFloat = 3
    static let cornerRadius: CGFloat = 10
  }
  class State: ObservableObject {
    @Published var action: (() -> Void)?
    @Published var daysThreshold: Int?
    @Published var count: Int?
  }
  @ObservedObject var state: State
  @Environment(\.dynamicTypeSize) var typeSize

  var body: some View {
    Button {
      state.action?()
    } label: {
      if typeSize < .accessibility1 {
        regularLayout()
      } else {
        xxlLayout()
      }
    }
    .buttonStyle(InactiveTabsButtonStyle())
    .accessibilityIdentifier(kInactiveTabsButtonAccessibilityIdentifier)
  }

  /// MARK - Layouts

  /// Displays the elements mostly horizontally, for when the size category is small.
  @ViewBuilder
  private func regularLayout() -> some View {
    HStack {
      leadingTextVStack {
        title()
        subtitle()
      }
      Spacer()
      counter()
      disclosure()
    }
  }

  /// Displays the button elements mostly vertically, for when the size category is large.
  @ViewBuilder
  private func xxlLayout() -> some View {
    HStack {
      leadingTextVStack {
        title()
        counter()
        subtitle()
      }
      Spacer()
      disclosure()
    }
  }

  /// MARK - Elements

  /// Displays the button title.
  @ViewBuilder
  private func title() -> some View {
    Text(titleString)
      .foregroundColor(.white)
  }

  /// Displays the button subtitle.
  @ViewBuilder
  private func subtitle() -> some View {
    if let subtitleString {
      Text(subtitleString)
        .font(.footnote)
        .foregroundColor(.textSecondary)
    }
  }

  /// Displays the tabs count.
  ///
  /// If the count is larger than 99, "99+" is displayed.
  /// If the count is not set, this returns nothing.
  @ViewBuilder
  private func counter() -> some View {
    if let counterString {
      Text(counterString)
        .foregroundColor(.textSecondary)
    }
  }

  /// Displays the disclosure indicator.
  @ViewBuilder
  private func disclosure() -> some View {
    Image(systemName: kChevronForwardSymbol)
      .foregroundColor(.textTertiary)
      .fontWeight(.semibold)
  }

  /// VStack displaying its contents from the leading edge. Textual content is also leading aligned
  /// when displayed on multiple lines.
  @ViewBuilder
  private func leadingTextVStack(@ViewBuilder content: () -> some View) -> some View {
    VStack(alignment: .leading, spacing: Dimensions.spacing) {
      content()
    }
    .multilineTextAlignment(.leading)
  }

  /// MARK - Styles

  /// Style for the main button, i.e. the top-level view.
  private struct InactiveTabsButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
      configuration.label
        .padding([.top, .bottom], Dimensions.verticalPadding)
        .padding([.leading, .trailing], Dimensions.horizontalPadding)
        .background(
          configuration.isPressed ? Color(.systemGray4) : Color.groupedSecondaryBackground
        )
        .cornerRadius(Dimensions.cornerRadius)
        .environment(\.colorScheme, .dark)
    }
  }

  /// MARK - Strings

  /// String for the title.
  private var titleString: String {
    L10nUtils.string(messageId: IDS_IOS_INACTIVE_TABS_BUTTON_TITLE)
  }

  /// Optional string for the subtitle.
  private var subtitleString: String? {
    if let daysThreshold = state.daysThreshold {
      return L10nUtils.formatString(
        messageId: IDS_IOS_INACTIVE_TABS_BUTTON_SUBTITLE,
        argument: String(daysThreshold))
    } else {
      return nil
    }
  }

  /// Optional string for the counter.
  private var counterString: String? {
    if let count = state.count {
      return count > 99 ? "99+" : "\(count)"
    } else {
      return nil
    }
  }
}