Remove layout styling abstraction (#2982)

This commit is contained in:
Mauro 2024-06-27 18:25:25 +02:00 committed by GitHub
parent 064b1bf224
commit b7345aafca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 29 additions and 126 deletions

View File

@ -149,7 +149,6 @@ struct RoomScreenViewState: BindableState {
var typingMembers: [String] = []
var showLoading = false
var showReadReceipts = false
let timelineStyle = TimelineStyle.bubbles
var isEncryptedOneToOneRoom = false
var timelineViewState: TimelineViewState // check the doc before changing this

View File

@ -87,7 +87,6 @@ struct RoomScreen: View {
TimelineView()
.id(context.viewState.roomID)
.environmentObject(context)
.environment(\.timelineStyle, context.viewState.timelineStyle)
.environment(\.focussedEventID, context.viewState.timelineViewState.focussedEvent?.eventID)
.overlay(alignment: .bottomTrailing) {
scrollToBottomButton

View File

@ -73,7 +73,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
.padding(.leading, bubbleAvatarPadding)
}
}
.padding(TimelineStyle.bubbles.rowInsets)
.padding(EdgeInsets(top: 1, leading: 8, bottom: 1, trailing: 8))
.highlightedTimelineItem(isFocussed)
}
@ -532,7 +532,6 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
}
}
}
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
}

View File

@ -17,26 +17,6 @@
import Foundation
import SwiftUI
enum TimelineStyle: String, CaseIterable, Codable {
case bubbles
/// List row insets for a timeline
var rowInsets: EdgeInsets {
switch self {
case .bubbles:
return EdgeInsets(top: 1, leading: 8, bottom: 1, trailing: 8)
}
}
/// Short hand for `self == .bubbles`
var isBubbles: Bool {
switch self {
case .bubbles:
return true
}
}
}
enum TimelineGroupStyle: Hashable {
case single
case first
@ -55,20 +35,11 @@ enum TimelineGroupStyle: Hashable {
// MARK: - Environment
private struct TimelineStyleKey: EnvironmentKey {
static let defaultValue = TimelineStyle.bubbles
}
private struct TimelineGroupStyleKey: EnvironmentKey {
static let defaultValue = TimelineGroupStyle.single
}
extension EnvironmentValues {
var timelineStyle: TimelineStyle {
get { self[TimelineStyleKey.self] }
set { self[TimelineStyleKey.self] = newValue }
}
var timelineGroupStyle: TimelineGroupStyle {
get { self[TimelineGroupStyleKey.self] }
set { self[TimelineGroupStyleKey.self] = newValue }

View File

@ -20,8 +20,6 @@ import SwiftUI
// MARK: - TimelineStyler
struct TimelineStyler<Content: View>: View {
@Environment(\.timelineStyle) private var style
let timelineItem: EventBasedTimelineItemProtocol
@ViewBuilder let content: () -> Content
@ -59,10 +57,7 @@ struct TimelineStyler<Content: View>: View {
@ViewBuilder
var mainContent: some View {
switch style {
case .bubbles:
TimelineItemBubbledStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
}
TimelineItemBubbledStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
}
}
@ -200,17 +195,14 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
testView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .bubbles)
.previewDisplayName("Bubbles")
languagesTestView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .bubbles)
.previewDisplayName("Bubbles LTR with different layout languages")
languagesTestView
.environmentObject(viewModel.context)
.environment(\.timelineStyle, .bubbles)
.environment(\.layoutDirection, .rightToLeft)
.previewDisplayName("Bubbles RTL with different layout languages")
}

View File

@ -20,7 +20,6 @@ import SwiftUI
struct TimelineItemStatusView: View {
let timelineItem: EventBasedTimelineItemProtocol
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
@Environment(\.timelineStyle) private var style
@EnvironmentObject private var context: RoomScreenViewModel.Context
private var isLastOutgoingMessage: Bool {

View File

@ -18,15 +18,14 @@ import Foundation
import SwiftUI
struct EmoteRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
@Environment(\.timelineStyle) var timelineStyle
let timelineItem: EmoteRoomTimelineItem
var body: some View {
TimelineStyler(timelineItem: timelineItem) {
if let attributedString = timelineItem.content.formattedBody {
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
} else {
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
}
}
}

View File

@ -44,8 +44,6 @@ struct EncryptedRoomTimelineView: View {
}
struct RoomTimelineViewLabelStyle: LabelStyle {
@Environment(\.timelineStyle) private var timelineStyle
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .center, spacing: 8) {
configuration.icon
@ -53,7 +51,7 @@ struct RoomTimelineViewLabelStyle: LabelStyle {
configuration.title
.foregroundColor(.compound.textPrimary)
}
.padding(.horizontal, timelineStyle == .bubbles ? 4 : 0)
.padding(.horizontal, 4)
}
}

View File

@ -17,7 +17,6 @@
import SwiftUI
struct FormattedBodyText: View {
@Environment(\.timelineStyle) private var timelineStyle
@Environment(\.layoutDirection) private var layoutDirection
private let attributedString: AttributedString
@ -81,17 +80,12 @@ struct FormattedBodyText: View {
@ViewBuilder
var mainContent: some View {
if timelineStyle == .bubbles {
bubbleLayout
.tint(.compound.textLinkExternal)
} else {
plainLayout
.tint(.compound.textLinkExternal)
}
layout
.tint(.compound.textLinkExternal)
}
/// The attributed components laid out for the bubbles timeline style.
var bubbleLayout: some View {
var layout: some View {
TimelineBubbleLayout(spacing: 8) {
ForEach(attributedComponents) { component in
// Ignore if the string contains only the layout correction
@ -115,7 +109,7 @@ struct FormattedBodyText: View {
.layoutPriority(TimelineBubbleLayout.Priority.visibleQuote)
} else {
MessageText(attributedString: component.attributedString)
.padding(.horizontal, timelineStyle == .bubbles ? 4 : 0)
.padding(.horizontal, 4)
.fixedSize(horizontal: false, vertical: true)
.layoutPriority(TimelineBubbleLayout.Priority.regularText)
}
@ -135,27 +129,6 @@ struct FormattedBodyText: View {
}
}
/// The attributed components laid out for the plain timeline style.
var plainLayout: some View {
VStack(alignment: .leading, spacing: 8.0) {
ForEach(attributedComponents) { component in
if component.isBlockquote {
HStack(spacing: 4.0) {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 4.0)
MessageText(attributedString: component.attributedString)
}
.fixedSize(horizontal: false, vertical: true)
} else {
MessageText(attributedString: component.attributedString)
.padding(.horizontal, timelineStyle == .bubbles ? 4 : 0)
.fixedSize(horizontal: false, vertical: true)
}
}
}
}
private var blockquoteAttributes: AttributeContainer {
// The paragraph style removes the block style paragraph that the parser adds by default
// Set directly in the constructor to avoid `Conformance to 'Sendable'` warnings
@ -246,13 +219,11 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview {
}
private struct PreviewBubbleModifier: ViewModifier {
@Environment(\.timelineStyle) private var timelineStyle
func body(content: Content) -> some View {
content
.padding(timelineStyle == .bubbles ? 8 : 0)
.background(timelineStyle == .bubbles ? Color.compound._bgBubbleOutgoing : nil)
.cornerRadius(timelineStyle == .bubbles ? 12 : 0)
.padding(8)
.background(Color.compound._bgBubbleOutgoing)
.cornerRadius(12)
.environmentObject(RoomScreenViewModel.mock.context)
}
}

View File

@ -18,7 +18,6 @@ import SwiftUI
struct LocationRoomTimelineView: View {
let timelineItem: LocationRoomTimelineItem
@Environment(\.timelineStyle) var timelineStyle
var body: some View {
TimelineStyler(timelineItem: timelineItem) {
@ -43,7 +42,7 @@ struct LocationRoomTimelineView: View {
.clipped()
}
} else {
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
}
}
@ -61,8 +60,7 @@ struct LocationRoomTimelineView: View {
private var descriptionView: some View {
if let description = timelineItem.content.description, !description.isEmpty {
FormattedBodyText(text: description)
.padding(.vertical, 8)
.padding(.horizontal, timelineStyle.isBubbles ? 8 : 0)
.padding(8)
}
}

View File

@ -19,7 +19,6 @@ import SwiftUI
struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
let timelineItem: NoticeRoomTimelineItem
@Environment(\.timelineStyle) var timelineStyle
var body: some View {
TimelineStyler(timelineItem: timelineItem) {
@ -30,9 +29,9 @@ struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
Label {
if let attributedString = timelineItem.content.formattedBody {
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
} else {
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
}
} icon: {
CompoundIcon(\.info, size: .small, relativeTo: .compound.bodyLG)

View File

@ -54,32 +54,26 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isOutgoing: false))
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Disclosed, Bubble")
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed(), isOutgoing: false))
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Undisclosed, Bubble")
PollRoomTimelineView(timelineItem: .mock(poll: .endedDisclosed))
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Ended, Disclosed, Bubble")
PollRoomTimelineView(timelineItem: .mock(poll: .endedUndisclosed))
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Ended, Undisclosed, Bubble")
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(createdByAccountOwner: true)))
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Creator, disclosed, Bubble")
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
.environment(\.timelineStyle, .bubbles)
.environmentObject(viewModel.context)
.previewDisplayName("Creator, no votes, Bubble")
}

View File

@ -19,5 +19,4 @@ protocol TextBasedRoomTimelineViewProtocol {
associatedtype TimelineItemType: TextBasedRoomTimelineItem
var timelineItem: TimelineItemType { get }
var timelineStyle: TimelineStyle { get }
}

View File

@ -19,17 +19,16 @@ import SwiftUI
struct TextRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
let timelineItem: TextRoomTimelineItem
@Environment(\.timelineStyle) var timelineStyle
var body: some View {
TimelineStyler(timelineItem: timelineItem) {
if let attributedString = timelineItem.content.formattedBody {
FormattedBodyText(attributedString: attributedString,
additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle),
additionalWhitespacesCount: timelineItem.additionalWhitespaces(),
boostEmojiSize: true)
} else {
FormattedBodyText(text: timelineItem.body,
additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle),
additionalWhitespacesCount: timelineItem.additionalWhitespaces(),
boostEmojiSize: true)
}
}

View File

@ -56,7 +56,6 @@ class TimelineTableViewController: UIViewController {
private let coordinator: TimelineView.Coordinator
private let tableView = UITableView(frame: .zero, style: .plain)
var timelineStyle: TimelineStyle
var timelineItemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>() {
didSet {
guard canApplySnapshot else {
@ -166,11 +165,9 @@ class TimelineTableViewController: UIViewController {
private var hasAppearedOnce = false
init(coordinator: TimelineView.Coordinator,
timelineStyle: TimelineStyle,
isScrolledToBottom: Binding<Bool>,
scrollToBottomPublisher: PassthroughSubject<Void, Never>) {
self.coordinator = coordinator
self.timelineStyle = timelineStyle
_isScrolledToBottom = isScrolledToBottom
super.init(nibName: nil, bundle: nil)

View File

@ -20,18 +20,16 @@ import WysiwygComposer
/// A table view wrapper that displays the timeline of a room.
struct TimelineView: UIViewControllerRepresentable {
@EnvironmentObject private var viewModelContext: RoomScreenViewModel.Context
@Environment(\.timelineStyle) private var timelineStyle
func makeUIViewController(context: Context) -> TimelineTableViewController {
let tableViewController = TimelineTableViewController(coordinator: context.coordinator,
timelineStyle: timelineStyle,
isScrolledToBottom: $viewModelContext.isScrolledToBottom,
scrollToBottomPublisher: viewModelContext.viewState.timelineViewState.scrollToBottomPublisher)
return tableViewController
}
func updateUIViewController(_ uiViewController: TimelineTableViewController, context: Context) {
context.coordinator.update(tableViewController: uiViewController, timelineStyle: timelineStyle)
context.coordinator.update(tableViewController: uiViewController)
}
func makeCoordinator() -> Coordinator {
@ -49,14 +47,11 @@ struct TimelineView: UIViewControllerRepresentable {
}
/// Updates the specified table view's properties from the current view state.
func update(tableViewController: TimelineTableViewController, timelineStyle: TimelineStyle) {
func update(tableViewController: TimelineTableViewController) {
if tableViewController.isSwitchingTimelines != context.viewState.timelineViewState.isSwitchingTimelines {
// Must come before timelineItemsDictionary in order to disable animations.
tableViewController.isSwitchingTimelines = context.viewState.timelineViewState.isSwitchingTimelines
}
if tableViewController.timelineStyle != timelineStyle {
tableViewController.timelineStyle = timelineStyle
}
if tableViewController.timelineItemsDictionary != context.viewState.timelineViewState.itemsDictionary {
tableViewController.timelineItemsDictionary = context.viewState.timelineViewState.itemsDictionary
}

View File

@ -51,8 +51,7 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
// MARK: - Setup
init(parameters: SettingsScreenCoordinatorParameters) {
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
appSettings: parameters.appSettings)
viewModel = SettingsScreenViewModel(userSession: parameters.userSession)
viewModel.actions
.sink { [weak self] action in

View File

@ -26,7 +26,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
actionsSubject.eraseToAnyPublisher()
}
init(userSession: UserSessionProtocol, appSettings: AppSettings) {
init(userSession: UserSessionProtocol) {
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
userID: userSession.clientProxy.userID,
showDeveloperOptions: AppSettings.isDevelopmentBuild),

View File

@ -227,8 +227,7 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com",
deviceID: "AAAAAAAAAAA"))))
return SettingsScreenViewModel(userSession: userSession,
appSettings: ServiceLocator.shared.settings)
return SettingsScreenViewModel(userSession: userSession)
}()
static var previews: some View {

View File

@ -68,10 +68,7 @@ extension EventBasedTimelineItemProtocol {
}
}
func additionalWhitespaces(timelineStyle: TimelineStyle) -> Int {
guard timelineStyle == .bubbles else {
return 0
}
func additionalWhitespaces() -> Int {
var whiteSpaces = 1
localizedSendInfo.forEach { _ in
whiteSpaces += 1

View File

@ -28,7 +28,7 @@ class SettingsScreenViewModelTests: XCTestCase {
@MainActor override func setUpWithError() throws {
cancellables.removeAll()
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
viewModel = SettingsScreenViewModel(userSession: userSession, appSettings: ServiceLocator.shared.settings)
viewModel = SettingsScreenViewModel(userSession: userSession)
context = viewModel.context
}

View File

@ -28,7 +28,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
isThreaded: false,
sender: .init(id: UUID().uuidString),
content: .init(body: "Test"))
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + 1)
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + 1)
}
func testTextRoomTimelineItemWhitespaceEndLonger() {
@ -41,7 +41,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
isThreaded: false,
sender: .init(id: UUID().uuidString),
content: .init(body: "Test"))
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + 1)
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + 1)
}
func testTextRoomTimelineItemWhitespaceEndWithEdit() {
@ -56,7 +56,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
content: .init(body: "Test"))
timelineItem.properties.isEdited = true
let editedCount = L10n.commonEditedSuffix.count
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + editedCount + 2)
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + editedCount + 2)
}
func testTextRoomTimelineItemWhitespaceEndWithEditAndAlert() {
@ -72,6 +72,6 @@ final class TextBasedRoomTimelineTests: XCTestCase {
timelineItem.properties.isEdited = true
timelineItem.properties.deliveryStatus = .sendingFailed
let editedCount = L10n.commonEditedSuffix.count
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + editedCount + 5)
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + editedCount + 5)
}
}