mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
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:
@@ -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\"}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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 {
|
||||||
|
|||||||
+1
-1
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user