mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-08-19 11:40:56 +00:00
176 lines
5.6 KiB
Swift
176 lines
5.6 KiB
Swift
import SwiftUI
|
|
import UniformTypeIdentifiers
|
|
|
|
struct CopyableView: View {
|
|
|
|
var title: LocalizedStringKey
|
|
var image: Image
|
|
var text: String
|
|
|
|
@State private var interactionState: InteractionState = .normal
|
|
@Namespace var namespace
|
|
|
|
var content: some View {
|
|
VStack(alignment: .leading) {
|
|
HStack {
|
|
image
|
|
.renderingMode(.template)
|
|
.imageScale(.large)
|
|
.foregroundColor(primaryTextColor)
|
|
Text(title)
|
|
.font(.headline)
|
|
.foregroundColor(primaryTextColor)
|
|
Spacer()
|
|
if interactionState != .normal {
|
|
hoverIcon
|
|
.bold()
|
|
.textCase(.uppercase)
|
|
.foregroundColor(secondaryTextColor)
|
|
.transition(.opacity)
|
|
}
|
|
|
|
}
|
|
.padding(EdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20))
|
|
Divider()
|
|
Text(text)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.foregroundColor(primaryTextColor)
|
|
.padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20))
|
|
.multilineTextAlignment(.leading)
|
|
.font(.system(.body, design: .monospaced))
|
|
}
|
|
._background(interactionState: interactionState)
|
|
.frame(minWidth: 150, maxWidth: .infinity)
|
|
}
|
|
|
|
var body: some View {
|
|
content
|
|
.onHover { hovering in
|
|
withAnimation {
|
|
interactionState = hovering ? .hovering : .normal
|
|
}
|
|
}
|
|
.onDrag({
|
|
NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier)
|
|
}, preview: {
|
|
content
|
|
._background(interactionState: .dragging)
|
|
})
|
|
.onTapGesture {
|
|
copy()
|
|
withAnimation {
|
|
interactionState = .clicking
|
|
}
|
|
}
|
|
.gesture(
|
|
TapGesture()
|
|
.onEnded {
|
|
withAnimation {
|
|
interactionState = .normal
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
var hoverIcon: Image {
|
|
switch interactionState {
|
|
case .hovering, .dragging:
|
|
return Image(systemName: "document.on.document")
|
|
case .clicking:
|
|
return Image(systemName: "checkmark.circle.fill")
|
|
case .normal:
|
|
fatalError()
|
|
}
|
|
}
|
|
|
|
var primaryTextColor: Color {
|
|
switch interactionState {
|
|
case .normal, .hovering, .dragging:
|
|
return Color(.textColor)
|
|
case .clicking:
|
|
return .white
|
|
}
|
|
}
|
|
|
|
var secondaryTextColor: Color {
|
|
switch interactionState {
|
|
case .normal, .hovering, .dragging:
|
|
return Color(.secondaryLabelColor)
|
|
case .clicking:
|
|
return .white
|
|
}
|
|
}
|
|
|
|
func copy() {
|
|
NSPasteboard.general.declareTypes([.string], owner: nil)
|
|
NSPasteboard.general.setString(text, forType: .string)
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate enum InteractionState {
|
|
case normal, hovering, clicking, dragging
|
|
}
|
|
|
|
extension View {
|
|
|
|
fileprivate func _background(interactionState: InteractionState) -> some View {
|
|
modifier(BackgroundViewModifier(interactionState: interactionState))
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate struct BackgroundViewModifier: ViewModifier {
|
|
|
|
@Environment(\.colorScheme) private var colorScheme
|
|
|
|
let interactionState: InteractionState
|
|
|
|
func body(content: Content) -> some View {
|
|
if interactionState == .dragging {
|
|
content
|
|
.background(backgroundColor(interactionState: interactionState), in: RoundedRectangle(cornerRadius: 15))
|
|
} else {
|
|
if #available(macOS 26.0, *) {
|
|
content
|
|
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
|
|
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
|
|
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
|
|
|
|
} else {
|
|
content
|
|
.background(backgroundColor(interactionState: interactionState))
|
|
.cornerRadius(10)
|
|
}
|
|
}
|
|
}
|
|
|
|
func backgroundColor(interactionState: InteractionState) -> Color {
|
|
switch interactionState {
|
|
case .normal:
|
|
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
|
|
case .hovering, .dragging:
|
|
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
|
|
case .clicking:
|
|
return .accentColor
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
#if DEBUG
|
|
|
|
struct CopyableView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
Group {
|
|
CopyableView(title: "secret_detail_sha256_fingerprint_label", image: Image(systemName: "figure.wave"), text: "Hello world.")
|
|
.padding()
|
|
CopyableView(title: "secret_detail_sha256_fingerprint_label", image: Image(systemName: "figure.wave"), text: "Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. ")
|
|
.padding()
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|