Tweaks discovered when using Compound overrides. (#4456)

- Fixes a bug where gradients wouldn't use the latest tokens after first use.
- Makes the bloom aware of colour overrides (and discovers a bug with dark mode).
- Decouples HomeScreenCell from the HomeScreenViewModel.Context (so it can be previewed with colour overrides).
This commit is contained in:
Doug
2025-09-02 09:40:03 +01:00
committed by GitHub
parent 40fb20bfb9
commit 441f5ae975
8 changed files with 50 additions and 36 deletions
+1 -1
View File
@@ -9406,7 +9406,7 @@
repositoryURL = "https://github.com/element-hq/compound-ios"; repositoryURL = "https://github.com/element-hq/compound-ios";
requirement = { requirement = {
kind = revision; kind = revision;
revision = 8d7ca2e413026735b724044847ff1ae3b40563f0; revision = 4ad0bd0acd825a905bb4955b32979fe34cc538eb;
}; };
}; };
F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */ = { F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */ = {
@@ -15,7 +15,7 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/compound-ios", "location" : "https://github.com/element-hq/compound-ios",
"state" : { "state" : {
"revision" : "8d7ca2e413026735b724044847ff1ae3b40563f0" "revision" : "4ad0bd0acd825a905bb4955b32979fe34cc538eb"
} }
}, },
{ {
@@ -275,8 +275,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing", "location" : "https://github.com/pointfreeco/swift-snapshot-testing",
"state" : { "state" : {
"revision" : "37230a37e83f1b7023be08e1b1a2603fcb1567fb", "revision" : "1be8144023c367c5de701a6313ed29a3a10bf59b",
"version" : "1.18.4" "version" : "1.18.3"
} }
}, },
{ {
@@ -67,17 +67,10 @@ struct ShimmerModifier: ViewModifier {
} }
struct ShimmerOverlay_Previews: PreviewProvider, TestablePreview { struct ShimmerOverlay_Previews: PreviewProvider, TestablePreview {
static let viewModel = HomeScreenViewModel(userSession: UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "")))),
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
notificationManager: NotificationManagerMock(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
static var previews: some View { static var previews: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
ForEach(0...8, id: \.self) { _ in ForEach(0...8, id: \.self) { _ in
HomeScreenRoomCell(room: .placeholder(), context: viewModel.context, isSelected: false) HomeScreenRoomCell(room: .placeholder(), isSelected: false, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
} }
} }
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
@@ -17,10 +17,12 @@ extension View {
} }
private struct BloomModifier: ViewModifier { private struct BloomModifier: ViewModifier {
@Environment(\.colorScheme) private var colorScheme
@State private var standardAppearance = UINavigationBarAppearance() @State private var standardAppearance = UINavigationBarAppearance()
@State private var scrollEdgeAppearance = UINavigationBarAppearance() @State private var scrollEdgeAppearance = UINavigationBarAppearance()
@State private var bloomGradientImage: UIImage? @State private var bloom = Bloom()
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
@@ -28,33 +30,39 @@ private struct BloomModifier: ViewModifier {
} }
private func configureBloom(controller: UIViewController) { private func configureBloom(controller: UIViewController) {
guard controller.navigationItem.standardAppearance != standardAppearance, if controller.navigationItem.standardAppearance == standardAppearance,
controller.navigationItem.scrollEdgeAppearance != scrollEdgeAppearance else { controller.navigationItem.scrollEdgeAppearance == scrollEdgeAppearance,
canUse(bloom) {
return return
} }
let image = makeBloomImage() let bloom = makeBloom()
standardAppearance.configureWithDefaultBackground() standardAppearance.configureWithDefaultBackground()
standardAppearance.backgroundImage = image standardAppearance.backgroundImage = bloom.image
standardAppearance.backgroundImageContentMode = .scaleToFill standardAppearance.backgroundImageContentMode = .scaleToFill
controller.navigationItem.standardAppearance = standardAppearance controller.navigationItem.standardAppearance = standardAppearance
scrollEdgeAppearance.configureWithTransparentBackground() scrollEdgeAppearance.configureWithTransparentBackground()
scrollEdgeAppearance.backgroundImage = image scrollEdgeAppearance.backgroundImage = bloom.image
scrollEdgeAppearance.backgroundImageContentMode = .scaleToFill scrollEdgeAppearance.backgroundImageContentMode = .scaleToFill
scrollEdgeAppearance.backgroundColor = .compound.bgCanvasDefault scrollEdgeAppearance.backgroundColor = .compound.bgCanvasDefault
controller.navigationItem.scrollEdgeAppearance = scrollEdgeAppearance controller.navigationItem.scrollEdgeAppearance = scrollEdgeAppearance
} }
private func makeBloomImage() -> UIImage? { private func makeBloom() -> Bloom {
if let bloomGradientImage { if bloom.image != nil, canUse(bloom) {
return bloomGradientImage return bloom
} }
let newImage = ImageRenderer(content: bloomGradient).uiImage // There's a bug somewhere when rendering in dark mode (which we've mistakenly not been doing)
Task { bloomGradientImage = newImage } // which results in the first 5 stops not having any alpha, only the last one…
return newImage let newImage = ImageRenderer(content: bloomGradient /* .colorScheme(colorScheme) */ ).uiImage
bloom.image = newImage
bloom.colorScheme = colorScheme
bloom.baseColor = .compound.gradientSubtleStop1
return bloom
} }
private var bloomGradient: some View { private var bloomGradient: some View {
@@ -64,4 +72,18 @@ private struct BloomModifier: ViewModifier {
.ignoresSafeArea(edges: .all) .ignoresSafeArea(edges: .all)
.frame(width: 256, height: 256) .frame(width: 256, height: 256)
} }
private func canUse(_ bloom: Bloom) -> Bool {
// Don't check for a nil image in here, there's no point re-rendering over and over if the render fails.
bloom.colorScheme == colorScheme && bloom.baseColor == .compound.gradientSubtleStop1
}
// This is a class to avoid a "Modifying state during view update" warning when storing
// the result on the same run-loop - we want to avoid dispatching that to the next loop as
// that can result in further (unnecessary) renders being made.
class Bloom {
var image: UIImage?
var colorScheme: ColorScheme?
var baseColor: Color?
}
} }
@@ -27,7 +27,7 @@ struct HomeScreenContent: View {
case .skeletons: case .skeletons:
LazyVStack(spacing: 0) { LazyVStack(spacing: 0) {
ForEach(context.viewState.visibleRooms) { room in ForEach(context.viewState.visibleRooms) { room in
HomeScreenRoomCell(room: room, context: context, isSelected: false) HomeScreenRoomCell(room: room, isSelected: false, mediaProvider: context.mediaProvider, action: context.send)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.shimmer() // Putting this directly on the LazyVStack creates an accordion animation on iOS 16. .shimmer() // Putting this directly on the LazyVStack creates an accordion animation on iOS 16.
} }
@@ -14,8 +14,9 @@ struct HomeScreenRoomCell: View {
@Environment(\.redactionReasons) private var redactionReasons @Environment(\.redactionReasons) private var redactionReasons
let room: HomeScreenRoom let room: HomeScreenRoom
let context: HomeScreenViewModel.Context
let isSelected: Bool let isSelected: Bool
let mediaProvider: MediaProviderProtocol!
let action: (HomeScreenViewAction) -> Void
private let verticalInsets = 12.0 private let verticalInsets = 12.0
private let horizontalInsets = 16.0 private let horizontalInsets = 16.0
@@ -23,7 +24,7 @@ struct HomeScreenRoomCell: View {
var body: some View { var body: some View {
Button { Button {
if let roomID = room.roomID { if let roomID = room.roomID {
context.send(viewAction: .selectRoom(roomIdentifier: roomID)) action(.selectRoom(roomIdentifier: roomID))
} }
} label: { } label: {
HStack(spacing: 16.0) { HStack(spacing: 16.0) {
@@ -51,7 +52,7 @@ struct HomeScreenRoomCell: View {
if dynamicTypeSize < .accessibility3 { if dynamicTypeSize < .accessibility3 {
RoomAvatarImage(avatar: room.avatar, RoomAvatarImage(avatar: room.avatar,
avatarSize: .room(on: .chats), avatarSize: .room(on: .chats),
mediaProvider: context.mediaProvider) mediaProvider: mediaProvider)
.dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1) .dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1)
.accessibilityHidden(true) .accessibilityHidden(true)
} }
@@ -167,27 +168,25 @@ private extension View {
struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview { struct HomeScreenRoomCell_Previews: PreviewProvider, TestablePreview {
static let summaryProviderGeneric = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))) static let summaryProviderGeneric = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))
static let viewModelGeneric = makeViewModel(roomSummaryProvider: summaryProviderGeneric)
static let genericRooms = summaryProviderGeneric.roomListPublisher.value.compactMap(mockRoom) static let genericRooms = summaryProviderGeneric.roomListPublisher.value.compactMap(mockRoom)
static let summaryProviderForNotificationsState = RoomSummaryProviderMock(.init(state: .loaded(.mockRoomsWithNotificationsState))) static let summaryProviderForNotificationsState = RoomSummaryProviderMock(.init(state: .loaded(.mockRoomsWithNotificationsState)))
static let viewModelForNotificationsState = makeViewModel(roomSummaryProvider: summaryProviderForNotificationsState)
static let notificationsStateRooms = summaryProviderForNotificationsState.roomListPublisher.value.compactMap(mockRoom) static let notificationsStateRooms = summaryProviderForNotificationsState.roomListPublisher.value.compactMap(mockRoom)
static var previews: some View { static var previews: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
ForEach(genericRooms) { room in ForEach(genericRooms) { room in
HomeScreenRoomCell(room: room, context: viewModelGeneric.context, isSelected: false) HomeScreenRoomCell(room: room, isSelected: false, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
} }
HomeScreenRoomCell(room: .placeholder(), context: viewModelGeneric.context, isSelected: false) HomeScreenRoomCell(room: .placeholder(), isSelected: false, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
} }
.previewDisplayName("Generic") .previewDisplayName("Generic")
VStack(spacing: 0) { VStack(spacing: 0) {
ForEach(notificationsStateRooms) { room in ForEach(notificationsStateRooms) { room in
HomeScreenRoomCell(room: room, context: viewModelForNotificationsState.context, isSelected: false) HomeScreenRoomCell(room: room, isSelected: false, mediaProvider: MediaProviderMock(configuration: .init())) { _ in }
} }
} }
.previewLayout(.sizeThatFits) .previewLayout(.sizeThatFits)
@@ -24,7 +24,7 @@ struct HomeScreenRoomList: View {
ForEach(context.viewState.visibleRooms) { room in ForEach(context.viewState.visibleRooms) { room in
switch room.type { switch room.type {
case .placeholder: case .placeholder:
HomeScreenRoomCell(room: room, context: context, isSelected: false) HomeScreenRoomCell(room: room, isSelected: false, mediaProvider: context.mediaProvider, action: context.send)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
case .invite: case .invite:
HomeScreenInviteCell(room: room, context: context, hideInviteAvatars: context.viewState.hideInviteAvatars) HomeScreenInviteCell(room: room, context: context, hideInviteAvatars: context.viewState.hideInviteAvatars)
@@ -33,7 +33,7 @@ struct HomeScreenRoomList: View {
case .room: case .room:
let isSelected = context.viewState.selectedRoomID == room.id let isSelected = context.viewState.selectedRoomID == room.id
HomeScreenRoomCell(room: room, context: context, isSelected: isSelected) HomeScreenRoomCell(room: room, isSelected: isSelected, mediaProvider: context.mediaProvider, action: context.send)
.contextMenu { .contextMenu {
if room.badges.isDotShown { if room.badges.isDotShown {
Button { Button {
+1 -1
View File
@@ -72,7 +72,7 @@ packages:
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
Compound: Compound:
url: https://github.com/element-hq/compound-ios url: https://github.com/element-hq/compound-ios
revision: 8d7ca2e413026735b724044847ff1ae3b40563f0 revision: 4ad0bd0acd825a905bb4955b32979fe34cc538eb
# path: ../compound-ios # path: ../compound-ios
AnalyticsEvents: AnalyticsEvents:
url: https://github.com/matrix-org/matrix-analytics-events url: https://github.com/matrix-org/matrix-analytics-events