Some random tweaks made on a train 🚆 (#4636)
* 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.
This commit is contained in:
@@ -11,6 +11,7 @@ import MatrixRustSDK
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum ChatsFlowCoordinatorAction {
|
enum ChatsFlowCoordinatorAction {
|
||||||
|
case switchToChatsTab
|
||||||
case showSettings
|
case showSettings
|
||||||
case showChatBackupSettings
|
case showChatBackupSettings
|
||||||
case sessionVerification(SessionVerificationScreenFlow)
|
case sessionVerification(SessionVerificationScreenFlow)
|
||||||
@@ -661,6 +662,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .select(let roomID):
|
case .select(let roomID):
|
||||||
dismissGlobalSearch()
|
dismissGlobalSearch()
|
||||||
handleAppRoute(.room(roomID: roomID, via: []), animated: true)
|
handleAppRoute(.room(roomID: roomID, via: []), animated: true)
|
||||||
|
actionsSubject.send(.switchToChatsTab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
|
case .switchToChatsTab:
|
||||||
|
navigationTabCoordinator.selectedTab = .chats
|
||||||
case .showSettings:
|
case .showSettings:
|
||||||
handleAppRoute(.settings, animated: true)
|
handleAppRoute(.settings, animated: true)
|
||||||
case .showChatBackupSettings:
|
case .showChatBackupSettings:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SwiftUI
|
|||||||
struct LoadableAvatarImage: View {
|
struct LoadableAvatarImage: View {
|
||||||
private let url: URL?
|
private let url: URL?
|
||||||
private let name: String?
|
private let name: String?
|
||||||
private let contentID: String?
|
private let contentID: String
|
||||||
private let isSpace: Bool
|
private let isSpace: Bool
|
||||||
private let avatarSize: Avatars.Size
|
private let avatarSize: Avatars.Size
|
||||||
private let mediaProvider: MediaProviderProtocol?
|
private let mediaProvider: MediaProviderProtocol?
|
||||||
@@ -20,7 +20,7 @@ struct LoadableAvatarImage: View {
|
|||||||
|
|
||||||
init(url: URL?,
|
init(url: URL?,
|
||||||
name: String?,
|
name: String?,
|
||||||
contentID: String?,
|
contentID: String,
|
||||||
isSpace: Bool = false,
|
isSpace: Bool = false,
|
||||||
avatarSize: Avatars.Size,
|
avatarSize: Avatars.Size,
|
||||||
mediaProvider: MediaProviderProtocol?,
|
mediaProvider: MediaProviderProtocol?,
|
||||||
|
|||||||
@@ -5,28 +5,16 @@
|
|||||||
// Please see LICENSE files in the repository root for full details.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Compound
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct OverridableAvatarImage: View {
|
struct OverridableAvatarImage: View {
|
||||||
private let overrideURL: URL?
|
let overrideURL: URL?
|
||||||
private let url: URL?
|
let url: URL?
|
||||||
private let name: String?
|
let name: String?
|
||||||
private let contentID: String?
|
let contentID: String
|
||||||
private let avatarSize: Avatars.Size
|
let avatarSize: Avatars.Size
|
||||||
private let mediaProvider: MediaProviderProtocol?
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let overrideURL {
|
if let overrideURL {
|
||||||
@@ -37,7 +25,7 @@ struct OverridableAvatarImage: View {
|
|||||||
} placeholder: {
|
} placeholder: {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
}
|
}
|
||||||
.frame(width: frameSize, height: frameSize)
|
.scaledFrame(size: avatarSize.value)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
} else {
|
} else {
|
||||||
LoadableAvatarImage(url: url,
|
LoadableAvatarImage(url: url,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ struct PlaceholderAvatarImage: View {
|
|||||||
@Environment(\.redactionReasons) private var redactionReasons
|
@Environment(\.redactionReasons) private var redactionReasons
|
||||||
|
|
||||||
private let textForImage: String
|
private let textForImage: String
|
||||||
private let contentID: String?
|
private let contentID: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
@@ -32,9 +32,9 @@ struct PlaceholderAvatarImage: View {
|
|||||||
.aspectRatio(1, contentMode: .fill)
|
.aspectRatio(1, contentMode: .fill)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(name: String?, contentID: String?) {
|
init(name: String?, contentID: String) {
|
||||||
let baseName = name ?? contentID?.trimmingCharacters(in: .punctuationCharacters)
|
let baseName = name ?? contentID.trimmingCharacters(in: .punctuationCharacters)
|
||||||
textForImage = baseName?.first?.uppercased() ?? ""
|
textForImage = baseName.first?.uppercased() ?? ""
|
||||||
self.contentID = contentID
|
self.contentID = contentID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +47,7 @@ struct PlaceholderAvatarImage: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var avatarColor: DecorativeColor? {
|
private var avatarColor: DecorativeColor? {
|
||||||
guard let contentID else {
|
Color.compound.decorativeColor(for: contentID)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return Color.compound.decorativeColor(for: contentID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ struct RoomAvatarImage: View {
|
|||||||
// We will expand upon this with more stack sizes in the future.
|
// We will expand upon this with more stack sizes in the future.
|
||||||
if users.count == 0 {
|
if users.count == 0 {
|
||||||
let _ = assertionFailure("We should never pass empty heroes here.")
|
let _ = assertionFailure("We should never pass empty heroes here.")
|
||||||
PlaceholderAvatarImage(name: nil, contentID: nil)
|
PlaceholderAvatarImage(name: nil, contentID: "")
|
||||||
} else if users.count == 2 {
|
} else if users.count == 2 {
|
||||||
let clusterSize = avatarSize.value * 1.6
|
let clusterSize = avatarSize.value * 1.6
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ struct TimelineThreadSummaryView: View {
|
|||||||
|
|
||||||
LoadableAvatarImage(url: sender?.avatarURL,
|
LoadableAvatarImage(url: sender?.avatarURL,
|
||||||
name: sender?.displayName,
|
name: sender?.displayName,
|
||||||
contentID: sender?.id,
|
contentID: senderID,
|
||||||
avatarSize: .user(on: .threadSummary),
|
avatarSize: .user(on: .threadSummary),
|
||||||
mediaProvider: context.mediaProvider)
|
mediaProvider: context.mediaProvider)
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:560872aaa26dfa28023e7f73145ad82959dcd69a0afdc1174465108182d5e3b8
|
oid sha256:f52d1b62d203f60d05624be87c8044946dbfb4e0d2065b486c48ab34116d1ea0
|
||||||
size 180990
|
size 183671
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c7f55a94cf459288c4a34c8b553038be8eb41acf7ad67268eea3ca68a4a5a9a5
|
oid sha256:8ff95fcc75e71bd2a6b1225a6ad56eaff8fa45a00aae77ea738e793976100f69
|
||||||
size 143969
|
size 145788
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ struct BuildSDK: AsyncParsableCommand {
|
|||||||
Run the following command to install them:
|
Run the following command to install them:
|
||||||
|
|
||||||
rustup target add \(missingTargets.joined(separator: " "))
|
rustup target add \(missingTargets.joined(separator: " "))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ struct ColorsScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ColorItem: View {
|
struct ColorItem: View {
|
||||||
|
@Environment(\.self) private var environment
|
||||||
|
|
||||||
let color: Color
|
let color: Color
|
||||||
let name: String
|
let name: String
|
||||||
|
|
||||||
@@ -30,10 +32,11 @@ struct ColorItem: View {
|
|||||||
Text(name)
|
Text(name)
|
||||||
.font(.compound.bodyLG)
|
.font(.compound.bodyLG)
|
||||||
.foregroundColor(.compound.textPrimary)
|
.foregroundColor(.compound.textPrimary)
|
||||||
Text(color.hexValue())
|
Text(color.hexValue(in: environment))
|
||||||
.font(.compound.bodySM.monospaced())
|
.font(.compound.bodySM.monospaced())
|
||||||
.foregroundColor(.compound.textSecondary)
|
.foregroundColor(.compound.textSecondary)
|
||||||
}
|
}
|
||||||
|
.layoutPriority(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,24 +58,24 @@ struct ColorItem: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension Color {
|
private extension Color {
|
||||||
func hexValue() -> String {
|
func hexValue(in environment: EnvironmentValues) -> String {
|
||||||
let uiColor = UIColor(self)
|
let resolved = resolve(in: environment)
|
||||||
|
return if resolved.opacity == 1 {
|
||||||
var red: CGFloat = 0
|
"#\(resolved.red.asHex)\(resolved.green.asHex)\(resolved.blue.asHex)"
|
||||||
var green: CGFloat = 0
|
} else {
|
||||||
var blue: CGFloat = 0
|
"#\(resolved.red.asHex)\(resolved.green.asHex)\(resolved.blue.asHex) (\(resolved.opacity.asPercentage) opacity)"
|
||||||
var alpha: CGFloat = 0
|
}
|
||||||
|
|
||||||
uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
|
||||||
|
|
||||||
return "#\(red.asHex)\(green.asHex)\(blue.asHex)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension CGFloat {
|
private extension Float {
|
||||||
var asHex: String {
|
var asHex: String {
|
||||||
String(format: "%02X", Int((self * 255).rounded()))
|
String(format: "%02X", Int((self * 255).rounded()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var asPercentage: String {
|
||||||
|
String(format: "%.0f%%", self * 100)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ColorsScreen_Previews: PreviewProvider {
|
struct ColorsScreen_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -14,21 +14,18 @@ public extension View {
|
|||||||
@MainActor
|
@MainActor
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func compoundSearchField() -> some View {
|
func compoundSearchField() -> some View {
|
||||||
if #available(iOS 26, *) {
|
introspect(.navigationStack, on: .supportedVersions, scope: .ancestor) { navigationController in
|
||||||
self
|
// Uses the navigation stack as .searchField is unreliable when pushing the second search bar, during the create rooms flow.
|
||||||
} else {
|
guard let searchController = navigationController.navigationBar.topItem?.searchController else { return }
|
||||||
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.
|
// Ported from Riot iOS as this is the only reliable way to get the exact look we want.
|
||||||
guard let searchController = navigationController.navigationBar.topItem?.searchController else { return }
|
// However this is fragile and tied to gutwrenching the current UISearchBar internals.
|
||||||
|
|
||||||
// Ported from Riot iOS as this is the only reliable way to get the exact look we want.
|
let searchTextField = searchController.searchBar.searchTextField
|
||||||
// However this is fragile and tied to gutwrenching the current UISearchBar internals.
|
searchTextField.tintColor = .compound.iconAccentTertiary
|
||||||
let textColor = UIColor.compound.textPrimary
|
|
||||||
|
if #unavailable(iOS 26.0) {
|
||||||
let placeholderColor = UIColor.compound.textSecondary
|
let placeholderColor = UIColor.compound.textSecondary
|
||||||
let textFieldTintColor = UIColor.compound.iconAccentTertiary
|
|
||||||
let textFieldBackgroundColor = UIColor.compound._bgSubtleSecondaryAlpha
|
|
||||||
|
|
||||||
let searchTextField = searchController.searchBar.searchTextField
|
|
||||||
|
|
||||||
// Magnifying glass icon.
|
// Magnifying glass icon.
|
||||||
let leftImageView = searchTextField.leftView as? UIImageView
|
let leftImageView = searchTextField.leftView as? UIImageView
|
||||||
@@ -43,9 +40,8 @@ public extension View {
|
|||||||
clearButton?.tintColor = placeholderColor
|
clearButton?.tintColor = placeholderColor
|
||||||
|
|
||||||
// Text field.
|
// Text field.
|
||||||
searchTextField.textColor = textColor
|
searchTextField.textColor = .compound.textPrimary
|
||||||
searchTextField.backgroundColor = textFieldBackgroundColor
|
searchTextField.backgroundColor = .compound._bgSubtleSecondaryAlpha
|
||||||
searchTextField.tintColor = textFieldTintColor
|
|
||||||
|
|
||||||
// Hide the effect views so we can use the rounded rect style without any materials.
|
// Hide the effect views so we can use the rounded rect style without any materials.
|
||||||
let effectBackgroundTop = searchTextField.value(forKey: "_effectBackgroundTop") as? UIView
|
let effectBackgroundTop = searchTextField.value(forKey: "_effectBackgroundTop") as? UIView
|
||||||
|
|||||||
Reference in New Issue
Block a user