Introduced the RoomTimelineViewProvider with different timeline items/views. Added timeline date separators (currently breaks back pagination)

This commit is contained in:
Stefan Ceriu
2022-03-11 14:47:11 +02:00
parent d413a67329
commit 3d7b3d3d63
17 changed files with 329 additions and 192 deletions
@@ -24,79 +24,76 @@ struct HomeScreen: View {
// MARK: Views
var body: some View {
NavigationView {
VStack(spacing: 16.0) {
if context.viewState.isLoadingRooms {
VStack {
Text("Loading rooms")
ProgressView()
}
} else {
List {
Section("Rooms") {
ForEach(context.viewState.unencryptedRooms) { room in
RoomCell(room: room, context: context)
}
let other = context.viewState.encryptedRooms
if other.count > 0 {
DisclosureGroup("Encrypted") {
ForEach(other) { room in
RoomCell(room: room, context: context)
}
}
}
VStack(spacing: 16.0) {
if context.viewState.isLoadingRooms {
VStack {
Text("Loading rooms")
ProgressView()
}
} else {
List {
Section("Rooms") {
ForEach(context.viewState.unencryptedRooms) { room in
RoomCell(room: room, context: context)
}
Section("People") {
ForEach(context.viewState.unencryptedDMs) { room in
RoomCell(room: room, context: context)
}
let other = context.viewState.encryptedDMs
if other.count > 0 {
DisclosureGroup("Encrypted") {
ForEach(other) { room in
RoomCell(room: room, context: context)
}
let other = context.viewState.encryptedRooms
if other.count > 0 {
DisclosureGroup("Encrypted") {
ForEach(other) { room in
RoomCell(room: room, context: context)
}
}
}
}
.listStyle(.plain)
}
Spacer()
}
.ignoresSafeArea(.all, edges: .bottom)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
HStack {
if let avatar = context.viewState.userAvatar {
Image(uiImage: avatar)
.resizable()
.scaledToFill()
.frame(width: 40, height: 40, alignment: .center)
.mask(Circle())
} else {
let _ = context.send(viewAction: .loadUserAvatar)
Section("People") {
ForEach(context.viewState.unencryptedDMs) { room in
RoomCell(room: room, context: context)
}
let other = context.viewState.encryptedDMs
if other.count > 0 {
DisclosureGroup("Encrypted") {
ForEach(other) { room in
RoomCell(room: room, context: context)
}
}
}
Text("Hello, \(context.viewState.userDisplayName)!")
.font(.subheadline)
.fontWeight(.bold)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Logout") {
context.send(viewAction: .logout)
.listStyle(.plain)
}
Spacer()
}
.ignoresSafeArea(.all, edges: .bottom)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
HStack {
if let avatar = context.viewState.userAvatar {
Image(uiImage: avatar)
.resizable()
.scaledToFill()
.frame(width: 40, height: 40, alignment: .center)
.mask(Circle())
} else {
let _ = context.send(viewAction: .loadUserAvatar)
}
Text("Hello, \(context.viewState.userDisplayName)!")
.font(.subheadline)
.fontWeight(.bold)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Logout") {
context.send(viewAction: .logout)
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
@@ -22,9 +22,11 @@ enum RoomScreenViewModelResult {
enum RoomScreenViewAction {
case loadPreviousPage
case itemAppeared(id: String)
case itemDisappeared(id: String)
}
struct RoomScreenViewState: BindableState {
var roomTitle: String = ""
var messages: [RoomTimelineItem] = []
var timelineItems: [RoomTimelineViewProvider] = []
}
@@ -41,24 +41,28 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
super.init(initialViewState: RoomScreenViewState())
state.roomTitle = roomProxy.name ?? ""
state.messages = timelineController.timelineItems
state.timelineItems = timelineController.timelineItems
timelineController.callbacks.sink { [weak self] callback in
guard let self = self else { return }
switch callback {
case .updatedTimelineItems:
self.state.messages = timelineController.timelineItems
self.state.timelineItems = timelineController.timelineItems
}
}.store(in: &cancellables)
}
// MARK: - Public
override func process(viewAction: RoomScreenViewAction) {
switch viewAction {
case .loadPreviousPage:
timelineController.paginateBackwards(Constants.backPaginationPageSize)
case .itemAppeared:
break
case .itemDisappeared:
break
}
}
}
@@ -21,7 +21,7 @@ import Combine
struct RoomScreen: View {
@State private var scrollViewObserver: ScrollViewObserver = ScrollViewObserver()
@State private var messages: [RoomTimelineItem] = []
@State private var timelineItems: [RoomTimelineViewProvider] = []
@State private var didRequestBackPagination = false
@State private var hasPendingMessages = false
@@ -58,8 +58,15 @@ struct RoomScreen: View {
}
}
ForEach(messages) { message in
message.body
ForEach(timelineItems) { timelineItem in
timelineItem
.listRowSeparator(.hidden)
.onAppear {
context.send(viewAction: .itemAppeared(id: timelineItem.id))
}
.onDisappear {
context.send(viewAction: .itemDisappeared(id: timelineItem.id))
}
}
Color.clear
@@ -85,8 +92,8 @@ struct RoomScreen: View {
// When the view state changes check whether the user is interacting with the scroll view.
// Updating in that case causes undesired scrolling. Delay until the scroll view stops scrolling.
// Also store previous top most message identifier to have something to scroll to after the update.
.onChange(of: context.viewState.messages) { newValue in
previousTopMostMessageIdentifier = messages.first?.id
.onChange(of: context.viewState.timelineItems) { newValue in
previousTopMostMessageIdentifier = timelineItems.first?.id
wasBottomVisible = scrollViewObserver.isBottomVisible
if scrollViewObserver.isTracking == true {
@@ -94,17 +101,17 @@ struct RoomScreen: View {
return
}
messages = newValue
timelineItems = newValue
}
// Check if we have pending messages to apply and apply them when the scroll finishes scrolling
.onReceive(scrollViewObserver.didEndScrolling, perform: {
if hasPendingMessages {
messages = context.viewState.messages
timelineItems = context.viewState.timelineItems
hasPendingMessages = false
}
})
// Process timeline updates
.onChange(of: messages, perform: { _ in
.onChange(of: timelineItems, perform: { _ in
if didRequestBackPagination && wasBottomVisible {
scrollViewProxy.scrollTo(timelineBottomAnchor, anchor: .bottom)
} else if didRequestBackPagination == false {