From 392b1bd01a0ed4485592ac1ea970fa9a36436024 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 30 Jan 2023 06:25:55 +0000 Subject: [PATCH] Add the ability to set a custom font (#519) * Add the ability to set a custom font * Small fixes * Indent * Add missing localization --------- Co-authored-by: Thomas Ricouard --- .../Tabs/Settings/DisplaySettingsView.swift | 21 +++- .../Localization/ca.lproj/Localizable.strings | 3 + .../Localization/de.lproj/Localizable.strings | 3 + .../Localization/en.lproj/Localizable.strings | 3 + .../Localization/es.lproj/Localizable.strings | 3 + .../Localization/fr.lproj/Localizable.strings | 3 + .../Localization/it.lproj/Localizable.strings | 4 + .../Localization/ja.lproj/Localizable.strings | 4 + .../Localization/ko.lproj/Localizable.strings | 4 + .../Localization/nb.lproj/Localizable.strings | 3 + .../Localization/nl.lproj/Localizable.strings | 3 + .../Localization/pl.lproj/Localizable.strings | 3 + .../pt-BR.lproj/Localizable.strings | 3 + .../Localization/tr.lproj/Localizable.strings | 3 + .../zh-Hans.lproj/Localizable.strings | 4 + .../Sources/DesignSystem/Font.swift | 99 +++++++++---------- .../Sources/DesignSystem/FontPicker.swift | 37 +++++++ .../Sources/DesignSystem/Theme.swift | 15 +++ .../Env/Sources/Env/UserPreferences.swift | 18 ++++ 19 files changed, 184 insertions(+), 52 deletions(-) create mode 100644 Packages/DesignSystem/Sources/DesignSystem/FontPicker.swift diff --git a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift index a65f4813..908745c5 100644 --- a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift @@ -5,11 +5,13 @@ import Status import SwiftUI struct DisplaySettingsView: View { + typealias FontState = Theme.FontState + @Environment(\.colorScheme) private var colorScheme @EnvironmentObject private var theme: Theme @EnvironmentObject private var userPreferences: UserPreferences - - @State private var isThemeSelectorPresented = false + + @State private var isFontSelectorPresented = false var body: some View { Form { @@ -33,6 +35,21 @@ struct DisplaySettingsView: View { .listRowBackground(theme.primaryBackgroundColor) Section("settings.display.section.display") { + Picker("settings.display.font", selection: .init(get: { + userPreferences.chosenFontData != nil ? FontState.custom : FontState.system + }, set: { newValue in + switch newValue { + case .system: + userPreferences.chosenFont = nil + case .custom: + isFontSelectorPresented = true + } + })) { + ForEach(FontState.allCases, id: \.rawValue) { fontState in + Text(fontState.title).tag(fontState) + } + } + .navigationDestination(isPresented: $isFontSelectorPresented, destination: { FontPicker() }) Picker("settings.display.avatar.position", selection: $theme.avatarPosition) { ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in Text(position.description).tag(position) diff --git a/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings index 5a9446b6..b8e4f0f6 100644 --- a/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/ca.lproj/Localizable.strings @@ -128,6 +128,9 @@ "settings.push.duplicate.footer" = "Rebeu les notificacions duplicades? Proveu aquest botó màgic per a solucionar-ho"; "settings.push.duplicate.button.fix" = "🪄 Soluciona-ho"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; // MARK: Tabs "tab.explore" = "Explora"; diff --git a/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings index 5ad9e517..9a1d6958 100644 --- a/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/de.lproj/Localizable.strings @@ -126,6 +126,9 @@ "settings.push.duplicate.footer" = "Bekommst du doppelte Benachrichtigungen? Probier diesen magischen Knopf aus"; "settings.push.duplicate.button.fix" = "🪄 Beheben"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; "enum.expand-media.show" = "Alle zeigen"; "enum.expand-media.hide" = "Alle verstecken"; diff --git a/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings index ba7f29ce..50e5d826 100644 --- a/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/en.lproj/Localizable.strings @@ -57,6 +57,9 @@ "settings.app.icon.navigation-title" = "Icons"; "settings.app.source" = "Source (GitHub link)"; "settings.app.support" = "Support the app"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; "settings.display.avatar.position" = "Avatar position"; "settings.display.avatar.shape" = "Avatar shape"; "settings.display.navigation-title" = "Display Settings"; diff --git a/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings index a2a5fb3f..c911de88 100644 --- a/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/es.lproj/Localizable.strings @@ -126,6 +126,9 @@ "settings.push.duplicate.footer" = "¿Recibes notificaciones por duplicado? Usa este botón mágico para arreglarlo"; "settings.push.duplicate.button.fix" = "🪄 Arréglalo"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; "enum.expand-media.show" = "Siempre"; "enum.expand-media.hide" = "Nunca"; diff --git a/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings index 0df5178b..db39e7d1 100644 --- a/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/fr.lproj/Localizable.strings @@ -129,6 +129,9 @@ "settings.push.duplicate.footer" = "Recevez-vous des notifications en double ? Essayez ce bouton magique pour résoudre le problème"; "settings.push.duplicate.button.fix" = "🪄 Résoudre"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; // MARK: Tabs "tab.explore" = "Explorer"; diff --git a/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings index 9249ef49..65d73fb4 100644 --- a/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/it.lproj/Localizable.strings @@ -128,6 +128,10 @@ "settings.push.duplicate.title" = "Sistema le notifiche duplicate"; "settings.push.duplicate.footer" = "Ricevi notifiche duplicate? Prova questo bottone magico per aggiustarle"; "settings.push.duplicate.button.fix" = "🪄 Aggiusta"; +"settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; "settings.other.autoplay-video" = "Auto Play dei video"; // MARK: Tabs diff --git a/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings index 69a23018..4864a1ec 100644 --- a/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/ja.lproj/Localizable.strings @@ -111,6 +111,10 @@ "settings.push.duplicate.title" = "重複通知修正ツール"; "settings.push.duplicate.footer" = "重複して通知を受け取っていませんか?それを修正するためにこの魔法のボタンを試してみて"; "settings.push.duplicate.button.fix" = "🪄 修正する"; +"settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; "settings.other.autoplay-video" = "動画自動再生"; // MARK: Tabs diff --git a/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings index 37de563b..485535f0 100644 --- a/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/ko.lproj/Localizable.strings @@ -128,6 +128,10 @@ "settings.push.duplicate.title" = "중복 알림 해결사"; "settings.push.duplicate.footer" = "같은 알림이 여러 번 오나요? 여기 있는 버튼을 누르면 마법처럼 해결될 거에요."; "settings.push.duplicate.button.fix" = "🪄 고치기"; +"settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; "settings.other.autoplay-video" = "동영상 자동 재생"; // MARK: Tabs diff --git a/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings index 4e6e4f32..ad378a4e 100644 --- a/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/nb.lproj/Localizable.strings @@ -129,6 +129,9 @@ "settings.push.duplicate.footer" = "Får du dupliserte varsler? Prøv denne magiske knappen for å fikse det."; "settings.push.duplicate.button.fix" = "🪄 Fiks det"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; // MARK: Tabs "tab.explore" = "Utforsk"; diff --git a/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings index 9fdbf485..e2a573c1 100644 --- a/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/nl.lproj/Localizable.strings @@ -126,6 +126,9 @@ "settings.push.duplicate.footer" = "Ontvang je dubbele meldingen? Gebruik deze magische knop om dit probleem te verhelpen"; "settings.push.duplicate.button.fix" = "🪄 Los op"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; // MARK: Tabs "tab.explore" = "Ontdekken"; diff --git a/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings index a03044ee..67b83786 100644 --- a/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/pl.lproj/Localizable.strings @@ -129,6 +129,9 @@ "settings.push.duplicate.footer" = "Otrzymujesz zduplikowane powiadomienia? Spróbuj tego magicznego przycisku, aby to naprawić"; "settings.push.duplicate.button.fix" = "🪄 Napraw to"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; // MARK: Tabs "tab.explore" = "Odkrywaj"; diff --git a/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings index 824a046d..870827a8 100644 --- a/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/pt-BR.lproj/Localizable.strings @@ -129,6 +129,9 @@ "settings.push.duplicate.footer" = "Recebendo notificações duplicadas? Tente este botão mágico para tentar corrigir"; "settings.push.duplicate.button.fix" = "🪄 Corrigir"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; // MARK: Tabs "tab.explore" = "Explorar"; diff --git a/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings index 7efbb5ae..d3bfbe2e 100644 --- a/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/tr.lproj/Localizable.strings @@ -115,6 +115,9 @@ "settings.push.duplicate.footer" = "Receiving duplicate notifications? Try this magic button in order to fix it"; "settings.push.duplicate.button.fix" = "🪄 Fix it"; "settings.other.autoplay-video" = "Auto Play Videos"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; // MARK: Tabs "tab.explore" = "Keşfet"; diff --git a/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings b/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings index e5417479..d06c2604 100644 --- a/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings +++ b/IceCubesApp/Resources/Localization/zh-Hans.lproj/Localizable.strings @@ -129,6 +129,10 @@ "settings.push.duplicate.button.fix" = "🪄 修复"; "settings.other.autoplay-video" = "自动播放视频"; +"settings.display.font" = "Timeline Font"; +"settings.display.font.system" = "System"; +"settings.display.font.custom" = "Custom"; + // MARK: Tabs "tab.explore" = "探索"; "tab.federated" = "跨站"; diff --git a/Packages/DesignSystem/Sources/DesignSystem/Font.swift b/Packages/DesignSystem/Sources/DesignSystem/Font.swift index fd7a67b4..ecd5b361 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Font.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Font.swift @@ -3,71 +3,70 @@ import SwiftUI @MainActor public extension Font { - static func userScaledFontSize(baseSize: CGFloat) -> CGFloat { - UIFontMetrics.default.scaledValue(for: baseSize * UserPreferences.shared.fontSizeScale) + // See https://gist.github.com/zacwest/916d31da5d03405809c4 for iOS values + // Custom values for Mac + private static let title = 28.0 + private static let headline = onMac ? 20.0 : 17.0 + private static let body = onMac ? 19.0 : 17.0 + private static let callout = onMac ? 17.0 : 16.0 + private static let subheadline = onMac ? 16.0 : 15.0 + private static let footnote = onMac ? 15.0 : 13.0 + private static let caption = onMac ? 14.0 : 12.0 + private static let onMac = ProcessInfo.processInfo.isiOSAppOnMac + + private static func customFont(size: CGFloat, relativeTo textStyle: TextStyle) -> Font { + if let chosenFont = UserPreferences.shared.chosenFont { + return .custom(chosenFont.fontName, size: size, relativeTo: textStyle) + } + + return onMac ? .system(size: size) : .system(textStyle) } - + + private static func customUIFont(size: CGFloat) -> UIFont { + if let chosenFont = UserPreferences.shared.chosenFont { + return chosenFont.withSize(size) + } + + return .systemFont(ofSize: size) + } + + private static func userScaledFontSize(baseSize: CGFloat) -> CGFloat { + if onMac { + return UIFontMetrics.default.scaledValue(for: baseSize * UserPreferences.shared.fontSizeScale) + } + + return baseSize + } + static var scaledTitle: Font { - if ProcessInfo.processInfo.isiOSAppOnMac { - return .system(size: userScaledFontSize(baseSize: 28)) - } else { - return .title - } + customFont(size: userScaledFontSize(baseSize: title), relativeTo: .title) } - + static var scaledHeadline: Font { - if ProcessInfo.processInfo.isiOSAppOnMac { - return .system(size: userScaledFontSize(baseSize: 20), weight: .semibold) - } else { - return .headline - } + customFont(size: userScaledFontSize(baseSize: headline), relativeTo: .headline).weight(.semibold) } - + static var scaledBody: Font { - if ProcessInfo.processInfo.isiOSAppOnMac { - return .system(size: userScaledFontSize(baseSize: 19)) - } else { - return .body - } + customFont(size: userScaledFontSize(baseSize: body), relativeTo: .body) } - + static var scaledBodyUIFont: UIFont { - if ProcessInfo.processInfo.isiOSAppOnMac { - return UIFont.systemFont(ofSize: userScaledFontSize(baseSize: 19)) - } else { - return UIFont.systemFont(ofSize: 17) - } + customUIFont(size: userScaledFontSize(baseSize: body)) } - + static var scaledCallout: Font { - if ProcessInfo.processInfo.isiOSAppOnMac { - return .system(size: userScaledFontSize(baseSize: 17)) - } else { - return .callout - } + customFont(size: userScaledFontSize(baseSize: callout), relativeTo: .callout) } - + static var scaledSubheadline: Font { - if ProcessInfo.processInfo.isiOSAppOnMac { - return .system(size: userScaledFontSize(baseSize: 16)) - } else { - return .subheadline - } + customFont(size: userScaledFontSize(baseSize: subheadline), relativeTo: .subheadline) } - + static var scaledFootnote: Font { - if ProcessInfo.processInfo.isiOSAppOnMac { - return .system(size: userScaledFontSize(baseSize: 15)) - } else { - return .footnote - } + customFont(size: userScaledFontSize(baseSize: footnote), relativeTo: .footnote) } - + static var scaledCaption: Font { - if ProcessInfo.processInfo.isiOSAppOnMac { - return .system(size: userScaledFontSize(baseSize: 14)) - } else { - return .caption - } + customFont(size: userScaledFontSize(baseSize: caption), relativeTo: .caption) } } diff --git a/Packages/DesignSystem/Sources/DesignSystem/FontPicker.swift b/Packages/DesignSystem/Sources/DesignSystem/FontPicker.swift new file mode 100644 index 00000000..0e745f42 --- /dev/null +++ b/Packages/DesignSystem/Sources/DesignSystem/FontPicker.swift @@ -0,0 +1,37 @@ +import Env +import SwiftUI + +public struct FontPicker: UIViewControllerRepresentable { + @Environment(\.dismiss) var dismiss + + public class Coordinator: NSObject, UIFontPickerViewControllerDelegate { + private let dismiss: DismissAction + + public init(dismiss: DismissAction) { + self.dismiss = dismiss + } + + public func fontPickerViewControllerDidCancel(_ viewController: UIFontPickerViewController) { + dismiss() + } + + public func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) { + UserPreferences.shared.chosenFont = UIFont(descriptor: viewController.selectedFontDescriptor!, size: 0) + dismiss() + } + } + + public init() {} + + public func makeCoordinator() -> Coordinator { + Coordinator(dismiss: dismiss) + } + + public func makeUIViewController(context: Context) -> UIFontPickerViewController { + let controller = UIFontPickerViewController() + controller.delegate = context.coordinator + return controller + } + + public func updateUIViewController(_ viewController: UIFontPickerViewController, context: Context) {} +} diff --git a/Packages/DesignSystem/Sources/DesignSystem/Theme.swift b/Packages/DesignSystem/Sources/DesignSystem/Theme.swift index fd392863..0b22885d 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Theme.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Theme.swift @@ -8,6 +8,21 @@ public class Theme: ObservableObject { case selectedSet, selectedScheme case followSystemColorSchme } + + public enum FontState: Int, CaseIterable { + case system + case custom + + @MainActor + public var title: LocalizedStringKey { + switch self { + case .system: + return "settings.display.font.system" + case .custom: + return "settings.display.font.custom" + } + } + } public enum AvatarPosition: String, CaseIterable { case leading, top diff --git a/Packages/Env/Sources/Env/UserPreferences.swift b/Packages/Env/Sources/Env/UserPreferences.swift index 0217003e..17bb59b6 100644 --- a/Packages/Env/Sources/Env/UserPreferences.swift +++ b/Packages/Env/Sources/Env/UserPreferences.swift @@ -26,6 +26,7 @@ public class UserPreferences: ObservableObject { @AppStorage("app_default_post_visibility") public var appDefaultPostVisibility: Models.Visibility = .pub @AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false @AppStorage("autoplay_video") public var autoPlayVideo = true + @AppStorage("chosen_font") public private(set) var chosenFontData: Data? public var postVisibility: Models.Visibility { if useInstanceContentSettings { @@ -67,6 +68,23 @@ public class UserPreferences: ObservableObject { Self.sharedDefault?.set(newValue, forKey: "push_notifications_count") } } + + public var chosenFont: UIFont? { + get { + guard let chosenFontData, + let font = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIFont.self, from: chosenFontData) else { return nil } + + return font + } + set { + if let font = newValue, + let data = try? NSKeyedArchiver.archivedData(withRootObject: font, requiringSecureCoding: false) { + chosenFontData = data + } else { + chosenFontData = nil + } + } + } @Published public var serverPreferences: ServerPreferences?