chromium/ios/chrome/browser/ui/popup_menu/overflow_menu/action_customization_model.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 Combine
import SwiftUI

/// Model class for holding and tracking action customization occuring in the
/// overflow menu.
@objcMembers
public class ActionCustomizationModel: NSObject, ObservableObject {
  @Published public private(set) var actionsGroup: OverflowMenuActionGroup

  public var shownActions: [OverflowMenuAction] {
    return actionsGroup.actions.filter(\.shown)
  }

  public var hiddenActions: [OverflowMenuAction] {
    return actionsGroup.actions.filter { !$0.shown }
  }

  private let initialData: [(action: OverflowMenuAction, shown: Bool)]

  public var hasChanged: Bool {
    zip(initialData, actionsGroup.actions).map { initial, current in
      initial.action == current && initial.shown == current.shown
    }.contains { !$0 }
  }

  /// Holds sinks for all the action observation.
  var cancellables: Set<AnyCancellable> = []

  public init(actions: [OverflowMenuAction]) {
    let actionsGroup = OverflowMenuActionGroup(
      groupName: "actions", actions: actions, footer: nil)
    actionsGroup.supportsReordering = true
    self.actionsGroup = actionsGroup

    // Store initial shown states for each action so changes to that can be
    // tracked.
    initialData = actions.map { (action: $0, shown: $0.shown) }

    super.init()

    /// Listen to the action arrays of the two groups changing so that this
    /// object can update when that happens. Making `OverflowMenuActionGroup` a
    /// `struct` would be a better solution to this issue. Then as a reference
    /// type, whenever its members change, it changes and this object can
    /// publish that change. However, `OverflowMenuActionGroup` is used from
    /// Objective-C, so that doesn't work.
    self.actionsGroup.objectWillChange.sink { [weak self] _ in
      self?.objectWillChange.send()
    }.store(in: &cancellables)
    self.actionsGroup.actions.forEach { action in
      action.objectWillChange.sink { [weak self] _ in
        self?.objectWillChange.send()
      }.store(in: &cancellables)
    }
  }
}