Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide/Show pin banner based on scroll direction #3080

Merged
merged 4 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ enum RoomScreenViewAction {
case scrolledToFocussedItem
/// The table view has loaded the first items for a new timeline.
case hasSwitchedTimeline

case hasScrolled(direction: ScrollDirection)
case nextPin
case viewAllPins
}

enum RoomScreenComposerAction {
Expand Down Expand Up @@ -165,7 +169,24 @@ struct RoomScreenViewState: BindableState {
var canCurrentUserRedactSelf = false
var canCurrentUserPin = false
var isViewSourceEnabled: Bool

var isPinningEnabled = false
var lastScrollDirection: ScrollDirection?
// These are just mocked items used for testing, their types might change
let pinnedItems = [
"Hello 1",
"How are you 2",
"I am fine 3",
"Thank you 4"
]
var currentPinIndex = 0
Velin92 marked this conversation as resolved.
Show resolved Hide resolved
var shouldShowPinBanner: Bool {
isPinningEnabled && !pinnedItems.isEmpty && lastScrollDirection != .top
}

var selectedPinContent: AttributedString {
.init(pinnedItems[currentPinIndex])
}

var canJoinCall = false
var hasOngoingCall = false
Expand Down Expand Up @@ -278,3 +299,8 @@ struct TimelineViewState {
itemViewStates.contains { $0.identifier.eventID == eventID }
}
}

enum ScrollDirection: Equatable {
case top
case bottom
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
didScrollToFocussedItem()
case .hasSwitchedTimeline:
Task { state.timelineViewState.isSwitchingTimelines = false }
case let .hasScrolled(direction):
state.lastScrollDirection = direction
case .nextPin:
state.currentPinIndex = (state.currentPinIndex + 1) % state.pinnedItems.count
case .viewAllPins:
// TODO: Implement
break
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ struct PinnedItemsIndicatorView: View {
if pinsCount <= 3 {
return pinsCount
}
let remainingPins = pinsCount - pinIndex
return remainingPins >= 3 ? 3 : pinsCount % 3
let maxUntruncatedIndicators = pinsCount - pinsCount % 3
if pinIndex < maxUntruncatedIndicators {
return 3
}
return pinsCount % 3
}

var body: some View {
Expand All @@ -46,20 +49,31 @@ struct PinnedItemsIndicatorView: View {
}

struct PinnedItemsIndicatorView_Previews: PreviewProvider, TestablePreview {
static func indicator(index: Int, count: Int) -> some View {
VStack(spacing: 0) {
Text("\(index + 1)/\(count)")
.font(.compound.bodyXS)
PinnedItemsIndicatorView(pinIndex: index, pinsCount: count)
}
}

static var previews: some View {
HStack(spacing: 10) {
PinnedItemsIndicatorView(pinIndex: 0, pinsCount: 1)
PinnedItemsIndicatorView(pinIndex: 0, pinsCount: 2)
PinnedItemsIndicatorView(pinIndex: 1, pinsCount: 2)
PinnedItemsIndicatorView(pinIndex: 0, pinsCount: 3)
PinnedItemsIndicatorView(pinIndex: 1, pinsCount: 3)
PinnedItemsIndicatorView(pinIndex: 2, pinsCount: 3)
PinnedItemsIndicatorView(pinIndex: 0, pinsCount: 5)
PinnedItemsIndicatorView(pinIndex: 1, pinsCount: 5)
PinnedItemsIndicatorView(pinIndex: 2, pinsCount: 5)
PinnedItemsIndicatorView(pinIndex: 3, pinsCount: 5)
PinnedItemsIndicatorView(pinIndex: 4, pinsCount: 5)
PinnedItemsIndicatorView(pinIndex: 3, pinsCount: 4)
HStack(spacing: 5) {
indicator(index: 0, count: 1)
indicator(index: 0, count: 2)
indicator(index: 1, count: 2)
indicator(index: 0, count: 3)
indicator(index: 1, count: 3)
indicator(index: 2, count: 3)
indicator(index: 0, count: 4)
indicator(index: 1, count: 4)
indicator(index: 2, count: 4)
indicator(index: 3, count: 4)
indicator(index: 0, count: 5)
indicator(index: 1, count: 5)
indicator(index: 2, count: 5)
indicator(index: 3, count: 5)
indicator(index: 4, count: 5)
}
.previewLayout(.sizeThatFits)
}
Expand Down
21 changes: 14 additions & 7 deletions ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,12 @@ struct RoomScreen: View {
.environment(\.roomContext, context)
}
.overlay(alignment: .top) {
if context.viewState.isPinningEnabled {
// TODO: Implement tapping logic + hiding when scrolling
PinnedItemsBannerView(pinIndex: 1,
pinsCount: 3,
pinContent: .init(stringLiteral: "Content"),
onMainButtonTap: { },
onViewAllButtonTap: { })
Group {
if context.viewState.shouldShowPinBanner {
pinnedItemsBanner
}
}
.animation(.elementDefault, value: context.viewState.shouldShowPinBanner)
}
.navigationTitle(L10n.screenRoomTitle) // Hidden but used for back button text.
.navigationBarTitleDisplayMode(.inline)
Expand Down Expand Up @@ -110,6 +108,15 @@ struct RoomScreen: View {
}
}

private var pinnedItemsBanner: some View {
PinnedItemsBannerView(pinIndex: context.viewState.currentPinIndex,
pinsCount: context.viewState.pinnedItems.count,
pinContent: context.viewState.selectedPinContent,
onMainButtonTap: { context.send(viewAction: .nextPin) },
onViewAllButtonTap: { context.send(viewAction: .viewAllPins) })
.transition(.move(edge: .top))
}

private var scrollToBottomButton: some View {
Button { context.send(viewAction: .scrollToBottom) } label: {
Image(systemName: "chevron.down")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ class TimelineTableViewController: UIViewController {
/// quick succession can execute before ``paginationState`` acknowledges that
/// pagination is in progress.
private let paginatePublisher = PassthroughSubject<Void, Never>()

/// A value to determine the scroll velocity threshold to detect a change in direction of the scroll view
private let scrollVelocityThreshold: CGFloat = 50.0
/// A publisher used to throttle scroll direction changes
private let scrollDirectionPublisher = PassthroughSubject<ScrollDirection, Never>()
/// Whether or not the view has been shown on screen yet.
private var hasAppearedOnce = false

Expand Down Expand Up @@ -198,6 +203,14 @@ class TimelineTableViewController: UIViewController {
}
.store(in: &cancellables)

scrollDirectionPublisher
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
.removeDuplicates()
.sink { direction in
coordinator.send(viewAction: .hasScrolled(direction: direction))
}
.store(in: &cancellables)

NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.sink { [weak self] _ in
self?.sendLastVisibleItemReadReceipt()
Expand Down Expand Up @@ -345,6 +358,7 @@ class TimelineTableViewController: UIViewController {
return
}
tableView.scrollToRow(at: IndexPath(item: 0, section: 0), at: .top, animated: animated)
scrollDirectionPublisher.send(.bottom)
}

/// Scrolls to the oldest item in the timeline.
Expand All @@ -353,6 +367,7 @@ class TimelineTableViewController: UIViewController {
return
}
tableView.scrollToRow(at: IndexPath(item: timelineItemsIDs.count - 1, section: 1), at: .bottom, animated: animated)
scrollDirectionPublisher.send(.top)
}

/// Scrolls to the item with the corresponding event ID if loaded in the timeline.
Expand Down Expand Up @@ -423,6 +438,13 @@ extension TimelineTableViewController: UITableViewDelegate {
if scrollView.contentOffset.y == 0 {
scrollView.contentOffset.y = -1
}

let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView.superview).y
if velocity > scrollVelocityThreshold {
scrollDirectionPublisher.send(.top)
} else if velocity < -scrollVelocityThreshold {
scrollDirectionPublisher.send(.bottom)
}
}

func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading