mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Fix a bug where the timeline disappeared when VoiceOver was enabled. (#4701)
This commit is contained in:
@@ -164,6 +164,7 @@
|
|||||||
1A83DD22F3E6F76B13B6E2F9 /* VideoRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8616254EE40CA8BA5E9BC2 /* VideoRoomTimelineItemContent.swift */; };
|
1A83DD22F3E6F76B13B6E2F9 /* VideoRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8616254EE40CA8BA5E9BC2 /* VideoRoomTimelineItemContent.swift */; };
|
||||||
1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15E0017717EAE3A1D02D005 /* StaticLocationScreenCoordinator.swift */; };
|
1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15E0017717EAE3A1D02D005 /* StaticLocationScreenCoordinator.swift */; };
|
||||||
1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; };
|
1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; };
|
||||||
|
1AF4A82B4332CAD2DB3EB5DA /* TopBannerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78483F0143E185EDC6ECD741 /* TopBannerModifier.swift */; };
|
||||||
1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */; };
|
1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */; };
|
||||||
1B2F9F368619FFF8C63C87CC /* BugReportScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EECE8B331CD169790EF284F /* BugReportScreenViewModelTests.swift */; };
|
1B2F9F368619FFF8C63C87CC /* BugReportScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EECE8B331CD169790EF284F /* BugReportScreenViewModelTests.swift */; };
|
||||||
1B5B30839656AE2F957C6B1E /* test_pdf.pdf in Resources */ = {isa = PBXBuildFile; fileRef = BE98688578F8B0541D853695 /* test_pdf.pdf */; };
|
1B5B30839656AE2F957C6B1E /* test_pdf.pdf in Resources */ = {isa = PBXBuildFile; fileRef = BE98688578F8B0541D853695 /* test_pdf.pdf */; };
|
||||||
@@ -2115,6 +2116,7 @@
|
|||||||
7720ACAC6155AB7F9C70B546 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nb; path = nb.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
7720ACAC6155AB7F9C70B546 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nb; path = nb.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
7773CBFDBD458E0B7E270507 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = "<group>"; };
|
7773CBFDBD458E0B7E270507 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = "<group>"; };
|
||||||
780258F1B9D15E30549FF4BE /* NotificationSettingsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModel.swift; sourceTree = "<group>"; };
|
780258F1B9D15E30549FF4BE /* NotificationSettingsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
78483F0143E185EDC6ECD741 /* TopBannerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBannerModifier.swift; sourceTree = "<group>"; };
|
||||||
787E84119E626E2F0E0BFBE8 /* ManageRoomMemberSheetModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageRoomMemberSheetModels.swift; sourceTree = "<group>"; };
|
787E84119E626E2F0E0BFBE8 /* ManageRoomMemberSheetModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageRoomMemberSheetModels.swift; sourceTree = "<group>"; };
|
||||||
78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
78910787F967CBC6042A101E /* StartChatScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitCoordinatorTests.swift; sourceTree = "<group>"; };
|
78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -3599,6 +3601,7 @@
|
|||||||
A8558D41DD4B553A752C868A /* StackedAvatarsView.swift */,
|
A8558D41DD4B553A752C868A /* StackedAvatarsView.swift */,
|
||||||
E10765FBC83B34A3BC4ADB23 /* TimelineScrollToBottomButton.swift */,
|
E10765FBC83B34A3BC4ADB23 /* TimelineScrollToBottomButton.swift */,
|
||||||
16D353E10A64172D863769BF /* TombstonedAvatarImage.swift */,
|
16D353E10A64172D863769BF /* TombstonedAvatarImage.swift */,
|
||||||
|
78483F0143E185EDC6ECD741 /* TopBannerModifier.swift */,
|
||||||
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */,
|
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */,
|
||||||
AD529C89924EE32CE307F36F /* VisualListItem.swift */,
|
AD529C89924EE32CE307F36F /* VisualListItem.swift */,
|
||||||
);
|
);
|
||||||
@@ -8424,6 +8427,7 @@
|
|||||||
98EE4259A4A49BC757BA442C /* TimelineViewModel.swift in Sources */,
|
98EE4259A4A49BC757BA442C /* TimelineViewModel.swift in Sources */,
|
||||||
F8B2F5CBCF2A0E0798E8D646 /* TimelineViewModelProtocol.swift in Sources */,
|
F8B2F5CBCF2A0E0798E8D646 /* TimelineViewModelProtocol.swift in Sources */,
|
||||||
B3D8AA9988F8A000B162DCB5 /* TombstonedAvatarImage.swift in Sources */,
|
B3D8AA9988F8A000B162DCB5 /* TombstonedAvatarImage.swift in Sources */,
|
||||||
|
1AF4A82B4332CAD2DB3EB5DA /* TopBannerModifier.swift in Sources */,
|
||||||
6E44638FDF7D4B0F80EFA7EA /* TraceLogPack.swift in Sources */,
|
6E44638FDF7D4B0F80EFA7EA /* TraceLogPack.swift in Sources */,
|
||||||
126CBCF5B0145FA1377C1316 /* Tracing.swift in Sources */,
|
126CBCF5B0145FA1377C1316 /* Tracing.swift in Sources */,
|
||||||
E010DDE938032D3B8E84CC35 /* TracingHook.swift in Sources */,
|
E010DDE938032D3B8E84CC35 /* TracingHook.swift in Sources */,
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2025 Element Creations Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
/// Overlays the given banner view at the top edge of this view, using a
|
||||||
|
/// slide from the top edge when `isVisible` is toggled.
|
||||||
|
func topBanner(_ banner: some View, isVisible: Bool) -> some View {
|
||||||
|
overlay(alignment: .top) {
|
||||||
|
ZStack {
|
||||||
|
if isVisible {
|
||||||
|
banner.transition(.move(edge: .top))
|
||||||
|
} else {
|
||||||
|
// An equal amount of space needs to be reserved in order for the transition to work.
|
||||||
|
banner.hidden()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.animation(.elementDefault, value: isVisible)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,20 +33,15 @@ struct RoomScreen: View {
|
|||||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.scrollToBottom)
|
.accessibilityIdentifier(A11yIdentifiers.roomScreen.scrollToBottom)
|
||||||
}
|
}
|
||||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||||
.overlay(alignment: .top) {
|
.topBanner(pinnedItemsBanner, isVisible: context.viewState.shouldShowPinnedEventsBanner && !isVoiceOverEnabled)
|
||||||
if !isVoiceOverEnabled {
|
|
||||||
pinnedItemsBanner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This can overlay on top of the pinnedItemsBanner
|
// This can overlay on top of the pinnedItemsBanner
|
||||||
.overlay(alignment: .top) {
|
.topBanner(knockRequestsBanner, isVisible: context.viewState.shouldSeeKnockRequests)
|
||||||
knockRequestsBanner
|
|
||||||
}
|
|
||||||
.safeAreaInset(edge: .top) {
|
.safeAreaInset(edge: .top) {
|
||||||
// When voice over is on the table view is not reversed
|
// When VoiceOver is enabled, the table view isn't reversed and the scroll gestures
|
||||||
// and the scroll gestures are not intercepted
|
// don't trigger meaning the banner never hides itself and so the .overlay layout
|
||||||
// so we render the pinned banner on top.
|
// above permanently obscures the top of the timeline. So whenever VoiceOver is
|
||||||
if isVoiceOverEnabled {
|
// enabled we use a safe area inset to vertically stack it above the timeline.
|
||||||
|
if context.viewState.shouldShowPinnedEventsBanner, isVoiceOverEnabled {
|
||||||
pinnedItemsBanner
|
pinnedItemsBanner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,41 +74,19 @@ struct RoomScreen: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var pinnedItemsBanner: some View {
|
private var pinnedItemsBanner: some View {
|
||||||
// Color.clear and clipped() are required for iOS 26 transparent nav bar
|
PinnedItemsBannerView(state: context.viewState.pinnedEventsBannerState,
|
||||||
VStack(spacing: 0) {
|
onMainButtonTap: { context.send(viewAction: .tappedPinnedEventsBanner) },
|
||||||
if context.viewState.shouldShowPinnedEventsBanner {
|
onViewAllButtonTap: { context.send(viewAction: .viewAllPins) })
|
||||||
PinnedItemsBannerView(state: context.viewState.pinnedEventsBannerState,
|
|
||||||
onMainButtonTap: { context.send(viewAction: .tappedPinnedEventsBanner) },
|
|
||||||
onViewAllButtonTap: { context.send(viewAction: .viewAllPins) })
|
|
||||||
.transition(.move(edge: .top))
|
|
||||||
} else {
|
|
||||||
Color.clear
|
|
||||||
.allowsHitTesting(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.animation(.elementDefault, value: context.viewState.shouldShowPinnedEventsBanner)
|
|
||||||
.clipped()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var knockRequestsBanner: some View {
|
private var knockRequestsBanner: some View {
|
||||||
// Color.clear and clipped() are required for iOS 26 transparent nav bar
|
KnockRequestsBannerView(requests: context.viewState.displayedKnockRequests,
|
||||||
VStack(spacing: 0) {
|
onDismiss: dismissKnockRequestsBanner,
|
||||||
if context.viewState.shouldSeeKnockRequests {
|
onAccept: context.viewState.canAcceptKnocks ? acceptKnockRequest : nil,
|
||||||
KnockRequestsBannerView(requests: context.viewState.displayedKnockRequests,
|
onViewAll: onViewAllKnockRequests,
|
||||||
onDismiss: dismissKnockRequestsBanner,
|
mediaProvider: context.mediaProvider)
|
||||||
onAccept: context.viewState.canAcceptKnocks ? acceptKnockRequest : nil,
|
.padding(.top, 16)
|
||||||
onViewAll: onViewAllKnockRequests,
|
|
||||||
mediaProvider: context.mediaProvider)
|
|
||||||
.padding(.top, 16)
|
|
||||||
.transition(.move(edge: .top))
|
|
||||||
} else {
|
|
||||||
Color.clear
|
|
||||||
.allowsHitTesting(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.animation(.elementDefault, value: context.viewState.shouldSeeKnockRequests)
|
|
||||||
.clipped()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissKnockRequestsBanner() {
|
private func dismissKnockRequestsBanner() {
|
||||||
|
|||||||
Reference in New Issue
Block a user