From c0924dc8f3bde143d8fcbbaf9044803c9a41c2ee Mon Sep 17 00:00:00 2001 From: baegteun Date: Thu, 27 Jul 2023 12:51:04 +0900 Subject: [PATCH 1/3] =?UTF-8?q?:bento:=20::=20[#116]=20DesignSystem=20/=20?= =?UTF-8?q?Lottie=20ResourceSynthesizers=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserInterface/DesignSystem/Project.swift | 3 + .../Resources/Lottie/DotoriLoading.json | 1 + Tuist/ResourceSynthesizers/Lottie.stencil | 56 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 Projects/UserInterface/DesignSystem/Resources/Lottie/DotoriLoading.json create mode 100644 Tuist/ResourceSynthesizers/Lottie.stencil diff --git a/Projects/UserInterface/DesignSystem/Project.swift b/Projects/UserInterface/DesignSystem/Project.swift index ba7c8817..6ca7a560 100644 --- a/Projects/UserInterface/DesignSystem/Project.swift +++ b/Projects/UserInterface/DesignSystem/Project.swift @@ -22,5 +22,8 @@ let project = Project.module( .demo(module: .userInterface(.DesignSystem), dependencies: [ .userInterface(target: .DesignSystem) ]) + ], + resourceSynthesizers: .default + [ + .custom(name: "Lottie", parser: .json, extensions: ["json"]) ] ) diff --git a/Projects/UserInterface/DesignSystem/Resources/Lottie/DotoriLoading.json b/Projects/UserInterface/DesignSystem/Resources/Lottie/DotoriLoading.json new file mode 100644 index 00000000..ceb232fd --- /dev/null +++ b/Projects/UserInterface/DesignSystem/Resources/Lottie/DotoriLoading.json @@ -0,0 +1 @@ +{"v":"4.6.3","fr":25,"ip":0,"op":15,"w":200,"h":200,"nm":"Graph Load","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 3","ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":12,"s":[100],"e":[0]},{"t":15}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[128,100,0]},"a":{"a":0,"k":[3.481,-1.019,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.963,14.963]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":0,"k":[0.7921569,0.8156863,0.8352941,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[3.481,-1.019],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":15,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":3,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[100],"e":[0]},{"t":12}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[99.875,100,0]},"a":{"a":0,"k":[3.481,-1.019,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.963,14.963]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":0,"k":[0.7921569,0.8156863,0.8352941,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[3.481,-1.019],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":15,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":3,"s":[100],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[100],"e":[0]},{"t":9}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[71.75,100,0]},"a":{"a":0,"k":[3.481,-1.019,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[14.963,14.963]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":0,"k":[0.7921569,0.8156863,0.8352941,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[3.481,-1.019],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":15,"st":0,"bm":0,"sr":1}]} \ No newline at end of file diff --git a/Tuist/ResourceSynthesizers/Lottie.stencil b/Tuist/ResourceSynthesizers/Lottie.stencil new file mode 100644 index 00000000..6247ee23 --- /dev/null +++ b/Tuist/ResourceSynthesizers/Lottie.stencil @@ -0,0 +1,56 @@ +// swiftformat:disable all +// swiftlint:disable all +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %} +import Foundation +#if canImport(Lottie) +import Lottie +// MARK: - Animations Assets +{{accessModifier}} extension AnimationAsset { + {% for file in files %} + static let {{file.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Self(named: "{{file.name}}") + {% endfor %} +} +// MARK: - Animation Helpers +{{accessModifier}} extension AnimationAsset { + /// All the available animation. Can be used to preload them + static let allAnimations: [Self] = [ + {% for file in files %} + Self.{{file.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, + {% endfor %} + ] +} +// MARK: - Structures +{{accessModifier}} struct AnimationAsset: Hashable { + {{accessModifier}} fileprivate(set) var name: String + {{accessModifier}} let animation: LottieAnimation? + {{accessModifier}} init(named name: String) { + self.name = name + if let url = Bundle.module.url(forResource: name, withExtension: "json") { + self.animation = LottieAnimation.filepath(url.path) + } else { + self.animation = nil + } + } + // MARK: Hashable Conformance + {{accessModifier}} static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.name == rhs.name + } + {{accessModifier}} func hash(into hasher: inout Hasher) { + hasher.combine(self.name) + } +} +// MARK: - Preload Helpers +{{accessModifier}} extension AnimationAsset { + /// Preloads all the Lottie Animations to avoid performance issues when loading them + static func preload() -> Void { + for animationAsset in Self.allAnimations { + _ = animationAsset.animation + } + } +} +#endif +{% else %} +// No files found +{% endif %} From bca04f454d261153b87e6b4474bd46c1b9806158 Mon Sep 17 00:00:00 2001 From: baegteun Date: Thu, 27 Jul 2023 13:16:12 +0900 Subject: [PATCH 2/3] =?UTF-8?q?:dizzy:=20::=20[#116]=20FeatureLayer=20/=20?= =?UTF-8?q?PullToRefresh=20=EC=8B=A4=ED=96=89=20=EC=8B=9C=20LottieAnimatio?= =?UTF-8?q?n=EC=9D=84=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dependency+SPM.swift | 1 + .../Sources/Scene/MassageViewController.swift | 2 +- .../Sources/Scene/NoticeViewController.swift | 2 +- .../Scene/SelfStudyViewController.swift | 2 +- .../UserInterface/DesignSystem/Project.swift | 1 + .../RefreshControl/DotoriRefreshControl.swift | 33 +++++++++++++++++++ Tuist/Dependencies.swift | 1 + 7 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 Projects/UserInterface/DesignSystem/Sources/RefreshControl/DotoriRefreshControl.swift diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift index 02ee9725..ded84b93 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift @@ -5,6 +5,7 @@ public extension TargetDependency { } public extension TargetDependency.SPM { + static let Lottie = TargetDependency.external(name: "Lottie") static let Anim = TargetDependency.external(name: "Anim") static let CombineMiniature = TargetDependency.external(name: "CombineMiniature") static let AsyncNeiSwift = TargetDependency.external(name: "AsyncNeiSwift") diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift index e9d9a532..2db36fc4 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift @@ -17,7 +17,7 @@ final class MassageViewController: BaseStoredViewController { .then { $0.register(cellType: MassageCell.self) } - private let massageRefreshContorol = UIRefreshControl() + private let massageRefreshContorol = DotoriRefreshControl() private lazy var massageTableAdapter = TableViewAdapter>( tableView: massageTableView ) { tableView, indexPath, item in diff --git a/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift b/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift index de9186b9..469a9b7a 100644 --- a/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift +++ b/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift @@ -37,7 +37,7 @@ final class NoticeViewController: BaseStoredViewController { .then { $0.register(cellType: NoticeCell.self) } - private let noticeRefreshControl = UIRefreshControl() + private let noticeRefreshControl = DotoriRefreshControl() private lazy var noticeTableAdapter = TableViewAdapter>( tableView: noticeTableView ) { tableView, indexPath, notice in diff --git a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift index 7585b7a4..5e053743 100644 --- a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift +++ b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift @@ -27,7 +27,7 @@ final class SelfStudyViewController: BaseStoredViewController { .then { $0.register(cellType: SelfStudyCell.self) } - private let selfStudyRefreshContorol = UIRefreshControl() + private let selfStudyRefreshContorol = DotoriRefreshControl() private lazy var selfStudyTableAdapter = TableViewAdapter>( tableView: selfStudyTableView ) { [store] tableView, indexPath, item in diff --git a/Projects/UserInterface/DesignSystem/Project.swift b/Projects/UserInterface/DesignSystem/Project.swift index 6ca7a560..848680be 100644 --- a/Projects/UserInterface/DesignSystem/Project.swift +++ b/Projects/UserInterface/DesignSystem/Project.swift @@ -13,6 +13,7 @@ let project = Project.module( dependencies: [ .SPM.Anim, .SPM.MSGLayout, + .SPM.Lottie, .userInterface(target: .DWebKit), .shared(target: .GlobalThirdPartyLibrary), .shared(target: .UIKitUtil) diff --git a/Projects/UserInterface/DesignSystem/Sources/RefreshControl/DotoriRefreshControl.swift b/Projects/UserInterface/DesignSystem/Sources/RefreshControl/DotoriRefreshControl.swift new file mode 100644 index 00000000..8a0a96ba --- /dev/null +++ b/Projects/UserInterface/DesignSystem/Sources/RefreshControl/DotoriRefreshControl.swift @@ -0,0 +1,33 @@ +import Lottie +import MSGLayout +import UIKit + +public final class DotoriRefreshControl: UIRefreshControl { + private let dotoriLoadingView = LottieAnimationView( + animation: AnimationAsset.dotoriLoading.animation + ) + + public override init() { + super.init() + self.tintColor = .clear + dotoriLoadingView.loopMode = .loop + self.addSubviews { + dotoriLoadingView + } + MSGLayout.buildLayout { + dotoriLoadingView.layout + .size(100) + .center(.toSuperview()) + } + dotoriLoadingView.play() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func beginRefreshing() { + super.beginRefreshing() + dotoriLoadingView.play() + } +} diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift index 78988179..e4ce2c57 100644 --- a/Tuist/Dependencies.swift +++ b/Tuist/Dependencies.swift @@ -5,6 +5,7 @@ let dependencies = Dependencies( carthage: nil, swiftPackageManager: SwiftPackageManagerDependencies( [ + .remote(url: "https://github.com/airbnb/lottie-ios.git", requirement: .exact("4.2.0")), .remote(url: "https://github.com/GSM-MSG/Anim.git", requirement: .exact("1.1.0")), .remote(url: "https://github.com/GSM-MSG/Miniature.git", requirement: .exact("1.3.1")), .remote(url: "https://github.com/baekteun/NeiSwift.git", requirement: .exact("2.0.2")), From 3b011ef66f6be9a4c609e8199b9d4957b42e20c3 Mon Sep 17 00:00:00 2001 From: baegteun Date: Sat, 29 Jul 2023 23:06:08 +0900 Subject: [PATCH 3/3] =?UTF-8?q?:lipstick:=20::=20[#116]=20MusicFeature=20/?= =?UTF-8?q?=20=EA=B8=B0=EC=83=81=EC=9D=8C=EC=95=85=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=AC=EB=95=8C=20refreshControl=EB=A1=9C?= =?UTF-8?q?=20DotoriRefreshControl=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MusicFeature/Sources/Scene/MusicViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/MusicFeature/Sources/Scene/MusicViewController.swift b/Projects/Feature/MusicFeature/Sources/Scene/MusicViewController.swift index ba5baa94..6a614a11 100644 --- a/Projects/Feature/MusicFeature/Sources/Scene/MusicViewController.swift +++ b/Projects/Feature/MusicFeature/Sources/Scene/MusicViewController.swift @@ -25,7 +25,7 @@ final class MusicViewController: BaseStoredViewController { .then { $0.register(cellType: MusicCell.self) } - private let musicRefreshControl = UIRefreshControl() + private let musicRefreshControl = DotoriRefreshControl() private lazy var musicTableAdapter = TableViewAdapter>( tableView: musicTableView ) { [weak self] tableView, indexPath, item in