Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Application/App/Sources/Resource/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,23 @@
}
}
},
"common_copy" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Copy"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "복사"
}
}
}
},
"common_delete" : {
"extractionState" : "manual",
"localizations" : {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//
// TagCopyMenuView.swift
// PresentationShared
//
// Created by opfic on 8/9/26.
//

import SwiftUI
import UIKit

struct TagCopyMenuView: UIViewRepresentable {
let tagText: String

func makeCoordinator() -> TagCopyMenuCoordinator {
TagCopyMenuCoordinator()
}

func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
view.isAccessibilityElement = false
context.coordinator.install(on: view)
context.coordinator.tagText = tagText
return view
}

func updateUIView(_ view: UIView, context: Context) {
context.coordinator.tagText = tagText
}
}

final class TagCopyMenuCoordinator: NSObject {
var tagText = ""
private weak var view: UIView?
private var editMenuInteraction: UIEditMenuInteraction?

func install(on view: UIView) {
self.view = view

let editMenuInteraction = UIEditMenuInteraction(delegate: self)
view.addInteraction(editMenuInteraction)
self.editMenuInteraction = editMenuInteraction

let longPressGesture = UILongPressGestureRecognizer(
target: self,
action: #selector(handleLongPressGesture(_:))
)
longPressGesture.allowedTouchTypes = [
NSNumber(value: UITouch.TouchType.direct.rawValue),
NSNumber(value: UITouch.TouchType.indirectPointer.rawValue)
]
longPressGesture.cancelsTouchesInView = false
longPressGesture.delegate = self
view.addGestureRecognizer(longPressGesture)

let secondaryClickGesture = UITapGestureRecognizer(
target: self,
action: #selector(handleSecondaryClickGesture(_:))
)
secondaryClickGesture.allowedTouchTypes = [
NSNumber(value: UITouch.TouchType.indirectPointer.rawValue)
]
secondaryClickGesture.buttonMaskRequired = .secondary
secondaryClickGesture.cancelsTouchesInView = false
secondaryClickGesture.delegate = self
view.addGestureRecognizer(secondaryClickGesture)
}

@objc
private func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
guard gesture.state == .began, let view else { return }

presentEditMenu(at: gesture.location(in: view))
}

@objc
private func handleSecondaryClickGesture(_ gesture: UITapGestureRecognizer) {
guard gesture.state == .ended, let view else { return }

presentEditMenu(at: gesture.location(in: view))
}

private func presentEditMenu(at sourcePoint: CGPoint) {
let configuration = UIEditMenuConfiguration(
identifier: nil,
sourcePoint: sourcePoint
)
editMenuInteraction?.presentEditMenu(with: configuration)
}
}

extension TagCopyMenuCoordinator: UIEditMenuInteractionDelegate {
func editMenuInteraction(
_ interaction: UIEditMenuInteraction,
menuFor configuration: UIEditMenuConfiguration,
suggestedActions: [UIMenuElement]
) -> UIMenu? {
let tagText = tagText
let copyAction = UIAction(title: String(localized: "common_copy")) { _ in
UIPasteboard.general.string = tagText
}
return UIMenu(children: [copyAction])
}

func editMenuInteraction(
_ interaction: UIEditMenuInteraction,
targetRectFor configuration: UIEditMenuConfiguration
) -> CGRect {
view?.bounds ?? .zero
}
}

extension TagCopyMenuCoordinator: UIGestureRecognizerDelegate {
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,31 @@ public struct Tag: View {
@State private var height: CGFloat = 0
private let name: String
private let isEditing: Bool
private let isCopyEnabled: Bool
private var action: (() -> Void)?

public init(_ name: String, isEditing: Bool, action: (() -> Void)? = nil) {
public init(
_ name: String,
isEditing: Bool,
action: (() -> Void)? = nil
) {
self.init(
name,
isEditing: isEditing,
isCopyEnabled: false,
action: action
)
}

init(
_ name: String,
isEditing: Bool,
isCopyEnabled: Bool,
action: (() -> Void)?
) {
self.name = name
self.isEditing = isEditing
self.isCopyEnabled = isCopyEnabled
self.action = action
}

Expand All @@ -30,6 +50,11 @@ public struct Tag: View {
.padding(.vertical, 4)
.padding(.leading, 8)
.padding(.trailing, isEditing ? 0 : 8)
.overlay {
if isCopyEnabled {
TagCopyMenuView(tagText: name)
}
}
.background {
GeometryReader { geo in
Color.clear
Expand Down Expand Up @@ -120,7 +145,11 @@ public struct TagList: View {
@ViewBuilder
private var contentTags: some View {
ForEach(tags, id: \.self) { tagText in
Tag(tagText, isEditing: isEditing) {
Tag(
tagText,
isEditing: isEditing,
isCopyEnabled: true
) {
action?(tagText)
}
}
Expand Down
Loading