diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a837f15..ccebc16ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Pulse 1.x +## Pulse 1.1.0 + +*May 14, 2022* + +- [iOS, watchOS] Update message details design, display custom metadata – [#81](https://github.com/kean/Pulse/pull/81) +- [iOS] Fix an issue with search toolbar not showing up during searching + ## Pulse 1.0.3 *May 3, 2022* diff --git a/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS (Complication).xcscheme b/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS (Complication).xcscheme index 6b098f190..b69bd06c8 100644 --- a/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS (Complication).xcscheme +++ b/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS (Complication).xcscheme @@ -55,10 +55,8 @@ debugServiceExtension = "internal" allowLocationSimulation = "YES" launchAutomaticallySubstyle = "32"> - + - + - + - - - - - + diff --git a/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS.xcscheme b/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS.xcscheme index 21c04c8e8..9a3f7d31d 100644 --- a/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS.xcscheme +++ b/Pulse.xcodeproj/xcshareddata/xcschemes/Pulse Demo watchOS.xcscheme @@ -54,10 +54,8 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + - + - - - - - + diff --git a/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsView.swift b/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsView.swift index d21fcc8c3..af337dc82 100644 --- a/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsView.swift +++ b/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsView.swift @@ -16,10 +16,13 @@ struct ConsoleMessageDetailsView: View { var body: some View { contents .navigationBarTitle("", displayMode: .inline) - .navigationBarItems(trailing: HStack(spacing: 18) { + .navigationBarItems(trailing: HStack(spacing: 14) { if let badge = viewModel.badge { BadgeView(viewModel: BadgeViewModel(title: badge.title, color: badge.color.opacity(colorScheme == .light ? 0.25 : 0.5))) } + NavigationLink(destination: ConsoleMessageMetadataView(message: viewModel.message)) { + Image(systemName: "info.circle") + } PinButton(viewModel: viewModel.pin, isTextNeeded: false) ShareButton { self.isShowingShareSheet = true @@ -32,10 +35,21 @@ struct ConsoleMessageDetailsView: View { #elseif os(watchOS) var body: some View { ScrollView { - contents - }.toolbar(content: { - PinButton(viewModel: viewModel.pin, isTextNeeded: false) - }) + VStack { + HStack { + PinButton3(viewModel: viewModel.pin) + NavigationLink(destination: ConsoleMessageMetadataView(message: viewModel.message)) { + VStack(spacing: 4) { + Image(systemName: "info.circle") + .foregroundColor(.blue) + Text("Details") + .font(.caption2) + }.frame(height: 42) + } + } + contents + } + } } #elseif os(tvOS) var body: some View { @@ -45,7 +59,6 @@ struct ConsoleMessageDetailsView: View { private var contents: some View { VStack { - tags textView }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } @@ -76,25 +89,40 @@ struct ConsoleMessageDetailsView: View { .background(Color.gray.opacity(0.15)) .cornerRadius(8) } - #else - private var tags: some View { - VStack(alignment: .leading) { - ForEach(viewModel.tags, id: \.title) { tag in - HStack { - Text(tag.title) - .font(.caption) - .foregroundColor(.secondary) - Text(tag.value) - .font(.caption) - .bold() - .foregroundColor(.secondary) - } + #endif +} + +#if DEBUG +@available(iOS 13.0, tvOS 14.0, watchOS 7.0, *) +struct ConsoleMessageDetailsView_Previews: PreviewProvider { + static var previews: some View { + Group { + NavigationView { + ConsoleMessageDetailsView(viewModel: .init(store: LoggerStore.mock, message: makeMockMessage())) } } - .padding(16) - .frame(maxWidth: .infinity, alignment: .leading) - .background(Color.gray.opacity(0.15)) } - #endif } + +func makeMockMessage() -> LoggerMessageEntity { + let entity = LoggerMessageEntity(context: LoggerStore.mock.container.viewContext) + entity.text = "test" + entity.createdAt = Date() + entity.label = "auth" + entity.level = "critical" + entity.session = UUID().uuidString + entity.file = "~/Develop/Pulse/LoggerStore.swift" + entity.filename = "LoggerStore.swift" + entity.function = "createMockMessage()" + entity.line = 12 + + let meta = LoggerMetadataEntity(context: LoggerStore.mock.container.viewContext) + meta.key = "customKey" + meta.value = "customValue" + + entity.metadata = Set([meta]) + return entity +} +#endif + #endif diff --git a/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsViewModel.swift b/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsViewModel.swift index a8f5cc1f9..690faf001 100644 --- a/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsViewModel.swift +++ b/Sources/PulseUI/Features/MessageDetails/ConsoleMessageDetailsViewModel.swift @@ -13,7 +13,7 @@ final class ConsoleMessageDetailsViewModel { let text: String let badge: BadgeViewModel? - private let message: LoggerMessageEntity + let message: LoggerMessageEntity private let store: LoggerStore static let dateFormatter: DateFormatter = { diff --git a/Sources/PulseUI/Features/MessageDetails/ConsoleMessageMetadataView.swift b/Sources/PulseUI/Features/MessageDetails/ConsoleMessageMetadataView.swift new file mode 100644 index 000000000..7d60de13b --- /dev/null +++ b/Sources/PulseUI/Features/MessageDetails/ConsoleMessageMetadataView.swift @@ -0,0 +1,104 @@ +// The MIT License (MIT) +// +// Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean). + +import SwiftUI +import PulseCore + +#if os(iOS) || os(tvOS) || os(watchOS) +@available(iOS 13.0, tvOS 14.0, watchOS 7.0, *) +struct ConsoleMessageMetadataView: View { + let message: LoggerMessageEntity + + @State private var isMetadataRawLinkActive = false + + var body: some View { + contents + .background(linksView) +#if os(iOS) + .navigationBarTitle("Details", displayMode: .inline) +#endif + } + + @ViewBuilder + private var contents: some View { + ScrollView { + #if os(iOS) || os(tvOS) + VStack { + stackContents + }.padding() + #elseif os(watchOS) + VStack(spacing: 16) { + stackContents + } + #endif + } + } + + @ViewBuilder + private var stackContents: some View { + KeyValueSectionView(viewModel: .init(title: "Summary", color: message.tintColor, items: [ + ("Date", dateFormatter.string(from: message.createdAt)), + ("Level", message.level), + ("Label", message.label.nonEmpty) + ])) + KeyValueSectionView(viewModel: .init(title: "Details", color: .secondary, items: [ + ("Session", message.session.nonEmpty), + ("File", message.file.nonEmpty), + ("Filename", message.filename.nonEmpty), + ("Function", message.function.nonEmpty), + ("Line", message.line == 0 ? nil : "\(message.line)"), + ])) + KeyValueSectionView(viewModel: metadataViewModel) + } + + private var metadataViewModel: KeyValueSectionViewModel { + KeyValueSectionViewModel(title: "Metadata", color: .indigo, action: .init(action: { + isMetadataRawLinkActive = true + }, title: "View"), items: metadataItems) + } + + private var metadataItems: [(String, String?)] { + message.metadata.sorted(by: { $0.key < $1.key }).map { ($0.key, $0.value )} + } + + private var linksView: some View { + NavigationLink(destination: NetworkHeadersDetailsView(viewModel: metadataViewModel), isActive: $isMetadataRawLinkActive) { + EmptyView() + }.opacity(0) + } +} + +@available(iOS 13.0, tvOS 14.0, watchOS 7.0, *) +private extension LoggerMessageEntity { + var tintColor: Color { + Color.badgeColor(for: .init(rawValue: level) ?? .debug) + } +} + +private extension String { + var nonEmpty: String? { + isEmpty ? nil : self + } +} + +private let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss.SSS, yyyy-MM-dd" + return formatter +}() + +#if DEBUG +@available(iOS 13.0, tvOS 14.0, watchOS 7.0, *) +struct ConsoleMessageMetadataView_Previews: PreviewProvider { + static var previews: some View { + Group { + NavigationView { + ConsoleMessageMetadataView(message: makeMockMessage()) + } + } + } +} +#endif + +#endif diff --git a/Sources/PulseUI/Views/PinButton.swift b/Sources/PulseUI/Views/PinButton.swift index d691c9566..a9bcd658d 100644 --- a/Sources/PulseUI/Views/PinButton.swift +++ b/Sources/PulseUI/Views/PinButton.swift @@ -34,6 +34,24 @@ struct PinButton2: View { } } +#if os(watchOS) +@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) +struct PinButton3: View { + @ObservedObject var viewModel: PinButtonViewModel + + var body: some View { + Button(action: viewModel.togglePin) { + VStack(spacing: 4) { + Image(systemName: viewModel.isPinned ? "pin.slash" : "pin") + .foregroundColor(.blue) + Text(viewModel.isPinned ? "Remove Pin" : "Pin") + .font(.caption2) + }.frame(height: 42) + } + } +} +#endif + #if os(iOS) @available(iOS 13.0, *) extension UIAction { diff --git a/Sources/PulseUI/Views/SearchBar.swift b/Sources/PulseUI/Views/SearchBar.swift index c41933db2..2afee19f9 100644 --- a/Sources/PulseUI/Views/SearchBar.swift +++ b/Sources/PulseUI/Views/SearchBar.swift @@ -29,11 +29,11 @@ struct SearchBar: UIViewRepresentable { } func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { - + self.onEditingChanged?(true) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { - + self.onEditingChanged?(false) } }