Skip to content

Commit

Permalink
Switched to discrete timeline items that directly expose view builders.
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanceriu committed Mar 11, 2022
1 parent 8d4f6f3 commit d413a67
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 106 deletions.
14 changes: 4 additions & 10 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
1859CF5527D7A6FF00E86E4E /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 1859CF5427D7A6FF00E86E4E /* MatrixRustSDK */; };
1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A3FB27BA5A9100B52E4D /* KeychainAccess */; };
1863A40627BA6DFC00B52E4D /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A40527BA6DFC00B52E4D /* SwiftyBeaver */; };
18A318DC27DA42C9000867CD /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A318DA27DA42C9000867CD /* RoomTimelineItemProtocol.swift */; };
18A318DD27DA42C9000867CD /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A318DB27DA42C9000867CD /* TextRoomTimelineItem.swift */; };
18A318DD27DA42C9000867CD /* RoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A318DB27DA42C9000867CD /* RoomTimelineItem.swift */; };
18F2BAD727D25B4000DD1988 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7327D25B4000DD1988 /* RoomProxyProtocol.swift */; };
18F2BAD827D25B4000DD1988 /* RoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7427D25B4000DD1988 /* RoomProxy.swift */; };
18F2BAD927D25B4000DD1988 /* MockRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7527D25B4000DD1988 /* MockRoomProxy.swift */; };
Expand Down Expand Up @@ -112,9 +111,7 @@
1850256727B6A135002E6B18 /* ElementX.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; };
1850256827B6A135002E6B18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1850256A27B6A135002E6B18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
18A318D827D9E7AD000867CD /* matrix-rust-components-swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "matrix-rust-components-swift"; path = "../matrix-rust-components-swift"; sourceTree = "<group>"; };
18A318DA27DA42C9000867CD /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
18A318DB27DA42C9000867CD /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
18A318DB27DA42C9000867CD /* RoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineItem.swift; sourceTree = "<group>"; };
18F2BA7327D25B4000DD1988 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = "<group>"; };
18F2BA7427D25B4000DD1988 /* RoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
18F2BA7527D25B4000DD1988 /* MockRoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -211,7 +208,6 @@
1850251B27B6918C002E6B18 = {
isa = PBXGroup;
children = (
18A318D827D9E7AD000867CD /* matrix-rust-components-swift */,
1850252627B6918C002E6B18 /* ElementX */,
1850253D27B6918D002E6B18 /* ElementXTests */,
1850254727B6918D002E6B18 /* ElementXUITests */,
Expand Down Expand Up @@ -281,8 +277,7 @@
18A318D927DA42C9000867CD /* TimelineItems */ = {
isa = PBXGroup;
children = (
18A318DA27DA42C9000867CD /* RoomTimelineItemProtocol.swift */,
18A318DB27DA42C9000867CD /* TextRoomTimelineItem.swift */,
18A318DB27DA42C9000867CD /* RoomTimelineItem.swift */,
);
path = TimelineItems;
sourceTree = "<group>";
Expand Down Expand Up @@ -742,7 +737,6 @@
18F2BAFF27D25B4000DD1988 /* HomeScreenModels.swift in Sources */,
18F2BB1527D25B4000DD1988 /* LoginScreenViewModelProtocol.swift in Sources */,
18F2BAEB27D25B4000DD1988 /* LabelledActivityIndicatorView.swift in Sources */,
18A318DC27DA42C9000867CD /* RoomTimelineItemProtocol.swift in Sources */,
18F2BAE427D25B4000DD1988 /* Presentable.swift in Sources */,
18F2BAF927D25B4000DD1988 /* SplashViewController.swift in Sources */,
18F2BAE327D25B4000DD1988 /* RootRouter.swift in Sources */,
Expand Down Expand Up @@ -782,7 +776,7 @@
18F2BB0127D25B4000DD1988 /* HomeScreenViewModel.swift in Sources */,
18F2BAF027D25B4000DD1988 /* ActivityDismissal.swift in Sources */,
18F2BADD27D25B4000DD1988 /* KeychainController.swift in Sources */,
18A318DD27DA42C9000867CD /* TextRoomTimelineItem.swift in Sources */,
18A318DD27DA42C9000867CD /* RoomTimelineItem.swift in Sources */,
18F2BAFB27D25B4000DD1988 /* HomeScreenCoordinator.swift in Sources */,
18F2BB0C27D25B4000DD1988 /* RoomScreenCoordinator.swift in Sources */,
18F2BB0E27D25B4000DD1988 /* RoomScreenViewModelProtocol.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@
"repositoryURL": "https://github.com/onevcat/Kingfisher",
"state": {
"branch": null,
"revision": "0c02c46cfdc0656ce74fd0963a75e5000a0b7f23",
"version": "7.1.2"
"revision": "32e4acdf6971f58f5ad552389cf2d7d016334eaf",
"version": "7.2.0"
}
},
{
"package": "MatrixRustSDK",
"repositoryURL": "https://github.com/matrix-org/matrix-rust-components-swift.git",
"state": {
"branch": "main",
"revision": "497122432c79488e370df2164ae5637f32f82ca3",
"revision": "6741f728fedbceb53154c043486dc1790ed37811",
"version": null
}
},
{
"package": "Introspect",
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
"state": {
"branch": "master",
"revision": "72a509c93166540c0adf8323fd2652daade7f9f6",
"version": null
}
},
Expand Down
22 changes: 2 additions & 20 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,7 @@ enum RoomScreenViewAction {
case loadPreviousPage
}

private var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short
return dateFormatter
}()

struct RoomScreenMessage: Identifiable, Equatable {
let id: String
let sender: String
let text: String
let originServerTs: Date

var timestamp: String {
dateFormatter.string(from: originServerTs)
}
}

struct RoomScreenViewState: BindableState {
var roomTitle: String?
var messages: [RoomScreenMessage] = []
var roomTitle: String = ""
var messages: [RoomTimelineItem] = []
}
14 changes: 3 additions & 11 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol

super.init(initialViewState: RoomScreenViewState())

state.messages = buildRoomScreenMessages(timelineController.timelineItems)
state.roomTitle = roomProxy.name ?? ""
state.messages = timelineController.timelineItems

timelineController.callbacks.sink { [weak self] callback in
guard let self = self else { return }

switch callback {
case .updatedTimelineItems:
self.state.messages = self.buildRoomScreenMessages(timelineController.timelineItems)
self.state.messages = timelineController.timelineItems
}
}.store(in: &cancellables)
}
Expand All @@ -60,13 +61,4 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
timelineController.paginateBackwards(Constants.backPaginationPageSize)
}
}

// MARK: - Private

private func buildRoomScreenMessages(_ timelineItems: [RoomTimelineItemProtocol]) -> [RoomScreenMessage] {
timelineItems.map { RoomScreenMessage(id: $0.id,
sender: $0.senderDisplayName,
text: $0.text,
originServerTs: $0.originServerTs) }
}
}
36 changes: 15 additions & 21 deletions ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Combine
struct RoomScreen: View {

@State private var scrollViewObserver: ScrollViewObserver = ScrollViewObserver()
@State private var messages: [RoomScreenMessage] = []
@State private var messages: [RoomTimelineItem] = []

@State private var didRequestBackPagination = false
@State private var hasPendingMessages = false
Expand All @@ -37,16 +37,19 @@ struct RoomScreen: View {
ScrollViewReader { scrollViewProxy in
List {
if didRequestBackPagination == false {
Color
.clear
.onAppear {
guard didRequestBackPagination == false else {
return
}

didRequestBackPagination = true
context.send(viewAction: .loadPreviousPage)
HStack {
Spacer()
ProgressView()
Spacer()
}
.onAppear {
guard didRequestBackPagination == false else {
return
}

didRequestBackPagination = true
context.send(viewAction: .loadPreviousPage)
}
} else {
HStack {
Spacer()
Expand All @@ -56,24 +59,15 @@ struct RoomScreen: View {
}

ForEach(messages) { message in
VStack(alignment: .leading) {
HStack {
Text(message.sender)
Spacer()
Text(message.timestamp)
}
.font(.footnote)
Text(message.text)
}
.listRowSeparator(.hidden)
.id(message.id)
message.body
}

Color.clear
.listRowSeparator(.hidden)
.id(timelineBottomAnchor)
}
.listStyle(.plain)
.navigationTitle(context.viewState.roomTitle)
.environment(\.defaultMinListRowHeight, 0.0)
.navigationBarTitleDisplayMode(.inline)
// Fetch the underlying UIScrollView and start observing it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import Foundation
import Combine

class MockRoomTimelineController: RoomTimelineControllerProtocol {
let timelineItems: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(id: UUID().uuidString, senderDisplayName: "Anne", text: "You rock!", originServerTs: .now),
TextRoomTimelineItem(id: UUID().uuidString, senderDisplayName: "Bob", text: "You rule!", originServerTs: .now)]
let timelineItems: [RoomTimelineItem] = [RoomTimelineItem.text(id: UUID().uuidString, senderDisplayName: "Anne", text: "You rock!", originServerTs: .now, shouldShowSenderDetails: true),
RoomTimelineItem.text(id: UUID().uuidString, senderDisplayName: "Anne", text: "Some other message from Anne", originServerTs: .now, shouldShowSenderDetails: false),
RoomTimelineItem.sectionTitle(id: UUID().uuidString, text: "The next day"),
RoomTimelineItem.text(id: UUID().uuidString, senderDisplayName: "Bob", text: "You rule!", originServerTs: .now, shouldShowSenderDetails: true)]
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()

func paginateBackwards(_ count: UInt) {
Expand Down
56 changes: 50 additions & 6 deletions ElementX/Sources/Services/Timeline/RoomTimelineController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ enum RoomTimelineControllerCallback {
case updatedTimelineItems
}

private var sectionTitleDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .none
return dateFormatter
}()

class RoomTimelineController: RoomTimelineControllerProtocol {
private let timelineProvider: RoomTimelineProvider
private var cancellables = Set<AnyCancellable>()

let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()

private(set) var timelineItems = [RoomTimelineItemProtocol]()
private(set) var timelineItems = [RoomTimelineItem]()

init(timelineProvider: RoomTimelineProvider) {
self.timelineProvider = timelineProvider
Expand All @@ -30,13 +37,36 @@ class RoomTimelineController: RoomTimelineControllerProtocol {

switch callback {
case .updatedMessages:
self.timelineItems = self.timelineProvider.messages.map { message in
var newTimelineItems = [RoomTimelineItem]()

var previousMessage: Message?
var previousSender: String?
for message in self.timelineProvider.messages {
let timestamp = Date(timeIntervalSince1970: TimeInterval(message.originServerTs()))
return TextRoomTimelineItem(id: message.id(),
senderDisplayName: message.sender(),
text: message.content(),
originServerTs: timestamp)

let areMessagesFromTheSameDay = self.haveSameDay(lhs: previousMessage, rhs: message)
// let shouldAddSectionHeader = !areMessagesFromTheSameDay
//
// if shouldAddSectionHeader {
// newTimelineItems.append(RoomTimelineItem.sectionTitle(id: message.id(),
// text: sectionTitleDateFormatter.string(from: timestamp)))
// }

let areMessagesFromTheSameSender = previousSender == message.sender()
let shouldShowSenderDetails = !areMessagesFromTheSameSender || !areMessagesFromTheSameDay

newTimelineItems.append(RoomTimelineItem.text(id: message.id(),
senderDisplayName: message.sender(),
text: message.content(),
originServerTs: timestamp,
shouldShowSenderDetails: shouldShowSenderDetails))

previousMessage = message
previousSender = message.sender()
}

self.timelineItems = newTimelineItems

self.callbacks.send(.updatedTimelineItems)
}
}.store(in: &cancellables)
Expand All @@ -45,4 +75,18 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
func paginateBackwards(_ count: UInt) {
timelineProvider.paginateBackwards(count)
}

// MARK: - Private

private func haveSameDay(lhs: Message?, rhs: Message?) -> Bool {
guard let lhs = lhs, let rhs = rhs else {
return false
}

let lhsTimestamp = Date(timeIntervalSince1970: TimeInterval(lhs.originServerTs()))
let rhsTimestamp = Date(timeIntervalSince1970: TimeInterval(rhs.originServerTs()))

return Calendar.current.isDate(lhsTimestamp, inSameDayAs: rhsTimestamp)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
import Combine

protocol RoomTimelineControllerProtocol {
var timelineItems: [RoomTimelineItemProtocol] { get }
var timelineItems: [RoomTimelineItem] { get }
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }

func paginateBackwards(_ count: UInt)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// TextRoomTimelineItem.swift
// ElementX
//
// Created by Stefan Ceriu on 04.03.2022.
// Copyright © 2022 Element. All rights reserved.
//

import Foundation
import SwiftUI

private var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
return dateFormatter
}()

enum RoomTimelineItem: Identifiable, Equatable {
case text(id: String, senderDisplayName: String, text: String, originServerTs: Date, shouldShowSenderDetails: Bool)
case sectionTitle(id: String, text: String)

var id: String {
switch self {
case .text(let id, _, _, _, _):
return id
case .sectionTitle(let id, _):
return id
}
}
}

extension RoomTimelineItem: View {
var body: some View {
switch self {
case .text(let id, let senderDisplayName, let text, let originServerTs, let shouldShowSenderDetails):
VStack(alignment: .leading) {
if shouldShowSenderDetails {
HStack {
Text(senderDisplayName)
.font(.footnote)
.bold()
Spacer()
Text(dateFormatter.string(from: originServerTs))
.font(.footnote)
}
Divider()
Spacer()
}
Text(text)
}
.listRowSeparator(.hidden)
.id(id)
case .sectionTitle(let id, let text):
LabelledDivider(label: text)
.id(id)
}
}
}

struct LabelledDivider: View {

let label: String
let color: Color

init(label: String, color: Color = .gray) {
self.label = label
self.color = color
}

var body: some View {
HStack {
line
Text(label)
.foregroundColor(color)
.fixedSize()
line
}
}

var line: some View {
VStack { Divider().background(color) }
}
}
Loading

0 comments on commit d413a67

Please sign in to comment.