diff --git a/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift b/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift index 07ee65d0..87a5cd61 100644 --- a/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift +++ b/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift @@ -42,3 +42,51 @@ extension TimeInterval: StyledLocalizableEntity { } } } + +extension UUID { + var flatString: String { + let str = uuidString.replacingOccurrences(of: "-", with: "") + assert(str.count == 32) + return str + } +} + +extension String: StyledLocalizableEntity { + public enum Style { + case quartets + } + + public func localizedDescription(style: Style) -> String { + switch style { + case .quartets: + return matrix(of: 4, each: 4) + } + } + + private func matrix(of word: Int, each: Int, _ columnSeparator: String = " ", _ rowSeparator: String = "\n") -> String { + var groups: [[String]] = [] + var currentGroup: [String] = [] + var currentString: [Character] = [] + var i = 0 + var j = 0 + for ch in self { + currentString.append(ch) + i = (i + 1) % word + if i == 0 { + currentGroup.append(String(currentString)) + currentString = [] + + j = (j + 1) % each + if j == 0 { + groups.append(currentGroup) + currentGroup = [] + } + } + } + return groups + .map { + $0.joined(separator: columnSeparator) + } + .joined(separator: rowSeparator) + } +} diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift index f8f8a306..c416e508 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift @@ -130,7 +130,9 @@ private extension IPView { func row(forRoute route: Route, removeAction: @escaping () -> Void) -> some View { ThemeRemovableItemRow(isEditing: true) { - ThemeCopiableText(value: route.localizedDescription) + ThemeCopiableText(value: route.localizedDescription) { + Text($0) + } } removeAction: { removeAction() } diff --git a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift index e772b5bd..8c9b8392 100644 --- a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift +++ b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift @@ -113,7 +113,9 @@ private extension View { } case .copiableText(let caption, let value, let multiline): - ThemeCopiableText(title: caption, value: value, isMultiLine: multiline) + ThemeCopiableText(title: caption, value: value, isMultiLine: multiline) { + Text($0) + } case .longContent(let title, let content): LongContentLink(title, content: .constant(content)) { diff --git a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift index 2f90f1c0..bf606e4a 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift @@ -377,7 +377,7 @@ struct ThemeImageLabel: View { } } -struct ThemeCopiableText: View { +struct ThemeCopiableText: View where ValueView: View { @EnvironmentObject private var theme: Theme @@ -388,13 +388,15 @@ struct ThemeCopiableText: View { var isMultiLine = true + let valueView: (String) -> ValueView + var body: some View { HStack { if let title { Text(title) Spacer() } - Text(value) + valueView(value) .foregroundStyle(title == nil ? theme.titleColor : theme.valueColor) .themeMultiLine(isMultiLine) if title == nil { diff --git a/Passepartout/Library/Sources/AppUI/Views/UI/StorageSection.swift b/Passepartout/Library/Sources/AppUI/Views/UI/StorageSection.swift index afaba2da..0c252157 100644 --- a/Passepartout/Library/Sources/AppUI/Views/UI/StorageSection.swift +++ b/Passepartout/Library/Sources/AppUI/Views/UI/StorageSection.swift @@ -41,12 +41,14 @@ struct StorageSection: View { debugChanges() return Group { sharingToggle -#if DEBUG ThemeCopiableText( title: Strings.Unlocalized.uuid, - value: profileEditor.id.uuidString + value: profileEditor.id.flatString.localizedDescription(style: .quartets), + valueView: { + Text($0) + .monospaced() + } ) -#endif } .themeSection( header: Strings.Global.storage,