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.
var alertInfo: AlertInfo<RoomScreenErrorType>?
var debugInfo: TimelineItemDebugView.DebugInfo?
var debugInfo: TimelineItemDebugInfo?
}
enum RoomScreenErrorType: Hashable {
@@ -319,9 +319,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.bindings.composerFocused = true
state.composerMode = .reply(id: item.id, displayName: item.sender.displayName ?? item.sender.id)
case .viewSource:
let debugDescription = timelineController.debugDescription(for: item.id)
MXLog.info(debugDescription)
state.bindings.debugInfo = .init(title: "Timeline item", content: debugDescription)
let debugInfo = timelineController.debugInfo(for: item.id)
MXLog.info(debugInfo)
state.bindings.debugInfo = debugInfo
case .retryDecryption(let sessionID):
Task {
await timelineController.retryDecryption(for: sessionID)
@@ -17,41 +17,84 @@
import SwiftUI
struct TimelineItemDebugView: View {
struct DebugInfo: Identifiable {
let id = UUID()
let title: String
var content: String
}
@Environment(\.dismiss) private var dismiss
let info: DebugInfo
let info: TimelineItemDebugInfo
var body: some View {
NavigationStack {
ScrollView {
Text(info.content)
.padding()
.font(.element.footnote)
.foregroundColor(.element.primaryContent)
.frame(maxWidth: .infinity, alignment: .leading)
VStack {
TimelineItemInfoDisclosureGroup(title: "Model", text: info.model, isInitiallyExpanded: true)
if let originalJSONInfo = info.originalJSON {
TimelineItemInfoDisclosureGroup(title: "Original JSON", text: originalJSONInfo)
}
if let latestEditJSONInfo = info.latestEditJSON {
TimelineItemInfoDisclosureGroup(title: "Latest edit JSON", text: latestEditJSONInfo)
}
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(info.title)
.navigationTitle("Timeline item")
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
dismiss()
}
}
ToolbarItem(placement: .secondaryAction) {
ToolbarItem(placement: .primaryAction) {
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 {
@@ -71,6 +114,8 @@ struct TimelineItemDebugView_Previews: PreviewProvider {
"""
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 debugDescription(for itemID: String) -> String {
"Mock debug description"
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
.init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
}
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
// to bundle the Rust side objects within them
func debugDescription(for itemID: String) -> String {
var description = "Unknown item"
timelineProvider.itemsPublisher.value.forEach { timelineItemProxy in
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
for timelineItemProxy in timelineProvider.itemsPublisher.value {
switch timelineItemProxy {
case .event(let item):
if item.id == itemID {
description = item.debugDescription
return
return item.debugInfo
}
default:
break
continue
}
}
return description
return .init(model: "Unknown item", originalJSON: nil, latestEditJSON: nil)
}
func retryDecryption(for sessionID: String) async {
@@ -58,7 +58,7 @@ protocol RoomTimelineControllerProtocol {
func redact(_ itemID: String) async
func debugDescription(for itemID: String) -> String
func debugInfo(for itemID: String) -> TimelineItemDebugInfo
func retryDecryption(for sessionID: String) async
}
@@ -42,7 +42,7 @@ enum TimelineItemDeliveryStatus: Hashable {
}
/// A light wrapper around event timeline items returned from Rust.
struct EventTimelineItemProxy: CustomDebugStringConvertible {
struct EventTimelineItemProxy {
let item: MatrixRustSDK.EventTimelineItem
init(item: MatrixRustSDK.EventTimelineItem) {
@@ -113,22 +113,9 @@ struct EventTimelineItemProxy: CustomDebugStringConvertible {
Date(timeIntervalSince1970: TimeInterval(item.timestamp() / 1000))
}
// MARK: - CustomDebugStringConvertible
var debugDescription: String {
var debugInfo: TimelineItemDebugInfo {
let debugInfo = item.debugInfo()
var debugDescription = debugInfo.model
if let originalJson = debugInfo.originalJson {
debugDescription += "\n\n\(originalJson)"
}
if let latestEditJson = debugInfo.latestEditJson {
debugDescription += "\n\n\(latestEditJson)"
}
return debugDescription
return TimelineItemDebugInfo(model: debugInfo.model, originalJSON: debugInfo.originalJson, latestEditJSON: debugInfo.latestEditJson)
}
}
@@ -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)
}
}