mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:02:10 +00:00
Remove the "unflip" of the timeline when in voice over mode (#5835)
Keep the timeline as it is in voice over mode, and don't "unflip" it
This commit is contained in:
@@ -81,10 +81,10 @@ struct RoomScreen: View {
|
|||||||
TopBannerLayer(knockRequestsBanner, isVisible: context.viewState.shouldSeeKnockRequests)
|
TopBannerLayer(knockRequestsBanner, isVisible: context.viewState.shouldSeeKnockRequests)
|
||||||
], footer: dateBadge)
|
], footer: dateBadge)
|
||||||
.safeAreaInset(edge: .top) {
|
.safeAreaInset(edge: .top) {
|
||||||
// When VoiceOver is enabled, the table view isn't reversed and the scroll gestures
|
// When VoiceOver is enabled the scroll gestures don't trigger, so the banner never
|
||||||
// don't trigger meaning the banner never hides itself and so the .overlay layout
|
// hides itself and the .overlay layout above would permanently obscure the top of
|
||||||
// above permanently obscures the top of the timeline. So whenever VoiceOver is
|
// the timeline. So whenever VoiceOver is enabled we use a safe area inset to
|
||||||
// enabled we use a safe area inset to vertically stack it above the timeline.
|
// vertically stack it above the timeline instead.
|
||||||
if context.viewState.shouldShowPinnedEventsBanner || context.viewState.isSharingLiveLocation, isVoiceOverEnabled {
|
if context.viewState.shouldShowPinnedEventsBanner || context.viewState.isSharingLiveLocation, isVoiceOverEnabled {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if context.viewState.shouldShowPinnedEventsBanner {
|
if context.viewState.shouldShowPinnedEventsBanner {
|
||||||
|
|||||||
@@ -180,11 +180,6 @@ class TimelineTableViewController: UIViewController {
|
|||||||
/// Whether or not the view has been shown on screen yet.
|
/// Whether or not the view has been shown on screen yet.
|
||||||
private var hasAppearedOnce = false
|
private var hasAppearedOnce = false
|
||||||
|
|
||||||
/// Value that determines if the table view is flipped or not according to the VoiceOver status.
|
|
||||||
private var scaleY: CGFloat {
|
|
||||||
UIAccessibility.isVoiceOverRunning ? 1 : -1
|
|
||||||
}
|
|
||||||
|
|
||||||
init(coordinator: TimelineViewRepresentable.Coordinator,
|
init(coordinator: TimelineViewRepresentable.Coordinator,
|
||||||
isScrolledToBottom: Binding<Bool>,
|
isScrolledToBottom: Binding<Bool>,
|
||||||
isReadMarkerVisible: Binding<Bool>,
|
isReadMarkerVisible: Binding<Bool>,
|
||||||
@@ -208,9 +203,8 @@ class TimelineTableViewController: UIViewController {
|
|||||||
tableView.keyboardDismissMode = .onDrag
|
tableView.keyboardDismissMode = .onDrag
|
||||||
tableView.backgroundColor = .compound.bgCanvasDefault
|
tableView.backgroundColor = .compound.bgCanvasDefault
|
||||||
|
|
||||||
// The tableview should be flipped to display the newest items at the top
|
// The tableview is flipped to display the newest items at the bottom.
|
||||||
// the only exception is VoiceOver, where we want to keep the latest item at the top as Android.
|
tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
|
||||||
tableView.transform = CGAffineTransform(scaleX: 1, y: scaleY)
|
|
||||||
view.addSubview(tableView)
|
view.addSubview(tableView)
|
||||||
|
|
||||||
// Prevents XCUITest from invoking the diffable dataSource's cellProvider
|
// Prevents XCUITest from invoking the diffable dataSource's cellProvider
|
||||||
@@ -256,15 +250,6 @@ class TimelineTableViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
// Observe voice over status changes to flip the table view accordingly
|
|
||||||
NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification)
|
|
||||||
.sink { [weak self] _ in
|
|
||||||
guard let self else { return }
|
|
||||||
tableView.transform = CGAffineTransform(scaleX: 1, y: scaleY)
|
|
||||||
tableView.reloadData()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
configureDataSource()
|
configureDataSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +297,7 @@ class TimelineTableViewController: UIViewController {
|
|||||||
.background(Color.clear)
|
.background(Color.clear)
|
||||||
|
|
||||||
// Flipping the cell can create some issues with cell resizing, so flip the content View
|
// Flipping the cell can create some issues with cell resizing, so flip the content View
|
||||||
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: scaleY)
|
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
|
||||||
cell.accessibilityElements = [cell.contentView] // Ensure VoiceOver reads the content view only
|
cell.accessibilityElements = [cell.contentView] // Ensure VoiceOver reads the content view only
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
@@ -339,7 +324,7 @@ class TimelineTableViewController: UIViewController {
|
|||||||
.background(Color.clear)
|
.background(Color.clear)
|
||||||
|
|
||||||
// Flipping the cell can create some issues with cell resizing, so flip the content View
|
// Flipping the cell can create some issues with cell resizing, so flip the content View
|
||||||
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: scaleY)
|
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,19 +618,16 @@ extension TimelineTableViewController {
|
|||||||
|
|
||||||
/// Returns the timestamp of the newest visible timeline item.
|
/// Returns the timestamp of the newest visible timeline item.
|
||||||
///
|
///
|
||||||
/// The table view is flipped (unless VoiceOver is running), so the "newest"
|
/// The table view is flipped, so the "newest" visible cell on screen is
|
||||||
/// visible cell on screen is actually the *last* index path in `indexPathsForVisibleRows`
|
/// actually the *last* index path in `indexPathsForVisibleRows`.
|
||||||
/// when the table is flipped, or the *first* when it is not.
|
|
||||||
private func newestVisibleDate() -> Date? {
|
private func newestVisibleDate() -> Date? {
|
||||||
guard let visibleIndexPaths = tableView.indexPathsForVisibleRows,
|
guard let visibleIndexPaths = tableView.indexPathsForVisibleRows,
|
||||||
!visibleIndexPaths.isEmpty else {
|
!visibleIndexPaths.isEmpty else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// In a flipped table view the last index path is the topmost item on screen;
|
// In a flipped table view the last index path is the topmost item on screen.
|
||||||
// when VoiceOver is active the table is not flipped so the first is topmost.
|
let orderedPaths = visibleIndexPaths.reversed()
|
||||||
let isFlipped = scaleY == -1
|
|
||||||
let orderedPaths = isFlipped ? visibleIndexPaths.reversed() : visibleIndexPaths
|
|
||||||
|
|
||||||
// Walk from topmost downward and return the timestamp of the first item that has one.
|
// Walk from topmost downward and return the timestamp of the first item that has one.
|
||||||
for indexPath in orderedPaths {
|
for indexPath in orderedPaths {
|
||||||
|
|||||||
Reference in New Issue
Block a user