diff --git a/Anytype/Sources/PresentationLayer/Flows/EditorSetFlow/EditorSetCoordinatorViewModel.swift b/Anytype/Sources/PresentationLayer/Flows/EditorSetFlow/EditorSetCoordinatorViewModel.swift index b729469b27..1380064c5f 100644 --- a/Anytype/Sources/PresentationLayer/Flows/EditorSetFlow/EditorSetCoordinatorViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Flows/EditorSetFlow/EditorSetCoordinatorViewModel.swift @@ -253,10 +253,10 @@ final class EditorSetCoordinatorViewModel: func templateEditingHandler( setting: ObjectCreationSetting, - onSetAsDefaultTempalte: @escaping (String) -> Void, + onSetAsDefaultTemplate: @escaping (String) -> Void, onTemplateSelection: ((ObjectCreationSetting) -> Void)? ) { - legacySetObjectCreationSettingsCoordinator.templateEditingHandler(setting: setting, onSetAsDefaultTempalte: onSetAsDefaultTempalte, onTemplateSelection: onTemplateSelection) + legacySetObjectCreationSettingsCoordinator.templateEditingHandler(setting: setting, onSetAsDefaultTemplate: onSetAsDefaultTemplate, onTemplateSelection: onTemplateSelection) } func onObjectTypeLayoutTap(_ data: LayoutPickerData) { @@ -282,7 +282,7 @@ final class EditorSetCoordinatorViewModel: templatesCoordinator.showTemplatesPicker( data: data, - onSetAsDefaultTempalte: { [weak self] templateId in + onSetAsDefaultTemplate: { [weak self] templateId in self?.setTemplateAsDefault(objectId: objectId, templateId: templateId) } ) diff --git a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/SetObjectCreationSettingsCoordinator.swift b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/SetObjectCreationSettingsCoordinator.swift index eeb9a26800..a33f8806d7 100644 --- a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/SetObjectCreationSettingsCoordinator.swift +++ b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/SetObjectCreationSettingsCoordinator.swift @@ -8,7 +8,7 @@ protocol SetObjectCreationSettingsCoordinatorProtocol: AnyObject, SetObjectCreat func showTemplateEditing( setting: ObjectCreationSetting, onTemplateSelection: (() -> Void)?, - onSetAsDefaultTempalte: @escaping (String) -> Void, + onSetAsDefaultTemplate: @escaping (String) -> Void, completion: (() -> Void)? ) } @@ -32,7 +32,7 @@ final class SetObjectCreationSettingsCoordinator: func showTemplateEditing( setting: ObjectCreationSetting, onTemplateSelection: (() -> Void)?, - onSetAsDefaultTempalte: @escaping (String) -> Void, + onSetAsDefaultTemplate: @escaping (String) -> Void, completion: (() -> Void)? ) { let editorView = EditorPageCoordinatorView( @@ -47,10 +47,13 @@ final class SetObjectCreationSettingsCoordinator: } ) - self.useAsTemplateAction = onSetAsDefaultTempalte + self.useAsTemplateAction = onSetAsDefaultTemplate let editingTemplateViewController = TemplateEditingViewController( editorViewController: UIHostingController(rootView: editorView), + objectId: setting.templateId, + spaceId: setting.spaceId, + output: self, onSettingsTap: { [weak self] in guard let self = self else { return } editorModuleInput?.showSettings(output: self) @@ -112,20 +115,20 @@ final class SetObjectCreationSettingsCoordinator: func templateEditingHandler( setting: ObjectCreationSetting, - onSetAsDefaultTempalte: @escaping (String) -> Void, + onSetAsDefaultTemplate: @escaping (String) -> Void, onTemplateSelection: ((ObjectCreationSetting) -> Void)? ) { showTemplateEditing( setting: setting, onTemplateSelection: { [weak self] in self?.navigationContext.dismissAllPresented(animated: true) { - onSetAsDefaultTempalte(setting.templateId) + onSetAsDefaultTemplate(setting.templateId) onTemplateSelection?(setting) } }, - onSetAsDefaultTempalte: { [weak self] templateId in + onSetAsDefaultTemplate: { [weak self] templateId in self?.navigationContext.dismissTopPresented(animated: true, completion: { - onSetAsDefaultTempalte(templateId) + onSetAsDefaultTemplate(templateId) }) }, completion: nil diff --git a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/TemplatesCoordinator.swift b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/TemplatesCoordinator.swift index 153a747740..7001d5cf8c 100644 --- a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/TemplatesCoordinator.swift +++ b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/TemplatesCoordinator.swift @@ -8,13 +8,13 @@ protocol TemplatesCoordinatorProtocol { @MainActor func showTemplatesPicker( document: some BaseDocumentProtocol, - onSetAsDefaultTempalte: @escaping (String) -> Void + onSetAsDefaultTemplate: @escaping (String) -> Void ) @MainActor func showTemplatesPicker( data: TemplatePickerViewModelData, - onSetAsDefaultTempalte: @escaping (String) -> Void + onSetAsDefaultTemplate: @escaping (String) -> Void ) } @@ -26,14 +26,14 @@ final class TemplatesCoordinator: TemplatesCoordinatorProtocol, ObjectSettingsCo private var toastPresenter: any ToastPresenterProtocol private var editorModuleInputs = [String: any EditorPageModuleInput]() - private var onSetAsDefaultTempalte: ((String) -> Void)? + private var onSetAsDefaultTemplate: ((String) -> Void)? nonisolated init() {} @MainActor func showTemplatesPicker( document: some BaseDocumentProtocol, - onSetAsDefaultTempalte: @escaping (String) -> Void + onSetAsDefaultTemplate: @escaping (String) -> Void ) { let data = TemplatePickerViewModelData( mode: .objectTemplate(objectId: document.objectId), @@ -41,15 +41,15 @@ final class TemplatesCoordinator: TemplatesCoordinatorProtocol, ObjectSettingsCo spaceId: document.spaceId, defaultTemplateId: nil ) - showTemplatesPicker(data: data, onSetAsDefaultTempalte: onSetAsDefaultTempalte) + showTemplatesPicker(data: data, onSetAsDefaultTemplate: onSetAsDefaultTemplate) } @MainActor func showTemplatesPicker( data: TemplatePickerViewModelData, - onSetAsDefaultTempalte: @escaping (String) -> Void + onSetAsDefaultTemplate: @escaping (String) -> Void ) { - self.onSetAsDefaultTempalte = onSetAsDefaultTempalte + self.onSetAsDefaultTemplate = onSetAsDefaultTemplate let picker = TemplatePickerView(viewModel: .init(data: data, output: self)) let hostViewController = UIHostingController(rootView: picker) hostViewController.modalPresentationStyle = .fullScreen @@ -101,7 +101,7 @@ extension TemplatesCoordinator: TemplatePickerViewModuleOutput { func didCreateTemplate(templateId: String) {} func didTapUseTemplateAsDefault(templateId: String) { - onSetAsDefaultTempalte?(templateId) + onSetAsDefaultTemplate?(templateId) } func didUndoRedo() { diff --git a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Editing/TemplateEditingViewController.swift b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Editing/TemplateEditingViewController.swift index c5b4200bbe..0bbcea9207 100644 --- a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Editing/TemplateEditingViewController.swift +++ b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Editing/TemplateEditingViewController.swift @@ -8,6 +8,10 @@ final class TemplateEditingViewController: UIViewController { private let editorViewController: UIViewController private let onSettingsTap: () -> Void private let onSelectTemplateTap: (() -> Void)? + private let objectId: String + private let spaceId: String + private weak var output: (any ObjectSettingsCoordinatorOutput)? + private var settingsMenuView: UIView? private lazy var keyboardHelper = KeyboardEventsListnerHelper( didShowAction: { [weak selectTemplateButton] _ in @@ -35,10 +39,16 @@ final class TemplateEditingViewController: UIViewController { init( editorViewController: UIViewController, + objectId: String, + spaceId: String, + output: any ObjectSettingsCoordinatorOutput, onSettingsTap: @escaping () -> Void, onSelectTemplateTap: (() -> Void)? ) { self.editorViewController = editorViewController + self.objectId = objectId + self.spaceId = spaceId + self.output = output self.onSettingsTap = onSettingsTap self.onSelectTemplateTap = onSelectTemplateTap @@ -54,8 +64,8 @@ final class TemplateEditingViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - setupLayout() setupView() + setupLayout() } @objc @@ -73,6 +83,13 @@ final class TemplateEditingViewController: UIViewController { settingsButton.setImage(UIImage(asset: .X24.more), for: .normal) settingsButton.addTarget(self, action: #selector(didTapSettingButton), for: .touchUpInside) settingsButton.tintColor = .Control.secondary + + if FeatureFlags.newObjectSettings, let output { + let menuContainer = ObjectSettingsMenuContainer(objectId: objectId, spaceId: spaceId, output: output) + let hostingController = UIHostingController(rootView: menuContainer) + hostingController.view.backgroundColor = .clear + self.settingsMenuView = hostingController.view + } } private func setupLayout() { @@ -89,9 +106,16 @@ final class TemplateEditingViewController: UIViewController { $0.centerY.equal(to: fakeNavigationView.centerYAnchor) } - fakeNavigationView.addSubview(settingsButton) { - $0.trailing.equal(to: fakeNavigationView.trailingAnchor, constant: -20) - $0.centerY.equal(to: fakeNavigationView.centerYAnchor) + if FeatureFlags.newObjectSettings, let settingsMenuView { + fakeNavigationView.addSubview(settingsMenuView) { + $0.trailing.equal(to: fakeNavigationView.trailingAnchor, constant: -20) + $0.centerY.equal(to: fakeNavigationView.centerYAnchor) + } + } else { + fakeNavigationView.addSubview(settingsButton) { + $0.trailing.equal(to: fakeNavigationView.trailingAnchor, constant: -20) + $0.centerY.equal(to: fakeNavigationView.centerYAnchor) + } } embedChild(editorViewController, into: view) diff --git a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerView.swift b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerView.swift index 94c1bb74ab..8b81f993e5 100644 --- a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerView.swift +++ b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerView.swift @@ -1,4 +1,5 @@ import SwiftUI +import AnytypeCore struct TemplatePickerView: View { @StateObject var viewModel: TemplatePickerViewModel @@ -92,12 +93,23 @@ struct TemplatePickerView: View { } } + @ViewBuilder private var settingsButton: some View { - Button { - viewModel.onSettingsButtonTap() - } label: { - Image(asset: .X24.more) - .foregroundColor(.Control.secondary) + if FeatureFlags.newObjectSettings { + if viewModel.items.isNotEmpty { + ObjectSettingsMenuContainer( + objectId: viewModel.selectedItem().object.id, + spaceId: viewModel.spaceId, + output: viewModel.output + ) + } + } else { + Button { + viewModel.onSettingsButtonTap() + } label: { + Image(asset: .X24.more) + .foregroundColor(.Control.secondary) + } } } } diff --git a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerViewModel.swift b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerViewModel.swift index ed5634120d..d4421cd9a3 100644 --- a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerViewModel.swift +++ b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Picker/TemplatePickerViewModel.swift @@ -3,7 +3,7 @@ import Services import SwiftUI @MainActor -protocol TemplatePickerViewModuleOutput: AnyObject { +protocol TemplatePickerViewModuleOutput: AnyObject, ObjectSettingsCoordinatorOutput { func onTemplatesChanged(_ templates: [ObjectDetails], completion: ([TemplatePickerData]) -> Void) func onTemplateSettingsTap(_ model: TemplatePickerViewModel.Item) func selectionOptionsView(_ provider: some OptionsItemProvider) -> AnyView @@ -40,7 +40,9 @@ final class TemplatePickerViewModel: ObservableObject, OptionsItemProvider { if case .objectTemplate = data.mode { return true } return false } - + + var spaceId: String { data.spaceId } + private let data: TemplatePickerViewModelData private var didSetupDefaultItem = false private var dismiss: DismissAction? @@ -49,8 +51,8 @@ final class TemplatePickerViewModel: ObservableObject, OptionsItemProvider { private var objectService: any ObjectActionsServiceProtocol @Injected(\.templatesSubscription) private var templatesSubscriptionService: any TemplatesSubscriptionServiceProtocol - - private weak var output: (any TemplatePickerViewModuleOutput)? + + weak var output: (any TemplatePickerViewModuleOutput)? // MARK: - OptionsItemProvider diff --git a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Selection/SetObjectCreationSettingsViewModel.swift b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Selection/SetObjectCreationSettingsViewModel.swift index e9d1dd9195..6b5c409c25 100644 --- a/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Selection/SetObjectCreationSettingsViewModel.swift +++ b/Anytype/Sources/PresentationLayer/ObjectCreationSettings/Views/Selection/SetObjectCreationSettingsViewModel.swift @@ -15,7 +15,7 @@ protocol SetObjectCreationSettingsOutput: AnyObject { func onObjectTypesSearchAction(setDocument: some SetDocumentProtocol, completion: @escaping (ObjectType) -> Void) func templateEditingHandler( setting: ObjectCreationSetting, - onSetAsDefaultTempalte: @escaping (String) -> Void, + onSetAsDefaultTemplate: @escaping (String) -> Void, onTemplateSelection: ((ObjectCreationSetting) -> Void)? ) } @@ -98,7 +98,7 @@ final class SetObjectCreationSettingsViewModel: ObservableObject { AnytypeAnalytics.instance().logTemplateCreate(objectType: .object(typeId: objectTypeId), spaceId: spaceId) output?.templateEditingHandler( setting: ObjectCreationSetting(objectTypeId: objectTypeId, spaceId: spaceId, templateId: templateId), - onSetAsDefaultTempalte: { [weak self] templateId in + onSetAsDefaultTemplate: { [weak self] templateId in self?.setTemplateAsDefault(templateId: templateId) }, onTemplateSelection: data.onTemplateSelection @@ -221,7 +221,7 @@ final class SetObjectCreationSettingsViewModel: ObservableObject { case .editTemplate: output?.templateEditingHandler( setting: ObjectCreationSetting(objectTypeId: objectTypeId, spaceId: spaceId, templateId: templateViewModel.id), - onSetAsDefaultTempalte: { [weak self] templateId in + onSetAsDefaultTemplate: { [weak self] templateId in self?.setTemplateAsDefault(templateId: templateId) }, onTemplateSelection: data.onTemplateSelection diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/EditorPageController.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/EditorPageController.swift index c1884e5482..5b5d473948 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/EditorPageController.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/EditorPageController.swift @@ -54,11 +54,14 @@ final class EditorPageController: UIViewController { private lazy var navigationBarHelper: EditorNavigationBarHelper = EditorNavigationBarHelper( navigationBarView: navigationBarView, - navigationBarBackgroundView: navigationBarBackgroundView, + navigationBarBackgroundView: navigationBarBackgroundView, + objectId: viewModel.document.objectId, + spaceId: viewModel.document.spaceId, + output: viewModel.router, onSettingsBarButtonItemTap: { [weak viewModel] in UISelectionFeedbackGenerator().selectionChanged() viewModel?.showSettings() - }, + }, onSelectAllBarButtonItemTap: { [weak self] allSelected in self?.handleSelectState(allSelected: allSelected) }, @@ -67,7 +70,7 @@ final class EditorPageController: UIViewController { }, onTemplatesButtonTap: { [weak viewModel] in viewModel?.showTemplates() - }, + }, onSyncStatusTap: { [weak viewModel] in UISelectionFeedbackGenerator().selectionChanged() viewModel?.showSyncStatusInfo() diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Utils/EditorNavigationBarHelper/EditorNavigationBarHelper.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Utils/EditorNavigationBarHelper/EditorNavigationBarHelper.swift index b45855246d..eee872d88d 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Utils/EditorNavigationBarHelper/EditorNavigationBarHelper.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Utils/EditorNavigationBarHelper/EditorNavigationBarHelper.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Services import SwiftUI +import AnytypeCore @MainActor final class EditorNavigationBarHelper { @@ -16,6 +17,7 @@ final class EditorNavigationBarHelper { private let selectAllButton: UIButton private let settingsItem: UIEditorBarButtonItem + private let settingsMenuView: UIView? private let syncStatusItem: EditorSyncStatusItem private let webBannerItem: EditorWebBannerItem private let rightContanerForEditing: UIView @@ -38,6 +40,9 @@ final class EditorNavigationBarHelper { init( navigationBarView: EditorNavigationBarView, navigationBarBackgroundView: UIView, + objectId: String, + spaceId: String, + output: (any ObjectSettingsCoordinatorOutput)?, onSettingsBarButtonItemTap: @escaping () -> Void, onSelectAllBarButtonItemTap: @escaping (Bool) -> Void, onDoneBarButtonItemTap: @escaping () -> Void, @@ -48,6 +53,16 @@ final class EditorNavigationBarHelper { self.navigationBarView = navigationBarView self.navigationBarBackgroundView = navigationBarBackgroundView self.settingsItem = UIEditorBarButtonItem(imageAsset: .X24.more, action: onSettingsBarButtonItemTap) + + if FeatureFlags.newObjectSettings { + let menuContainer = ObjectSettingsMenuContainer(objectId: objectId, spaceId: spaceId, output: output) + let hostingController = UIHostingController(rootView: menuContainer) + hostingController.view.backgroundColor = .clear + self.settingsMenuView = hostingController.view + } else { + self.settingsMenuView = nil + } + self.syncStatusItem = EditorSyncStatusItem(onTap: onSyncStatusTap) self.webBannerItem = EditorWebBannerItem(onTap: onWebBannerTap) @@ -94,11 +109,19 @@ final class EditorNavigationBarHelper { ) self.rightContanerForEditing.layoutUsing.stack { - $0.hStack( - spacing: 12, - syncStatusItem, - settingsItem - ) + if FeatureFlags.newObjectSettings, let settingsMenuView { + $0.hStack( + spacing: 12, + syncStatusItem, + settingsMenuView + ) + } else { + $0.hStack( + spacing: 12, + syncStatusItem, + settingsItem + ) + } } navigationBarView.bannerView = webBannerItem diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectAction.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectAction.swift index c6193fd920..a17a3c24d5 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectAction.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectAction.swift @@ -83,4 +83,29 @@ enum ObjectAction: Hashable, Identifiable { return "copyLink" } } + + var menuOrder: Int { + switch self { + case .pin: + return 10 + case .undoRedo: + return 11 + case .linkItself: + return 20 + case .makeAsTemplate: + return 21 + case .templateToggleDefaultState: + return 22 + case .locked: + return 30 + case .copyLink: + return 40 + case .duplicate: + return 41 + case .archive: + return 42 + case .delete: + return 43 + } + } } diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionRow.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionRow.swift index 5dd825f2ea..09ad10de39 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionRow.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionRow.swift @@ -45,7 +45,7 @@ extension ObjectActionRow { } } -private extension ObjectAction { +extension ObjectAction { var title: String { switch self { diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsView.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsView.swift index eca0141cf8..c274baf275 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsView.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsView.swift @@ -29,7 +29,7 @@ struct ObjectActionsView: View { case .linkItself: viewModel.linkItselfAction() case .makeAsTemplate: - try await viewModel.makeAsTempalte() + try await viewModel.makeAsTemplate() case .templateToggleDefaultState: try await viewModel.templateToggleDefaultState() case .delete: diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsViewModel.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsViewModel.swift index 8b9b5b5fa5..59da81a05e 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsViewModel.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectActions/ObjectActionsViewModel.swift @@ -143,7 +143,7 @@ final class ObjectActionsViewModel: ObservableObject { output?.onLinkItselfAction(onSelect: onObjectSelection) } - func makeAsTempalte() async throws { + func makeAsTemplate() async throws { guard let details = document.details else { return } let templateId = try await templatesService.createTemplateFromObject(objectId: details.id) diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettings/Model/ObjectSetting.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettings/Model/ObjectSetting.swift index afcb8e0c8e..f79430398d 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettings/Model/ObjectSetting.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettings/Model/ObjectSetting.swift @@ -26,7 +26,26 @@ extension ObjectSetting { return .object } } - + + var menuOrder: Int { + switch self { + case .relations: + return 0 + case .icon: + return 1 + case .cover: + return 2 + case .description: + return 10 + case .resolveConflict: + return 11 + case .webPublishing: + return 12 + case .history: + return 21 + } + } + var title: String { switch self { case .icon: diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuItem.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuItem.swift new file mode 100644 index 0000000000..0489e6c9da --- /dev/null +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuItem.swift @@ -0,0 +1,24 @@ +import Foundation + +enum ObjectMenuItem: Identifiable { + case setting(ObjectSetting) + case action(ObjectAction) + + var id: String { + switch self { + case .setting(let setting): + return "setting-\(setting.title)" + case .action(let action): + return "action-\(action.id)" + } + } + + var order: Int { + switch self { + case .setting(let setting): + return setting.menuOrder + case .action(let action): + return action.menuOrder + } + } +} diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuSection.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuSection.swift new file mode 100644 index 0000000000..82e61aa63e --- /dev/null +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuSection.swift @@ -0,0 +1,20 @@ +import Foundation + +enum ObjectMenuSectionLayout { + case horizontal + case vertical +} + +struct ObjectMenuConfiguration { + let sections: [ObjectMenuSection] +} + +struct ObjectMenuSection: Identifiable { + let items: [ObjectMenuItem] + let layout: ObjectMenuSectionLayout + let showDividerBefore: Bool + + var id: String { + items.map { $0.id }.joined(separator: "-") + } +} diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuSectionType.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuSectionType.swift new file mode 100644 index 0000000000..db92dc721e --- /dev/null +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/Models/ObjectMenuSectionType.swift @@ -0,0 +1,35 @@ +import Foundation + +enum ObjectMenuSectionType { + case horizontal + case mainSettings + case objectActions + case management + case finalActions +} + +extension ObjectMenuSectionType { + static func section(for setting: ObjectSetting) -> ObjectMenuSectionType { + switch setting { + case .icon, .cover, .relations: + return .horizontal + case .description, .resolveConflict, .webPublishing: + return .mainSettings + case .history: + return .management + } + } + + static func section(for action: ObjectAction) -> ObjectMenuSectionType { + switch action { + case .pin, .undoRedo: + return .mainSettings + case .linkItself, .makeAsTemplate, .templateToggleDefaultState: + return .objectActions + case .locked: + return .management + case .copyLink, .duplicate, .archive, .delete: + return .finalActions + } + } +} diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectMenuBuilder.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectMenuBuilder.swift new file mode 100644 index 0000000000..6292cc4823 --- /dev/null +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectMenuBuilder.swift @@ -0,0 +1,53 @@ +import Foundation + +struct ObjectMenuBuilder { + static func buildMenu(settings: [ObjectSetting], actions: [ObjectAction]) -> ObjectMenuConfiguration { + let allItems = settings.map { (ObjectMenuSectionType.section(for: $0), ObjectMenuItem.setting($0)) } + + actions.map { (ObjectMenuSectionType.section(for: $0), ObjectMenuItem.action($0)) } + let grouped = Dictionary(grouping: allItems, by: { $0.0 }) + + var sections: [ObjectMenuSection] = [] + + if let items = grouped[.horizontal]?.map({ $0.1 }), !items.isEmpty { + sections.append(ObjectMenuSection( + items: items.sorted { $0.order < $1.order }, + layout: .horizontal, + showDividerBefore: false + )) + } + + if let items = grouped[.mainSettings]?.map({ $0.1 }), !items.isEmpty { + sections.append(ObjectMenuSection( + items: items.sorted { $0.order < $1.order }, + layout: .vertical, + showDividerBefore: false + )) + } + + if let items = grouped[.objectActions]?.map({ $0.1 }), !items.isEmpty { + sections.append(ObjectMenuSection( + items: items.sorted { $0.order < $1.order }, + layout: .vertical, + showDividerBefore: true + )) + } + + if let items = grouped[.management]?.map({ $0.1 }), !items.isEmpty { + sections.append(ObjectMenuSection( + items: items.sorted { $0.order < $1.order }, + layout: .vertical, + showDividerBefore: true + )) + } + + if let items = grouped[.finalActions]?.map({ $0.1 }), !items.isEmpty { + sections.append(ObjectMenuSection( + items: items.sorted { $0.order < $1.order }, + layout: .vertical, + showDividerBefore: true + )) + } + + return ObjectMenuConfiguration(sections: sections) + } +} diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuContainer.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuContainer.swift new file mode 100644 index 0000000000..3b7acb8ce7 --- /dev/null +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuContainer.swift @@ -0,0 +1,33 @@ +import SwiftUI +import AnytypeCore + +struct ObjectSettingsMenuContainer: View { + + @StateObject private var model: ObjectSettingsCoordinatorViewModel + + init(objectId: String, spaceId: String, output: (any ObjectSettingsCoordinatorOutput)?) { + self._model = StateObject(wrappedValue: ObjectSettingsCoordinatorViewModel(objectId: objectId, spaceId: spaceId, output: output)) + } + + var body: some View { + ObjectSettingsMenuView(objectId: model.objectId, spaceId: model.spaceId, output: model) + .sheet(item: $model.coverPickerData) { + ObjectCoverPicker(data: $0) + } + .sheet(item: $model.objectIconPickerData) { + ObjectIconPicker(data: $0) + } + .sheet(item: $model.blockObjectSearchData) { + BlockObjectSearchView(data: $0) + } + .sheet(item: $model.relationsListData) { + PropertiesListCoordinatorView(document: $0.document, output: model) + } + .sheet(item: $model.versionHistoryData) { + VersionHistoryCoordinatorView(data: $0, output: model) + } + .sheet(item: $model.publishingData) { + PublishToWebCoordinator(data: $0) + } + } +} diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuView.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuView.swift new file mode 100644 index 0000000000..d921920b18 --- /dev/null +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuView.swift @@ -0,0 +1,95 @@ +import SwiftUI +import AnytypeCore + +struct ObjectSettingsMenuView: View { + + @StateObject private var viewModel: ObjectSettingsMenuViewModel + + init( + objectId: String, + spaceId: String, + output: some ObjectSettingsModelOutput + ) { + let settingsVM = ObjectSettingsViewModel(objectId: objectId, spaceId: spaceId, output: output) + let actionsVM = ObjectActionsViewModel(objectId: objectId, spaceId: spaceId, output: settingsVM) + self._viewModel = StateObject(wrappedValue: ObjectSettingsMenuViewModel(settingsViewModel: settingsVM, actionsViewModel: actionsVM)) + } + + var body: some View { + Menu { + ForEach(viewModel.menuConfig.sections) { section in + if section.showDividerBefore { + Divider() + } + renderSection(section) + } + } label: { + Image(asset: .X24.more) + .foregroundColor(.Text.primary) + } + .task { + await viewModel.startTasks() + } + .anytypeSheet(isPresented: viewModel.showConflictAlert) { + ObjectSettingsResolveConflictAlert { + try await viewModel.onTapResolveConflictApprove() + } + } + .snackbar(toastBarData: viewModel.toastData) + } + + @ViewBuilder + private func renderSection(_ section: ObjectMenuSection) -> some View { + if section.layout == .horizontal { + ControlGroup { + ForEach(section.items) { item in + renderMenuItem(item) + } + } + .controlGroupStyle(.menu) + } else { + ForEach(section.items) { item in + renderMenuItem(item) + } + } + } + + @ViewBuilder + private func renderMenuItem(_ item: ObjectMenuItem) -> some View { + switch item { + case .setting(let setting): + Button { + Task { + await viewModel.handleSetting(setting) + } + } label: { + Label { + Text(setting.title) + } icon: { + Image(asset: setting.imageAsset) + .renderingMode(.template) + .foregroundColor(.Text.primary) + } + } + case .action(let action): + Button(role: viewModel.isDestructiveAction(action) ? .destructive : nil) { + Task { + await viewModel.handleAction(action) + } + } label: { + Label { + Text(action.title) + } icon: { + if viewModel.isDestructiveAction(action) { + Image(asset: action.imageAsset) + } else { + Image(asset: action.imageAsset) + .renderingMode(.template) + .foregroundColor(.Text.primary) + } + } + } + } + } + +} diff --git a/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuViewModel.swift b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuViewModel.swift new file mode 100644 index 0000000000..60799451ec --- /dev/null +++ b/Anytype/Sources/PresentationLayer/TextEditor/EditorPage/Views/Settings/ObjectSettingsMenu/ObjectSettingsMenuViewModel.swift @@ -0,0 +1,117 @@ +import Foundation +import Combine +import SwiftUI + +@MainActor +final class ObjectSettingsMenuViewModel: ObservableObject { + + @Published var menuConfig = ObjectMenuConfiguration(sections: []) + + let settingsViewModel: ObjectSettingsViewModel + let actionsViewModel: ObjectActionsViewModel + private var cancellables = Set() + + var showConflictAlert: Binding { + Binding( + get: { self.settingsViewModel.showConflictAlert }, + set: { self.settingsViewModel.showConflictAlert = $0 } + ) + } + + var toastData: Binding { + Binding( + get: { self.actionsViewModel.toastData }, + set: { self.actionsViewModel.toastData = $0 } + ) + } + + init(settingsViewModel: ObjectSettingsViewModel, actionsViewModel: ObjectActionsViewModel) { + self.settingsViewModel = settingsViewModel + self.actionsViewModel = actionsViewModel + setupSubscriptions() + } + + func startTasks() async { + async let settingsTask: () = settingsViewModel.startDocumentTask() + async let actionsTask: () = actionsViewModel.startSubscriptions() + _ = await (settingsTask, actionsTask) + } + + func onTapResolveConflictApprove() async throws { + try await settingsViewModel.onTapResolveConflictApprove() + } + + private func setupSubscriptions() { + settingsViewModel.$settings + .sink { [weak self] _ in + self?.rebuildMenu() + } + .store(in: &cancellables) + + actionsViewModel.$objectActions + .sink { [weak self] _ in + self?.rebuildMenu() + } + .store(in: &cancellables) + } + + private func rebuildMenu() { + menuConfig = ObjectMenuBuilder.buildMenu( + settings: settingsViewModel.settings, + actions: actionsViewModel.objectActions + ) + } + + func isDestructiveAction(_ action: ObjectAction) -> Bool { + switch action { + case .delete, .archive: + return true + default: + return false + } + } + + func handleSetting(_ setting: ObjectSetting) async { + switch setting { + case .icon: + settingsViewModel.onTapIconPicker() + case .cover: + settingsViewModel.onTapCoverPicker() + case .description: + try? await settingsViewModel.onTapDescription() + case .relations: + settingsViewModel.onTapRelations() + case .history: + settingsViewModel.onTapHistory() + case .resolveConflict: + settingsViewModel.onTapResolveConflict() + case .webPublishing: + settingsViewModel.onTapPublishing() + } + } + + func handleAction(_ action: ObjectAction) async { + switch action { + case .archive: + try? await actionsViewModel.changeArchiveState() + case let .pin(pinned): + try? await actionsViewModel.changePinState(pinned) + case .locked: + try? await actionsViewModel.changeLockState() + case .undoRedo: + actionsViewModel.undoRedoAction() + case .duplicate: + try? await actionsViewModel.duplicateAction() + case .linkItself: + actionsViewModel.linkItselfAction() + case .makeAsTemplate: + try? await actionsViewModel.makeAsTemplate() + case .templateToggleDefaultState: + try? await actionsViewModel.templateToggleDefaultState() + case .delete: + try? await actionsViewModel.deleteAction() + case .copyLink: + try? await actionsViewModel.copyLinkAction() + } + } +} diff --git a/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouter.swift b/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouter.swift index 360012bcc5..71a61feaf3 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouter.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouter.swift @@ -365,7 +365,7 @@ final class EditorRouter: NSObject, EditorRouterProtocol, ObjectSettingsCoordina func showTemplatesPicker() { templatesCoordinator.showTemplatesPicker( document: document, - onSetAsDefaultTempalte: { [weak self] templateId in + onSetAsDefaultTemplate: { [weak self] templateId in self?.didTapUseTemplateAsDefault(templateId: templateId) } ) @@ -440,7 +440,7 @@ extension EditorRouter { setObjectCreationSettingsCoordinator.showTemplateEditing( setting: setting, onTemplateSelection: nil, - onSetAsDefaultTempalte: { [weak self] templateId in + onSetAsDefaultTemplate: { [weak self] templateId in self?.didTapUseTemplateAsDefault(templateId: templateId) }, completion: { [weak self] in diff --git a/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouterProtocol.swift b/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouterProtocol.swift index f955d8983d..7332fa73bf 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouterProtocol.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/Routing/Editor/EditorRouterProtocol.swift @@ -6,7 +6,8 @@ import UIKit @MainActor protocol EditorRouterProtocol: AnyObject, - ObjectHeaderRouterProtocol + ObjectHeaderRouterProtocol, + ObjectSettingsCoordinatorOutput { func showAlert(alertModel: AlertModel) func showObject(objectId: String, forceOpenObject: Bool) diff --git a/Anytype/Sources/PresentationLayer/TextEditor/Set/EditorSetViewModel.swift b/Anytype/Sources/PresentationLayer/TextEditor/Set/EditorSetViewModel.swift index f67af832d2..d200ba9e16 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/Set/EditorSetViewModel.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/Set/EditorSetViewModel.swift @@ -32,6 +32,7 @@ final class EditorSetViewModel: ObservableObject { @MainActor lazy var headerSettingsViewModel = SetHeaderSettingsViewModel( setDocument: setDocument, + output: output as? ObjectSettingsCoordinatorOutput, onViewTap: { [weak self] in self?.showViewPicker() }, onSettingsTap: { [weak self] in self?.showSetSettings() } , onCreateTap: { [weak self] in self?.createObject() }, diff --git a/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsView.swift b/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsView.swift index 190fabd92f..cbba66a56a 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsView.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import AnytypeCore struct SetHeaderSettingsView: View { @@ -86,7 +87,7 @@ struct SetHeaderSettings_Previews: PreviewProvider { static var previews: some View { SetHeaderSettingsView( model: SetHeaderSettingsViewModel( - setDocument: Container.shared.documentsProvider().setDocument(objectId: "", spaceId: ""), + setDocument: Container.shared.documentsProvider().setDocument(objectId: "", spaceId: ""), output: nil, onViewTap: {}, onSettingsTap: {}, onCreateTap:{}, diff --git a/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsViewModel.swift b/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsViewModel.swift index 189a153b7e..545e79d5bb 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsViewModel.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetHeaderSettingsViewModel.swift @@ -7,7 +7,8 @@ class SetHeaderSettingsViewModel: ObservableObject { @Published var isActiveCreateButton = true @Published var isActiveHeader = true @Published var showUnsupportedBanner = false - private let setDocument: any SetDocumentProtocol + let setDocument: any SetDocumentProtocol + let output: (any ObjectSettingsCoordinatorOutput)? private var subscriptions = [AnyCancellable]() let onViewTap: () -> Void @@ -19,12 +20,14 @@ class SetHeaderSettingsViewModel: ObservableObject { init( setDocument: some SetDocumentProtocol, + output: (any ObjectSettingsCoordinatorOutput)?, onViewTap: @escaping () -> Void, onSettingsTap: @escaping () -> Void, onCreateTap: @escaping () -> Void, onSecondaryCreateTap: @escaping () -> Void ) { self.setDocument = setDocument + self.output = output self.onViewTap = onViewTap self.onSettingsTap = onSettingsTap self.onCreateTap = onCreateTap diff --git a/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetMinimizedHeader.swift b/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetMinimizedHeader.swift index 251c15e9a2..ae08b6655c 100644 --- a/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetMinimizedHeader.swift +++ b/Anytype/Sources/PresentationLayer/TextEditor/Set/Views/Header/SetMinimizedHeader.swift @@ -69,7 +69,25 @@ struct SetMinimizedHeader: View { .frame(width: 28, height: 28) } + @ViewBuilder private var settingsButton: some View { + if FeatureFlags.newObjectSettings { + settingsMenuButton + } else { + settingsActionButton + } + } + + private var settingsMenuButton: some View { + ObjectSettingsMenuContainer( + objectId: model.setDocument.objectId, + spaceId: model.setDocument.spaceId, + output: model.headerSettingsViewModel.output + ) + .frame(width: 44, height: 44) + } + + private var settingsActionButton: some View { EditorBarButtonItem( imageAsset: .X24.more, state: EditorBarItemState( diff --git a/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift b/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift index 84f9a522b1..c3dfe31def 100644 --- a/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift +++ b/Modules/AnytypeCore/AnytypeCore/Generated/FeatureFlags+Flags.swift @@ -50,6 +50,10 @@ public extension FeatureFlags { value(for: .channelTypeSwitcher) } + static var newObjectSettings: Bool { + value(for: .newObjectSettings) + } + static var setKanbanView: Bool { value(for: .setKanbanView) } @@ -143,6 +147,7 @@ public extension FeatureFlags { .showAllButtonInWidgets, .turnOffAutomaticWidgetOpening, .channelTypeSwitcher, + .newObjectSettings, .setKanbanView, .fullInlineSetImpl, .dndOnCollectionsAndSets, diff --git a/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift b/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift index 960519b216..7d6a946ca5 100644 --- a/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift +++ b/Modules/AnytypeCore/AnytypeCore/Utils/FeatureFlags/FeatureDescription+Flags.swift @@ -69,6 +69,13 @@ public extension FeatureDescription { type: .feature(author: "vova@anytype.io", releaseVersion: "14"), defaultValue: false ) + + static let newObjectSettings = FeatureDescription( + title: "New Object Settings", + type: .feature(author: "vova@anytype.io", releaseVersion: "14"), + defaultValue: false, + debugValue: true + ) // MARK: - Experemental