mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Introduced the RoomTimelineViewProvider with different timeline items/views. Added timeline date separators (currently breaks back pagination)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user