mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-09-19 14:48:01 +02:00
Reenable multilineinfoview
This commit is contained in:
@@ -3,17 +3,6 @@ import UniformTypeIdentifiers
|
|||||||
|
|
||||||
public struct MultilineInfoView<TitleView: View, ItemView: View>: View {
|
public struct MultilineInfoView<TitleView: View, ItemView: View>: View {
|
||||||
|
|
||||||
// public struct Item {
|
|
||||||
// public let text: String
|
|
||||||
// public let action: (Image, () -> Void)?
|
|
||||||
//
|
|
||||||
// public init(text: String, action: (Image, () -> Void)?) {
|
|
||||||
// self.text = text
|
|
||||||
// self.action = action
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
var titleView: TitleView
|
var titleView: TitleView
|
||||||
var items: ItemView
|
var items: ItemView
|
||||||
|
|
||||||
@@ -22,40 +11,20 @@ public struct MultilineInfoView<TitleView: View, ItemView: View>: View {
|
|||||||
self.items = items()
|
self.items = items()
|
||||||
}
|
}
|
||||||
|
|
||||||
// public init(title: LocalizedStringResource, subtitle: LocalizedStringResource, image: Image, items: [String]) where ItemView == Text {
|
public init(title: LocalizedStringResource, subtitle: LocalizedStringResource? = nil, image: Image, items: [String]) where TitleView == FixedTitleView, ItemView == FixedItemsView {
|
||||||
// self.init {
|
self.init {
|
||||||
// HStack {
|
FixedTitleView(title: title, subtitle: subtitle, image: image)
|
||||||
// image
|
} items: {
|
||||||
// .renderingMode(.template)
|
FixedItemsView(items: items)
|
||||||
//// .imageScale(.large)
|
}
|
||||||
// .foregroundColor(primaryTextColor)
|
}
|
||||||
// Text(title)
|
public init(title: LocalizedStringResource, subtitle: LocalizedStringResource? = nil, image: Image, @ContentBuilder items: () -> ItemView) where TitleView == FixedTitleView {
|
||||||
// .font(.headline)
|
self.init {
|
||||||
// .foregroundColor(primaryTextColor)
|
FixedTitleView(title: title, subtitle: subtitle, image: image)
|
||||||
// Spacer()
|
} items: {
|
||||||
// }
|
items()
|
||||||
// } items: {
|
}
|
||||||
// [Text("Hello")]
|
}
|
||||||
// }
|
|
||||||
//
|
|
||||||
//// self.init {
|
|
||||||
//// } items: {
|
|
||||||
//// ForEach(items) { item in
|
|
||||||
//// return HStack {
|
|
||||||
//// Text(item)
|
|
||||||
//// Spacer()
|
|
||||||
//// // if let (image, _) = $0.1 {
|
|
||||||
//// // image
|
|
||||||
//// // .foregroundStyle(.secondary)
|
|
||||||
//// // }
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
@State private var interactionState: InteractionState = .normal
|
|
||||||
@State private var interactionStateIndex: Int?
|
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
@@ -67,74 +36,97 @@ public struct MultilineInfoView<TitleView: View, ItemView: View>: View {
|
|||||||
Divider()
|
Divider()
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.opacity(subview.id == subviews.first?.id ? 1 : 0.5)
|
.opacity(subview.id == subviews.first?.id ? 1 : 0.5)
|
||||||
subview
|
HoveringSubview {
|
||||||
.safeAreaPadding(20)
|
subview
|
||||||
.onHover { hovering in
|
}
|
||||||
withAnimation {
|
|
||||||
// guard item.element.action != nil else { return }
|
|
||||||
interactionState = hovering ? .hovering : .normal
|
|
||||||
// interactionStateIndex = item.offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.gesture(
|
|
||||||
TapGesture()
|
|
||||||
.onEnded {
|
|
||||||
// item.element.action?.1()
|
|
||||||
withAnimation {
|
|
||||||
interactionState = .normal
|
|
||||||
interactionStateIndex = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEach(Array(items.enumerated()), id: \.offset) { item in
|
|
||||||
// Divider()
|
|
||||||
// .ignoresSafeArea()
|
|
||||||
// .opacity(item.offset == 0 ? 1 : 0.75)
|
|
||||||
// items.element
|
|
||||||
// .safeAreaPadding(20)
|
|
||||||
// .onHover { hovering in
|
|
||||||
// withAnimation {
|
|
||||||
// guard item.element.action != nil else { return }
|
|
||||||
// interactionState = hovering ? .hovering : .normal
|
|
||||||
// interactionStateIndex = item.offset
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .gesture(
|
|
||||||
// TapGesture()
|
|
||||||
// .onEnded {
|
|
||||||
// item.element.action?.1()
|
|
||||||
// withAnimation {
|
|
||||||
// interactionState = .normal
|
|
||||||
// interactionStateIndex = nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
._background(interactionState: .normal)
|
._background(interactionState: .normal)
|
||||||
.frame(minWidth: 150, maxWidth: .infinity)
|
.frame(minWidth: 150, maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var primaryTextColor: Color {
|
}
|
||||||
switch interactionState {
|
|
||||||
case .normal, .hovering:
|
struct HoveringSubview<Content: View>: View {
|
||||||
return Color(.textColor)
|
|
||||||
}
|
@State private var multilineAction: MultilineItemActionKey.Box?
|
||||||
|
private let content: Content
|
||||||
|
@State private var interactionState: InteractionState = .normal
|
||||||
|
|
||||||
|
init(@ViewBuilder _ content: () -> Content) {
|
||||||
|
self.multilineAction = nil
|
||||||
|
self.content = content()
|
||||||
}
|
}
|
||||||
|
|
||||||
var secondaryTextColor: Color {
|
var body: some View {
|
||||||
switch interactionState {
|
HStack {
|
||||||
case .normal, .hovering:
|
content
|
||||||
return Color(.secondaryLabelColor)
|
Spacer()
|
||||||
|
if let image = multilineAction?.image {
|
||||||
|
image
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.safeAreaPadding(20)
|
||||||
|
.onHover { hovering in
|
||||||
|
withAnimation {
|
||||||
|
guard multilineAction != nil else { return }
|
||||||
|
interactionState = hovering ? .hovering : .normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gesture(
|
||||||
|
TapGesture()
|
||||||
|
.onEnded {
|
||||||
|
multilineAction?.closure()
|
||||||
|
withAnimation {
|
||||||
|
interactionState = .normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.backgroundStyle(.primary.opacity(interactionState == .hovering ? 0.3 : 0))
|
||||||
|
.onPreferenceChange(MultilineItemActionKey.self) {
|
||||||
|
multilineAction = $0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public struct FixedTitleView: View {
|
||||||
|
let title: LocalizedStringResource
|
||||||
|
let subtitle: LocalizedStringResource?
|
||||||
|
let image: Image
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
HStack {
|
||||||
|
image
|
||||||
|
.renderingMode(.template)
|
||||||
|
.imageScale(.large)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(title)
|
||||||
|
.font(.headline)
|
||||||
|
if let subtitle {
|
||||||
|
Text(subtitle)
|
||||||
|
.font(.subheadline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct FixedItemsView: View {
|
||||||
|
|
||||||
|
let items: [String]
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
ForEach(Array(items.enumerated()), id: \.offset) {
|
||||||
|
Text($0.element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate enum InteractionState {
|
fileprivate enum InteractionState {
|
||||||
case normal, hovering
|
case normal, hovering
|
||||||
}
|
}
|
||||||
@@ -197,23 +189,68 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#Preview {
|
public struct MultilineItemAction: ViewModifier {
|
||||||
// MultilineInfoView(title: "Multiple", image: Image(systemName: "figure.wave"), items: [
|
|
||||||
// MultilineInfoView.Item(text: "hello", action: (Image(systemName: "chevron.forward"), {})),
|
let image: Image?
|
||||||
// MultilineInfoView.Item(text: "World", action: (Image(systemName: "chevron.forward"), {})),
|
let action: () -> Void
|
||||||
// ])
|
|
||||||
// .padding()
|
public init(image: Image?, action: @escaping () -> Void) {
|
||||||
//}
|
self.image = image
|
||||||
//
|
self.action = action
|
||||||
//
|
}
|
||||||
|
|
||||||
|
public func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.preference(key: MultilineItemActionKey.self, value: MultilineItemActionKey.Box(image: image, closure: action))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
public func multilineItemAction(image: Image? = nil, action: @escaping () -> Void) -> some View {
|
||||||
|
modifier(MultilineItemAction(image: image, action: action))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct MultilineItemActionKey : @MainActor PreferenceKey, ~Sendable {
|
||||||
|
|
||||||
|
public struct Box: Equatable {
|
||||||
|
|
||||||
|
let id: UUID = UUID()
|
||||||
|
let image: Image?
|
||||||
|
let closure: () -> Void
|
||||||
|
|
||||||
|
public static func == (lhs: borrowing MultilineItemActionKey.Box, rhs: borrowing MultilineItemActionKey.Box) -> Bool {
|
||||||
|
lhs.id == rhs.id
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias Value = Box?
|
||||||
|
@MainActor public static let defaultValue: Box? = nil
|
||||||
|
|
||||||
|
public static func reduce(value: inout Box?, nextValue: () -> Box?) {
|
||||||
|
value = nextValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
MultilineInfoView {
|
MultilineInfoView {
|
||||||
Text("Hello")
|
Text("Hello")
|
||||||
} items: {
|
} items: {
|
||||||
Text("World")
|
Text("World")
|
||||||
|
.multilineItemAction(image: Image(systemName: "person.wave")) {
|
||||||
|
print("Hello")
|
||||||
|
}
|
||||||
Text("World")
|
Text("World")
|
||||||
Text("World")
|
Text("World")
|
||||||
}
|
}
|
||||||
// MultilineInfoView(title: "One", image: Image(systemName: "figure.wave"), items: ["Hello world."])
|
.padding()
|
||||||
|
}
|
||||||
|
#Preview {
|
||||||
|
MultilineInfoView(title: "One", image: Image(systemName: "figure.wave"), items: ["Hello world.", "Hello world."])
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,42 +46,42 @@ struct CertificateDetailView: View {
|
|||||||
text: URL.certificatePath(for: certificate.id, in: URL.certificatesDirectory),
|
text: URL.certificatePath(for: certificate.id, in: URL.certificatesDirectory),
|
||||||
showRevealInFinder: true
|
showRevealInFinder: true
|
||||||
)
|
)
|
||||||
// if let validityRange = certificate.validityRange {
|
if let validityRange = certificate.validityRange {
|
||||||
// let epoch = Date(timeIntervalSince1970: 0)
|
let epoch = Date(timeIntervalSince1970: 0)
|
||||||
// let end = Date(timeIntervalSince1970: TimeInterval(UInt64.max))
|
let end = Date(timeIntervalSince1970: TimeInterval(UInt64.max))
|
||||||
// switch (validityRange.lowerBound, validityRange.upperBound) {
|
switch (validityRange.lowerBound, validityRange.upperBound) {
|
||||||
// case (epoch, end):
|
case (epoch, end):
|
||||||
// EmptyView()
|
EmptyView()
|
||||||
// case (epoch, let otherEnd):
|
case (epoch, let otherEnd):
|
||||||
// Spacer()
|
Spacer()
|
||||||
// .frame(height: 20)
|
.frame(height: 20)
|
||||||
// MultilineInfoView(title: .certificateDetailValidUntilLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherEnd.formatted()])
|
MultilineInfoView(title: .certificateDetailValidUntilLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherEnd.formatted()])
|
||||||
// case (let otherStart, end):
|
case (let otherStart, end):
|
||||||
// Spacer()
|
Spacer()
|
||||||
// .frame(height: 20)
|
.frame(height: 20)
|
||||||
// MultilineInfoView(title: .certificateDetailValidAfterLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherStart.formatted()])
|
MultilineInfoView(title: .certificateDetailValidAfterLabel, image: Image(systemName: "calendar.badge.clock"), items: [otherStart.formatted()])
|
||||||
// default:
|
default:
|
||||||
// Spacer()
|
Spacer()
|
||||||
// .frame(height: 20)
|
.frame(height: 20)
|
||||||
// MultilineInfoView(title: .certificateDetailValidityRangeLabel, image: Image(systemName: "calendar.badge.clock"), items: [validityRange.formatted()])
|
MultilineInfoView(title: .certificateDetailValidityRangeLabel, image: Image(systemName: "calendar.badge.clock"), items: [validityRange.formatted()])
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// if !certificate.principals.isEmpty {
|
if !certificate.principals.isEmpty {
|
||||||
// Spacer()
|
Spacer()
|
||||||
// .frame(height: 20)
|
.frame(height: 20)
|
||||||
// MultilineInfoView(title: .certificateDetailPrincipalsLabel, image: Image(systemName: "person.2"), items: certificate.principals)
|
MultilineInfoView(title: .certificateDetailPrincipalsLabel, image: Image(systemName: "person.2"), items: certificate.principals)
|
||||||
// }
|
}
|
||||||
// if !certificate.criticalOptions.isEmpty {
|
if !certificate.criticalOptions.isEmpty {
|
||||||
// Spacer()
|
Spacer()
|
||||||
// .frame(height: 20)
|
.frame(height: 20)
|
||||||
// MultilineInfoView(title: .certificateDetailCriticalOptionsLabel, image: Image(systemName: "person.2"), items: certificate.criticalOptions)
|
MultilineInfoView(title: .certificateDetailCriticalOptionsLabel, image: Image(systemName: "person.2"), items: certificate.criticalOptions)
|
||||||
// }
|
}
|
||||||
// if !certificate.extensions.isEmpty {
|
if !certificate.extensions.isEmpty {
|
||||||
// Spacer()
|
Spacer()
|
||||||
// .frame(height: 20)
|
.frame(height: 20)
|
||||||
// MultilineInfoView(title: .certificateDetailExtensionsLabel, image: Image(systemName: "person.2"), items: certificate.extensions)
|
MultilineInfoView(title: .certificateDetailExtensionsLabel, image: Image(systemName: "person.2"), items: certificate.extensions)
|
||||||
// }
|
}
|
||||||
// Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
@@ -43,22 +43,18 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory),
|
text: URL.publicKeyPath(for: secret, in: URL.publicKeyDirectory),
|
||||||
showRevealInFinder: true
|
showRevealInFinder: true
|
||||||
)
|
)
|
||||||
// if !certificates.isEmpty {
|
if !certificates.isEmpty {
|
||||||
// Spacer()
|
Spacer()
|
||||||
// .frame(height: 20)
|
.frame(height: 20)
|
||||||
// MultilineInfoView(
|
MultilineInfoView(title: .secretDetailCertificatePathLabel, image: Image(systemName: "checkmark.seal.text.page")) {
|
||||||
// title: .secretDetailCertificatePathLabel,
|
ForEach(certificates) { certificate in
|
||||||
// image: Image(
|
Text(certificate.name)
|
||||||
// systemName: "checkmark.seal.text.page"
|
.multilineItemAction(image: Image(systemName: "chevron.forward")) {
|
||||||
// ),
|
navigateToCertificate?(certificate)
|
||||||
// items: certificates.map({ certificate in
|
}
|
||||||
// MultilineInfoView.Item(
|
}
|
||||||
// text: certificate.name,
|
}
|
||||||
// action: (Image(systemName: "chevron.forward"), { navigateToCertificate?(certificate) })
|
}
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user