Run SwiftFormat.

This commit is contained in:
Doug
2026-05-27 14:38:50 +01:00
committed by Doug
parent 5138e05e47
commit fa67713726
573 changed files with 2426 additions and 2426 deletions
@@ -75,13 +75,13 @@ enum A11yIdentifiers {
let changePIN = "app_lock_setup_settings-change_pin"
let removePIN = "app_lock_setup_settings-remove_pin"
}
struct AnalyticsPromptScreen {
let title = "analytics_prompt-title"
let enable = "analytics_prompt-enable"
let notNow = "analytics_prompt-not_now"
}
struct BugReportScreen {
let report = "bug_report-report"
let sendLogs = "bug_report-send_logs"
@@ -164,9 +164,9 @@ enum A11yIdentifiers {
let messageComposer = "room-message_composer"
let sendButton = "room-send_button"
let composerToolbar = ComposerToolbar()
struct ComposerToolbar {
let bold = "composer_toolbar-bold"
let italic = "composer_toolbar-italic"
@@ -292,13 +292,13 @@ enum A11yIdentifiers {
let mediaPicker = "create_room-media_picker"
let filePicker = "create_room-file_picker"
}
struct PollFormScreen {
let addOption = "poll_form-add_option"
let pollKind = "poll_form-kind"
let question = "poll_form-question"
let submit = "poll_form-submit"
private let optionPrefix = "poll_form-option"
func optionID(_ index: Int) -> String {
"\(optionPrefix)-\(index)"
+4 -4
View File
@@ -15,7 +15,7 @@ enum Avatars {
case room(on: RoomAvatarSizeOnScreen)
/// custom
case custom(CGFloat)
/// Value in UIKit points
var value: CGFloat {
switch self {
@@ -27,7 +27,7 @@ enum Avatars {
return val
}
}
/// Value in pixels by using the scale of the main screen
var scaledValue: CGFloat {
value * UIScreen.main.scale
@@ -87,7 +87,7 @@ enum UserAvatarSizeOnScreen {
case threadSummary
case map
case classicAppAccount
var value: CGFloat {
switch self {
case .readReceipt:
@@ -143,7 +143,7 @@ enum RoomAvatarSizeOnScreen {
case spaceAddRoomsSelected
case completionSuggestions
case createRoomSelectSpace
var value: CGFloat {
switch self {
case .notificationSettings:
+14 -14
View File
@@ -27,16 +27,16 @@ import UIKit
public extension UIImage {
convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
guard blurHash.count >= 6 else { return nil }
let sizeFlag = String(blurHash[0]).decode83()
let numY = (sizeFlag / 9) + 1
let numX = (sizeFlag % 9) + 1
let quantisedMaximumValue = String(blurHash[1]).decode83()
let maximumValue = Float(quantisedMaximumValue + 1) / 166
guard blurHash.count == 4 + 2 * numX * numY else { return nil }
let colours: [(Float, Float, Float)] = (0..<numX * numY).map { i in
if i == 0 {
let value = String(blurHash[2..<6]).decode83()
@@ -46,20 +46,20 @@ public extension UIImage {
return decodeAC(value, maximumValue: maximumValue * punch)
}
}
let width = Int(size.width)
let height = Int(size.height)
let bytesPerRow = width * 3
guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
CFDataSetLength(data, bytesPerRow * height)
guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }
for y in 0..<height {
for x in 0..<width {
var r: Float = 0
var g: Float = 0
var b: Float = 0
for j in 0..<numY {
for i in 0..<numX {
let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height))
@@ -69,23 +69,23 @@ public extension UIImage {
b += colour.2 * basis
}
}
let intR = UInt8(linearTosRGB(r))
let intG = UInt8(linearTosRGB(g))
let intB = UInt8(linearTosRGB(b))
pixels[3 * x + 0 + y * bytesPerRow] = intR
pixels[3 * x + 1 + y * bytesPerRow] = intG
pixels[3 * x + 2 + y * bytesPerRow] = intB
}
}
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
guard let provider = CGDataProvider(data: data) else { return nil }
guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil }
self.init(cgImage: cgImage)
}
}
@@ -101,7 +101,7 @@ private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float
let quantR = value / (19 * 19)
let quantG = (value / 19) % 19
let quantB = value % 19
return (signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
signPow((Float(quantB) - 9) / 9, 2) * maximumValue)
@@ -149,7 +149,7 @@ private extension String {
subscript(offset: Int) -> Character {
self[index(startIndex, offsetBy: offset)]
}
subscript(bounds: CountableRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
@@ -59,7 +59,7 @@ struct CollapsibleReactionLayout: Layout {
}
return
}
// Calculate the layout of the rows with the reactions button and add more button
let reactionsAndAddMore = calculateRows(proposal: ProposedViewSize(bounds.size), subviews: Array(subviewsByType.reactions + [subviewsByType.addMoreButton]))
// If we have extended beyond the defined number of rows we are showing the expand/collapse ui
+2 -2
View File
@@ -20,9 +20,9 @@ import Foundation
value = newValue
}
}
private var value: Value?
init(value: Value? = nil) {
self.value = value
}
@@ -16,7 +16,7 @@ actor ExpiringTaskRunner<T: Sendable> {
private var continuation: CheckedContinuation<T, Error>?
private var task: @Sendable () async throws -> T
init(_ task: @escaping @Sendable () async throws -> T) {
self.task = task
}
@@ -81,7 +81,7 @@ private extension [Bit] {
return value
}
mutating func takeBits16(_ count: Int) throws(BitError) -> UInt16 {
guard count <= 16 else { throw .moreThan16BitsTaken }
@@ -92,7 +92,7 @@ private extension [Bit] {
return value
}
mutating func takeUInt8() throws(BitError) -> UInt8 {
try takeBits(8)
}
@@ -23,7 +23,7 @@ extension View {
})
return alert(item.wrappedValue?.title ?? "", isPresented: binding, presenting: item.wrappedValue, actions: actions, message: message)
}
// periphery: ignore - not used yet but might be useful
func alert<Item: AlertProtocol, Actions: View>(item: Binding<Item?>, @ViewBuilder actions: (Item) -> Actions) -> some View {
let binding = Binding<Bool>(get: {
@@ -51,7 +51,7 @@ struct AlertInfo<T: Hashable>: Identifiable, AlertProtocol {
var role: ButtonRole?
let action: (() -> Void)?
}
struct AlertTextField: Identifiable {
let id = UUID()
let placeholder: String
@@ -59,7 +59,7 @@ struct AlertInfo<T: Hashable>: Identifiable, AlertProtocol {
let autoCapitalization: TextInputAutocapitalization
let autoCorrectionDisabled: Bool
}
/// An identifier that can be used to distinguish one error from another.
let id: T
/// The alert's title.
@@ -86,7 +86,7 @@ extension AlertInfo {
title = L10n.commonError
message = L10n.errorUnknown
}
// periphery: ignore - might be useful in the future
/// Initialises the type with the title from an `Error`'s localised description along with the default Ok button.
///
@@ -109,7 +109,7 @@ extension View {
}
}
}
if let textFields = item.textFields {
VStack(spacing: 24) {
ForEach(textFields) { textField in
@@ -119,12 +119,12 @@ extension View {
}
}
}
Button(item.primaryButton.title, role: item.primaryButton.role) {
item.primaryButton.action?()
}
.accessibilityIdentifier(A11yIdentifiers.alertInfo.primaryButton)
if let secondaryButton = item.secondaryButton {
Button(secondaryButton.title, role: secondaryButton.role) {
secondaryButton.action?()
@@ -36,7 +36,7 @@ extension AttributedString {
case (false, false):
.plainText
}
components.append(AttributedStringBuilderComponent(id: String(attributedString.characters),
attributedString: attributedString,
type: componentType))
@@ -28,9 +28,9 @@ public extension Bundle {
/// Overrides `Bundle.app.preferredLocalizations` for testing translations since this is
/// only for testing, and is changed at runtime only in tests, it's fine to keep as `nonisolated(unsafe)`
nonisolated(unsafe) static var overrideLocalizations: [String]?
private static let cachedBundles = Mutex<[String: Bundle]>([:])
/// Get an lproj language bundle from the receiver bundle.
/// - Parameter language: The language to try to load.
/// - Returns: The lproj bundle if found otherwise nil.
@@ -38,24 +38,24 @@ public extension Bundle {
if let bundle = cachedValue(forKey: language) {
return bundle
}
guard let lprojURL = Bundle.app.url(forResource: language, withExtension: "lproj") else {
return nil
}
let bundle = Bundle(url: lprojURL)
cacheValue(bundle, forKey: language)
return bundle
}
// MARK: - Private
private static func cacheValue(_ value: Bundle?, forKey key: String) {
cachedBundles.withLock { $0[key] = value }
}
private static func cachedValue(forKey key: String) -> Bundle? {
cachedBundles.withLock { $0[key] }
}
@@ -43,7 +43,7 @@ extension ClientBuilder {
.enableShareHistoryOnInvite(enableShareHistoryOnInvite: true)
.autoEnableBackups(autoEnableBackups: true)
}
// Set recipient strategy and trust requirement even if `setupEncryption` is false to ensure messages
// from insecure devices aren't displayed in push notifications.
// See https://github.com/element-hq/element-x-ios/issues/4702.
@@ -25,7 +25,7 @@ extension View {
})
return confirmationDialog(item.wrappedValue?.title ?? "", isPresented: binding, titleVisibility: titleVisibility, presenting: item.wrappedValue, actions: actions)
}
// periphery: ignore - not used yet but might be useful
func confirmationDialog<Item: ConfirmationDialogProtocol, Actions: View, Message: View>(item: Binding<Item?>,
titleVisibility: Visibility = .automatic,
@@ -18,7 +18,7 @@ extension Encodable {
}
return json
}
func toJsonString(_ encoder: JSONEncoder = JSONEncoder()) throws -> String? {
try toJsonDictionary(encoder).jsonString
}
@@ -20,7 +20,7 @@ extension FileManager {
}
return isDirectory.boolValue
}
func createDirectoryIfNeeded(at url: URL, withIntermediateDirectories: Bool = true) throws {
guard !directoryExists(at: url) else {
return
@@ -36,7 +36,7 @@ extension FileManager {
return newURL
}
@discardableResult
func writeDataToTemporaryDirectory(data: Data, fileName: String, withinAppGroupContainer: Bool = false) throws -> URL {
let baseURL: URL = withinAppGroupContainer ? .appGroupTemporaryDirectory : .temporaryDirectory
@@ -59,7 +59,7 @@ extension FileManager {
return size
}
func sizeForDirectory(at url: URL) throws -> UInt {
guard let enumerator = enumerator(at: url, includingPropertiesForKeys: [.fileSizeKey, .isDirectoryKey]) else {
throw FileManagerError.invalidFileSize
@@ -16,7 +16,7 @@ extension ImageCache {
result.diskStorage.config.sizeLimit = 1
return result
}
static var onlyOnDisk: ImageCache {
let result = ImageCache.default
result.memoryStorage.config.totalCostLimit = 1
@@ -133,7 +133,7 @@ extension NSItemProvider {
guard registeredContentTypes.contains(where: { $0.conforms(to: .fileURL) }) else {
return nil
}
for type in registeredContentTypes {
if let fileExtension = type.preferredFilenameExtension {
return .init(type: type, fileExtension: fileExtension)
@@ -17,7 +17,7 @@ extension ProcessInfo {
false
#endif
}
/// Flag indicating whether the app is running the UI tests.
static var isRunningUITests: Bool {
#if DEBUG
@@ -42,7 +42,7 @@ extension ProcessInfo {
false
#endif
}
/// Flag indicating whether the app is running the UI tests or unit tests.
static var isRunningTests: Bool {
isRunningUITests || isRunningUnitTests || isRunningIntegrationTests || isRunningAccessibilityTests
@@ -11,7 +11,7 @@ import SwiftUI
struct SnapshotPrecisionPreferenceKey: PreferenceKey {
static var defaultValue: Float = 1.0
static func reduce(value: inout Float, nextValue: () -> Float) {
value = nextValue()
}
@@ -19,7 +19,7 @@ struct SnapshotPrecisionPreferenceKey: PreferenceKey {
struct SnapshotPerceptualPrecisionPreferenceKey: PreferenceKey {
static var defaultValue: Float = 0.98
static func reduce(value: inout Float, nextValue: () -> Float) {
value = nextValue()
}
@@ -27,7 +27,7 @@ struct SnapshotPerceptualPrecisionPreferenceKey: PreferenceKey {
struct SnapshotFulfillmentPreferenceKey: PreferenceKey {
static var defaultValue: Wrapper?
static func reduce(value: inout Wrapper?, nextValue: () -> Wrapper?) {
value = nextValue()
}
+2 -2
View File
@@ -29,7 +29,7 @@ public extension Task where Success == Never, Failure == Never {
}
}
}
/// Dispatches the given throwing closure onto the given queue, wrapped within
/// a continuation to make it non-blocking and awaitable.
///
@@ -59,7 +59,7 @@ extension Task {
func store(in cancellables: inout Set<AnyCancellable>) {
asCancellable().store(in: &cancellables)
}
func asCancellable() -> AnyCancellable {
AnyCancellable(cancel)
}
@@ -13,7 +13,7 @@ extension UIDevice {
var isPhone: Bool {
userInterfaceIdiom == .phone
}
var initialDeviceName: String {
L10n.loginInitialDeviceNameIos(InfoPlistReader.main.bundleDisplayName)
}
@@ -13,7 +13,7 @@ extension UNNotificationContent {
@objc var receiverID: String? {
userInfo[NotificationConstants.UserInfoKey.receiverIdentifier] as? String
}
@objc var roomID: String? {
userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String
}
@@ -21,7 +21,7 @@ extension UNNotificationContent {
@objc var eventID: String? {
userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String
}
@objc var pusherNotificationClientIdentifier: String? {
userInfo[NotificationConstants.UserInfoKey.pusherNotificationClientIdentifier] as? String
}
+14 -14
View File
@@ -31,13 +31,13 @@ extension URL {
.appending(component: "Logs", directoryHint: .isDirectory)
.appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory)
}
/// The base directory where all session data is stored.
static var sessionsBaseDirectory: URL {
let applicationSupportSessionsURL = applicationSupportBaseDirectory.appending(component: "Sessions", directoryHint: .isDirectory)
try? FileManager.default.createDirectoryIfNeeded(at: applicationSupportSessionsURL)
return applicationSupportSessionsURL
}
@@ -47,7 +47,7 @@ extension URL {
.appending(component: "Library", directoryHint: .isDirectory)
.appending(component: "Application Support", directoryHint: .isDirectory)
.appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory)
try? FileManager.default.createDirectoryIfNeeded(at: url)
do {
@@ -57,7 +57,7 @@ extension URL {
} catch {
MXLog.error("Failed excluding Application Support from backups")
}
return url
}
@@ -68,12 +68,12 @@ extension URL {
.appending(component: "Caches", directoryHint: .isDirectory)
.appending(component: InfoPlistReader.main.baseBundleIdentifier, directoryHint: .isDirectory)
.appending(component: "Sessions", directoryHint: .isDirectory)
try? FileManager.default.createDirectoryIfNeeded(at: url)
// Caches are excluded from backups automatically.
// https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
return url
}
@@ -84,12 +84,12 @@ extension URL {
static var appGroupTemporaryDirectory: URL {
let url = appGroupContainerDirectory
.appending(component: "tmp", directoryHint: .isDirectory)
try? FileManager.default.createDirectoryIfNeeded(at: url)
// Temporary files are excluded from backups automatically.
// https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
return url
}
@@ -116,7 +116,7 @@ extension URL {
MXLog.info("Found \(String(describing: proxies?.count)) proxies, using the first one.")
MXLog.info("Proxy type is \(proxyType)")
guard let host = firstProxy[kCFProxyHostNameKey] as? String else {
MXLog.error("Found proxy with invalid host name")
return nil
@@ -138,23 +138,23 @@ extension URL {
static var mockMXCAudio: URL {
"mxc://matrix.org/1234567890AuDiO"
}
static var mockMXCFile: URL {
"mxc://matrix.org/1234567890FiLe"
}
static var mockMXCImage: URL {
"mxc://matrix.org/1234567890ImAgE"
}
static var mockMXCVideo: URL {
"mxc://matrix.org/1234567890ViDeO"
}
static var mockMXCAvatar: URL {
"mxc://matrix.org/1234567890AvAtAr"
}
static var mockMXCUserAvatar: URL {
"mxc://matrix.org/1234567890AvAtArUsEr"
}
@@ -44,7 +44,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
private static let attributeMSC4286 = "msc4286-external-payment-details"
private static let caches = Mutex<[String: LRUCache<String, AttributedString>]>([:])
static func invalidateCaches() {
caches.withLock { $0.removeAll() }
}
@@ -53,7 +53,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
self.cacheKey = cacheKey
self.mentionBuilder = mentionBuilder
}
func fromPlain(_ string: String?) -> AttributedString? {
guard let string else {
return nil
@@ -62,7 +62,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if let cached = Self.cachedValue(forKey: string, cacheKey: cacheKey) {
return cached
}
let mutableAttributedString = NSMutableAttributedString(string: string)
addLinksAndMentions(mutableAttributedString)
addMatrixEntityPermalinkAttributesTo(mutableAttributedString)
@@ -72,7 +72,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
return result
}
/// Do not use the default HTML renderer of NSAttributedString because this method
/// runs on the UI thread which we want to avoid because renderHTMLString is called
/// most of the time from a background thread.
@@ -84,7 +84,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if let cached = Self.cachedValue(forKey: originalHTMLString, cacheKey: cacheKey) {
return cached
}
let htmlString = originalHTMLString.replacingHtmlBreaksOccurrences()
let doc = try? SwiftSoup.parseBodyFragment(htmlString)
@@ -105,7 +105,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
return result
}
// MARK: - Private
// swiftlint:disable:next function_body_length cyclomatic_complexity
@@ -133,7 +133,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if (node.previousSibling() as? Element)?.tagName() == "br" {
text.trimPrefix(" ")
}
result.append(NSAttributedString(string: text))
continue
}
@@ -155,7 +155,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
content = attributedString(element: childElement, documentBody: documentBody, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel)
content.append(NSAttributedString(string: "\n"))
content.setFontPreservingSymbolicTraits(UIFont.boldSystemFont(ofSize: size))
case "p", "div":
content = attributedString(element: childElement, documentBody: documentBody, preserveFormatting: preserveFormatting, listTag: listTag, listIndex: &childIndex, indentLevel: indentLevel)
content.append(NSAttributedString(string: "\n"))
@@ -245,7 +245,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if indentLevel > 0 || !element.ownText().isEmpty {
content.insert(NSAttributedString("\n"), at: 0)
}
case "li":
var bullet = String(repeating: " ", count: indentLevel)
if listTag == "ol" {
@@ -283,11 +283,11 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if caches[cacheKey] == nil {
caches[cacheKey] = LRUCache<String, AttributedString>(countLimit: 1000)
}
caches[cacheKey]?.setValue(value, forKey: key)
}
}
private static func cachedValue(forKey key: String, cacheKey: String) -> AttributedString? {
caches.withLock { $0[cacheKey]?.value(forKey: key) }
}
@@ -305,7 +305,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
}
let identifier = String(string[matchRange])
return TextParsingMatch(type: .userID(identifier: identifier), range: match.range)
}
@@ -371,7 +371,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
if hasLink {
return
}
switch match.type {
case .atRoom:
attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: match.range)
@@ -420,7 +420,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
}
}
}
private func detectPhishingAttempts(_ attributedString: NSMutableAttributedString) {
attributedString.enumerateAttribute(.link, in: .init(location: 0, length: attributedString.length), options: []) { value, range, _ in
guard value != nil, let internalURL = value as? URL else {
@@ -14,7 +14,7 @@ struct AttributedStringBuilderComponent: Hashable, Identifiable {
case blockquote
case codeBlock
}
/// Identifier for the `Identifiable` conformance, allows edits to the `FormattedBodyText` to animate seamlessly
let id: String
let attributedString: AttributedString
@@ -15,7 +15,7 @@ enum PhishingDetector {
let disambiguatedDisplayString = displayString.replacingOccurrences(of: "﹒", with: ".")
let linkMatch = MatrixEntityRegex.linkRegex.firstMatch(in: disambiguatedDisplayString)
let linkMatchLength = linkMatch?.range.length ?? 0
// We check if we the link string contains a matrix user ID.
if let match = MatrixEntityRegex.userIdentifierRegex.firstMatch(in: disambiguatedDisplayString),
// If there is a bigger permalink including it we leave it handled by the link branch
+12 -12
View File
@@ -33,26 +33,26 @@ struct InfoPlistReader {
private enum Values {
static let mentionPills = "Mention Pills"
}
/// Info.plist reader on the bundle object that contains the current executable.
static let main = InfoPlistReader(bundle: .main)
/// Info.plist reader on the bundle object that contains the main app executable.
static let app = InfoPlistReader(bundle: .app)
private let bundle: Bundle
/// Initializer
/// - Parameter bundle: bundle to read values from
init(bundle: Bundle) {
self.bundle = bundle
}
/// App group identifier set in Info.plist of the target
var appGroupIdentifier: String {
infoPlistValue(forKey: Keys.appGroupIdentifier)
}
/// Base bundle identifier set in Info.plist of the target
var baseBundleIdentifier: String {
infoPlistValue(forKey: Keys.baseBundleIdentifier)
@@ -62,27 +62,27 @@ struct InfoPlistReader {
var keychainAccessGroupIdentifier: String {
infoPlistValue(forKey: Keys.keychainAccessGroupIdentifier)
}
/// Bundle executable of the target
var bundleExecutable: String {
infoPlistValue(forKey: kCFBundleExecutableKey as String)
}
/// Bundle identifier of the target
var bundleIdentifier: String {
infoPlistValue(forKey: kCFBundleIdentifierKey as String)
}
/// Bundle short version string of the target
var bundleShortVersionString: String {
infoPlistValue(forKey: Keys.bundleShortVersion)
}
/// Bundle version of the target
var bundleVersion: String {
infoPlistValue(forKey: kCFBundleVersionKey as String)
}
/// Bundle display name of the target
var bundleDisplayName: String {
infoPlistValue(forKey: Keys.bundleDisplayName)
@@ -104,7 +104,7 @@ struct InfoPlistReader {
}
// MARK: - Mention Pills
/// Mention Pills UTType
var pillsUTType: String {
let exportedTypes: [[String: Any]] = infoPlistValue(forKey: Keys.utExportedTypeDeclarationsKey)
@@ -20,7 +20,7 @@ enum ExtensionLogger {
formatter.minimumFractionDigits = 2
return formatter
}
private static var formattedMemoryAvailable: String {
let freeBytes = os_proc_available_memory()
let freeMB = Double(freeBytes) / 1024 / 1024
@@ -29,7 +29,7 @@ enum ExtensionLogger {
}
return "\(formattedStr) MB"
}
/// Details: https://developer.apple.com/forums/thread/105088
/// - Returns: Current memory footprint
private static var memoryFootprint: Float? {
@@ -50,10 +50,10 @@ enum ExtensionLogger {
guard kr == KERN_SUCCESS, count >= TASK_VM_INFO_REV1_COUNT else {
return nil
}
return Float(info.phys_footprint)
}
/// Formatted memory footprint for debugging purposes
/// - Returns: Memory footprint in MBs as a readable string
static var formattedMemoryFootprint: String {
@@ -64,7 +64,7 @@ enum ExtensionLogger {
}
return "\(formattedStr) MB"
}
static func logMemory(with tag: String) {
MXLog.info("\(tag) Memory: footprint: \(formattedMemoryFootprint) - available: \(formattedMemoryAvailable)")
}
@@ -22,7 +22,7 @@ struct MapLibreMapView: UIViewRepresentable {
/// Map annotations
let annotations: [LocationAnnotation]
init(zoomLevel: Double, initialZoomLevel: Double, mapCenter: CLLocationCoordinate2D, annotations: [LocationAnnotation] = []) {
self.zoomLevel = zoomLevel
self.initialZoomLevel = initialZoomLevel
@@ -36,7 +36,7 @@ struct MapLibreMapView: UIViewRepresentable {
@Environment(\.colorScheme) private var colorScheme
let mapURLBuilder: MapTilerURLBuilderProtocol
let options: Options
let mediaProvider: MediaProviderProtocol?
@@ -93,7 +93,7 @@ struct MapLibreMapView: UIViewRepresentable {
}
// MARK: - Private
private func setupMap(mapView: MLNMapView, with options: Options) {
mapView.addAnnotations(options.annotations)
mapView.zoomLevel = options.annotations.isEmpty ? options.initialZoomLevel : options.zoomLevel
@@ -174,16 +174,16 @@ struct MapLibreMapView: UIViewRepresentable {
extension MapLibreMapView {
class Coordinator: NSObject, MLNMapViewDelegate {
// MARK: - Properties
var mapLibreView: MapLibreMapView
private var previousUserLocation: MLNUserLocation?
/// Tracks the last center coordinate reported by the map (or set programmatically),
/// so that `updateUIView` can tell apart external binding changes from internal ones.
var lastReportedCenter: CLLocationCoordinate2D?
// MARK: - Setup
init(_ mapLibreView: MapLibreMapView) {
self.mapLibreView = mapLibreView
}
@@ -212,7 +212,7 @@ extension MapLibreMapView {
mapView.setCenter(userLocation.coordinate, zoomLevel: self.mapLibreView.options.zoomLevel, animated: true)
}
}
previousUserLocation = userLocation
updateGeolocationUncertainty(location: userLocation)
}
@@ -238,7 +238,7 @@ extension MapLibreMapView {
mapLibreView.mapCenterCoordinate = center
}
}
func mapView(_ mapView: MLNMapView, shouldChangeFrom oldCamera: MLNMapCamera, to newCamera: MLNMapCamera, reason: MLNCameraChangeReason) -> Bool {
// we send the userDidPan event only for the reasons that actually will change the map center, and not zoom only / rotations only events.
switch reason {
@@ -259,21 +259,21 @@ extension MapLibreMapView {
}
return true
}
// MARK: Callout
func mapView(_ mapView: MLNMapView, annotationCanShowCallout annotation: MLNAnnotation) -> Bool {
false
}
// MARK: Private
private func updateGeolocationUncertainty(location: MLNUserLocation) {
guard let clLocation = location.location, clLocation.horizontalAccuracy >= 0 else {
mapLibreView.geolocationUncertainty = nil
return
}
mapLibreView.geolocationUncertainty = clLocation.horizontalAccuracy
}
}
@@ -17,7 +17,7 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
private let mapTilerAttributionPlacement: MapTilerAttributionPlacement
private let mapSize: CGSize
private let pinAnnotationView: PinAnnotation
@Environment(\.colorScheme) private var colorScheme
@State private var fetchAttempt = 0
@@ -79,13 +79,13 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
}
}
}
private var placeholderImage: some View {
Image(asset: Asset.Images.mapBlurred)
.resizable()
.scaledToFill()
}
private var errorView: some View {
Button {
fetchAttempt += 1
@@ -49,7 +49,7 @@ struct MentionBuilder: MentionBuilderProtocol {
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) {
let attributesToRestore = getAttributesToRestore(for: attributedString, in: range)
let attachmentData = PillTextAttachmentData(type: .allUsers, font: attributesToRestore.font)
guard let attachment = PillTextAttachment(attachmentData: attachmentData) else {
return
@@ -80,7 +80,7 @@ struct MentionBuilder: MentionBuilderProtocol {
.font: attributesToRestore.font,
.foregroundColor: attributesToRestore.foregroundColor]
attachmentAttributes.addBlockquoteIfNeeded(attributesToRestore.blockquote)
setPillAttachment(attachment: attachment,
attributedString: attributedString,
in: range,
@@ -93,7 +93,7 @@ struct MessageText: UIViewRepresentable {
guard let baseText = try? NSAttributedString(attributedString, including: \.elementX) else {
return nil
}
let combined = NSMutableAttributedString(attributedString: baseText)
if trailingReservedSize.width > 0 {
let attachment = TransparentTextAttachment()
@@ -101,7 +101,7 @@ struct MessageText: UIViewRepresentable {
attachment.bounds = CGRect(origin: .zero,
size: CGSize(width: trailingReservedSize.width,
height: max(trailingReservedSize.height, 1)))
// Inherit the font from the preceding character so the appended runs
// (newline and attachment) carry the same line metrics as the surrounding
// text — otherwise a small default font on those characters can shrink the
@@ -109,7 +109,7 @@ struct MessageText: UIViewRepresentable {
let trailingFont = baseText.length > 0
? baseText.attribute(.font, at: baseText.length - 1, effectiveRange: nil)
: nil
// When the last paragraph's natural text direction doesn't match the layout
// direction, the inline attachment would land on the wrong side and overlap the
// overlaid timestamp. Force it onto its own line so the bubble just grows taller.
@@ -122,7 +122,7 @@ struct MessageText: UIViewRepresentable {
}
combined.append(newlineString)
}
let attachmentString = NSMutableAttributedString(attachment: attachment)
if let trailingFont {
attachmentString.addAttribute(.font,
@@ -131,19 +131,19 @@ struct MessageText: UIViewRepresentable {
}
combined.append(attachmentString)
}
return combined
}
private func lastParagraphDirectionMatchesLayout(in attributedText: NSAttributedString) -> Bool {
let string = attributedText.string as NSString
guard string.length > 0 else { return true }
// Isolate the last paragraph because its direction is what decides
// which side the trailing attachment ends up on.
let lastParagraphRange = string.paragraphRange(for: NSRange(location: string.length - 1, length: 0))
let lastParagraph = string.substring(with: lastParagraphRange)
let textIsRTL = lastParagraph.firstStrongCharacterIsRTL
let layoutIsRTL = layoutDirection == .rightToLeft
return textIsRTL == layoutIsRTL
@@ -25,7 +25,7 @@ final class PillAttachmentViewProvider: NSTextAttachmentViewProvider, NSSecureCo
override init(textAttachment: NSTextAttachment, parentView: UIView?, textLayoutManager: NSTextLayoutManager?, location: NSTextLocation) {
super.init(textAttachment: textAttachment, parentView: parentView, textLayoutManager: textLayoutManager, location: location)
// Keep a reference to the parent text view for size adjustments and pills flushing.
delegate = parentView?.superview as? PillAttachmentViewProviderDelegate
tracksTextAttachmentViewBounds = true
@@ -34,7 +34,7 @@ final class PillAttachmentViewProvider: NSTextAttachmentViewProvider, NSSecureCo
@MainActor
override func loadView() {
super.loadView()
guard let textAttachment = textAttachment as? PillTextAttachment,
let pillData = textAttachment.pillData else {
MXLog.failure("Attachment is missing data or not of expected class")
@@ -82,7 +82,7 @@ final class PillAttachmentViewProvider: NSTextAttachmentViewProvider, NSSecureCo
final class ComposerMentionDisplayHelper: MentionDisplayHelper {
weak var timelineContext: TimelineViewModel.Context?
init(timelineContext: TimelineViewModel.Context) {
self.timelineContext = timelineContext
}
@@ -10,7 +10,7 @@ import Foundation
enum PillUtilities {
static let atRoom = "@room"
/// Used by the WYSIWYG as the urlString value to identify @room mentions
static let composerAtRoomURLString = "#"
+1 -1
View File
@@ -21,7 +21,7 @@ struct PillView: View {
var backgroundColor: Color {
context.viewState.isOwnMention ? .compound.bgBadgeAccent : .compound.bgBadgePrimary
}
var body: some View {
mainContent
.onChange(of: context.viewState.displayText) {
@@ -22,13 +22,13 @@ private struct ProgressMaskModifier: ViewModifier {
private let progress: CGFloat
private let trackColor: Color
private let backgroundTrackColor: Color
init(progress: CGFloat, trackColor: Color, backgroundTrackColor: Color) {
self.progress = progress
self.trackColor = trackColor
self.backgroundTrackColor = backgroundTrackColor
}
func body(content: Content) -> some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
+1 -1
View File
@@ -15,7 +15,7 @@ import MatrixRustSDK
/// protocol with a specialisation for the type it listens for.
final class SDKListener<T>: Sendable {
private let onUpdateClosure: @Sendable (T) -> Void
/// Creates a new listener.
/// - Parameter onUpdateClosure: A closure that will be called whenever a new value is available.
init(_ onUpdateClosure: @escaping @Sendable (T) -> Void) {
@@ -16,9 +16,9 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
scrollView?.delegate = self
}
}
var shouldScrollToTopClosure: ((UIScrollView) -> Bool)?
private let didScrollSubject = PassthroughSubject<Void, Never>()
var didScroll: AnyPublisher<Void, Never> {
didScrollSubject.eraseToAnyPublisher()
@@ -54,7 +54,7 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
updateDidScroll(scrollView)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
updateDidScroll(scrollView)
}
@@ -62,7 +62,7 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
updateDidScroll(scrollView)
}
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
guard let shouldScrollToTopClosure else {
// Default behaviour
@@ -15,34 +15,34 @@ final class ShareToMapsAppActivity: UIActivity {
case google
case osm
}
private let type: MapsAppType
private let location: CLLocationCoordinate2D
private let senderName: String?
init(type: MapsAppType, location: CLLocationCoordinate2D, senderName: String?) {
self.type = type
self.location = location
self.senderName = senderName
super.init()
}
override private init() {
fatalError()
}
override var activityTitle: String? {
type.activityTitle
}
override var activityType: UIActivity.ActivityType {
.shareToMapsApp
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
true
}
override func prepare(withActivityItems activityItems: [Any]) {
UIApplication.shared.open(type.activityURL(for: location, senderName: senderName), options: [:]) { [weak self] result in
self?.activityDidFinish(result)
@@ -76,7 +76,7 @@ extension ShareToMapsAppActivity.MapsAppType {
return url
}
}
var activityTitle: String {
switch self {
case .apple:
@@ -12,21 +12,21 @@ import SwiftUI
public extension Animation {
/// Animation to be used to disable animations.
static let noAnimation: Animation = .linear(duration: 0)
/// `noAnimation` if running tests, otherwise `default` animation if `UIAccessibility.isReduceMotionEnabled` is false
static var elementDefault: Animation {
let animation: Animation = ProcessInfo.isRunningTests ? .noAnimation : .default
return animation.disabledIfReduceMotionEnabled()
}
/// `noAnimation` if running tests, otherwise `self` if `UIAccessibility.isReduceMotionEnabled` is false
func disabledDuringTests() -> Self {
let animation: Animation = ProcessInfo.isRunningTests ? .noAnimation : self
return animation.disabledIfReduceMotionEnabled()
}
// MARK: - Private
private func disabledIfReduceMotionEnabled() -> Self {
UIAccessibility.isReduceMotionEnabled ? .noAnimation : self
}
@@ -22,28 +22,28 @@ class StateStoreViewModel<State: BindableState, ViewAction> {
///
/// Left as public for `ViewModel` implementations convenience.
var cancellables = Set<AnyCancellable>()
/// Constrained interface for passing to Views.
var context: Context
var state: State {
get { context.viewState }
set { context.viewState = newValue }
}
init(initialViewState: State, mediaProvider: MediaProviderProtocol? = nil) {
context = Context(initialViewState: initialViewState, mediaProvider: mediaProvider)
context.viewModel = self
}
/// Override to handles incoming `ViewAction`s from the `ViewModel`.
/// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation.
func process(viewAction: ViewAction) {
// Default implementation, -no-op
}
// MARK: - Context
/// A constrained and concise interface for interacting with the ViewModel.
///
/// This class is closely bound to`StateStoreViewModel`. It provides the exact interface the view should need to interact
@@ -60,26 +60,26 @@ class StateStoreViewModel<State: BindableState, ViewAction> {
@MainActor
final class Context: ObservableObject {
fileprivate weak var viewModel: StateStoreViewModel?
/// Get-able/Observable `Published` property for the `ViewState`
@Published fileprivate(set) var viewState: State
/// An optional image loading service so that views can manage themselves
/// Intentionally non-generic so that it doesn't grow uncontrollably
let mediaProvider: MediaProviderProtocol?
/// Set-able/Bindable access to the bindable state.
subscript<T>(dynamicMember keyPath: WritableKeyPath<State.BindStateType, T>) -> T {
get { viewState.bindings[keyPath: keyPath] }
set { viewState.bindings[keyPath: keyPath] = newValue }
}
/// Send a `ViewAction` to the `ViewModel` for processing.
/// - Parameter viewAction: The `ViewAction` to send to the `ViewModel`.
func send(viewAction: ViewAction) {
viewModel?.process(viewAction: viewAction)
}
fileprivate init(initialViewState: State, mediaProvider: MediaProviderProtocol?) {
self.viewState = initialViewState
self.mediaProvider = mediaProvider
@@ -24,26 +24,26 @@ class StateStoreViewModelV2<State: BindableState, ViewAction> {
///
/// Left as public for `ViewModel` implementations convenience.
var cancellables = Set<AnyCancellable>()
/// Constrained interface for passing to Views.
var context: Context
var state: State {
get { context.viewState }
set { context.viewState = newValue }
}
init(initialViewState: State, mediaProvider: MediaProviderProtocol? = nil) {
context = Context(initialViewState: initialViewState, mediaProvider: mediaProvider)
context.viewModel = self
}
/// Override to handles incoming `ViewAction`s from the `ViewModel`.
/// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation.
func process(viewAction: ViewAction) {
// Default implementation, -no-op
}
// MARK: - Context
/// A constrained and concise interface for interacting with the ViewModel.
@@ -62,26 +62,26 @@ class StateStoreViewModelV2<State: BindableState, ViewAction> {
@MainActor
@Observable final class Context {
fileprivate weak var viewModel: StateStoreViewModelV2?
/// Get-able property for the `ViewState`
fileprivate(set) var viewState: State
/// An optional image loading service so that views can manage themselves
/// Intentionally non-generic so that it doesn't grow uncontrollably
let mediaProvider: MediaProviderProtocol?
/// Set-able access to the bindable state.
subscript<T>(dynamicMember keyPath: WritableKeyPath<State.BindStateType, T>) -> T {
get { viewState.bindings[keyPath: keyPath] }
set { viewState.bindings[keyPath: keyPath] = newValue }
}
/// Send a `ViewAction` to the `ViewModel` for processing.
/// - Parameter viewAction: The `ViewAction` to send to the `ViewModel`.
func send(viewAction: ViewAction) {
viewModel?.process(viewAction: viewAction)
}
fileprivate init(initialViewState: State, mediaProvider: MediaProviderProtocol?) {
self.viewState = initialViewState
self.mediaProvider = mediaProvider
@@ -11,13 +11,13 @@ import SwiftUI
struct AppActivityView: UIViewControllerRepresentable {
typealias UIViewControllerType = UIActivityViewController
typealias CompletionType = (Result<(activity: UIActivity.ActivityType, items: [Any]?), Error>) -> Void
private let activityItems: [Any]
private let applicationActivities: [UIActivity]?
private var excludedActivityTypes: [UIActivity.ActivityType]
private var onCancel: (() -> Void)?
private var onComplete: CompletionType?
init(activityItems: [Any],
applicationActivities: [UIActivity]? = nil,
excludedActivityTypes: [UIActivity.ActivityType] = [],
@@ -29,11 +29,11 @@ struct AppActivityView: UIViewControllerRepresentable {
self.onCancel = onCancel
self.onComplete = onComplete
}
func makeUIViewController(context: Context) -> UIViewControllerType {
let viewController = UIViewControllerType(activityItems: activityItems, applicationActivities: applicationActivities)
viewController.excludedActivityTypes = excludedActivityTypes
viewController.completionWithItemsHandler = { activity, completed, items, error in
if let error {
onComplete?(.failure(error))
@@ -45,12 +45,12 @@ struct AppActivityView: UIViewControllerRepresentable {
assertionFailure()
}
}
return viewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Coordinator) {
uiViewController.completionWithItemsHandler = nil
}
@@ -11,7 +11,7 @@ import SwiftUI
/// A button that contains text that is copied on tap
struct CopyTextButton: View {
let content: String
var body: some View {
Button {
UIPasteboard.general.string = content
@@ -12,7 +12,7 @@ import SwiftUI
struct JoinCallButton: View {
let isVoiceCall: Bool
let action: () -> Void
var body: some View {
if #available(iOS 26, *) {
glassButton
@@ -358,7 +358,7 @@ struct LoadableImage_Previews: PreviewProvider, TestablePreview {
mediaProvider: loadingMediaProvider,
placeholder: placeholder)
.layout(title: "Loading (avatar)")
LoadableImage(url: "mxc://wherever/345",
mediaType: .timelineItem(uniqueID: .init("id")),
blurhash: "KbLM^j]q$jT|EfR-3rtjXk",
@@ -388,7 +388,7 @@ struct LoadableImage_Previews: PreviewProvider, TestablePreview {
static func placeholder() -> some View {
Color.compound._bgBubbleIncoming
}
static func transformer(_ view: AnyView) -> some View {
view.overlay {
Image(systemSymbol: .playCircleFill)
@@ -11,7 +11,7 @@ import SwiftUI
struct PlaceholderAvatarImage: View {
@Environment(\.redactionReasons) private var redactionReasons
private let textForImage: String
private let contentID: String
@@ -32,18 +32,18 @@ struct PlaceholderAvatarImage: View {
}
.aspectRatio(1, contentMode: .fill)
}
init(name: String?, contentID: String) {
let baseName = name ?? contentID.trimmingCharacters(in: .punctuationCharacters)
textForImage = baseName.first?.uppercased() ?? ""
self.contentID = contentID
}
private var backgroundColor: Color {
if redactionReasons.contains(.placeholder) {
return Color(.systemGray4) // matches the default text redaction
}
return avatarColor?.background ?? .compound.iconPrimary
}
@@ -11,42 +11,42 @@ import SwiftUI
struct RoundedCornerShape: Shape {
let radius: CGFloat
let corners: UIRectCorner
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.size.width
let height = rect.size.height
var topLeft: CGFloat = corners.contains(.topLeft) ? radius : 0.0
var topRight: CGFloat = corners.contains(.topRight) ? radius : 0.0
var bottomLeft: CGFloat = corners.contains(.bottomLeft) ? radius : 0.0
var bottomRight: CGFloat = corners.contains(.bottomRight) ? radius : 0.0
// Make sure we do not exceed the size of the rectangle
topRight = min(min(topRight, height / 2), width / 2)
topLeft = min(min(topLeft, height / 2), width / 2)
bottomLeft = min(min(bottomLeft, height / 2), width / 2)
bottomRight = min(min(bottomRight, height / 2), width / 2)
path.move(to: CGPoint(x: width / 2.0, y: 0))
path.addLine(to: CGPoint(x: width - topRight, y: 0))
path.addArc(center: CGPoint(x: width - topRight, y: topRight), radius: topRight,
startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
path.addLine(to: CGPoint(x: width, y: height - bottomRight))
path.addArc(center: CGPoint(x: width - bottomRight, y: height - bottomRight), radius: bottomRight,
startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
path.addLine(to: CGPoint(x: bottomLeft, y: height))
path.addArc(center: CGPoint(x: bottomLeft, y: height - bottomLeft), radius: bottomLeft,
startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: topLeft))
path.addArc(center: CGPoint(x: topLeft, y: topLeft), radius: topLeft,
startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
path.closeSubpath()
return path
}
}
@@ -48,7 +48,7 @@ struct StackedAvatarsView_Previews: PreviewProvider, TestablePreview {
.init(url: nil, name: "Charlie", contentID: "@charlie:matrix.org"),
.init(url: nil, name: "Dan", contentID: "@charlie:matrix.org")
]
static var previews: some View {
VStack(spacing: 10) {
StackedAvatarsView(overlap: 16,
@@ -16,29 +16,29 @@ struct TimelineScrollButton: View {
case down
case up
}
let direction: Direction
let isHidden: Bool
let showsBadge: Bool
let onLongPress: (() -> Void)?
let callback: () -> Void
@ScaledMetric(relativeTo: .compound.bodyLG) private var iconSize: CGFloat = 24
@ScaledMetric(relativeTo: .compound.bodyLG) private var badgeDotSize: CGFloat = 12
@GestureState private var isPressing = false
/// Set when a long-press is recognized so the trailing touch-up doesn't also fire `callback()`.
@State private var consumedByLongPress = false
/// Padding around the chevron, balanced so the total circle stays ~36pt at default text size.
private let iconPadding: CGFloat = 6
/// Vertical offset that half-centers the badge dot on the circle's edge. Magnitude is half
/// the dot's diameter so half sits inside the circle, half outside. Sign is applied at the
/// call site based on direction.
private var badgeOffsetMagnitude: CGFloat {
badgeDotSize / 2
}
init(direction: Direction = .down,
isHidden: Bool = false,
showsBadge: Bool = false,
@@ -50,7 +50,7 @@ struct TimelineScrollButton: View {
self.onLongPress = onLongPress
self.callback = callback
}
var body: some View {
if let onLongPress {
buttonContent
@@ -76,7 +76,7 @@ struct TimelineScrollButton: View {
buttonContent
}
}
private var buttonContent: some View {
Button {
if consumedByLongPress {
@@ -99,7 +99,7 @@ struct TimelineScrollButton: View {
.accessibilityLabel(direction == .down ? L10n.a11yJumpToBottom : L10n.a11yJumpToUnread)
.animation(.elementDefault, value: isHidden)
}
@ViewBuilder
private var icon: some View {
let chevron = CompoundIcon(direction == .down ? \.chevronDown : \.chevronUp,
@@ -117,7 +117,7 @@ struct TimelineScrollButton: View {
.overlay(Circle().stroke(Color.compound.borderInteractiveSecondary, lineWidth: 1))
}
}
private var badge: some View {
Circle()
.fill(Color.compound.iconAccentPrimary)
@@ -15,7 +15,7 @@ struct ToolbarButton: View {
static let done = Role.confirm(title: L10n.actionDone)
static let save = Role.confirm(title: L10n.actionSave)
static let close = Role.cancel(title: L10n.actionClose)
case cancel(title: String)
case confirm(title: String)
case destructive(title: String)
@@ -46,7 +46,7 @@ extension View {
func topBanner(_ banner: some View, isVisible: Bool, footer: some View = EmptyView()) -> some View {
topBanners([TopBannerLayer(banner, isVisible: isVisible)], footer: footer)
}
/// Overlays the given Z-axis banner slots at the top edge of this view.
/// Later items in the array are overlayed on top of earlier ones. Within
/// each slot, visible vertical banners are stacked in a VStack and slide
@@ -11,7 +11,7 @@ import SwiftUI
/// Represents the position of a checkmark item in a list.
enum ListPosition {
case top, middle, bottom
/// The corners that should be rounded for this position.
var roundedCorners: UIRectCorner {
switch self {
@@ -57,11 +57,11 @@ private struct VisualListItemLabelStyle: LabelStyle {
struct VisualListItem_Previews: PreviewProvider, TestablePreview {
static let strings = AnalyticsPromptScreenStrings(termsURL: AppSettings.volatile().analyticsTermsURL)
static var testImage1: some View {
Image(systemName: "circle")
}
static var testImage2: some View {
Image(systemName: "square")
}
@@ -21,7 +21,7 @@ enum UserAgentBuilder {
static func makeUserAgent() -> String? {
let clientName = InfoPlistReader.app.bundleDisplayName
let clientVersion = InfoPlistReader.app.bundleShortVersionString
#if os(iOS)
let scale = UIScreen.main.scale
return if ProcessInfo.processInfo.isiOSAppOnMac {
@@ -17,7 +17,7 @@ enum UserIndicatorType: Equatable {
static var toast: Self {
.toast(progress: .none)
}
static var modal: Self {
.modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: false)
}
@@ -54,7 +54,7 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
guard delayedIndicators.contains(indicator.id) else {
return
}
enqueue(indicator: indicator)
}
} else {
@@ -85,7 +85,7 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
}
// MARK: - Private
private func enqueue(indicator: UserIndicator) {
retractIndicatorWithId(indicator.id)
indicatorQueue.append(indicator)
@@ -13,7 +13,7 @@ import SwiftUI
struct UserIndicatorModalView: View {
let indicator: UserIndicator
@State private var progressFraction = 0.0
var body: some View {
ZStack {
VStack(spacing: 12.0) {
@@ -22,7 +22,7 @@ struct UserIndicatorModalView: View {
} else if case .published = indicator.progress {
ProgressView(value: progressFraction)
}
HStack(spacing: 8) {
if let icon = indicator.icon {
CompoundIcon(icon, size: iconSize, relativeTo: titleFont)
@@ -20,7 +20,7 @@ extension EstimatedWaveform {
guard maxSamplesCount > 0 else {
return []
}
// Filter the data to keep only the expected number of samples
let result: [UInt16]
if data.count > maxSamplesCount {
@@ -32,7 +32,7 @@ extension EstimatedWaveform {
} else {
result = data
}
// Normalize the sample in the allowed range
return result.map { Float($0) / Float(Self.dataRange.upperBound) }
}
@@ -10,14 +10,14 @@ import SwiftUI
struct PlaybackSpeedButton: View {
let speed: AudioPlaybackSpeed
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
ZStack {
Text(speed.placeholder)
.font(.compound.bodyXSSemibold)
.hidden()
Text(speed.label)
.font(.compound.bodyXSSemibold)
.foregroundColor(.compound.iconSecondary)
@@ -44,7 +44,7 @@ struct VoiceMessageButton: View {
self.state = state
self.action = action
}
var body: some View {
Button(action: action) {
buttonLabel
@@ -58,7 +58,7 @@ struct VoiceMessageButton: View {
.disabled(state == .loading)
.accessibilityLabel(accessibilityLabel)
}
@ViewBuilder
private var buttonLabel: some View {
switch state {
@@ -87,7 +87,7 @@ private struct VoiceMessageButtonStyle: ButtonStyle {
@Environment(\.isEnabled) var isEnabled: Bool
let color: Color
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(isEnabled ? color.opacity(configuration.isPressed ? 0.6 : 1) : .compound.iconDisabled)
@@ -11,7 +11,7 @@ import SwiftUI
struct WaveformCursorView: View {
var color: Color = .compound.iconAccentTertiary
var body: some View {
RoundedRectangle(cornerRadius: 1)
.fill(color)
@@ -19,11 +19,11 @@ private struct WaveformInteractionModifier: ViewModifier {
let progress: Double
let showCursor: Bool
let onSeek: (Double) -> Void
@ScaledMetric private var cursorVisibleWidth = 2.0
@ScaledMetric private var cursorVisibleHeight = 24.0
private let cursorInteractiveSize: CGFloat = 50
func body(content: Content) -> some View {
GeometryReader { geometry in
content
@@ -52,6 +52,6 @@ private struct WaveformInteractionModifier: ViewModifier {
.coordinateSpace(name: Self.namespaceName)
.animation(nil, value: progress)
}
private static let namespaceName = "voice-message-waveform"
}