From bab9b894166f98d0799daad038c90008cf797915 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:04:54 +0200 Subject: [PATCH] =?UTF-8?q?Some=20random=20tweaks=20made=20on=20a=20train?= =?UTF-8?q?=20=F0=9F=9A=86=20(#4636)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix the search text field's tint colour. * Don't allow optional content IDs in the placeholder avatar. * Use SwiftUI to resolve the hex values in the Inspector app. This fixes incorrect values being shown in dark/high-contrast modes. * Fix a layout bug with the colour swatch in the Inspector app on iPhone. * Switch to the chats tab when selecting a room with the global search screen. * Run the latest SwiftFormat. --- .../ChatsFlowCoordinator.swift | 2 ++ .../UserSessionFlowCoordinator.swift | 2 ++ .../SwiftUI/Views/LoadableAvatarImage.swift | 4 +-- .../Views/OverridableAvatarImage.swift | 28 +++++------------ .../Views/PlaceholderAvatarImage.swift | 14 ++++----- .../Other/SwiftUI/Views/RoomAvatarImage.swift | 2 +- .../Threads/TimelineThreadSummaryView.swift | 2 +- ...cesAnnouncementSheetView.iPad-pseudo-0.png | 4 +-- ...nouncementSheetView.iPhone-16-pseudo-0.png | 4 +-- Tools/Sources/Commands/BuildSDK.swift | 2 +- .../Sources/Tokens/ColorsScreen.swift | 29 ++++++++++-------- .../Text Field Styles/SearchFieldStyle.swift | 30 ++++++++----------- 12 files changed, 55 insertions(+), 68 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift index f7f7ab4ac..650e4a012 100644 --- a/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/ChatsFlowCoordinator.swift @@ -11,6 +11,7 @@ import MatrixRustSDK import SwiftUI enum ChatsFlowCoordinatorAction { + case switchToChatsTab case showSettings case showChatBackupSettings case sessionVerification(SessionVerificationScreenFlow) @@ -661,6 +662,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol { case .select(let roomID): dismissGlobalSearch() handleAppRoute(.room(roomID: roomID, via: []), animated: true) + actionsSubject.send(.switchToChatsTab) } } .store(in: &cancellables) diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index f3ddf7aea..22b7fc30c 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -193,6 +193,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { .sink { [weak self] action in guard let self else { return } switch action { + case .switchToChatsTab: + navigationTabCoordinator.selectedTab = .chats case .showSettings: handleAppRoute(.settings, animated: true) case .showChatBackupSettings: diff --git a/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift b/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift index 2b2ec87f4..323ffd202 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift @@ -10,7 +10,7 @@ import SwiftUI struct LoadableAvatarImage: View { private let url: URL? private let name: String? - private let contentID: String? + private let contentID: String private let isSpace: Bool private let avatarSize: Avatars.Size private let mediaProvider: MediaProviderProtocol? @@ -20,7 +20,7 @@ struct LoadableAvatarImage: View { init(url: URL?, name: String?, - contentID: String?, + contentID: String, isSpace: Bool = false, avatarSize: Avatars.Size, mediaProvider: MediaProviderProtocol?, diff --git a/ElementX/Sources/Other/SwiftUI/Views/OverridableAvatarImage.swift b/ElementX/Sources/Other/SwiftUI/Views/OverridableAvatarImage.swift index 5e3b96732..db5ec4cbf 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/OverridableAvatarImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/OverridableAvatarImage.swift @@ -5,28 +5,16 @@ // Please see LICENSE files in the repository root for full details. // +import Compound import SwiftUI struct OverridableAvatarImage: View { - private let overrideURL: URL? - private let url: URL? - private let name: String? - private let contentID: String? - private let avatarSize: Avatars.Size - private let mediaProvider: MediaProviderProtocol? - - @ScaledMetric private var frameSize: CGFloat - - init(overrideURL: URL?, url: URL?, name: String?, contentID: String?, avatarSize: Avatars.Size, mediaProvider: MediaProviderProtocol?) { - self.overrideURL = overrideURL - self.url = url - self.name = name - self.contentID = contentID - self.avatarSize = avatarSize - self.mediaProvider = mediaProvider - - _frameSize = ScaledMetric(wrappedValue: avatarSize.value) - } + let overrideURL: URL? + let url: URL? + let name: String? + let contentID: String + let avatarSize: Avatars.Size + let mediaProvider: MediaProviderProtocol? var body: some View { if let overrideURL { @@ -37,7 +25,7 @@ struct OverridableAvatarImage: View { } placeholder: { ProgressView() } - .frame(width: frameSize, height: frameSize) + .scaledFrame(size: avatarSize.value) .clipShape(Circle()) } else { LoadableAvatarImage(url: url, diff --git a/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift b/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift index 9b6a9be13..58f0d2ab8 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift @@ -12,7 +12,7 @@ struct PlaceholderAvatarImage: View { @Environment(\.redactionReasons) private var redactionReasons private let textForImage: String - private let contentID: String? + private let contentID: String var body: some View { GeometryReader { geometry in @@ -32,9 +32,9 @@ struct PlaceholderAvatarImage: View { .aspectRatio(1, contentMode: .fill) } - init(name: String?, contentID: String?) { - let baseName = name ?? contentID?.trimmingCharacters(in: .punctuationCharacters) - textForImage = baseName?.first?.uppercased() ?? "" + init(name: String?, contentID: String) { + let baseName = name ?? contentID.trimmingCharacters(in: .punctuationCharacters) + textForImage = baseName.first?.uppercased() ?? "" self.contentID = contentID } @@ -47,11 +47,7 @@ struct PlaceholderAvatarImage: View { } private var avatarColor: DecorativeColor? { - guard let contentID else { - return nil - } - - return Color.compound.decorativeColor(for: contentID) + Color.compound.decorativeColor(for: contentID) } } diff --git a/ElementX/Sources/Other/SwiftUI/Views/RoomAvatarImage.swift b/ElementX/Sources/Other/SwiftUI/Views/RoomAvatarImage.swift index 0e865eb66..8595c2563 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/RoomAvatarImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/RoomAvatarImage.swift @@ -69,7 +69,7 @@ struct RoomAvatarImage: View { // We will expand upon this with more stack sizes in the future. if users.count == 0 { let _ = assertionFailure("We should never pass empty heroes here.") - PlaceholderAvatarImage(name: nil, contentID: nil) + PlaceholderAvatarImage(name: nil, contentID: "") } else if users.count == 2 { let clusterSize = avatarSize.value * 1.6 ZStack { diff --git a/ElementX/Sources/Screens/Timeline/View/Threads/TimelineThreadSummaryView.swift b/ElementX/Sources/Screens/Timeline/View/Threads/TimelineThreadSummaryView.swift index a893116bf..60350b285 100644 --- a/ElementX/Sources/Screens/Timeline/View/Threads/TimelineThreadSummaryView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Threads/TimelineThreadSummaryView.swift @@ -133,7 +133,7 @@ struct TimelineThreadSummaryView: View { LoadableAvatarImage(url: sender?.avatarURL, name: sender?.displayName, - contentID: sender?.id, + contentID: senderID, avatarSize: .user(on: .threadSummary), mediaProvider: context.mediaProvider) .accessibilityHidden(true) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPad-pseudo-0.png index 932b303e6..915ab65f9 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPad-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPad-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:560872aaa26dfa28023e7f73145ad82959dcd69a0afdc1174465108182d5e3b8 -size 180990 +oid sha256:f52d1b62d203f60d05624be87c8044946dbfb4e0d2065b486c48ab34116d1ea0 +size 183671 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPhone-16-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPhone-16-pseudo-0.png index cb2d2999b..5918b4c65 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPhone-16-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/spacesAnnouncementSheetView.iPhone-16-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7f55a94cf459288c4a34c8b553038be8eb41acf7ad67268eea3ca68a4a5a9a5 -size 143969 +oid sha256:8ff95fcc75e71bd2a6b1225a6ad56eaff8fa45a00aae77ea738e793976100f69 +size 145788 diff --git a/Tools/Sources/Commands/BuildSDK.swift b/Tools/Sources/Commands/BuildSDK.swift index 59d146e80..b089cd0a7 100644 --- a/Tools/Sources/Commands/BuildSDK.swift +++ b/Tools/Sources/Commands/BuildSDK.swift @@ -47,7 +47,7 @@ struct BuildSDK: AsyncParsableCommand { Run the following command to install them: rustup target add \(missingTargets.joined(separator: " ")) - + """ default: return nil diff --git a/compound-ios/Inspector/Sources/Tokens/ColorsScreen.swift b/compound-ios/Inspector/Sources/Tokens/ColorsScreen.swift index 5914620d2..2004f175a 100644 --- a/compound-ios/Inspector/Sources/Tokens/ColorsScreen.swift +++ b/compound-ios/Inspector/Sources/Tokens/ColorsScreen.swift @@ -19,6 +19,8 @@ struct ColorsScreen: View { } struct ColorItem: View { + @Environment(\.self) private var environment + let color: Color let name: String @@ -30,10 +32,11 @@ struct ColorItem: View { Text(name) .font(.compound.bodyLG) .foregroundColor(.compound.textPrimary) - Text(color.hexValue()) + Text(color.hexValue(in: environment)) .font(.compound.bodySM.monospaced()) .foregroundColor(.compound.textSecondary) } + .layoutPriority(1) } } @@ -55,24 +58,24 @@ struct ColorItem: View { } private extension Color { - func hexValue() -> String { - let uiColor = UIColor(self) - - var red: CGFloat = 0 - var green: CGFloat = 0 - var blue: CGFloat = 0 - var alpha: CGFloat = 0 - - uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - - return "#\(red.asHex)\(green.asHex)\(blue.asHex)" + func hexValue(in environment: EnvironmentValues) -> String { + let resolved = resolve(in: environment) + return if resolved.opacity == 1 { + "#\(resolved.red.asHex)\(resolved.green.asHex)\(resolved.blue.asHex)" + } else { + "#\(resolved.red.asHex)\(resolved.green.asHex)\(resolved.blue.asHex) (\(resolved.opacity.asPercentage) opacity)" + } } } -private extension CGFloat { +private extension Float { var asHex: String { String(format: "%02X", Int((self * 255).rounded())) } + + var asPercentage: String { + String(format: "%.0f%%", self * 100) + } } struct ColorsScreen_Previews: PreviewProvider { diff --git a/compound-ios/Sources/Compound/Text Field Styles/SearchFieldStyle.swift b/compound-ios/Sources/Compound/Text Field Styles/SearchFieldStyle.swift index c16beba1f..7983fda21 100644 --- a/compound-ios/Sources/Compound/Text Field Styles/SearchFieldStyle.swift +++ b/compound-ios/Sources/Compound/Text Field Styles/SearchFieldStyle.swift @@ -14,21 +14,18 @@ public extension View { @MainActor @ViewBuilder func compoundSearchField() -> some View { - if #available(iOS 26, *) { - self - } else { - introspect(.navigationStack, on: .supportedVersions, scope: .ancestor) { navigationController in - // Uses the navigation stack as .searchField is unreliable when pushing the second search bar, during the create rooms flow. - guard let searchController = navigationController.navigationBar.topItem?.searchController else { return } - - // Ported from Riot iOS as this is the only reliable way to get the exact look we want. - // However this is fragile and tied to gutwrenching the current UISearchBar internals. - let textColor = UIColor.compound.textPrimary + introspect(.navigationStack, on: .supportedVersions, scope: .ancestor) { navigationController in + // Uses the navigation stack as .searchField is unreliable when pushing the second search bar, during the create rooms flow. + guard let searchController = navigationController.navigationBar.topItem?.searchController else { return } + + // Ported from Riot iOS as this is the only reliable way to get the exact look we want. + // However this is fragile and tied to gutwrenching the current UISearchBar internals. + + let searchTextField = searchController.searchBar.searchTextField + searchTextField.tintColor = .compound.iconAccentTertiary + + if #unavailable(iOS 26.0) { let placeholderColor = UIColor.compound.textSecondary - let textFieldTintColor = UIColor.compound.iconAccentTertiary - let textFieldBackgroundColor = UIColor.compound._bgSubtleSecondaryAlpha - - let searchTextField = searchController.searchBar.searchTextField // Magnifying glass icon. let leftImageView = searchTextField.leftView as? UIImageView @@ -43,9 +40,8 @@ public extension View { clearButton?.tintColor = placeholderColor // Text field. - searchTextField.textColor = textColor - searchTextField.backgroundColor = textFieldBackgroundColor - searchTextField.tintColor = textFieldTintColor + searchTextField.textColor = .compound.textPrimary + searchTextField.backgroundColor = .compound._bgSubtleSecondaryAlpha // Hide the effect views so we can use the rounded rect style without any materials. let effectBackgroundTop = searchTextField.value(forKey: "_effectBackgroundTop") as? UIView