Adopt new timeline item debug info and overhaul the timeline item deb… (#781)

* Adopt new timeline item debug info and overhaul the timeline item debug menu
This commit is contained in:
Stefan Ceriu
2023-04-19 18:07:51 +03:00
committed by GitHub
parent df2784114a
commit 59a4e0ac7c
7 changed files with 117 additions and 46 deletions
@@ -97,7 +97,7 @@ struct RoomScreenViewStateBindings {
/// Information describing the currently displayed alert. /// Information describing the currently displayed alert.
var alertInfo: AlertInfo<RoomScreenErrorType>? var alertInfo: AlertInfo<RoomScreenErrorType>?
var debugInfo: TimelineItemDebugView.DebugInfo? var debugInfo: TimelineItemDebugInfo?
} }
enum RoomScreenErrorType: Hashable { enum RoomScreenErrorType: Hashable {
@@ -319,9 +319,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.bindings.composerFocused = true state.bindings.composerFocused = true
state.composerMode = .reply(id: item.id, displayName: item.sender.displayName ?? item.sender.id) state.composerMode = .reply(id: item.id, displayName: item.sender.displayName ?? item.sender.id)
case .viewSource: case .viewSource:
let debugDescription = timelineController.debugDescription(for: item.id) let debugInfo = timelineController.debugInfo(for: item.id)
MXLog.info(debugDescription) MXLog.info(debugInfo)
state.bindings.debugInfo = .init(title: "Timeline item", content: debugDescription) state.bindings.debugInfo = debugInfo
case .retryDecryption(let sessionID): case .retryDecryption(let sessionID):
Task { Task {
await timelineController.retryDecryption(for: sessionID) await timelineController.retryDecryption(for: sessionID)
@@ -17,41 +17,84 @@
import SwiftUI import SwiftUI
struct TimelineItemDebugView: View { struct TimelineItemDebugView: View {
struct DebugInfo: Identifiable {
let id = UUID()
let title: String
var content: String
}
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
let info: DebugInfo let info: TimelineItemDebugInfo
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ScrollView { ScrollView {
Text(info.content) VStack {
.padding() TimelineItemInfoDisclosureGroup(title: "Model", text: info.model, isInitiallyExpanded: true)
.font(.element.footnote)
.foregroundColor(.element.primaryContent) if let originalJSONInfo = info.originalJSON {
.frame(maxWidth: .infinity, alignment: .leading) TimelineItemInfoDisclosureGroup(title: "Original JSON", text: originalJSONInfo)
}
if let latestEditJSONInfo = info.latestEditJSON {
TimelineItemInfoDisclosureGroup(title: "Latest edit JSON", text: latestEditJSONInfo)
}
}
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationTitle(info.title) .navigationTitle("Timeline item")
.toolbarBackground(.visible, for: .navigationBar)
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) { Button(L10n.actionCancel) {
dismiss() dismiss()
} }
} }
ToolbarItem(placement: .secondaryAction) {
ToolbarItem(placement: .primaryAction) {
Button(L10n.actionCopy) { Button(L10n.actionCopy) {
UIPasteboard.general.string = info.content UIPasteboard.general.string = info.description
} }
} }
} }
} }
} }
// MARK: - Private
private struct TimelineItemInfoDisclosureGroup: View {
@State private var isExpanded: Bool
let title: String
let text: String
init(title: String, text: String, isInitiallyExpanded: Bool = false) {
self.title = title
self.text = text
isExpanded = isInitiallyExpanded
}
var body: some View {
VStack(spacing: 0.0) {
DisclosureGroup(title, isExpanded: $isExpanded) {
disclosureGroupContent
}
.font(.element.subheadline)
.padding()
Divider()
}
}
@ViewBuilder
var disclosureGroupContent: some View {
VStack(alignment: .leading) {
Spacer()
Divider()
Text(text)
.font(.element.caption1.monospaced())
.foregroundColor(.element.primaryContent)
}
.frame(maxWidth: .infinity)
}
}
} }
struct TimelineItemDebugView_Previews: PreviewProvider { struct TimelineItemDebugView_Previews: PreviewProvider {
@@ -71,6 +114,8 @@ struct TimelineItemDebugView_Previews: PreviewProvider {
""" """
static var previews: some View { static var previews: some View {
TimelineItemDebugView(info: .init(title: "Timeline item", content: smallContent)) TimelineItemDebugView(info: .init(model: smallContent,
originalJSON: "{\"Hi\": \"Alice\"}",
latestEditJSON: "{\"Hi\": \"Bob\"}"))
} }
} }
@@ -65,8 +65,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
func redact(_ itemID: String) async { } func redact(_ itemID: String) async { }
func debugDescription(for itemID: String) -> String { func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
"Mock debug description" .init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
} }
func retryDecryption(for sessionID: String) async { } func retryDecryption(for sessionID: String) async { }
@@ -181,21 +181,19 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
// Handle this parallel to the timeline items so we're not forced // Handle this parallel to the timeline items so we're not forced
// to bundle the Rust side objects within them // to bundle the Rust side objects within them
func debugDescription(for itemID: String) -> String { func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
var description = "Unknown item" for timelineItemProxy in timelineProvider.itemsPublisher.value {
timelineProvider.itemsPublisher.value.forEach { timelineItemProxy in
switch timelineItemProxy { switch timelineItemProxy {
case .event(let item): case .event(let item):
if item.id == itemID { if item.id == itemID {
description = item.debugDescription return item.debugInfo
return
} }
default: default:
break continue
} }
} }
return description return .init(model: "Unknown item", originalJSON: nil, latestEditJSON: nil)
} }
func retryDecryption(for sessionID: String) async { func retryDecryption(for sessionID: String) async {
@@ -58,7 +58,7 @@ protocol RoomTimelineControllerProtocol {
func redact(_ itemID: String) async func redact(_ itemID: String) async
func debugDescription(for itemID: String) -> String func debugInfo(for itemID: String) -> TimelineItemDebugInfo
func retryDecryption(for sessionID: String) async func retryDecryption(for sessionID: String) async
} }
@@ -42,7 +42,7 @@ enum TimelineItemDeliveryStatus: Hashable {
} }
/// A light wrapper around event timeline items returned from Rust. /// A light wrapper around event timeline items returned from Rust.
struct EventTimelineItemProxy: CustomDebugStringConvertible { struct EventTimelineItemProxy {
let item: MatrixRustSDK.EventTimelineItem let item: MatrixRustSDK.EventTimelineItem
init(item: MatrixRustSDK.EventTimelineItem) { init(item: MatrixRustSDK.EventTimelineItem) {
@@ -113,22 +113,9 @@ struct EventTimelineItemProxy: CustomDebugStringConvertible {
Date(timeIntervalSince1970: TimeInterval(item.timestamp() / 1000)) Date(timeIntervalSince1970: TimeInterval(item.timestamp() / 1000))
} }
// MARK: - CustomDebugStringConvertible var debugInfo: TimelineItemDebugInfo {
var debugDescription: String {
let debugInfo = item.debugInfo() let debugInfo = item.debugInfo()
return TimelineItemDebugInfo(model: debugInfo.model, originalJSON: debugInfo.originalJson, latestEditJSON: debugInfo.latestEditJson)
var debugDescription = debugInfo.model
if let originalJson = debugInfo.originalJson {
debugDescription += "\n\n\(originalJson)"
}
if let latestEditJson = debugInfo.latestEditJson {
debugDescription += "\n\n\(latestEditJson)"
}
return debugDescription
} }
} }
@@ -142,3 +129,44 @@ extension TimelineItemContentKind {
} }
} }
} }
struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible {
let id = UUID()
let model: String
let originalJSON: String?
let latestEditJSON: String?
init(model: String, originalJSON: String?, latestEditJSON: String?) {
self.model = model
self.originalJSON = Self.prettyJsonFormattedString(from: originalJSON)
self.latestEditJSON = Self.prettyJsonFormattedString(from: latestEditJSON)
}
var description: String {
var description = model
if let originalJSON {
description += "\n\n\(originalJSON)"
}
if let latestEditJSON {
description += "\n\n\(latestEditJSON)"
}
return description
}
// MARK: - Private
private static func prettyJsonFormattedString(from string: String?) -> String? {
guard let string,
let data = string.data(using: .utf8),
let jsonDictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers),
let jsonData = try? JSONSerialization.data(withJSONObject: jsonDictionary, options: [.prettyPrinted]) else {
return nil
}
return String(data: jsonData, encoding: .utf8)
}
}