diff --git a/BezierSwift/Sources/Components/Emoji/BezierEmoji.swift b/BezierSwift/Sources/Components/Emoji/BezierEmoji.swift new file mode 100644 index 00000000..fb4dbc6f --- /dev/null +++ b/BezierSwift/Sources/Components/Emoji/BezierEmoji.swift @@ -0,0 +1,107 @@ +// +// BezierEmoji.swift +// +// +// Created by Tom on 9/19/24. +// + +import SwiftUI + +import SDWebImageSwiftUI + +private enum Metric { + static let bottomTrailingPadding: CGFloat = -4 +} + +private enum Constant { + static let emojiUrlString: String = "https://cf.channel.io/asset/emoji/images/80/%@.png" +} +// - MARK: BezierAvatar +public struct BezierEmoji: View { + // MARK: Size + public enum Size { + case pt16 + case pt20 + case pt24 + case pt30 + case pt36 + case pt42 + case pt48 + case pt60 + case pt72 + case pt90 + case pt120 + } + + // MARK: Badge + public enum Badge { + case chat + } + + // MARK: Properties + private let name: String + private let size: Size + private let badge: Badge? + + private var emojiUrl: URL? { + if let encodedEmojiUrlString = String(format: Constant.emojiUrlString, self.name).percentEncode() { + return URL(string: encodedEmojiUrlString) + } else { + return nil + } + } + + // MARK: Initializer + /// - Parameters: + /// - name: ch-asset 기반으로 emoji 의 file name 을 사용합니다. + /// - emojipedia 를 통해 name을 검색 할 수 있으며, Shortcodes/github 기준으로 사용합니다. + /// - ex) https://emojipedia.org/😄#technical + /// - size: emoji 사이즈는 `16pt`, `20pt`, `24pt`, `30pt`, `36pt`, `42pt`, `48pt`, `60pt`, `72pt`, `90pt`, `120pt` 로 총 11개 사이즈를 가집니다. + /// - emoji의 경우 예외적으로 semantic name 이 아닌, raw 한 수치 그대로 사용합니다. + /// - badge: 이모지의 상태 배지가 포함될 수 있습니다. + /// - chat (public / private) + public init(name: String, size: Size, badge: Badge? = nil) { + self.name = name + self.size = size + self.badge = badge + } + + // MARK: Body + public var body: some View { + WebImage(url: self.emojiUrl) + .resizable() + .scaledToFit() + .frame(length: self.length) + .if(self.badge.isNotNil) { view in + view + .overlay( + BezierChatBadge(size: .medium) + .padding([.bottom, .trailing], Metric.bottomTrailingPadding), + alignment: .bottomTrailing + ) + } + } +} + +// - MARK: Style +extension BezierEmoji { + private var length: CGFloat { + switch self.size { + case .pt16: return 16 + case .pt20: return 20 + case .pt24: return 24 + case .pt30: return 30 + case .pt36: return 36 + case .pt42: return 42 + case .pt48: return 48 + case .pt60: return 60 + case .pt72: return 72 + case .pt90: return 90 + case .pt120: return 120 + } + } +} + +#Preview { + BezierEmoji(name: "+1", size: .pt24, badge: .chat) +} diff --git a/BezierSwift/Sources/Extensions/OptionalExtensions.swift b/BezierSwift/Sources/Extensions/OptionalExtensions.swift new file mode 100644 index 00000000..3034ddd8 --- /dev/null +++ b/BezierSwift/Sources/Extensions/OptionalExtensions.swift @@ -0,0 +1,26 @@ +// +// File.swift +// +// +// Created by Tom on 9/19/24. +// + +import Foundation + +extension Optional { + var isNil: Bool { self == nil } + var isNotNil: Bool { self != nil } +} + +extension Optional where Wrapped == Bool { + var beTrue: Bool { self == true } + var beFalse: Bool { self == false } +} + +extension Optional where Wrapped == String { + public var isNilOrEmpty: Bool { + guard let self else { return true } + + return self.isEmpty + } +} diff --git a/BezierSwift/Sources/Extensions/StringExtensions.swift b/BezierSwift/Sources/Extensions/StringExtensions.swift index 36bd00c7..3adbbf6d 100644 --- a/BezierSwift/Sources/Extensions/StringExtensions.swift +++ b/BezierSwift/Sources/Extensions/StringExtensions.swift @@ -41,4 +41,11 @@ extension String { return (red: 1, green: 1, blue: 1, alpha: 1) } + + func percentEncode() -> String? { + let unreserved = ":-._~/?#" + let allowed = NSMutableCharacterSet.alphanumeric() + allowed.addCharacters(in: unreserved) + return self.addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet) + } } diff --git a/Examples/SwiftUIExample/SwiftUIExample.xcodeproj/project.pbxproj b/Examples/SwiftUIExample/SwiftUIExample.xcodeproj/project.pbxproj index 136fe048..89ec1f17 100644 --- a/Examples/SwiftUIExample/SwiftUIExample.xcodeproj/project.pbxproj +++ b/Examples/SwiftUIExample/SwiftUIExample.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ E220176D2C75D67900578E64 /* BezierIconButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E220176C2C75D67900578E64 /* BezierIconButtonExample.swift */; }; E22017702C75E09900578E64 /* BezierFloatingButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E220176F2C75E09900578E64 /* BezierFloatingButtonExample.swift */; }; E22017722C75E0A400578E64 /* BezierFloatingIconButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22017712C75E0A400578E64 /* BezierFloatingIconButtonExample.swift */; }; + E255DE992C9C824E00102219 /* BezierEmojiExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E255DE982C9C824E00102219 /* BezierEmojiExample.swift */; }; E258E6BE2C3D8D9C00F69680 /* BezierButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258E6BD2C3D8D9C00F69680 /* BezierButtonExample.swift */; }; E28212322A4B32F700018327 /* SwiftUIExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28212312A4B32F700018327 /* SwiftUIExampleApp.swift */; }; E28212342A4B32F700018327 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28212332A4B32F700018327 /* ContentView.swift */; }; @@ -27,6 +28,7 @@ E220176C2C75D67900578E64 /* BezierIconButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BezierIconButtonExample.swift; sourceTree = ""; }; E220176F2C75E09900578E64 /* BezierFloatingButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BezierFloatingButtonExample.swift; sourceTree = ""; }; E22017712C75E0A400578E64 /* BezierFloatingIconButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BezierFloatingIconButtonExample.swift; sourceTree = ""; }; + E255DE982C9C824E00102219 /* BezierEmojiExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BezierEmojiExample.swift; sourceTree = ""; }; E258E6BD2C3D8D9C00F69680 /* BezierButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BezierButtonExample.swift; sourceTree = ""; }; E282122E2A4B32F700018327 /* SwiftUIExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; E28212312A4B32F700018327 /* SwiftUIExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIExampleApp.swift; sourceTree = ""; }; @@ -70,6 +72,7 @@ E2EF05FB2C5A4CDC00C57676 /* BezierLoaderExample.swift */, E2A392B62C958A540015FA6F /* BezierAvatarExample.swift */, E2A392B82C98169D0015FA6F /* BezierAvatarGroupExample.swift */, + E255DE982C9C824E00102219 /* BezierEmojiExample.swift */, ); path = Examples; sourceTree = ""; @@ -197,6 +200,7 @@ E2EF05FC2C5A4CDC00C57676 /* BezierLoaderExample.swift in Sources */, E2A392B92C98169D0015FA6F /* BezierAvatarGroupExample.swift in Sources */, E22017722C75E0A400578E64 /* BezierFloatingIconButtonExample.swift in Sources */, + E255DE992C9C824E00102219 /* BezierEmojiExample.swift in Sources */, E28D19602C365557009B34A2 /* AppDelegate.swift in Sources */, E28212342A4B32F700018327 /* ContentView.swift in Sources */, E28212322A4B32F700018327 /* SwiftUIExampleApp.swift in Sources */, diff --git a/Examples/SwiftUIExample/SwiftUIExample/ContentView.swift b/Examples/SwiftUIExample/SwiftUIExample/ContentView.swift index 27e38ef8..ec4e700a 100644 --- a/Examples/SwiftUIExample/SwiftUIExample/ContentView.swift +++ b/Examples/SwiftUIExample/SwiftUIExample/ContentView.swift @@ -20,6 +20,11 @@ struct ContentView: View { } label: { Text("Loader") } + NavigationLink { + BezierEmojiExample() + } label: { + Text("Emoji") + } } header: { Text("Status") } diff --git a/Examples/SwiftUIExample/SwiftUIExample/Examples/BezierEmojiExample.swift b/Examples/SwiftUIExample/SwiftUIExample/Examples/BezierEmojiExample.swift new file mode 100644 index 00000000..07e073d1 --- /dev/null +++ b/Examples/SwiftUIExample/SwiftUIExample/Examples/BezierEmojiExample.swift @@ -0,0 +1,24 @@ +// +// BezierEmojiExample.swift +// SwiftUIExample +// +// Created by Tom on 9/20/24. +// + +import SwiftUI + +import BezierSwift + +struct BezierEmojiExample: View { + var body: some View { + BezierEmoji( + name: "+1", + size: .pt24, + badge: .chat + ) + } +} + +#Preview { + BezierEmojiExample() +}