mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Moved timeline views to the room screen folder. Extracted timeline code into its own component. Added more preview providers.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// EmoteRoomTimelineView.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11/03/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct EmoteRoomTimelineView: View {
|
||||
let timelineItem: EmoteRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
EventBasedTimelineView(timelineItem: timelineItem)
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "face.dashed").padding(.top, 1.0)
|
||||
if let attributedComponents = timelineItem.attributedComponents {
|
||||
FormattedBodyText(attributedComponents: attributedComponents)
|
||||
} else {
|
||||
Text(timelineItem.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
.id(timelineItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
struct EmoteRoomTimelineView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 20.0) {
|
||||
let timelineItem = EmoteRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
|
||||
timestamp: "Now",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Bob")
|
||||
EmoteRoomTimelineView(timelineItem: timelineItem)
|
||||
|
||||
let timelineItem = EmoteRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Some other text",
|
||||
timestamp: "Later",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Anne")
|
||||
EmoteRoomTimelineView(timelineItem: timelineItem)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// EventBasedTimelineView.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 18/03/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct EventBasedTimelineView: View {
|
||||
let timelineItem: EventBasedTimelineItemProtocol
|
||||
|
||||
var body: some View {
|
||||
if timelineItem.shouldShowSenderDetails {
|
||||
HStack {
|
||||
avatar
|
||||
Text(timelineItem.senderDisplayName ?? timelineItem.senderId)
|
||||
.font(.footnote)
|
||||
.bold()
|
||||
Text(timelineItem.timestamp)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var avatar: some View {
|
||||
ZStack(alignment: .center) {
|
||||
if let avatar = timelineItem.senderAvatar {
|
||||
Image(uiImage: avatar)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.overlay(Circle().stroke(Color.elementGreen))
|
||||
} else {
|
||||
PlaceholderAvatarImage(firstCharacter: String(firstLetter))
|
||||
}
|
||||
}
|
||||
.clipShape(Circle())
|
||||
.frame(width: 24.0, height: 24.0)
|
||||
}
|
||||
|
||||
private var firstLetter: String {
|
||||
if let senderDisplayName = timelineItem.senderDisplayName {
|
||||
return senderDisplayName.prefix(1).uppercased()
|
||||
} else {
|
||||
return timelineItem.senderId.prefix(2).suffix(1).uppercased()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EventBasedTimelineView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 20.0) {
|
||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
|
||||
timestamp: "Now",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Bob")
|
||||
EventBasedTimelineView(timelineItem: timelineItem)
|
||||
|
||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Some other text",
|
||||
timestamp: "Later",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Anne")
|
||||
EventBasedTimelineView(timelineItem: timelineItem)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// FormattedBodyText.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 24/03/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct FormattedBodyText: View {
|
||||
let attributedComponents: [AttributedStringBuilderComponent]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0.0) {
|
||||
ForEach(attributedComponents, id: \.self) { component in
|
||||
if component.isBlockquote {
|
||||
HStack(spacing: 4.0) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color.red)
|
||||
.frame(width: 4.0)
|
||||
Text(component.attributedString)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
} else {
|
||||
Text(component.attributedString)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
.tint(.elementGreen)
|
||||
}
|
||||
}
|
||||
|
||||
struct FormattedBodyText_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let htmlString = """
|
||||
Text before blockquote
|
||||
<blockquote>
|
||||
<b>bold</b> <i>italic</i>
|
||||
</blockquote>
|
||||
Text after blockquote
|
||||
"""
|
||||
|
||||
let attributedStringBuilder = AttributedStringBuilder()
|
||||
let attributedString = attributedStringBuilder.fromHTML(htmlString)
|
||||
|
||||
if let components = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedString) {
|
||||
VStack {
|
||||
FormattedBodyText(attributedComponents: components)
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// ImageRoomTimelineView.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11/03/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ImageRoomTimelineView: View {
|
||||
let timelineItem: ImageRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
if let image = timelineItem.image {
|
||||
VStack(alignment: .leading) {
|
||||
EventBasedTimelineView(timelineItem: timelineItem)
|
||||
Text(timelineItem.text)
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}
|
||||
} else {
|
||||
VStack(alignment: .center) {
|
||||
HStack {
|
||||
Text(timelineItem.text)
|
||||
Spacer()
|
||||
}
|
||||
ProgressView("Loading")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageRoomTimelineView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Some image",
|
||||
timestamp: "Now",
|
||||
shouldShowSenderDetails: false,
|
||||
senderId: "Bob",
|
||||
url: nil,
|
||||
image: UIImage(systemName: "photo"))
|
||||
ImageRoomTimelineView(timelineItem: timelineItem)
|
||||
|
||||
let timelineItem = ImageRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Some other image",
|
||||
timestamp: "Now",
|
||||
shouldShowSenderDetails: false,
|
||||
senderId: "Bob",
|
||||
url: nil,
|
||||
image: nil)
|
||||
ImageRoomTimelineView(timelineItem: timelineItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// NoticeRoomTimelineView.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11/03/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct NoticeRoomTimelineView: View {
|
||||
let timelineItem: NoticeRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
EventBasedTimelineView(timelineItem: timelineItem)
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "exclamationmark.bubble").padding(.top, 2.0)
|
||||
if let attributedComponents = timelineItem.attributedComponents {
|
||||
FormattedBodyText(attributedComponents: attributedComponents)
|
||||
} else {
|
||||
Text(timelineItem.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
.id(timelineItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
struct NoticeRoomTimelineView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 20.0) {
|
||||
let timelineItem = NoticeRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
|
||||
timestamp: "Now",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Bob")
|
||||
NoticeRoomTimelineView(timelineItem: timelineItem)
|
||||
|
||||
let timelineItem = NoticeRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Some other text",
|
||||
timestamp: "Later",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Anne")
|
||||
NoticeRoomTimelineView(timelineItem: timelineItem)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PlaceholderAvatarImage: View {
|
||||
|
||||
let firstCharacter: String
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(.sRGB, red: 0.05, green: 0.74, blue: 0.55, opacity: 1.0)
|
||||
Text(firstCharacter)
|
||||
.padding(4)
|
||||
.foregroundColor(.white)
|
||||
// Make the text resizable (i.e. Make it large and then allow it to scale down)
|
||||
.font(.system(size: 200))
|
||||
.minimumScaleFactor(0.001)
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fill)
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaceholderAvatarImage_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PlaceholderAvatarImage(firstCharacter: "X")
|
||||
.clipShape(Circle())
|
||||
.frame(width: 150, height: 100)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// SeparatorRoomTimelineView.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11/03/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct SeparatorRoomTimelineView: View {
|
||||
let timelineItem: SeparatorRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
LabelledDivider(label: timelineItem.text)
|
||||
.id(timelineItem.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) }
|
||||
}
|
||||
}
|
||||
|
||||
struct SeparatorRoomTimelineView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let item = SeparatorRoomTimelineItem(id: UUID().uuidString, text: "This is a separator")
|
||||
|
||||
SeparatorRoomTimelineView(timelineItem: item)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// TextRoomTimelineView.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11/03/2022.
|
||||
// Copyright © 2022 Element. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct TextRoomTimelineView: View {
|
||||
let timelineItem: TextRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
EventBasedTimelineView(timelineItem: timelineItem)
|
||||
if let attributedComponents = timelineItem.attributedComponents {
|
||||
FormattedBodyText(attributedComponents: attributedComponents)
|
||||
} else {
|
||||
Text(timelineItem.text)
|
||||
}
|
||||
}
|
||||
.id(timelineItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
struct TextRoomTimelineView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 20.0) {
|
||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
|
||||
timestamp: "Now",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Bob")
|
||||
TextRoomTimelineView(timelineItem: timelineItem)
|
||||
|
||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString,
|
||||
text: "Some other text",
|
||||
timestamp: "Later",
|
||||
shouldShowSenderDetails: true,
|
||||
senderId: "Anne")
|
||||
TextRoomTimelineView(timelineItem: timelineItem)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user