sendbird-uikit-ios/Sources/CustomSyntaxTest/MessageTemplateParser.swift

1013 lines
36 KiB
Swift

//
// MessageTemplateParser.swift
// SendbirdUIKit
//
// Created by Tez Park on 2022/09/30.
// Copyright © 2022 Sendbird, Inc. All rights reserved.
//
import UIKit
public class MessageTemplateParser: NSObject {
static let MockJson = """
{
"version": "1",
"body": {
"items": [
{
"type": "box",
"layout": "column",
"items": [
{
"type": "box",
"layout": "column",
"items": [
{
"type": "image",
"imageUrl": "https://dxstmhyqfqr1o.cloudfront.net/notifications/preset-notification-channel-cover.png",
"imageStyle": {
"contentMode": "aspectFill"
},
"viewStyle": {},
"metaData": {
"pixelWidth": "168",
"pixelHeight": "168"
}
},
{
"type": "box",
"layout": "column",
"viewStyle": {
"radius": "8",
"padding": {
"top": "12",
"bottom": "12",
"left": "12",
"right": "12"
}
},
"items": [
{
"type": "text",
"align": {
"horizontal": "left",
"vertical": "top"
},
"viewStyle": {},
"width": {
"type": "flex",
"value": "1"
},
"height": {
"type": "flex",
"value": "1"
},
"text": "Hello tez",
"textStyle": {
"color": "#ffbdb8bd",
"size": "16",
"weight": "normal"
},
"maxTextLines": "1"
},
{
"type": "text",
"align": {
"horizontal": "left",
"vertical": "top"
},
"viewStyle": {},
"width": {
"type": "fixed",
"value": "1"
},
"height": {
"type": "flex",
"value": "1"
},
"text": "Your order #123123 has been shipped.",
"textStyle": {
"color": "#ffbdb8bd",
"size": "16",
"weight": "normal"
},
"maxTextLines": "1"
},
{
"type": "textButton",
"viewStyle": {
"backgroundColor": "#E0E0E0",
"padding": {
"top": "10",
"bottom": "10",
"left": "20",
"right": "20"
}
},
"width": {
"type": "flex",
"value": "0"
},
"height": {
"type": "flex",
"value": "0"
},
"text": "Check status",
"textStyle": {
"color": "#742DDD",
"size": "16",
"weight": "normal"
},
"maxTextLines": "5",
"action": {
"type": "web",
"data": "https://naver.com"
}
}
],
"height": {
"type": "fixed",
"value": "300"
},
"width": {
"type": "flex",
"value": "0"
},
"align": {
"horizontal": "left",
"vertical": "top"
}
}
],
"viewStyle": {}
}
],
"viewStyle": {}
}
]
}
}
"""
public static func getMock(widthT: String, widthV: Int, heightT: String, heightV: Int, contentMode: String) -> String {
return """
{"version": 1,"body": {"items": [{"type": "box","layout": "column","items": [{"type": "image","metaData": {"pixelWidth": 4000,"pixelHeight": 3000},"width": {"type": "\(widthT)","value": \(widthV)},"height": {"type": "\(heightT)","value": \(heightV)},"imageStyle": {"contentMode": "\(contentMode)"},"imageUrl": "https://images.unsplash.com/photo-1579393329936-4bc9bc673651?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format"},{"type": "box","viewStyle": {"padding": {"top": 12,"right": 12,"bottom": 12,"left": 12}},"layout": "column","items": [{"type": "box","layout": "row","items": [{"type": "box","layout": "column","items": [{"type": "text","text": "Notification channel creation guide","maxTextLines": 3,"viewStyle": {"padding": {"top": 0,"bottom": 6,"left": 0,"right": 0}},"textStyle": {"size": 16,"weight": "bold"}},{"type": "text","text": "Notification Center is basically a group channel to which a single user, the receiver of a notification, belongs. A notification channel, which is a single group channel dedicated to the Notification Center, must be created for each user.","maxTextLines": 10,"textStyle": {"size": 14}}]}]},{"type": "box","layout": "column","items": [{"type": "box","viewStyle": {"margin": {"top": 16,"bottom": 0,"left": 0,"right": 0}},"align": {"horizontal": "left","vertical": "center"},"layout": "row","action": {"type": "web","data": "www.sendbird.com"},"items": [{"type": "box","viewStyle": {"margin": {"top": 0,"bottom": 0,"left": 12,"right": 0}},"layout": "column","items": [{"type": "text","text": "Title","maxTextLines": 1,"textStyle": {"size": 16,"weight": "bold"}},{"type": "text","viewStyle": {"margin": {"top": 4,"bottom": 0,"left": 0,"right": 0}},"text": "Hi","maxTextLines": 1,"textStyle": {"size": 14}}]}]}]}]}]}]}}
"""
}
/**
var tmpData = MessageTemplateParser.getMock(
// widthT: "fixed", widthV: 200,
// widthT: "flex", widthV: 0,
widthT: "flex", widthV: 1,
// heightT: "fixed", heightV: 200,
// heightT: "flex", heightV: 0,
heightT: "flex", heightV: 1,
// contentMode: "aspectFit"
// contentMode: "aspectFill"
contentMode: "scalesToFill"
)
*/
public func parserTest() {
let data = Data(MessageTemplateParser.MockJson.utf8)
let decoded = try? JSONDecoder().decode(MessageTemplateData.self, from: data)
let items = decoded?.body?.items
let item = items?[0]
switch item {
case .box(let box):
print(box)
case .text(let text):
print(text)
case .image(let image):
print(image)
case .textButton(let textButton):
print(textButton)
case .imageButton(let imageButton):
print(imageButton)
case .none:
break
}
}
}
/**
Root: `MessageTemplateData`
ㄴ body
ㄴ items: `[SBUMessageTemplate.Item]`
ㄴ Box: `SBUMessageTemplate.Box`
ㄴ Text: `SBUMessageTemplate.Text`
ㄴ Image: `SBUMessageTemplate.Image`
ㄴ Button: `SBUMessageTemplate.TextButton`
ㄴ Button: `SBUMessageTemplate.ImageButton`
All item of `SBUMessageTemplate.Item` inherited `SBUMessageTemplate.View`.
*/
// MARK: - Root
class MessageTemplateData: Decodable {
var version: Int?
var body: SBUMessageTemplate.Body?
enum CodingKeys: String, CodingKey {
case version, body
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.version = SBUMessageTemplate.decodeIfPresentMultipleTypeForInt(
forKey: .version,
from: container,
defaultValue: 0
)
self.body = try container.decodeIfPresent(SBUMessageTemplate.Body.self, forKey: .body)
}
}
// MARK: - Body
public class SBUMessageTemplate {
static let urlForTemplateDownload = "TEMPLATE_DOWNLOAD"
class Body: Decodable {
var items: [SBUMessageTemplate.Item]?
}
enum Item {
case box(Box)
case text(Text)
case textButton(TextButton)
case imageButton(ImageButton)
case image(Image)
}
// MARK: Base Item
class View: Decodable {
let type: Item.ItemType
let action: Action?
let viewStyle: ViewStyle?
let width: SizeSpec // fill
let height: SizeSpec // wrap
enum CodingKeys: String, CodingKey {
case type, action, width, height, viewStyle
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(Item.ItemType.self, forKey: .type)
self.action = try container.decodeIfPresent(Action.self, forKey: .action)
self.width = try container.decodeIfPresent(SizeSpec.self, forKey: .width) ?? SizeSpec.fillParent()
self.height = try container.decodeIfPresent(SizeSpec.self, forKey: .height) ?? SizeSpec.wrapContent()
self.viewStyle = try container.decodeIfPresent(ViewStyle.self, forKey: .viewStyle)
}
init(
type: Item.ItemType,
viewStyle: ViewStyle? = nil,
width: SizeSpec = .fillParent(),
height: SizeSpec = .wrapContent(),
action: Action? = nil
) {
self.type = type
self.viewStyle = viewStyle
self.width = width
self.height = height
self.action = action
}
// MARK: Common
func setDefaultRadiusIfNeeded(_ radius: Int) {
if self.viewStyle?.radius == nil {
self.viewStyle?.radius = radius
}
}
func setDefaultPaddingIfNeeded(top: CGFloat, bottom: CGFloat, left: CGFloat, right: CGFloat) {
if self.viewStyle?.padding == nil {
self.viewStyle?.padding = Padding(top: top, bottom: bottom, left: left, right: right)
}
}
}
// MARK: Items
class Box: View {
let layout: LayoutType
let items: [Item]?
let align: ItemsAlign
enum CodingKeys: String, CodingKey {
case items, layout, align
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.layout = try container.decodeIfPresent(LayoutType.self, forKey: .layout) ?? .row
self.items = try container.decodeIfPresent([Item].self, forKey: .items)
self.align = try container.decodeIfPresent(ItemsAlign.self, forKey: .align) ?? ItemsAlign.defaultAlign()
try super.init(from: decoder)
}
init(
layout: LayoutType,
align: ItemsAlign,
type: SBUMessageTemplate.Item.ItemType,
viewStyle: SBUMessageTemplate.ViewStyle? = nil,
width: SizeSpec = .fillParent(),
height: SizeSpec = .wrapContent(),
items: [Item]?,
action: SBUMessageTemplate.Action? = nil
) {
self.layout = layout
self.items = items
self.align = align
super.init(
type: type,
viewStyle: viewStyle,
width: width,
height: height,
action: action
)
}
}
class Text: View {
let text: String
let maxTextLines: Int
let textStyle: TextStyle?
let align: TextAlign
enum CodingKeys: String, CodingKey {
case text, maxTextLines, textStyle, align
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.text = try container.decode(String.self, forKey: .text)
self.maxTextLines = SBUMessageTemplate.decodeIfPresentMultipleTypeForInt(
forKey: .maxTextLines,
from: container,
defaultValue: 0
) ?? 0
self.textStyle = try container.decodeIfPresent(TextStyle.self, forKey: .textStyle)
self.align = try container.decodeIfPresent(TextAlign.self, forKey: .align) ?? TextAlign.defaultAlign()
try super.init(from: decoder)
}
init(
text: String,
maxTextLines: Int,
textStyle: TextStyle?,
type: SBUMessageTemplate.Item.ItemType,
viewStyle: SBUMessageTemplate.ViewStyle? = nil,
width: SizeSpec = .fillParent(),
height: SizeSpec = .wrapContent(),
action: SBUMessageTemplate.Action? = nil,
align: TextAlign = .defaultAlign()
) {
self.text = text
self.maxTextLines = maxTextLines
self.textStyle = textStyle
self.align = align
super.init(
type: type,
viewStyle: viewStyle,
width: width,
height: height,
action: action
)
}
}
class Image: View {
let imageUrl: String
let imageStyle: ImageStyle
let metaData: MetaData?
enum CodingKeys: String, CodingKey {
case imageUrl, imageStyle, metaData
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.imageUrl = try container.decode(String.self, forKey: .imageUrl)
self.imageStyle = try container.decodeIfPresent(ImageStyle.self, forKey: .imageStyle) ?? ImageStyle()
self.metaData = try container.decodeIfPresent(MetaData.self, forKey: .metaData)
try super.init(from: decoder)
}
init(
imageUrl: String,
imageStyle: ImageStyle,
metaData: MetaData?,
viewStyle: SBUMessageTemplate.ViewStyle? = nil,
width: SBUMessageTemplate.SizeSpec = .fillParent(),
height: SBUMessageTemplate.SizeSpec = .wrapContent(),
action: SBUMessageTemplate.Action? = nil
) {
self.imageUrl = imageUrl
self.imageStyle = imageStyle
self.metaData = metaData
super.init(
type: .image,
viewStyle: viewStyle,
width: width,
height: height,
action: action
)
}
}
class TextButton: View {
let text: String?
let maxTextLines: Int
let textStyle: TextStyle?
enum CodingKeys: String, CodingKey {
case text, maxTextLines, textStyle
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.text = try container.decode(String.self, forKey: .text)
self.maxTextLines = SBUMessageTemplate.decodeIfPresentMultipleTypeForInt(
forKey: .maxTextLines,
from: container,
defaultValue: 1
) ?? 1
self.textStyle = try container.decodeIfPresent(TextStyle.self, forKey: .textStyle)
try super.init(from: decoder)
self.setDefaultRadiusIfNeeded(6)
self.setDefaultPaddingIfNeeded(top: 10.0, bottom: 10.0, left: 20.0, right: 20.0)
}
}
class ImageButton: View {
let imageUrl: String
let imageStyle: ImageStyle
let metaData: MetaData?
enum CodingKeys: String, CodingKey {
case imageUrl, imageStyle, metaData
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.imageUrl = try container.decode(String.self, forKey: .imageUrl)
self.imageStyle = try container.decodeIfPresent(ImageStyle.self, forKey: .imageStyle) ?? ImageStyle()
self.metaData = try container.decodeIfPresent(MetaData.self, forKey: .metaData)
try super.init(from: decoder)
}
}
// MARK: - Style
class ViewStyle: Decodable {
let backgroundColor: String?
let backgroundImageUrl: String?
let borderWidth: Int?
let borderColor: String?
var radius: Int?
let margin: Margin?
var padding: Padding?
enum CodingKeys: String, CodingKey {
case backgroundColor, backgroundImageUrl, borderWidth, borderColor, radius, margin, padding
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.backgroundColor = try container.decodeIfPresent(String.self, forKey: .backgroundColor)
self.backgroundImageUrl = try container.decodeIfPresent(String.self, forKey: .backgroundImageUrl)
self.borderWidth = SBUMessageTemplate.decodeIfPresentMultipleTypeForInt(
forKey: .borderWidth,
from: container
)
self.borderColor = try container.decodeIfPresent(String.self, forKey: .borderColor)
self.radius = SBUMessageTemplate.decodeIfPresentMultipleTypeForInt(
forKey: .radius,
from: container
)
self.margin = try container.decodeIfPresent(Margin.self, forKey: .margin)
self.padding = try container.decodeIfPresent(Padding.self, forKey: .padding)
}
init(
backgroundColor: String? = nil,
backgroundImageUrl: String? = nil,
borderWidth: Int? = nil,
borderColor: String? = nil,
radius: Int? = nil,
margin: Margin? = nil,
padding: Padding? = nil
) {
self.backgroundColor = backgroundColor
self.backgroundImageUrl = backgroundImageUrl
self.borderWidth = borderWidth
self.borderColor = borderColor
self.radius = radius
self.margin = margin
self.padding = padding
}
}
class TextStyle: Decodable {
let size: Int?
let color: String?
let weight: WeightType?
enum CodingKeys: String, CodingKey {
case size, color, weight, align
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.size = SBUMessageTemplate.decodeIfPresentMultipleTypeForInt(
forKey: .size,
from: container
)
self.color = try container.decodeIfPresent(String.self, forKey: .color)
self.weight = try container.decodeIfPresent(WeightType.self, forKey: .weight) ?? .normal
}
init(
size: Int? = nil,
color: String? = nil,
weight: WeightType? = nil
) {
self.size = size
self.color = color
self.weight = weight
}
}
class Align: Decodable {
var horizontal: HorizontalAlign?
var vertical: VerticalAlign?
enum CodingKeys: String, CodingKey {
case horizontal, vertical
}
init(horizontal: HorizontalAlign = .left, vertical: VerticalAlign = .top) {
self.horizontal = horizontal
self.vertical = vertical
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.horizontal = try container.decode(HorizontalAlign.self, forKey: .horizontal)
self.vertical = try container.decode(VerticalAlign.self, forKey: .vertical)
}
}
class TextAlign: Align {
class func defaultAlign() -> TextAlign {
let align = TextAlign()
align.horizontal = .left
align.vertical = .top
return align
}
}
class ItemsAlign: Align {
class func defaultAlign() -> ItemsAlign {
let align = ItemsAlign()
align.horizontal = .left
align.vertical = .top
return align
}
}
class ImageStyle: Decodable {
let contentMode: UIView.ContentMode
private let decodedContentMode: ContentMode
let tintColor: String?
enum CodingKeys: String, CodingKey {
case contentMode, tintColor
}
init() {
self.contentMode = .scaleAspectFit
self.decodedContentMode = .aspectFit
self.tintColor = nil
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.decodedContentMode = try container.decodeIfPresent(ContentMode.self, forKey: .contentMode) ?? .aspectFit
switch self.decodedContentMode {
case .scalesToFill:
self.contentMode = .scaleToFill
case .aspectFit:
self.contentMode = .scaleAspectFit
case .aspectFill:
self.contentMode = .scaleAspectFill
}
self.tintColor = try container.decodeIfPresent(String.self, forKey: .tintColor)
}
init (
contentMode: UIView.ContentMode,
tintColor: String?
) {
self.contentMode = contentMode
self.decodedContentMode = .aspectFit
self.tintColor = tintColor
}
}
// MARK: - Action
public class Action: Decodable {
public let type: ActionType
public let data: String
public let alterData: String?
enum CodingKeys: String, CodingKey {
case type, data, alterData
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(ActionType.self, forKey: .type)
self.data = try container.decode(String.self, forKey: .data)
self.alterData = try container.decodeIfPresent(String.self, forKey: .alterData)
}
}
// MARK: - Size
class SizeSpec: Decodable {
var type: SizeType
var value: Int // flex -> 0: fillParent, 1: wrapContent
enum CodingKeys: String, CodingKey {
case type, value
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(SizeType.self, forKey: .type)
self.value = SBUMessageTemplate.decodeMultipleTypeForInt(
forKey: .value,
from: container
)
}
init(type: SizeType = .fixed, value: Int = 0) {
self.type = type
self.value = value
}
class func fillParent() -> SizeSpec {
let sizeSpec = SizeSpec(
type: .flex,
value: FlexSizeType.fillParent.rawValue
)
return sizeSpec
}
class func wrapContent() -> SizeSpec {
let sizeSpec = SizeSpec(
type: .flex,
value: FlexSizeType.wrapContent.rawValue
)
return sizeSpec
}
}
class MetaData: Decodable {
var pixelWidth: Int
var pixelHeight: Int
enum CodingKeys: String, CodingKey {
case pixelWidth, pixelHeight
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pixelWidth = SBUMessageTemplate.decodeMultipleTypeForInt(
forKey: .pixelWidth,
from: container
)
self.pixelHeight = SBUMessageTemplate.decodeMultipleTypeForInt(
forKey: .pixelHeight,
from: container
)
}
}
// MARK: - Margin, Padding
class Margin: Decodable {
let top: CGFloat
let bottom: CGFloat
let left: CGFloat
let right: CGFloat
enum CodingKeys: String, CodingKey {
case top, bottom, left, right
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.top = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .top,
from: container
)
self.bottom = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .bottom,
from: container
)
self.left = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .left,
from: container
)
self.right = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .right,
from: container
)
}
}
class Padding: Decodable {
var top: CGFloat
var bottom: CGFloat
var left: CGFloat
var right: CGFloat
enum CodingKeys: String, CodingKey {
case top, bottom, left, right
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.top = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .top,
from: container
)
self.bottom = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .bottom,
from: container
)
self.left = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .left,
from: container
)
self.right = SBUMessageTemplate.decodeMultipleTypeForCGFloat(
forKey: .right,
from: container
)
}
init(top: CGFloat, bottom: CGFloat, left: CGFloat, right: CGFloat) {
self.top = top
self.bottom = bottom
self.left = left
self.right = right
}
}
// MARK: - Type
enum LayoutType: String, Decodable {
case row, column
}
enum WeightType: String, Decodable {
case normal, bold
}
enum ContentMode: String, Decodable {
case aspectFill, aspectFit, scalesToFill
}
public enum ActionType: String, Decodable {
case web, custom, uikit
}
enum SizeType: String, Decodable {
case fixed, flex
init(from decoder: Decoder) throws {
self = try SizeType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .fixed
}
}
enum FlexSizeType: Int, Decodable {
case fillParent = 0
case wrapContent = 1
}
enum HorizontalAlign: String, Decodable {
case left, center, right
}
enum VerticalAlign: String, Decodable {
case top, center, bottom
}
}
extension SBUMessageTemplate.Item: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TypeCodingKey.self)
let singleContainer = try decoder.singleValueContainer()
let type = try container.decode(ItemType.self, forKey: .type)
switch type {
case .box:
let box = try singleContainer.decode(SBUMessageTemplate.Box.self)
self = .box(box)
case .text:
let text = try singleContainer.decode(SBUMessageTemplate.Text.self)
self = .text(text)
case .textButton:
let textButton = try singleContainer.decode(SBUMessageTemplate.TextButton.self)
self = .textButton(textButton)
case .imageButton:
let imageButton = try singleContainer.decode(SBUMessageTemplate.ImageButton.self)
self = .imageButton(imageButton)
case .image:
let image = try singleContainer.decode(SBUMessageTemplate.Image.self)
self = .image(image)
}
}
enum TypeCodingKey: String, CodingKey {
case type
}
enum ItemType: String, Decodable {
case box
case text
case image
case textButton
case imageButton
}
}
// MARK: - Utils
extension SBUMessageTemplate {
static func decodeMultipleTypeForInt<C: CodingKey>(
forKey key: C,
from container: KeyedDecodingContainer<C>
) -> Int {
if let stringValue = try? container.decode(String.self, forKey: key) {
return Int(stringValue) ?? 0
} else if let intValue = try? container.decode(Int.self, forKey: key) {
return intValue
} else {
return 0
}
}
static func decodeIfPresentMultipleTypeForInt<C: CodingKey>(
forKey key: C,
from container: KeyedDecodingContainer<C>,
defaultValue: Int = 0
) -> Int? {
if let stringValue = try? container.decodeIfPresent(String.self, forKey: key) {
return Int(stringValue)
} else if let intValue = try? container.decodeIfPresent(Int.self, forKey: key) {
return intValue
} else {
return nil
}
}
static func decodeMultipleTypeForCGFloat<C: CodingKey>(
forKey key: C,
from container: KeyedDecodingContainer<C>
) -> CGFloat {
if let stringValue = try? container.decode(String.self, forKey: key) {
return CGFloat(Double(stringValue) ?? 0.0)
} else if let floatValue = try? container.decode(CGFloat.self, forKey: key) {
return floatValue
} else {
return 0.0
}
}
static func decodeIfPresentMultipleTypeForCGFloat<C: CodingKey>(
forKey key: C,
from container: KeyedDecodingContainer<C>,
defaultValue: CGFloat = 0
) -> CGFloat? {
if let stringValue = try? container.decodeIfPresent(String.self, forKey: key) {
if let doubleValue = Double(stringValue) {
return CGFloat(doubleValue)
} else {
return nil
}
} else if let floatValue = try? container.decodeIfPresent(CGFloat.self, forKey: key) {
return floatValue
} else {
return nil
}
}
}
// MARK: - Error message
extension SBUMessageTemplate.Body {
static func parsingError(text: String, subText: String? = nil) -> SBUMessageTemplate.Body {
var textItems: [SBUMessageTemplate.Item] = [
.text(.init(
text: text,
maxTextLines: 10,
textStyle: .init(
size: 14,
color: SBUTheme.notificationTheme.notificationCell.fallbackMessageTitleHexColor,
weight: .normal
),
type: .text,
viewStyle: .init(
padding: .init(top: 0, bottom: 0, left: 0, right: 0)
)
))
]
if let subText = subText {
textItems.append(
.text(.init(
text: subText,
maxTextLines: 10,
textStyle: .init(
size: 14,
color: SBUTheme.notificationTheme.notificationCell.fallbackMessageSubtitleHexColor,
weight: .normal
),
type: .text,
viewStyle: .init(
padding: .init(top: 0, bottom: 0, left: 0, right: 0)
)
))
)
}
let body = SBUMessageTemplate.Body()
body.items = [
.box(.init(
layout: .column,
align: SBUMessageTemplate.ItemsAlign(horizontal: .left, vertical: .center),
type: .box,
items: [
.box(.init(
layout: .column,
align: .init(horizontal: .left, vertical: .center),
type: .box,
viewStyle: .init(
padding: .init(top: 12, bottom: 12, left: 12, right: 12)
),
items: textItems
))
]
))
]
return body
}
static func downloadingTemplate(height: CGFloat) -> SBUMessageTemplate.Body {
let spinnerItems: [SBUMessageTemplate.Item] = [
.image(.init(
imageUrl: SBUMessageTemplate.urlForTemplateDownload,
imageStyle: .init(
contentMode: .center,
tintColor: SBUTheme.notificationTheme.notificationCell.downloadingBackgroundHexColor
),
metaData: nil
))
]
let body = SBUMessageTemplate.Body()
body.items = [
.box(.init(
layout: .column,
align: SBUMessageTemplate.ItemsAlign(horizontal: .center, vertical: .center),
type: .box,
height: .init(type: .fixed, value: Int(height)),
items: [
.box(.init(
layout: .column,
align: .init(horizontal: .center, vertical: .center),
type: .box,
viewStyle: .init(
padding: .init(top: 0, bottom: 0, left: 0, right: 0)
),
width: .init(type: .fixed, value: 36),
height: .init(type: .fixed, value: 36),
items: spinnerItems
))
]
))
]
return body
}
}