Commit b5af561d68
b5af561d68e7535726b79d4ceec0d0a45abf0042
parent: a5073a1fa1
Unsigned
cmc <hello@cleberg.net> · 2026-03-19 00:52 UTC
use native markdown rendering for settings bio
Layout: unified · split
Hutch.xcodeproj/project.pbxproj
+4 −4
| @@ -363,7 +363,7 @@ |
| 363 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
363 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| 364 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
364 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
| 365 | CODE_SIGN_STYLE = Automatic; |
365 | CODE_SIGN_STYLE = Automatic; |
| 366 | CURRENT_PROJECT_VERSION = 1; |
366 | CURRENT_PROJECT_VERSION = 2; |
| 367 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
367 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 368 | ENABLE_PREVIEWS = YES; |
368 | ENABLE_PREVIEWS = YES; |
| 369 | GENERATE_INFOPLIST_FILE = YES; |
369 | GENERATE_INFOPLIST_FILE = YES; |
| @@ -380,7 +380,7 @@ |
| 380 | "$(inherited)", |
380 | "$(inherited)", |
| 381 | "@executable_path/Frameworks", |
381 | "@executable_path/Frameworks", |
| 382 | ); |
382 | ); |
| 383 | MARKETING_VERSION = 1.0; |
383 | MARKETING_VERSION = 1.2; |
| 384 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
384 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 385 | PRODUCT_NAME = "$(TARGET_NAME)"; |
385 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 386 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
386 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -399,7 +399,7 @@ |
| 399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| 400 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
400 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
| 401 | CODE_SIGN_STYLE = Automatic; |
401 | CODE_SIGN_STYLE = Automatic; |
| 402 | CURRENT_PROJECT_VERSION = 1; |
402 | CURRENT_PROJECT_VERSION = 2; |
| 403 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
403 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 404 | ENABLE_PREVIEWS = YES; |
404 | ENABLE_PREVIEWS = YES; |
| 405 | GENERATE_INFOPLIST_FILE = YES; |
405 | GENERATE_INFOPLIST_FILE = YES; |
| @@ -416,7 +416,7 @@ |
| 416 | "$(inherited)", |
416 | "$(inherited)", |
| 417 | "@executable_path/Frameworks", |
417 | "@executable_path/Frameworks", |
| 418 | ); |
418 | ); |
| 419 | MARKETING_VERSION = 1.0; |
419 | MARKETING_VERSION = 1.2; |
| 420 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
420 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 421 | PRODUCT_NAME = "$(TARGET_NAME)"; |
421 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 422 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
422 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
Hutch/Views/Settings/SettingsView.swift
+26 −7
| @@ -1,6 +1,10 @@ |
| 1 | import PhotosUI |
1 | import PhotosUI |
| 2 | import SwiftUI |
2 | import SwiftUI |
| 3 | |
3 | |
| |
4 | private let settingsBioMarkdownOptions = AttributedString.MarkdownParsingOptions( |
| |
5 | interpretedSyntax: .inlineOnlyPreservingWhitespace |
| |
6 | ) |
| |
7 | |
| 4 | struct SettingsView: View { |
8 | struct SettingsView: View { |
| 5 | @Environment(AppState.self) private var appState |
9 | @Environment(AppState.self) private var appState |
| 6 | @Environment(\.colorScheme) private var colorScheme |
10 | @Environment(\.colorScheme) private var colorScheme |
| @@ -163,13 +167,7 @@ struct SettingsView: View { |
| 163 | Text("Bio") |
167 | Text("Bio") |
| 164 | .font(.caption) |
168 | .font(.caption) |
| 165 | .foregroundStyle(.secondary) |
169 | .foregroundStyle(.secondary) |
| 166 | RenderedMarkupContentView( |
170 | SettingsBioView(markdown: bio) |
| 167 | content: .markdown(bio), |
| |
| 168 | readmePath: nil, |
| |
| 169 | colorScheme: colorScheme, |
| |
| 170 | ownerCanonicalName: "", |
| |
| 171 | repositoryName: "" |
| |
| 172 | ) |
| |
| 173 | } |
171 | } |
| 174 | } |
172 | } |
| 175 | |
173 | |
| @@ -424,6 +422,27 @@ struct SettingsView: View { |
| 424 | } |
422 | } |
| 425 | } |
423 | } |
| 426 | |
424 | |
| |
425 | private struct SettingsBioView: View { |
| |
426 | let markdown: String |
| |
427 | |
| |
428 | var body: some View { |
| |
429 | Text(settingsBioAttributedString(markdown)) |
| |
430 | .frame(maxWidth: .infinity, alignment: .leading) |
| |
431 | .tint(.accentColor) |
| |
432 | .textSelection(.enabled) |
| |
433 | } |
| |
434 | } |
| |
435 | |
| |
436 | func settingsBioAttributedString(_ markdown: String) -> AttributedString { |
| |
437 | guard let attributed = try? AttributedString( |
| |
438 | markdown: markdown, |
| |
439 | options: settingsBioMarkdownOptions |
| |
440 | ) else { |
| |
441 | return AttributedString(markdown) |
| |
442 | } |
| |
443 | return attributed |
| |
444 | } |
| |
445 | |
| 427 | // MARK: - Edit Profile Sheet |
446 | // MARK: - Edit Profile Sheet |
| 428 | |
447 | |
| 429 | private struct EditProfileSheet: View { |
448 | private struct EditProfileSheet: View { |
HutchTests/SettingsViewTests.swift
added
+22
| @@ -0,0 +1,22 @@ |
| |
1 | import Foundation |
| |
2 | import Testing |
| |
3 | @testable import Hutch |
| |
4 | |
| |
5 | struct SettingsViewTests { |
| |
6 | |
| |
7 | @Test |
| |
8 | @MainActor |
| |
9 | func settingsBioAttributedStringPreservesInlineMarkdown() { |
| |
10 | let attributed = settingsBioAttributedString("Hello **world** and [link](https://example.com)") |
| |
11 | |
| |
12 | #expect(String(attributed.characters).contains("Hello world and link")) |
| |
13 | } |
| |
14 | |
| |
15 | @Test |
| |
16 | @MainActor |
| |
17 | func settingsBioAttributedStringFallsBackForInvalidMarkdown() { |
| |
18 | let attributed = settingsBioAttributedString("[broken") |
| |
19 | |
| |
20 | #expect(String(attributed.characters) == "[broken") |
| |
21 | } |
| |
22 | } |