Skip to content

Commit

Permalink
everything implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Velin92 committed Jul 24, 2023
1 parent b8a96ff commit cb49a7e
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 69 deletions.
12 changes: 11 additions & 1 deletion ElementX/Sources/Other/ScrollViewAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
scrollView?.delegate = self
}
}


var shouldScrollToTopClosure: ((UIScrollView) -> Bool)?

private let didScrollSubject = PassthroughSubject<Void, Never>()
var didScroll: AnyPublisher<Void, Never> {
didScrollSubject.eraseToAnyPublisher()
Expand Down Expand Up @@ -73,6 +75,14 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
updateDidScroll(scrollView)
}

func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
guard let shouldScrollToTopClosure else {
// Default behaviour
return true
}
return shouldScrollToTopClosure(scrollView)
}

// MARK: - Private

Expand Down
1 change: 0 additions & 1 deletion ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ enum RoomScreenComposerMode: Equatable {

enum RoomScreenViewAction {
case displayRoomDetails
case paginateBackwards
case itemAppeared(itemID: TimelineItemIdentifier)
case itemDisappeared(itemID: TimelineItemIdentifier)
case itemTapped(itemID: TimelineItemIdentifier)
Expand Down
46 changes: 21 additions & 25 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
imageProvider: mediaProvider)

state.timelineViewState.paginateAction = { [weak self] in
Task {
await self?.paginateBackwards()
}
self?.paginateBackwards()
}

setupSubscriptions()

state.timelineItemMenuActionProvider = { [weak self] itemId -> TimelineItemMenuActions? in
Expand All @@ -90,8 +89,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
switch viewAction {
case .displayRoomDetails:
callback?(.displayRoomDetails)
case .paginateBackwards:
Task { await paginateBackwards() }
case .itemAppeared(let id):
Task { await timelineController.processItemAppearance(id) }
case .itemDisappeared(let id):
Expand Down Expand Up @@ -221,12 +218,21 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
.store(in: &cancellables)
}

private func paginateBackwards() async {
switch await timelineController.paginateBackwards(requestSize: Constants.backPaginationEventLimit, untilNumberOfItems: Constants.backPaginationPageSize) {
case .failure:
displayError(.toast(L10n.errorFailedLoadingMessages))
default:
break
private var paginateBackwardsTask: Task<Void, Never>?

func paginateBackwards() {
guard paginateBackwardsTask == nil else {
return
}

paginateBackwardsTask = Task {
switch await timelineController.paginateBackwards(requestSize: Constants.backPaginationEventLimit, untilNumberOfItems: Constants.backPaginationPageSize) {
case .failure:
displayError(.toast(L10n.errorFailedLoadingMessages))
default:
break
}
paginateBackwardsTask = nil
}
}

Expand Down Expand Up @@ -265,19 +271,19 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol

if itemGroup.count == 1 {
if let firstItem = itemGroup.first {
timelineItemsDictionary.updateValue(updateViewModel(item: firstItem, groupStyle: .single),
timelineItemsDictionary.updateValue(RoomTimelineItemViewModel(item: firstItem, groupStyle: .single),
forKey: firstItem.id.timelineID)
}
} else {
for (index, item) in itemGroup.enumerated() {
if index == 0 {
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .first),
timelineItemsDictionary.updateValue(RoomTimelineItemViewModel(item: item, groupStyle: .first),
forKey: item.id.timelineID)
} else if index == itemGroup.count - 1 {
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .last),
timelineItemsDictionary.updateValue(RoomTimelineItemViewModel(item: item, groupStyle: .last),
forKey: item.id.timelineID)
} else {
timelineItemsDictionary.updateValue(updateViewModel(item: item, groupStyle: .middle),
timelineItemsDictionary.updateValue(RoomTimelineItemViewModel(item: item, groupStyle: .middle),
forKey: item.id.timelineID)
}
}
Expand All @@ -287,16 +293,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.timelineViewState.itemsDictionary = timelineItemsDictionary
}

private func updateViewModel(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) -> RoomTimelineItemViewModel {
if let timelineItemViewModel = state.timelineViewState.itemsDictionary[item.id.timelineID] {
timelineItemViewModel.groupStyle = groupStyle
timelineItemViewModel.type = .init(item: item)
return timelineItemViewModel
} else {
return RoomTimelineItemViewModel(item: item, groupStyle: groupStyle)
}
}

private func canGroupItem(timelineItem: RoomTimelineItemProtocol, with otherTimelineItem: RoomTimelineItemProtocol) -> Bool {
if timelineItem is CollapsibleTimelineItem || otherTimelineItem is CollapsibleTimelineItem {
return false
Expand Down
80 changes: 57 additions & 23 deletions ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct TimelineView: View {
@Environment(\.timelineStyle) private var timelineStyle

private let bottomID = "bottomID"
private let topID = "topID"

@State private var scrollViewAdapter = ScrollViewAdapter()
@State private var paginateBackwardsPublisher = PassthroughSubject<Void, Never>()
Expand All @@ -33,11 +34,7 @@ struct TimelineView: View {
var body: some View {
ScrollViewReader { scrollView in
ScrollView {
// Only used ton get to the bottom of the scroll view
Divider()
.id(bottomID)
.hidden()
.frame(height: 0)
bottomPin

LazyVStack(spacing: 0) {
ForEach(viewState.itemViewModels.reversed()) { viewModel in
Expand All @@ -47,34 +44,71 @@ struct TimelineView: View {
.scaleEffect(x: 1, y: -1)
}
}

topPin
}
.introspect(.scrollView, on: .iOS(.v16)) { scrollView in
guard scrollView != scrollViewAdapter.scrollView else { return }
scrollViewAdapter.scrollView = scrollView
}
.animation(.elementDefault, value: viewState.itemViewModels)
.scaleEffect(x: 1, y: -1)
.onReceive(scrollViewAdapter.didScroll) { _ in
guard let scrollView = scrollViewAdapter.scrollView else {
return
}
let offset = scrollView.contentOffset.y + scrollView.contentInset.top
let scrollToBottomButtonVisibleValue = offset > 0
if scrollToBottomButtonVisibleValue != scrollToBottomButtonVisible {
scrollToBottomButtonVisible = scrollToBottomButtonVisibleValue
.introspect(.scrollView, on: .iOS(.v16)) { uiScrollView in
guard uiScrollView != scrollViewAdapter.scrollView else { return }
scrollViewAdapter.scrollView = uiScrollView
scrollViewAdapter.shouldScrollToTopClosure = { _ in
withAnimation {
scrollView.scrollTo(topID)
}
return false
}
paginateBackwardsPublisher.send()

// Allows the scroll to top to work properly
uiScrollView.contentOffset.y -= 1
}
.scaleEffect(x: 1, y: -1)
.onReceive(scrollToBottomPublisher) { _ in
withAnimation {
scrollView.scrollTo(bottomID)
}
}
.onReceive(paginateBackwardsPublisher.collect(.byTime(DispatchQueue.main, 0.1))) { _ in
tryPaginateBackwards()
}
.scrollDismissesKeyboard(.interactively)
}
.overlay(scrollToBottomButton, alignment: .bottomTrailing)
.animation(.elementDefault, value: viewState.itemViewModels)
.onReceive(scrollViewAdapter.didScroll) { _ in
guard let scrollView = scrollViewAdapter.scrollView else {
return
}
let offset = scrollView.contentOffset.y + scrollView.contentInset.top
let scrollToBottomButtonVisibleValue = offset > 0
if scrollToBottomButtonVisibleValue != scrollToBottomButtonVisible {
scrollToBottomButtonVisible = scrollToBottomButtonVisibleValue
}
paginateBackwardsPublisher.send()

// Allows the scroll to top to work properly
if offset == 0 {
scrollView.contentOffset.y -= 1
}
}
.onReceive(paginateBackwardsPublisher.collect(.byTime(DispatchQueue.main, 0.1))) { _ in
tryPaginateBackwards()
}
.onAppear {
paginateBackwardsPublisher.send()
guard let scrollView = scrollViewAdapter.scrollView else {
return
}
}
}

private var topPin: some View {
Divider()
.id(topID)
.hidden()
.frame(height: 0)
}

private var bottomPin: some View {
Divider()
.id(bottomID)
.hidden()
.frame(height: 0)
}

private var scrollToBottomButton: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ import SwiftUI

struct RoomTimelineItemView: View {
@EnvironmentObject private var context: RoomScreenViewModel.Context
@ObservedObject var viewModel: RoomTimelineItemViewModel
var viewModel: RoomTimelineItemViewModel

var body: some View {
timelineView
.environmentObject(context)
.environment(\.timelineGroupStyle, viewModel.groupStyle)
.animation(.elementDefault, value: viewModel.type)
.animation(.elementDefault, value: viewModel.groupStyle)
.onAppear {
context.send(viewAction: .itemAppeared(itemID: viewModel.identifier))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@

import Foundation

final class RoomTimelineItemViewModel: Identifiable, Equatable, ObservableObject {
static func == (lhs: RoomTimelineItemViewModel, rhs: RoomTimelineItemViewModel) -> Bool {
lhs.type == rhs.type && lhs.groupStyle == rhs.groupStyle
}

@Published var type: RoomTimelineItemType
@Published var groupStyle: TimelineGroupStyle
struct RoomTimelineItemViewModel: Identifiable, Equatable {
var type: RoomTimelineItemType
var groupStyle: TimelineGroupStyle

var identifier: TimelineItemIdentifier {
type.id
Expand All @@ -32,20 +28,17 @@ final class RoomTimelineItemViewModel: Identifiable, Equatable, ObservableObject
identifier.timelineID
}

convenience init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
self.init(type: .init(item: item), groupStyle: groupStyle)
}

init(type: RoomTimelineItemType, groupStyle: TimelineGroupStyle) {
self.type = type
self.groupStyle = groupStyle
}

var isReactable: Bool {
type.isReactable
}
}

extension RoomTimelineItemViewModel {
init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
self.init(type: .init(item: item), groupStyle: groupStyle)
}
}

enum RoomTimelineItemType: Equatable {
case text(TextRoomTimelineItem)
case separator(SeparatorRoomTimelineItem)
Expand Down

0 comments on commit cb49a7e

Please sign in to comment.