mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
Remove layout styling abstraction (#2982)
This commit is contained in:
parent
064b1bf224
commit
b7345aafca
@ -149,7 +149,6 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var typingMembers: [String] = []
|
var typingMembers: [String] = []
|
||||||
var showLoading = false
|
var showLoading = false
|
||||||
var showReadReceipts = false
|
var showReadReceipts = false
|
||||||
let timelineStyle = TimelineStyle.bubbles
|
|
||||||
var isEncryptedOneToOneRoom = false
|
var isEncryptedOneToOneRoom = false
|
||||||
var timelineViewState: TimelineViewState // check the doc before changing this
|
var timelineViewState: TimelineViewState // check the doc before changing this
|
||||||
|
|
||||||
|
@ -87,7 +87,6 @@ struct RoomScreen: View {
|
|||||||
TimelineView()
|
TimelineView()
|
||||||
.id(context.viewState.roomID)
|
.id(context.viewState.roomID)
|
||||||
.environmentObject(context)
|
.environmentObject(context)
|
||||||
.environment(\.timelineStyle, context.viewState.timelineStyle)
|
|
||||||
.environment(\.focussedEventID, context.viewState.timelineViewState.focussedEvent?.eventID)
|
.environment(\.focussedEventID, context.viewState.timelineViewState.focussedEvent?.eventID)
|
||||||
.overlay(alignment: .bottomTrailing) {
|
.overlay(alignment: .bottomTrailing) {
|
||||||
scrollToBottomButton
|
scrollToBottomButton
|
||||||
|
@ -73,7 +73,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
.padding(.leading, bubbleAvatarPadding)
|
.padding(.leading, bubbleAvatarPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(TimelineStyle.bubbles.rowInsets)
|
.padding(EdgeInsets(top: 1, leading: 8, bottom: 1, trailing: 8))
|
||||||
.highlightedTimelineItem(isFocussed)
|
.highlightedTimelineItem(isFocussed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,7 +532,6 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,26 +17,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
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 {
|
enum TimelineGroupStyle: Hashable {
|
||||||
case single
|
case single
|
||||||
case first
|
case first
|
||||||
@ -55,20 +35,11 @@ enum TimelineGroupStyle: Hashable {
|
|||||||
|
|
||||||
// MARK: - Environment
|
// MARK: - Environment
|
||||||
|
|
||||||
private struct TimelineStyleKey: EnvironmentKey {
|
|
||||||
static let defaultValue = TimelineStyle.bubbles
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct TimelineGroupStyleKey: EnvironmentKey {
|
private struct TimelineGroupStyleKey: EnvironmentKey {
|
||||||
static let defaultValue = TimelineGroupStyle.single
|
static let defaultValue = TimelineGroupStyle.single
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EnvironmentValues {
|
extension EnvironmentValues {
|
||||||
var timelineStyle: TimelineStyle {
|
|
||||||
get { self[TimelineStyleKey.self] }
|
|
||||||
set { self[TimelineStyleKey.self] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
var timelineGroupStyle: TimelineGroupStyle {
|
var timelineGroupStyle: TimelineGroupStyle {
|
||||||
get { self[TimelineGroupStyleKey.self] }
|
get { self[TimelineGroupStyleKey.self] }
|
||||||
set { self[TimelineGroupStyleKey.self] = newValue }
|
set { self[TimelineGroupStyleKey.self] = newValue }
|
||||||
|
@ -20,8 +20,6 @@ import SwiftUI
|
|||||||
// MARK: - TimelineStyler
|
// MARK: - TimelineStyler
|
||||||
|
|
||||||
struct TimelineStyler<Content: View>: View {
|
struct TimelineStyler<Content: View>: View {
|
||||||
@Environment(\.timelineStyle) private var style
|
|
||||||
|
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
|
|
||||||
@ -59,10 +57,7 @@ struct TimelineStyler<Content: View>: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var mainContent: some View {
|
var mainContent: some View {
|
||||||
switch style {
|
TimelineItemBubbledStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
|
||||||
case .bubbles:
|
|
||||||
TimelineItemBubbledStylerView(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, content: content)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,17 +195,14 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
testView
|
testView
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.previewDisplayName("Bubbles")
|
.previewDisplayName("Bubbles")
|
||||||
|
|
||||||
languagesTestView
|
languagesTestView
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.previewDisplayName("Bubbles LTR with different layout languages")
|
.previewDisplayName("Bubbles LTR with different layout languages")
|
||||||
|
|
||||||
languagesTestView
|
languagesTestView
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environment(\.layoutDirection, .rightToLeft)
|
.environment(\.layoutDirection, .rightToLeft)
|
||||||
.previewDisplayName("Bubbles RTL with different layout languages")
|
.previewDisplayName("Bubbles RTL with different layout languages")
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import SwiftUI
|
|||||||
struct TimelineItemStatusView: View {
|
struct TimelineItemStatusView: View {
|
||||||
let timelineItem: EventBasedTimelineItemProtocol
|
let timelineItem: EventBasedTimelineItemProtocol
|
||||||
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
|
let adjustedDeliveryStatus: TimelineItemDeliveryStatus?
|
||||||
@Environment(\.timelineStyle) private var style
|
|
||||||
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
@EnvironmentObject private var context: RoomScreenViewModel.Context
|
||||||
|
|
||||||
private var isLastOutgoingMessage: Bool {
|
private var isLastOutgoingMessage: Bool {
|
||||||
|
@ -18,15 +18,14 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EmoteRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
struct EmoteRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||||
@Environment(\.timelineStyle) var timelineStyle
|
|
||||||
let timelineItem: EmoteRoomTimelineItem
|
let timelineItem: EmoteRoomTimelineItem
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
if let attributedString = timelineItem.content.formattedBody {
|
if let attributedString = timelineItem.content.formattedBody {
|
||||||
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,6 @@ struct EncryptedRoomTimelineView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RoomTimelineViewLabelStyle: LabelStyle {
|
struct RoomTimelineViewLabelStyle: LabelStyle {
|
||||||
@Environment(\.timelineStyle) private var timelineStyle
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
HStack(alignment: .center, spacing: 8) {
|
HStack(alignment: .center, spacing: 8) {
|
||||||
configuration.icon
|
configuration.icon
|
||||||
@ -53,7 +51,7 @@ struct RoomTimelineViewLabelStyle: LabelStyle {
|
|||||||
configuration.title
|
configuration.title
|
||||||
.foregroundColor(.compound.textPrimary)
|
.foregroundColor(.compound.textPrimary)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, timelineStyle == .bubbles ? 4 : 0)
|
.padding(.horizontal, 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct FormattedBodyText: View {
|
struct FormattedBodyText: View {
|
||||||
@Environment(\.timelineStyle) private var timelineStyle
|
|
||||||
@Environment(\.layoutDirection) private var layoutDirection
|
@Environment(\.layoutDirection) private var layoutDirection
|
||||||
|
|
||||||
private let attributedString: AttributedString
|
private let attributedString: AttributedString
|
||||||
@ -81,17 +80,12 @@ struct FormattedBodyText: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var mainContent: some View {
|
var mainContent: some View {
|
||||||
if timelineStyle == .bubbles {
|
layout
|
||||||
bubbleLayout
|
.tint(.compound.textLinkExternal)
|
||||||
.tint(.compound.textLinkExternal)
|
|
||||||
} else {
|
|
||||||
plainLayout
|
|
||||||
.tint(.compound.textLinkExternal)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The attributed components laid out for the bubbles timeline style.
|
/// The attributed components laid out for the bubbles timeline style.
|
||||||
var bubbleLayout: some View {
|
var layout: some View {
|
||||||
TimelineBubbleLayout(spacing: 8) {
|
TimelineBubbleLayout(spacing: 8) {
|
||||||
ForEach(attributedComponents) { component in
|
ForEach(attributedComponents) { component in
|
||||||
// Ignore if the string contains only the layout correction
|
// Ignore if the string contains only the layout correction
|
||||||
@ -115,7 +109,7 @@ struct FormattedBodyText: View {
|
|||||||
.layoutPriority(TimelineBubbleLayout.Priority.visibleQuote)
|
.layoutPriority(TimelineBubbleLayout.Priority.visibleQuote)
|
||||||
} else {
|
} else {
|
||||||
MessageText(attributedString: component.attributedString)
|
MessageText(attributedString: component.attributedString)
|
||||||
.padding(.horizontal, timelineStyle == .bubbles ? 4 : 0)
|
.padding(.horizontal, 4)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.layoutPriority(TimelineBubbleLayout.Priority.regularText)
|
.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 {
|
private var blockquoteAttributes: AttributeContainer {
|
||||||
// The paragraph style removes the block style paragraph that the parser adds by default
|
// 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
|
// Set directly in the constructor to avoid `Conformance to 'Sendable'` warnings
|
||||||
@ -246,13 +219,11 @@ struct FormattedBodyText_Previews: PreviewProvider, TestablePreview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct PreviewBubbleModifier: ViewModifier {
|
private struct PreviewBubbleModifier: ViewModifier {
|
||||||
@Environment(\.timelineStyle) private var timelineStyle
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.padding(timelineStyle == .bubbles ? 8 : 0)
|
.padding(8)
|
||||||
.background(timelineStyle == .bubbles ? Color.compound._bgBubbleOutgoing : nil)
|
.background(Color.compound._bgBubbleOutgoing)
|
||||||
.cornerRadius(timelineStyle == .bubbles ? 12 : 0)
|
.cornerRadius(12)
|
||||||
.environmentObject(RoomScreenViewModel.mock.context)
|
.environmentObject(RoomScreenViewModel.mock.context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import SwiftUI
|
|||||||
|
|
||||||
struct LocationRoomTimelineView: View {
|
struct LocationRoomTimelineView: View {
|
||||||
let timelineItem: LocationRoomTimelineItem
|
let timelineItem: LocationRoomTimelineItem
|
||||||
@Environment(\.timelineStyle) var timelineStyle
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
@ -43,7 +42,7 @@ struct LocationRoomTimelineView: View {
|
|||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
private var descriptionView: some View {
|
||||||
if let description = timelineItem.content.description, !description.isEmpty {
|
if let description = timelineItem.content.description, !description.isEmpty {
|
||||||
FormattedBodyText(text: description)
|
FormattedBodyText(text: description)
|
||||||
.padding(.vertical, 8)
|
.padding(8)
|
||||||
.padding(.horizontal, timelineStyle.isBubbles ? 8 : 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ import SwiftUI
|
|||||||
|
|
||||||
struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||||
let timelineItem: NoticeRoomTimelineItem
|
let timelineItem: NoticeRoomTimelineItem
|
||||||
@Environment(\.timelineStyle) var timelineStyle
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
@ -30,9 +29,9 @@ struct NoticeRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
if let attributedString = timelineItem.content.formattedBody {
|
if let attributedString = timelineItem.content.formattedBody {
|
||||||
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
FormattedBodyText(attributedString: attributedString, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle))
|
FormattedBodyText(text: timelineItem.content.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces())
|
||||||
}
|
}
|
||||||
} icon: {
|
} icon: {
|
||||||
CompoundIcon(\.info, size: .small, relativeTo: .compound.bodyLG)
|
CompoundIcon(\.info, size: .small, relativeTo: .compound.bodyLG)
|
||||||
|
@ -54,32 +54,26 @@ struct PollRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isOutgoing: false))
|
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(), isOutgoing: false))
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.previewDisplayName("Disclosed, Bubble")
|
.previewDisplayName("Disclosed, Bubble")
|
||||||
|
|
||||||
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed(), isOutgoing: false))
|
PollRoomTimelineView(timelineItem: .mock(poll: .undisclosed(), isOutgoing: false))
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.previewDisplayName("Undisclosed, Bubble")
|
.previewDisplayName("Undisclosed, Bubble")
|
||||||
|
|
||||||
PollRoomTimelineView(timelineItem: .mock(poll: .endedDisclosed))
|
PollRoomTimelineView(timelineItem: .mock(poll: .endedDisclosed))
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.previewDisplayName("Ended, Disclosed, Bubble")
|
.previewDisplayName("Ended, Disclosed, Bubble")
|
||||||
|
|
||||||
PollRoomTimelineView(timelineItem: .mock(poll: .endedUndisclosed))
|
PollRoomTimelineView(timelineItem: .mock(poll: .endedUndisclosed))
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.previewDisplayName("Ended, Undisclosed, Bubble")
|
.previewDisplayName("Ended, Undisclosed, Bubble")
|
||||||
|
|
||||||
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(createdByAccountOwner: true)))
|
PollRoomTimelineView(timelineItem: .mock(poll: .disclosed(createdByAccountOwner: true)))
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.previewDisplayName("Creator, disclosed, Bubble")
|
.previewDisplayName("Creator, disclosed, Bubble")
|
||||||
|
|
||||||
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
|
PollRoomTimelineView(timelineItem: .mock(poll: .emptyDisclosed, isEditable: true))
|
||||||
.environment(\.timelineStyle, .bubbles)
|
|
||||||
.environmentObject(viewModel.context)
|
.environmentObject(viewModel.context)
|
||||||
.previewDisplayName("Creator, no votes, Bubble")
|
.previewDisplayName("Creator, no votes, Bubble")
|
||||||
}
|
}
|
||||||
|
@ -19,5 +19,4 @@ protocol TextBasedRoomTimelineViewProtocol {
|
|||||||
associatedtype TimelineItemType: TextBasedRoomTimelineItem
|
associatedtype TimelineItemType: TextBasedRoomTimelineItem
|
||||||
|
|
||||||
var timelineItem: TimelineItemType { get }
|
var timelineItem: TimelineItemType { get }
|
||||||
var timelineStyle: TimelineStyle { get }
|
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,16 @@ import SwiftUI
|
|||||||
|
|
||||||
struct TextRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
struct TextRoomTimelineView: View, TextBasedRoomTimelineViewProtocol {
|
||||||
let timelineItem: TextRoomTimelineItem
|
let timelineItem: TextRoomTimelineItem
|
||||||
@Environment(\.timelineStyle) var timelineStyle
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineStyler(timelineItem: timelineItem) {
|
TimelineStyler(timelineItem: timelineItem) {
|
||||||
if let attributedString = timelineItem.content.formattedBody {
|
if let attributedString = timelineItem.content.formattedBody {
|
||||||
FormattedBodyText(attributedString: attributedString,
|
FormattedBodyText(attributedString: attributedString,
|
||||||
additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle),
|
additionalWhitespacesCount: timelineItem.additionalWhitespaces(),
|
||||||
boostEmojiSize: true)
|
boostEmojiSize: true)
|
||||||
} else {
|
} else {
|
||||||
FormattedBodyText(text: timelineItem.body,
|
FormattedBodyText(text: timelineItem.body,
|
||||||
additionalWhitespacesCount: timelineItem.additionalWhitespaces(timelineStyle: timelineStyle),
|
additionalWhitespacesCount: timelineItem.additionalWhitespaces(),
|
||||||
boostEmojiSize: true)
|
boostEmojiSize: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,6 @@ class TimelineTableViewController: UIViewController {
|
|||||||
private let coordinator: TimelineView.Coordinator
|
private let coordinator: TimelineView.Coordinator
|
||||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
|
|
||||||
var timelineStyle: TimelineStyle
|
|
||||||
var timelineItemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>() {
|
var timelineItemsDictionary = OrderedDictionary<String, RoomTimelineItemViewState>() {
|
||||||
didSet {
|
didSet {
|
||||||
guard canApplySnapshot else {
|
guard canApplySnapshot else {
|
||||||
@ -166,11 +165,9 @@ class TimelineTableViewController: UIViewController {
|
|||||||
private var hasAppearedOnce = false
|
private var hasAppearedOnce = false
|
||||||
|
|
||||||
init(coordinator: TimelineView.Coordinator,
|
init(coordinator: TimelineView.Coordinator,
|
||||||
timelineStyle: TimelineStyle,
|
|
||||||
isScrolledToBottom: Binding<Bool>,
|
isScrolledToBottom: Binding<Bool>,
|
||||||
scrollToBottomPublisher: PassthroughSubject<Void, Never>) {
|
scrollToBottomPublisher: PassthroughSubject<Void, Never>) {
|
||||||
self.coordinator = coordinator
|
self.coordinator = coordinator
|
||||||
self.timelineStyle = timelineStyle
|
|
||||||
_isScrolledToBottom = isScrolledToBottom
|
_isScrolledToBottom = isScrolledToBottom
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
@ -20,18 +20,16 @@ import WysiwygComposer
|
|||||||
/// A table view wrapper that displays the timeline of a room.
|
/// A table view wrapper that displays the timeline of a room.
|
||||||
struct TimelineView: UIViewControllerRepresentable {
|
struct TimelineView: UIViewControllerRepresentable {
|
||||||
@EnvironmentObject private var viewModelContext: RoomScreenViewModel.Context
|
@EnvironmentObject private var viewModelContext: RoomScreenViewModel.Context
|
||||||
@Environment(\.timelineStyle) private var timelineStyle
|
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> TimelineTableViewController {
|
func makeUIViewController(context: Context) -> TimelineTableViewController {
|
||||||
let tableViewController = TimelineTableViewController(coordinator: context.coordinator,
|
let tableViewController = TimelineTableViewController(coordinator: context.coordinator,
|
||||||
timelineStyle: timelineStyle,
|
|
||||||
isScrolledToBottom: $viewModelContext.isScrolledToBottom,
|
isScrolledToBottom: $viewModelContext.isScrolledToBottom,
|
||||||
scrollToBottomPublisher: viewModelContext.viewState.timelineViewState.scrollToBottomPublisher)
|
scrollToBottomPublisher: viewModelContext.viewState.timelineViewState.scrollToBottomPublisher)
|
||||||
return tableViewController
|
return tableViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: TimelineTableViewController, context: Context) {
|
func updateUIViewController(_ uiViewController: TimelineTableViewController, context: Context) {
|
||||||
context.coordinator.update(tableViewController: uiViewController, timelineStyle: timelineStyle)
|
context.coordinator.update(tableViewController: uiViewController)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
func makeCoordinator() -> Coordinator {
|
||||||
@ -49,14 +47,11 @@ struct TimelineView: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the specified table view's properties from the current view state.
|
/// 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 {
|
if tableViewController.isSwitchingTimelines != context.viewState.timelineViewState.isSwitchingTimelines {
|
||||||
// Must come before timelineItemsDictionary in order to disable animations.
|
// Must come before timelineItemsDictionary in order to disable animations.
|
||||||
tableViewController.isSwitchingTimelines = context.viewState.timelineViewState.isSwitchingTimelines
|
tableViewController.isSwitchingTimelines = context.viewState.timelineViewState.isSwitchingTimelines
|
||||||
}
|
}
|
||||||
if tableViewController.timelineStyle != timelineStyle {
|
|
||||||
tableViewController.timelineStyle = timelineStyle
|
|
||||||
}
|
|
||||||
if tableViewController.timelineItemsDictionary != context.viewState.timelineViewState.itemsDictionary {
|
if tableViewController.timelineItemsDictionary != context.viewState.timelineViewState.itemsDictionary {
|
||||||
tableViewController.timelineItemsDictionary = context.viewState.timelineViewState.itemsDictionary
|
tableViewController.timelineItemsDictionary = context.viewState.timelineViewState.itemsDictionary
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,7 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
init(parameters: SettingsScreenCoordinatorParameters) {
|
init(parameters: SettingsScreenCoordinatorParameters) {
|
||||||
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
|
viewModel = SettingsScreenViewModel(userSession: parameters.userSession)
|
||||||
appSettings: parameters.appSettings)
|
|
||||||
|
|
||||||
viewModel.actions
|
viewModel.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
@ -26,7 +26,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol, appSettings: AppSettings) {
|
init(userSession: UserSessionProtocol) {
|
||||||
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
||||||
userID: userSession.clientProxy.userID,
|
userID: userSession.clientProxy.userID,
|
||||||
showDeveloperOptions: AppSettings.isDevelopmentBuild),
|
showDeveloperOptions: AppSettings.isDevelopmentBuild),
|
||||||
|
@ -227,8 +227,7 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
static let viewModel = {
|
static let viewModel = {
|
||||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com",
|
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com",
|
||||||
deviceID: "AAAAAAAAAAA"))))
|
deviceID: "AAAAAAAAAAA"))))
|
||||||
return SettingsScreenViewModel(userSession: userSession,
|
return SettingsScreenViewModel(userSession: userSession)
|
||||||
appSettings: ServiceLocator.shared.settings)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
@ -68,10 +68,7 @@ extension EventBasedTimelineItemProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func additionalWhitespaces(timelineStyle: TimelineStyle) -> Int {
|
func additionalWhitespaces() -> Int {
|
||||||
guard timelineStyle == .bubbles else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
var whiteSpaces = 1
|
var whiteSpaces = 1
|
||||||
localizedSendInfo.forEach { _ in
|
localizedSendInfo.forEach { _ in
|
||||||
whiteSpaces += 1
|
whiteSpaces += 1
|
||||||
|
@ -28,7 +28,7 @@ class SettingsScreenViewModelTests: XCTestCase {
|
|||||||
@MainActor override func setUpWithError() throws {
|
@MainActor override func setUpWithError() throws {
|
||||||
cancellables.removeAll()
|
cancellables.removeAll()
|
||||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
||||||
viewModel = SettingsScreenViewModel(userSession: userSession, appSettings: ServiceLocator.shared.settings)
|
viewModel = SettingsScreenViewModel(userSession: userSession)
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
isThreaded: false,
|
isThreaded: false,
|
||||||
sender: .init(id: UUID().uuidString),
|
sender: .init(id: UUID().uuidString),
|
||||||
content: .init(body: "Test"))
|
content: .init(body: "Test"))
|
||||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + 1)
|
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTextRoomTimelineItemWhitespaceEndLonger() {
|
func testTextRoomTimelineItemWhitespaceEndLonger() {
|
||||||
@ -41,7 +41,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
isThreaded: false,
|
isThreaded: false,
|
||||||
sender: .init(id: UUID().uuidString),
|
sender: .init(id: UUID().uuidString),
|
||||||
content: .init(body: "Test"))
|
content: .init(body: "Test"))
|
||||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + 1)
|
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTextRoomTimelineItemWhitespaceEndWithEdit() {
|
func testTextRoomTimelineItemWhitespaceEndWithEdit() {
|
||||||
@ -56,7 +56,7 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
content: .init(body: "Test"))
|
content: .init(body: "Test"))
|
||||||
timelineItem.properties.isEdited = true
|
timelineItem.properties.isEdited = true
|
||||||
let editedCount = L10n.commonEditedSuffix.count
|
let editedCount = L10n.commonEditedSuffix.count
|
||||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + editedCount + 2)
|
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + editedCount + 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTextRoomTimelineItemWhitespaceEndWithEditAndAlert() {
|
func testTextRoomTimelineItemWhitespaceEndWithEditAndAlert() {
|
||||||
@ -72,6 +72,6 @@ final class TextBasedRoomTimelineTests: XCTestCase {
|
|||||||
timelineItem.properties.isEdited = true
|
timelineItem.properties.isEdited = true
|
||||||
timelineItem.properties.deliveryStatus = .sendingFailed
|
timelineItem.properties.deliveryStatus = .sendingFailed
|
||||||
let editedCount = L10n.commonEditedSuffix.count
|
let editedCount = L10n.commonEditedSuffix.count
|
||||||
XCTAssertEqual(timelineItem.additionalWhitespaces(timelineStyle: .bubbles), timestamp.count + editedCount + 5)
|
XCTAssertEqual(timelineItem.additionalWhitespaces(), timestamp.count + editedCount + 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user