From 56874deb357b72db3cd667e53e3bdd73346f344a Mon Sep 17 00:00:00 2001 From: Luis Gustavo <59032921+luisgustavozup@users.noreply.github.com> Date: Tue, 13 Oct 2020 11:08:04 -0300 Subject: [PATCH] feat: image expression (#914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adding image expression * Changing the image component and adding tests * small modification * change navigation type screen * implement context tab bar + navigationbar * adding image test * adding test to navigationBar item with context * change demo + remove deprecated * renaming typealias + fixes * fixing comment * updating snapshot test * just reducing snapshot image size * code indentation * image configure binding fix * fix markDirty * refactoring tab bar * adding image black hole * fixing some bugs + tests * fix indicator * fixing size * organize methods * fixing item size and tests * undoing expression test change * undoing expression test change * removing unecessary condition * removing unecessary function * different approach instead of using addBindin * apply style improvements * removing unecessary code * adding scrollView to the image screen * change in animation of the component * change in animation of the component * remove swiftlint:disable * fix image-tests * change tabbar scroll Co-authored-by: theffc Co-authored-by: Gabriela Coelho Co-authored-by: Daniel Tes Carrasque Co-authored-by: Lucas Araújo --- .../BeagleDemo.xcodeproj/project.pbxproj | 12 +- .../BeagleDemo/BeagleDemo/AppDelegate.swift | 3 +- .../beagle.imageset/Contents.json | 10 +- .../beagle.imageset/beagle@2x.png | Bin 0 -> 35208 bytes .../beagle.imageset/beagle@3x.png | Bin 0 -> 37660 bytes .../blackHole.imageset/Contents.json | 21 ++ .../blackHole.imageset/buracoNegro.jpg | Bin 0 -> 7817 bytes .../BeagleDemo/BeagleDemo/BeagleStyle.swift | 2 +- .../Generated/AutoDecodable.generated.swift | 2 +- .../Generated/Equality.generated.swift | 2 +- .../BeagleDemo/Constants/Constants.swift | 3 +- .../BeagleDemo/Screens/ImageScreen.swift | 118 +++++++ .../BeagleDemo/Screens/ListViewScreen.swift | 22 +- .../BeagleDemo/Screens/MainScreen.swift | 11 +- .../BeagleDemo/Screens/PageViewScreen.swift | 2 +- .../BeagleDemo/Screens/TabBarScreen.swift | 146 +++++++++ .../BeagleDemo/Screens/TabViewScreen.swift | 65 ---- .../Action/Types/Navigate/Navigate.swift | 15 +- .../Generated/AutoDecodable.generated.swift | 2 +- .../Generated/Equality.generated.swift | 2 +- .../ContextExpression/Expression.swift | 3 + .../Screen/NavigationBar.swift | 2 +- .../ServerDrivenComponent/TabBar/TabBar.swift | 4 +- .../TabView/TabView.swift | 6 +- iOS/Schema/Sources/Widgets/Image/Image.swift | 18 +- .../Beagle/Beagle.xcodeproj/project.pbxproj | 20 +- .../Generated/AutoDecodable.generated.swift | 2 +- .../Generated/Equality.generated.swift | 2 +- .../Tests/BeaglePrefetchHelperTests.swift | 26 +- .../Button+Renderable/Tests/ButtonTests.swift | 2 +- .../Image+Renderable/Image+Renderable.swift | 53 +++- .../Image+Renderable/Tests/ImageTests.swift | 48 ++- .../testLocalImageWithContext.1.png | Bin 0 -> 7670 bytes .../Screen/NavigationBar+UIKit.swift | 23 +- .../Screen/Tests/ScreenComponentTests.swift | 26 +- ...tNavigationBarItemWithContextOnImage.1.png | Bin 0 -> 5839 bytes .../Style/StyleViewConfigurator.swift | 10 +- .../ContainerIndicatorView.swift | 39 --- .../TabBar+Renderable/TabBar+Renderable.swift | 21 +- .../TabBarCollectionViewCell.swift | 149 --------- .../TabBarItemUIComponent.swift | 137 ++++++++ .../TabBar+Renderable/TabBarUIComponent.swift | 294 ++++++++++-------- .../Tests/TabBarCollectionViewCellTests.swift | 68 ---- .../TabBar+Renderable/Tests/TabBarTests.swift | 99 +++++- .../Tests/TabBarUIComponentTests.swift | 55 ---- .../testCurrentTabWithContext.1.png | Bin 0 -> 13352 bytes .../TabBarTests/testImageWithContext.1.png | Bin 0 -> 13363 bytes .../TabBarTests/test_viewWithTabBar.1.png | Bin 21480 -> 0 bytes .../TabView+Renderable.swift | 2 +- .../TabViewUIComponent.swift | 12 +- .../Tests/TabViewUIComponentTests.swift | 22 +- .../Tests/UIView+ContextTests.swift | 4 +- .../Logger/Tests/BeagleLoggerTests.swift | 2 +- .../Renderer/BeagleScreenViewController.swift | 5 +- iOS/Sources/Beagle/Sources/Theme/Theme.swift | 15 +- .../Beagle/Sources/Theme/ThemeTests.swift | 8 +- 56 files changed, 966 insertions(+), 649 deletions(-) create mode 100644 iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/beagle@2x.png create mode 100644 iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/beagle@3x.png create mode 100644 iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/blackHole.imageset/Contents.json create mode 100644 iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/blackHole.imageset/buracoNegro.jpg create mode 100644 iOS/Example/BeagleDemo/BeagleDemo/Screens/ImageScreen.swift create mode 100644 iOS/Example/BeagleDemo/BeagleDemo/Screens/TabBarScreen.swift delete mode 100644 iOS/Example/BeagleDemo/BeagleDemo/Screens/TabViewScreen.swift create mode 100644 iOS/Sources/Beagle/Sources/Components/Image+Renderable/Tests/__Snapshots__/ImageTests/testLocalImageWithContext.1.png create mode 100644 iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/Tests/__Snapshots__/ScreenComponentTests/testNavigationBarItemWithContextOnImage.1.png delete mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/ContainerIndicatorView.swift delete mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarCollectionViewCell.swift create mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarItemUIComponent.swift delete mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarCollectionViewCellTests.swift delete mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarUIComponentTests.swift create mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/__Snapshots__/TabBarTests/testCurrentTabWithContext.1.png create mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/__Snapshots__/TabBarTests/testImageWithContext.1.png delete mode 100644 iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/__Snapshots__/TabBarTests/test_viewWithTabBar.1.png diff --git a/iOS/Example/BeagleDemo/BeagleDemo.xcodeproj/project.pbxproj b/iOS/Example/BeagleDemo/BeagleDemo.xcodeproj/project.pbxproj index 318526c83f..f1d1890209 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo.xcodeproj/project.pbxproj +++ b/iOS/Example/BeagleDemo/BeagleDemo.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 6E4EE6C1240E970000624DFB /* ScreenDeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4EE6C0240E970000624DFB /* ScreenDeepLink.swift */; }; 6E4EE6C7240E9C0B00624DFB /* ViewLayoutHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4EE6C6240E9C0B00624DFB /* ViewLayoutHelper.swift */; }; 6E8E1CC1249C57300053AD58 /* SimpleForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8E1CC0249C57300053AD58 /* SimpleForm.swift */; }; + 6EA428722514457F00AEE741 /* ImageScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA428712514457F00AEE741 /* ImageScreen.swift */; }; 6ED5B09E240464520070CB9D /* BeagleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED5B09D240464520070CB9D /* BeagleStyle.swift */; }; 7685B043249A879F007EDED0 /* SendRequestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7685B042249A879F007EDED0 /* SendRequestScreen.swift */; }; 932925B22327E0E400A61F01 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932925B12327E0E400A61F01 /* AppDelegate.swift */; }; @@ -27,7 +28,7 @@ 93D24A742514F9DA005A5CBD /* BeagleViewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D24A732514F9DA005A5CBD /* BeagleViewScreen.swift */; }; 93D8DC0D248965D10070CB41 /* ComponentInteractionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D8DC0C248965D10070CB41 /* ComponentInteractionScreen.swift */; }; 93EAFA4C24083C7B00B73522 /* ListViewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EAFA4B24083C7B00B73522 /* ListViewScreen.swift */; }; - 98A7F36C23A926C600E88C70 /* TabViewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7F36B23A926C600E88C70 /* TabViewScreen.swift */; }; + 98A7F36C23A926C600E88C70 /* TabBarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A7F36B23A926C600E88C70 /* TabBarScreen.swift */; }; 98E08D2324228DD200FBD21B /* WebViewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E08D2224228DD200FBD21B /* WebViewScreen.swift */; }; 98F3D41124ED770F006DAB05 /* NavigateTypeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3D41024ED770F006DAB05 /* NavigateTypeScreen.swift */; }; A038C8FD2407073D001164B1 /* FormScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A038C8FC2407073D001164B1 /* FormScreen.swift */; }; @@ -104,6 +105,7 @@ 6E4EE6C0240E970000624DFB /* ScreenDeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenDeepLink.swift; sourceTree = ""; }; 6E4EE6C6240E9C0B00624DFB /* ViewLayoutHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLayoutHelper.swift; sourceTree = ""; }; 6E8E1CC0249C57300053AD58 /* SimpleForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleForm.swift; sourceTree = ""; }; + 6EA428712514457F00AEE741 /* ImageScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScreen.swift; sourceTree = ""; }; 6ED5B09D240464520070CB9D /* BeagleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeagleStyle.swift; sourceTree = ""; }; 7685B042249A879F007EDED0 /* SendRequestScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendRequestScreen.swift; sourceTree = ""; }; 932925AE2327E0E400A61F01 /* BeagleDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeagleDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -117,7 +119,7 @@ 93D24A732514F9DA005A5CBD /* BeagleViewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeagleViewScreen.swift; sourceTree = ""; }; 93D8DC0C248965D10070CB41 /* ComponentInteractionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentInteractionScreen.swift; sourceTree = ""; }; 93EAFA4B24083C7B00B73522 /* ListViewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewScreen.swift; sourceTree = ""; }; - 98A7F36B23A926C600E88C70 /* TabViewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewScreen.swift; sourceTree = ""; }; + 98A7F36B23A926C600E88C70 /* TabBarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarScreen.swift; sourceTree = ""; }; 98E08D2224228DD200FBD21B /* WebViewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewScreen.swift; sourceTree = ""; }; 98F3D41024ED770F006DAB05 /* NavigateTypeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigateTypeScreen.swift; sourceTree = ""; }; A038C8FC2407073D001164B1 /* FormScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormScreen.swift; sourceTree = ""; }; @@ -262,9 +264,10 @@ 6E4EE6C0240E970000624DFB /* ScreenDeepLink.swift */, 7685B042249A879F007EDED0 /* SendRequestScreen.swift */, 6E8E1CC0249C57300053AD58 /* SimpleForm.swift */, - 98A7F36B23A926C600E88C70 /* TabViewScreen.swift */, + 98A7F36B23A926C600E88C70 /* TabBarScreen.swift */, 6E4EE6C6240E9C0B00624DFB /* ViewLayoutHelper.swift */, 98E08D2224228DD200FBD21B /* WebViewScreen.swift */, + 6EA428712514457F00AEE741 /* ImageScreen.swift */, 4F36AB2D251E7A5A0036F839 /* GlobalContextScreen.swift */, ); path = Screens; @@ -524,11 +527,12 @@ A0642DCB23968A020028AFF3 /* MainScreen.swift in Sources */, C80AF1342493F0950097D200 /* OtherComponent.swift in Sources */, C09F4100249AAB0F002605EE /* TextContainerWithAction.swift in Sources */, + 6EA428722514457F00AEE741 /* ImageScreen.swift in Sources */, 2576F469250ACF6C000CDB2A /* NativeViewController.swift in Sources */, C8A2F6F42464C1F9002198B1 /* AutoDecodable.generated.swift in Sources */, 93EAFA4C24083C7B00B73522 /* ListViewScreen.swift in Sources */, 932925B22327E0E400A61F01 /* AppDelegate.swift in Sources */, - 98A7F36C23A926C600E88C70 /* TabViewScreen.swift in Sources */, + 98A7F36C23A926C600E88C70 /* TabBarScreen.swift in Sources */, A057D59824C9FE1D001F6835 /* ErrorView.swift in Sources */, 98E08D2324228DD200FBD21B /* WebViewScreen.swift in Sources */, A0642DC82395A1840028AFF3 /* DeeplinkScreenManager.swift in Sources */, diff --git a/iOS/Example/BeagleDemo/BeagleDemo/AppDelegate.swift b/iOS/Example/BeagleDemo/BeagleDemo/AppDelegate.swift index 7a4f334e48..30eed49707 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/AppDelegate.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/AppDelegate.swift @@ -27,7 +27,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let deepLinkHandler = DeeplinkScreenManager.shared deepLinkHandler[.lazyComponentEndpoint] = LazyComponentScreen.self deepLinkHandler[.pageViewEndpoint] = PageViewScreen.self - deepLinkHandler[.tabViewEndpoint] = TabViewScreen.self + deepLinkHandler[.tabBarEndpoint] = TabBarScreen.self deepLinkHandler[.formEndpoint] = FormScreen.self deepLinkHandler[.customComponentEndpoint] = CustomComponentScreen.self deepLinkHandler[.screenDeeplinkEndpoint] = ScreenDeepLink.self @@ -38,6 +38,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { deepLinkHandler[.simpleFormEndpoint] = SimpleFormScreen.self deepLinkHandler[.navigateStep1Endpoint] = NavigateStep1Screen.self deepLinkHandler[.navigateStep2Endpoint] = NavigateStep2Screen.self + deepLinkHandler[.imageEndpoint] = ImageScreen.self deepLinkHandler[.globalContextEndpoint] = GlobalContexScreen.self deepLinkHandler[.beagleView] = BeagleViewScreen.self diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/Contents.json b/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/Contents.json index 999a25bdf0..b438c8fc57 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/Contents.json +++ b/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/Contents.json @@ -1,21 +1,23 @@ { "images" : [ { - "idiom" : "universal", "filename" : "beagle.png", + "idiom" : "universal", "scale" : "1x" }, { + "filename" : "beagle@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "beagle@3x.png", "idiom" : "universal", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/beagle@2x.png b/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/beagle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..01ae23c2554d40b191f36c190a71ececd644f314 GIT binary patch literal 35208 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 HG&JKMf;Gv+ literal 0 HcmV?d00001 diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/beagle@3x.png b/iOS/Example/BeagleDemo/BeagleDemo/Assets.xcassets/beagle.imageset/beagle@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..892b25ae59937b2d962fbd7ea22ed01fed82333d GIT binary patch literal 37660 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 W!`&sp-+@@<;2Fa7ljY#<=zjo3EfNR- literal 0 HcmV?d00001 diff --git a/iOS/Example/BeagleDemo/BeagleDemo/BeagleStyle.swift b/iOS/Example/BeagleDemo/BeagleDemo/BeagleStyle.swift index 99a351cf67..613e74dcf7 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/BeagleStyle.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/BeagleStyle.swift @@ -97,7 +97,7 @@ struct AppTheme { } static func tabView() -> (UIView?) -> Void { - return BeagleStyle.tabBar(backgroundColor: .clear, indicatorColor: .demoGray, selectedTextColor: .demoGray, unselectedTextColor: .demoDarkGray, selectedIconColor: .demoGray, unselectedIconColor: .demoDarkGray) + return BeagleStyle.tabBar(backgroundColor: .clear, indicatorColor: .demoGray, selectedTextColor: .demoGray, unselectedTextColor: .demoDarkGray) } static func textInput() -> (UITextField?) -> Void { diff --git a/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/AutoDecodable.generated.swift b/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/AutoDecodable.generated.swift index 0aa1eb279b..fda65c457a 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/AutoDecodable.generated.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/AutoDecodable.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.0.0 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.18.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* diff --git a/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/Equality.generated.swift b/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/Equality.generated.swift index 3b549669b5..aeec380173 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/Equality.generated.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/CodeGeneration/Generated/Equality.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.0.0 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.18.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Constants/Constants.swift b/iOS/Example/BeagleDemo/BeagleDemo/Constants/Constants.swift index 8c4cc00c67..ce4dbbb42a 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/Constants/Constants.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/Constants/Constants.swift @@ -26,7 +26,7 @@ extension String { // MARK: - Endpoint static let lazyComponentEndpoint = "lazycomponent" static let pageViewEndpoint = "pageview" - static let tabViewEndpoint = "tabview" + static let tabBarEndpoint = "tabbar" static let listViewEndpoint = "listview" static let formEndpoint = "form" static let customComponentEndpoint = "customComponent" @@ -42,6 +42,7 @@ extension String { static let componentInterationEndpoint = "componentInteractionText" static let conditionActionEndpoint = "conditionActionText" static let simpleFormEndpoint = "simpleFormComponent" + static let imageEndpoint = "image" // MARK: - URL static let webViewURL = "https://maps.google.com/" diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Screens/ImageScreen.swift b/iOS/Example/BeagleDemo/BeagleDemo/Screens/ImageScreen.swift new file mode 100644 index 0000000000..6861a4c7ed --- /dev/null +++ b/iOS/Example/BeagleDemo/BeagleDemo/Screens/ImageScreen.swift @@ -0,0 +1,118 @@ +/* + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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 UIKit +import Beagle +import BeagleSchema + +struct ImageScreen: DeeplinkScreen { + + init(path: String, data: [String: String]?) { + // Intentionally unimplemented... + } + + func screenController() -> UIViewController { + return Beagle.screen(.declarative(screen)) + } + + var screen: Screen { + return Screen( + navigationBar: NavigationBar(title: "Image"), + child: container() + ) + } + + private let sizeImage = WidgetProperties( + style: Style( + size: Size().height(100).width(100), + flex: Flex().alignSelf(.center) + ) + ) + + private func container() -> Container { + return Container(context: Context( + id: "img", + value: [ + "remote": .string(.networkImageBeagle), + "local": "imageBeagle", + "pathLocal": ["_beagleImagePath_": "local", "mobileId": "imageBeagle"], + "pathRemote": ["_beagleImagePath_": "remote", "url": .string(.networkImageBeagle)] + ] + )) { + ScrollView { + createText(text: "Image url with context!") + Image( + .remote(Image.Remote(url: "@{img.remote}")), + widgetProperties: sizeImage + ) + + createText(text: "Image mobileId with context!") + Image( + .local("@{img.local}"), + widgetProperties: sizeImage + ) + createText(text: "Image with type remote path with context!") + Image( + "@{img.pathRemote}", + widgetProperties: sizeImage + ) + createText(text: "Image with type local path with context!") + Image( + "@{img.pathLocal}", + widgetProperties: sizeImage + ) + createText(text: "Image url without context!") + Image( + .remote(.init(url: .networkImageBeagle)), + widgetProperties: sizeImage + ) + + createText(text: "Image mobileId without context!") + Image( + .local("beagle"), + widgetProperties: sizeImage + ) + + Button( + text: "Chage Context", + onPress: [ + SetContext( + contextId: "img", + value: [ + "remote": "https://cdn.eso.org/images/screen/eso1907a.jpg", + "local": "beagle", + "pathLocal": ["_beagleImagePath_": "local", "mobileId": "beagle"], + "pathRemote": ["_beagleImagePath_": "remote", "url": "https://cdn.eso.org/images/screen/eso1907a.jpg"] + ] + ) + ] + ) + } + } + } + + private func createText(text: String) -> Text { + return Text( + .value(text), + widgetProperties: WidgetProperties( + style: Style( + margin: EdgeValue().vertical(10), + flex: Flex().alignSelf(.center) + ) + ) + ) + } +} diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Screens/ListViewScreen.swift b/iOS/Example/BeagleDemo/BeagleDemo/Screens/ListViewScreen.swift index fb28f63c6d..ac0883afe9 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/Screens/ListViewScreen.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/Screens/ListViewScreen.swift @@ -29,14 +29,24 @@ struct ListViewScreen: DeeplinkScreen { var screen: Screen { return Screen( - navigationBar: NavigationBar(title: "ListView"), - child: listView + navigationBar: NavigationBar( + title: "ListView", + navigationBarItems: [ + NavigationBarItem( + image: "@{img}", + text: "", + action: SetContext(contextId: "img", value: "imageBeagle") + ) + ] + ), + child: listView, + context: Context(id: "img", value: "informationImage") ) } var listView = ListView(direction: .horizontal) { - Touchable(onPress: [Navigate.pushView(.remote(.init(url: .value(.textLazyComponentEndpoint))))]) { - Text("0000") + Touchable(onPress: [Navigate.pushView(.remote(.init(url: .textLazyComponentEndpoint)))]) { + Text("0000") } Text("0001", widgetProperties: .init(style: Style(size: Size().width(100).height(100)))) Text("0002") @@ -55,11 +65,11 @@ struct ListViewScreen: DeeplinkScreen { Text("0011") Text("0012") Text("0013") - Image(.value(.local("beagle"))) + Image(.local("beagle")) Text("0014") Text("0015") Text("0016") - Image(.value(.remote(.init(url: .networkImageBeagle)))) + Image(.remote(.init(url: .networkImageBeagle))) Text("0017") Text("0018") Text("0019") diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Screens/MainScreen.swift b/iOS/Example/BeagleDemo/BeagleDemo/Screens/MainScreen.swift index bd2ce17666..ee6c11af8a 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/Screens/MainScreen.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/Screens/MainScreen.swift @@ -51,13 +51,17 @@ struct MainScreen: DeeplinkScreen { onPress: [Navigate.openNativeRoute(.init(route: .pageViewEndpoint))] ) Button( - text: "Tab View", - onPress: [Navigate.openNativeRoute(.init(route: .tabViewEndpoint))] + text: "Tab Bar", + onPress: [Navigate.openNativeRoute(.init(route: .tabBarEndpoint))] ) Button( text: "List View", onPress: [Navigate.openNativeRoute(.init(route: .listViewEndpoint))] ) + Button( + text: "Image", + onPress: [Navigate.openNativeRoute(.init(route: .imageEndpoint))] + ) Button( text: "Form", onPress: [Navigate.openNativeRoute(.init(route: .formEndpoint))] @@ -104,7 +108,7 @@ struct MainScreen: DeeplinkScreen { ) Button( text: "Sample BFF", - onPress: [Navigate.pushView(.remote(.init(url: .value(.componentsEndpoint))))] + onPress: [Navigate.pushView(.remote(.init(url: .componentsEndpoint)))] ) } } @@ -114,4 +118,5 @@ struct MainScreen: DeeplinkScreen { title: "Beagle Demo" ) } + } diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Screens/PageViewScreen.swift b/iOS/Example/BeagleDemo/BeagleDemo/Screens/PageViewScreen.swift index 8e931d8f0c..26911969bc 100644 --- a/iOS/Example/BeagleDemo/BeagleDemo/Screens/PageViewScreen.swift +++ b/iOS/Example/BeagleDemo/BeagleDemo/Screens/PageViewScreen.swift @@ -47,7 +47,7 @@ struct Page { Text("Text with alignment attribute set to center", alignment: Expression.value(.center)) Text("Text with alignment attribute set to right", alignment: Expression.value(.right)) Text("Text with alignment attribute set to left", alignment: Expression.value(.left)) - Image(.value(.remote(.init(url: .networkImageBeagle)))) + Image(.remote(.init(url: .networkImageBeagle))) } } } diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Screens/TabBarScreen.swift b/iOS/Example/BeagleDemo/BeagleDemo/Screens/TabBarScreen.swift new file mode 100644 index 0000000000..73c9466ec0 --- /dev/null +++ b/iOS/Example/BeagleDemo/BeagleDemo/Screens/TabBarScreen.swift @@ -0,0 +1,146 @@ +/* + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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 UIKit +import Beagle +import BeagleSchema + +struct TabBarScreen: DeeplinkScreen { + init(path: String, data: [String: String]?) { + } + + func screenController() -> UIViewController { + return Beagle.screen(.declarative(screen)) + } + + var screen = Screen(navigationBar: NavigationBar(title: "TabBar")) { + Container(context: + Context(id: "tab", + value: [ + "currentTab": 0, + "icon": [ + "tab1": "beagle", + "tab4": "imageBeagle", + "tab5": "informationImage", + "tab8": "blackHole" + ] + ] + ), widgetProperties: .init(Flex().grow(1))) { + TabBar( + items: [ + TabBarItem(icon: "@{tab.icon.tab1}"), + TabBarItem(title: "Tab 2"), + TabBarItem(title: "Tab 3"), + TabBarItem(icon: "@{tab.icon.tab4}", title: "Tab 4"), + TabBarItem(icon: "@{tab.icon.tab5}"), + TabBarItem(title: "Tab 6"), + TabBarItem(title: "Tab 7"), + TabBarItem(icon: "@{tab.icon.tab8}", title: "Tab 8") + ], + styleId: .tabViewStyle, + currentTab: "@{tab.currentTab}", + onTabSelection: [SetContext(contextId: "tab", path: "currentTab", value: "@{onTabSelection}")] + ) + PageView( + onPageChange: [SetContext(contextId: "tab", path: "currentTab", value: "@{onPageChange}")], + currentPage: "@{tab.currentTab}" + ) { + Container(widgetProperties: .init(Flex().alignContent(.center))) { + Text("Text1 Tab 1") + Image(.remote(.init(url: .networkImageBeagle, placeholder: "imageBeagle"))) + Text("Text2 Tab 1") + Button(text: "change Context Tab 1", onPress: [ + SetContext(contextId: "tab", path: "icon", value: [ + "tab1": "beagle", + "tab4": "imageBeagle", + "tab5": "informationImage", + "tab8": "blackHole" + ]) + ]) + } + Container(widgetProperties: .init(Flex().justifyContent(.center).alignItems(.center))) { + Text("Text1 Tab 2") + Text("Text2 Tab 2") + Button(text: "change Context Tab 2", onPress: [ + SetContext(contextId: "tab", path: "icon", value: [ + "tab1": "blackHole", + "tab4": "informationImage", + "tab5": "imageBeagle", + "tab8": "beagle" + ]) + ]) + } + Container(widgetProperties: .init(Flex().justifyContent(.flexStart))) { + Text("Text1 Tab 3") + Text("Text2 Tab 3") + } + Container(widgetProperties: .init(Flex().alignItems(.center))) { + Text("Text1 Tab 4") + Text("Text2 Tab 4") + Button(text: "change Context Tab 4", onPress: [ + SetContext(contextId: "tab", path: "icon", value: [ + "tab1": "informationImage", + "tab4": "blackHole", + "tab5": "beagle", + "tab8": "imageBeagle" + ]) + ]) + } + Container(widgetProperties: .init(Flex().alignContent(.center))) { + Text("Text1 Tab 5") + Image(.remote(.init(url: .networkImageBeagle, placeholder: "imageBeagle"))) + Text("Text2 Tab 5") + Button(text: "change Context Tab 5", onPress: [ + SetContext(contextId: "tab", path: "icon", value: [ + "tab1": "beagle", + "tab4": "informationImage", + "tab5": "beagle", + "tab8": "blackHole" + ]) + ]) + } + Container(widgetProperties: .init(Flex().justifyContent(.center).alignItems(.center))) { + Text("Text1 Tab 6") + Text("Text2 Tab 6") + Button(text: "change Context Tab 6", onPress: [ + SetContext(contextId: "tab", path: "icon", value: [ + "tab1": "beagle", + "tab4": "informationImage", + "tab5": "beagle", + "tab8": "blackHole" + ]) + ]) + } + Container(widgetProperties: .init(Flex().justifyContent(.flexStart))) { + Text("Text1 Tab 7") + Text("Text2 Tab 7") + } + Container(widgetProperties: .init(Flex().alignItems(.center))) { + Text("Text1 Tab 8") + Text("Text2 Tab 8") + Button(text: "change Context tab 8", onPress: [ + SetContext(contextId: "tab", path: "icon", value: [ + "tab1": "imageBeagle", + "tab4": "beagle", + "tab5": "blackHole", + "tab8": "informationImage" + ]) + ]) + } + } + } + } +} diff --git a/iOS/Example/BeagleDemo/BeagleDemo/Screens/TabViewScreen.swift b/iOS/Example/BeagleDemo/BeagleDemo/Screens/TabViewScreen.swift deleted file mode 100644 index 04f436b00b..0000000000 --- a/iOS/Example/BeagleDemo/BeagleDemo/Screens/TabViewScreen.swift +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA - * - * 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 UIKit -import Beagle -import BeagleSchema - -struct TabViewScreen: DeeplinkScreen { - init(path: String, data: [String: String]?) { - } - - func screenController() -> UIViewController { - return Beagle.screen(.declarative(screen)) - } - - var screen = Screen(navigationBar: NavigationBar(title: "TabView")) { - Container(context: Context(id: "currentTab", value: 0), widgetProperties: .init(Flex().grow(1))) { - TabBar( - items: [ - TabBarItem(icon: "beagle"), - TabBarItem(title: "Tab 1"), - TabBarItem(title: "Tab 2"), - TabBarItem(icon: "beagle", title: "Tab 3") - ], - currentTab: "@{currentTab}", - onTabSelection: [SetContext(contextId: "currentTab", value: "@{onTabSelection}")] - ) - PageView( - onPageChange: [SetContext(contextId: "currentTab", value: "@{onPageChange}")], - currentPage: "@{currentTab}" - ) { - Container(widgetProperties: .init(Flex().alignContent(.center))) { - Text("Text1 Tab 0") - Image(.value(.remote(.init(url: .networkImageBeagle, placeholder: "imageBeagle")))) - Text("Text2 Tab 0") - } - Container(widgetProperties: .init(Flex().justifyContent(.center).alignItems(.center))) { - Text("Text1 Tab 1") - Text("Text2 Tab 1") - } - Container(widgetProperties: .init(Flex().justifyContent(.flexStart))) { - Text("Text1 Tab 2") - Text("Text2 Tab 2") - } - Container(widgetProperties: .init(Flex().alignItems(.center))) { - Text("Text1 Tab 3") - Text("Text2 Tab 3") - } - } - } - } -} diff --git a/iOS/Schema/Sources/Action/Types/Navigate/Navigate.swift b/iOS/Schema/Sources/Action/Types/Navigate/Navigate.swift index 88a970815b..7a54e68753 100644 --- a/iOS/Schema/Sources/Action/Types/Navigate/Navigate.swift +++ b/iOS/Schema/Sources/Action/Types/Navigate/Navigate.swift @@ -96,14 +96,13 @@ extension Route { /// A screen that should be rendered in case of request fail. public let fallback: Screen? - public init(url: Expression, shouldPrefetch: Bool = false, fallback: Screen? = nil) { - self.url = url - self.shouldPrefetch = shouldPrefetch - self.fallback = fallback - } - - @available(*, deprecated, message: "It was deprecated in version 1.2.2 and will be removed in a future version. Please use constructor with bind") - public init(url: String, shouldPrefetch: Bool = false, fallback: Screen? = nil) { + /// Constructs a new path to a remote screen. + /// + /// - Parameters: + /// - url: Contains the navigation endpoint. Since its a _ExpressibleString_ type you can pass a Expression or a regular String. + /// - shouldPrefetch: Changes _when_ this screen is requested. + /// - fallback: A screen that should be rendered in case of request fail. + public init(url: StringOrExpression, shouldPrefetch: Bool = false, fallback: Screen? = nil) { self.url = "\(url)" self.shouldPrefetch = shouldPrefetch self.fallback = fallback diff --git a/iOS/Schema/Sources/CodeGeneration/Generated/AutoDecodable.generated.swift b/iOS/Schema/Sources/CodeGeneration/Generated/AutoDecodable.generated.swift index 50d40a1500..e3846da410 100644 --- a/iOS/Schema/Sources/CodeGeneration/Generated/AutoDecodable.generated.swift +++ b/iOS/Schema/Sources/CodeGeneration/Generated/AutoDecodable.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.0.0 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.18.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* diff --git a/iOS/Schema/Sources/CodeGeneration/Generated/Equality.generated.swift b/iOS/Schema/Sources/CodeGeneration/Generated/Equality.generated.swift index 1f052aed34..3b0aac87ac 100644 --- a/iOS/Schema/Sources/CodeGeneration/Generated/Equality.generated.swift +++ b/iOS/Schema/Sources/CodeGeneration/Generated/Equality.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.0.0 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.18.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* diff --git a/iOS/Schema/Sources/ContextExpression/Expression.swift b/iOS/Schema/Sources/ContextExpression/Expression.swift index ec8b55f88e..66fc54dc6a 100644 --- a/iOS/Schema/Sources/ContextExpression/Expression.swift +++ b/iOS/Schema/Sources/ContextExpression/Expression.swift @@ -14,6 +14,9 @@ * limitations under the License. */ +/// It's a `String` that will be treated internally as an `Expression` if passed a value like "@{someExression}". Otherwise, it will be just a normal `String`. +public typealias StringOrExpression = String + public enum Expression { case value(T) case expression(ContextExpression) diff --git a/iOS/Schema/Sources/ServerDrivenComponent/Screen/NavigationBar.swift b/iOS/Schema/Sources/ServerDrivenComponent/Screen/NavigationBar.swift index 8537191347..e6365479b6 100644 --- a/iOS/Schema/Sources/ServerDrivenComponent/Screen/NavigationBar.swift +++ b/iOS/Schema/Sources/ServerDrivenComponent/Screen/NavigationBar.swift @@ -45,7 +45,7 @@ public struct NavigationBar: Decodable, AutoInitiable { public struct NavigationBarItem: Decodable, AccessibilityComponent, IdentifiableComponent { public let id: String? - public let image: String? + public let image: StringOrExpression? public let text: String public let action: RawAction public let accessibility: Accessibility? diff --git a/iOS/Schema/Sources/ServerDrivenComponent/TabBar/TabBar.swift b/iOS/Schema/Sources/ServerDrivenComponent/TabBar/TabBar.swift index 34e78814d8..86c75bab94 100644 --- a/iOS/Schema/Sources/ServerDrivenComponent/TabBar/TabBar.swift +++ b/iOS/Schema/Sources/ServerDrivenComponent/TabBar/TabBar.swift @@ -37,12 +37,12 @@ public struct TabBar: RawComponent, AutoInitiableAndDecodable { } public struct TabBarItem: Decodable, AutoInitiable { - public let icon: String? + public let icon: StringOrExpression? public let title: String? // sourcery:inline:auto:TabBarItem.Init public init( - icon: String? = nil, + icon: StringOrExpression? = nil, title: String? = nil ) { self.icon = icon diff --git a/iOS/Schema/Sources/ServerDrivenComponent/TabView/TabView.swift b/iOS/Schema/Sources/ServerDrivenComponent/TabView/TabView.swift index 3ef2d8f474..19436a26fe 100644 --- a/iOS/Schema/Sources/ServerDrivenComponent/TabView/TabView.swift +++ b/iOS/Schema/Sources/ServerDrivenComponent/TabView/TabView.swift @@ -16,12 +16,12 @@ public struct TabItem: Decodable { - public let icon: String? + public let icon: StringOrExpression? public let title: String? public let child: RawComponent public init( - icon: String? = nil, + icon: StringOrExpression? = nil, title: String? = nil, child: RawComponent ) { @@ -31,7 +31,7 @@ public struct TabItem: Decodable { } public init( - icon: String? = nil, + icon: StringOrExpression? = nil, title: String? = nil, @ChildBuilder _ child: () -> RawComponent diff --git a/iOS/Schema/Sources/Widgets/Image/Image.swift b/iOS/Schema/Sources/Widgets/Image/Image.swift index 5360d9744e..08cf15d16e 100644 --- a/iOS/Schema/Sources/Widgets/Image/Image.swift +++ b/iOS/Schema/Sources/Widgets/Image/Image.swift @@ -33,9 +33,19 @@ public struct Image: RawWidget, AutoDecodable { self.widgetProperties = widgetProperties } - indirect public enum ImagePath: Decodable { + public init( + _ path: ImagePath, + mode: ImageContentMode? = nil, + widgetProperties: WidgetProperties = WidgetProperties() + ) { + self.path = .value(path) + self.mode = mode + self.widgetProperties = widgetProperties + } + + public enum ImagePath: Decodable { case remote(Remote) - case local(String) + case local(StringOrExpression) enum CodingKeys: String, CodingKey { case type = "_beagleImagePath_" @@ -61,10 +71,10 @@ public struct Image: RawWidget, AutoDecodable { public extension Image { struct Remote: Decodable { - public let url: String + public let url: StringOrExpression public let placeholder: String? - public init(url: String, placeholder: String? = nil) { + public init(url: StringOrExpression, placeholder: String? = nil) { self.url = url self.placeholder = placeholder } diff --git a/iOS/Sources/Beagle/Beagle.xcodeproj/project.pbxproj b/iOS/Sources/Beagle/Beagle.xcodeproj/project.pbxproj index 226d21daba..6aafe6a49a 100644 --- a/iOS/Sources/Beagle/Beagle.xcodeproj/project.pbxproj +++ b/iOS/Sources/Beagle/Beagle.xcodeproj/project.pbxproj @@ -58,7 +58,6 @@ 7CA2E83D249822D400B24782 /* BeagleLoggerProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA2E83C249822D400B24782 /* BeagleLoggerProxyTests.swift */; }; 7CC2D9B424C614FB0059D5F9 /* TabBar+Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC2D9B324C614FB0059D5F9 /* TabBar+Renderable.swift */; }; 7CC2D9B624C6150E0059D5F9 /* TabBarUIComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC2D9B524C6150E0059D5F9 /* TabBarUIComponent.swift */; }; - 7CC2D9BA24C785750059D5F9 /* TabBarUIComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC2D9B924C785750059D5F9 /* TabBarUIComponentTests.swift */; }; 7CC737FE245729B1004FBC16 /* FormDataStoreHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC737FD245729B1004FBC16 /* FormDataStoreHandlerTests.swift */; }; 7CDF84732416AE0D00234F40 /* UnitValueExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDF84712416ADCB00234F40 /* UnitValueExtensionTests.swift */; }; 7CDF84852416DABC00234F40 /* StyleBuildersExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDF84842416DABC00234F40 /* StyleBuildersExtensionTests.swift */; }; @@ -95,11 +94,9 @@ 981DB2EF23748BAB001CE5E0 /* UnitValueExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981DB2EE23748BAB001CE5E0 /* UnitValueExtension.swift */; }; 983E8C862396C7570018C6CA /* TabView+Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E8C852396C7570018C6CA /* TabView+Renderable.swift */; }; 983E8C8C2396C7FA0018C6CA /* TabViewUIComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E8C8B2396C7FA0018C6CA /* TabViewUIComponent.swift */; }; - 983E8C8E2396C81C0018C6CA /* TabBarCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E8C8D2396C81C0018C6CA /* TabBarCollectionViewCell.swift */; }; + 983E8C8E2396C81C0018C6CA /* TabBarItemUIComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E8C8D2396C81C0018C6CA /* TabBarItemUIComponent.swift */; }; 983E8C912396DF430018C6CA /* TabViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E8C902396DF430018C6CA /* TabViewTests.swift */; }; - 983E8C972396DFAA0018C6CA /* TabBarCollectionViewCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E8C962396DFAA0018C6CA /* TabBarCollectionViewCellTests.swift */; }; 983E8C992396DFC90018C6CA /* TabViewUIComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983E8C982396DFC90018C6CA /* TabViewUIComponentTests.swift */; }; - 9848A53023AAF1C2002474AD /* ContainerIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9848A52F23AAF1C2002474AD /* ContainerIndicatorView.swift */; }; 988F81FB23E24ECA008562BE /* UIView+Subviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988F81FA23E24ECA008562BE /* UIView+Subviews.swift */; }; 98B7D95623DB2722002622AC /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B7D95523DB2722002622AC /* Observable.swift */; }; 98E18D16249BE714009D6A54 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E18D15249BE714009D6A54 /* Alert.swift */; }; @@ -320,7 +317,6 @@ 7CA2E83C249822D400B24782 /* BeagleLoggerProxyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeagleLoggerProxyTests.swift; sourceTree = ""; }; 7CC2D9B324C614FB0059D5F9 /* TabBar+Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TabBar+Renderable.swift"; sourceTree = ""; }; 7CC2D9B524C6150E0059D5F9 /* TabBarUIComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarUIComponent.swift; sourceTree = ""; }; - 7CC2D9B924C785750059D5F9 /* TabBarUIComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarUIComponentTests.swift; sourceTree = ""; }; 7CC737FD245729B1004FBC16 /* FormDataStoreHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormDataStoreHandlerTests.swift; sourceTree = ""; }; 7CDF84712416ADCB00234F40 /* UnitValueExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitValueExtensionTests.swift; sourceTree = ""; }; 7CDF84842416DABC00234F40 /* StyleBuildersExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleBuildersExtensionTests.swift; sourceTree = ""; }; @@ -366,11 +362,9 @@ 981DB2EE23748BAB001CE5E0 /* UnitValueExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitValueExtension.swift; sourceTree = ""; }; 983E8C852396C7570018C6CA /* TabView+Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TabView+Renderable.swift"; sourceTree = ""; }; 983E8C8B2396C7FA0018C6CA /* TabViewUIComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewUIComponent.swift; sourceTree = ""; }; - 983E8C8D2396C81C0018C6CA /* TabBarCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCollectionViewCell.swift; sourceTree = ""; }; + 983E8C8D2396C81C0018C6CA /* TabBarItemUIComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItemUIComponent.swift; sourceTree = ""; }; 983E8C902396DF430018C6CA /* TabViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewTests.swift; sourceTree = ""; }; - 983E8C962396DFAA0018C6CA /* TabBarCollectionViewCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCollectionViewCellTests.swift; sourceTree = ""; }; 983E8C982396DFC90018C6CA /* TabViewUIComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewUIComponentTests.swift; sourceTree = ""; }; - 9848A52F23AAF1C2002474AD /* ContainerIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerIndicatorView.swift; sourceTree = ""; }; 988F81FA23E24ECA008562BE /* UIView+Subviews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Subviews.swift"; sourceTree = ""; }; 98B7D95523DB2722002622AC /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 98E18D15249BE714009D6A54 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; @@ -715,8 +709,7 @@ 7C8C21C924DC8A4800B95374 /* TabBarItemExtension.swift */, 7CC2D9B324C614FB0059D5F9 /* TabBar+Renderable.swift */, 7CC2D9B524C6150E0059D5F9 /* TabBarUIComponent.swift */, - 983E8C8D2396C81C0018C6CA /* TabBarCollectionViewCell.swift */, - 9848A52F23AAF1C2002474AD /* ContainerIndicatorView.swift */, + 983E8C8D2396C81C0018C6CA /* TabBarItemUIComponent.swift */, 7CC2D9B824C785630059D5F9 /* Tests */, ); path = "TabBar+Renderable"; @@ -726,8 +719,6 @@ isa = PBXGroup; children = ( 7C7B249324C78F43005A4D3D /* __Snapshots__ */, - 983E8C962396DFAA0018C6CA /* TabBarCollectionViewCellTests.swift */, - 7CC2D9B924C785750059D5F9 /* TabBarUIComponentTests.swift */, 7C7B249124C78E4C005A4D3D /* TabBarTests.swift */, ); path = Tests; @@ -1914,7 +1905,7 @@ A024CF0F24991E42009BB92D /* UIView+Style.swift in Sources */, C08D4E1F24857B4E00AAAC0A /* NavigationBar+UIKit.swift in Sources */, A04B19FA23ECA3F7007B4279 /* Screen+Renderable.swift in Sources */, - 983E8C8E2396C81C0018C6CA /* TabBarCollectionViewCell.swift in Sources */, + 983E8C8E2396C81C0018C6CA /* TabBarItemUIComponent.swift in Sources */, C09F4106249AB146002605EE /* KeyedDecodingContainerExtension.swift in Sources */, C08D4E9A248594EA00AAAC0A /* Image+Renderable.swift in Sources */, 7CFE3A752412E3B50087F664 /* StyleBuildersExtension.swift in Sources */, @@ -1957,7 +1948,6 @@ 93EE0CC3252E56BC0032BE77 /* BindingConfigurator.swift in Sources */, 6EB3368C24A52D34000A9D17 /* SubmitForm.swift in Sources */, E531D6B12359FD18007EEBA7 /* LoadingView.swift in Sources */, - 9848A53023AAF1C2002474AD /* ContainerIndicatorView.swift in Sources */, C08D4E1E24857B4800AAAC0A /* Container+Renderable.swift in Sources */, 57A80981243CF4620034D180 /* DependencyCacheManager.swift in Sources */, C85E1CCF23EB493200C1E323 /* NetworkClientDefault.swift in Sources */, @@ -2032,7 +2022,6 @@ C08D4E472485804D00AAAC0A /* LazyComponentTests.swift in Sources */, 93ADB87923FAD31D005B7CD2 /* ComposeComponentTests.swift in Sources */, 7CDF84852416DABC00234F40 /* StyleBuildersExtensionTests.swift in Sources */, - 7CC2D9BA24C785750059D5F9 /* TabBarUIComponentTests.swift in Sources */, E79D846D23AABFA100BAE9FA /* BeaglePrefetchHelperTests.swift in Sources */, 25CDD46D24EC4A4600CC49CB /* OperationNumberEvaluationTests.swift in Sources */, C08D4E8A2485945700AAAC0A /* WebViewTests.swift in Sources */, @@ -2042,7 +2031,6 @@ 25CDD47424ED666D00CC49CB /* OperationStringEvaluationTests.swift in Sources */, C0291889247D5BFA00C3AA13 /* ComponentFromJsonFile.swift in Sources */, C829CF2323B197CF001B7225 /* FormInputTests.swift in Sources */, - 983E8C972396DFAA0018C6CA /* TabBarCollectionViewCellTests.swift in Sources */, A0EF0ABE238B05A10019BE83 /* ValidatorHandlerTests.swift in Sources */, 93EE0C94252CA16C0032BE77 /* SetContextTests.swift in Sources */, 93B9746E23A287A800B0D1CF /* NSErrorExtensionTests.swift in Sources */, diff --git a/iOS/Sources/Beagle/CodeGeneration/Generated/AutoDecodable.generated.swift b/iOS/Sources/Beagle/CodeGeneration/Generated/AutoDecodable.generated.swift index 2e425d31f1..3cdc41ac42 100644 --- a/iOS/Sources/Beagle/CodeGeneration/Generated/AutoDecodable.generated.swift +++ b/iOS/Sources/Beagle/CodeGeneration/Generated/AutoDecodable.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.0.0 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.18.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* diff --git a/iOS/Sources/Beagle/CodeGeneration/Generated/Equality.generated.swift b/iOS/Sources/Beagle/CodeGeneration/Generated/Equality.generated.swift index 2c4837333d..71eb2c67b8 100644 --- a/iOS/Sources/Beagle/CodeGeneration/Generated/Equality.generated.swift +++ b/iOS/Sources/Beagle/CodeGeneration/Generated/Equality.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.0.0 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 0.18.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* diff --git a/iOS/Sources/Beagle/Sources/Caching/Prefetch/Tests/BeaglePrefetchHelperTests.swift b/iOS/Sources/Beagle/Sources/Caching/Prefetch/Tests/BeaglePrefetchHelperTests.swift index 50899b0063..09d19a69a8 100644 --- a/iOS/Sources/Beagle/Sources/Caching/Prefetch/Tests/BeaglePrefetchHelperTests.swift +++ b/iOS/Sources/Beagle/Sources/Caching/Prefetch/Tests/BeaglePrefetchHelperTests.swift @@ -51,7 +51,7 @@ final class BeaglePrefetchHelperTests: XCTestCase { let reference = CacheReference(identifier: url, data: jsonData, hash: "123") //When - sut.prefetchComponent(newPath: .init(url: .value(url), shouldPrefetch: true)) + sut.prefetchComponent(newPath: .init(url: url, shouldPrefetch: true)) cacheManager.addToCache(reference) let result = dependencies.cacheManager?.getReference(identifiedBy: url) @@ -73,9 +73,9 @@ final class BeaglePrefetchHelperTests: XCTestCase { //When cacheManager.addToCache(reference) - sut.prefetchComponent(newPath: .init(url: .value(url), shouldPrefetch: true)) + sut.prefetchComponent(newPath: .init(url: url, shouldPrefetch: true)) let result1 = dependencies.cacheManager?.getReference(identifiedBy: url) - sut.prefetchComponent(newPath: .init(url: .value(url), shouldPrefetch: true)) + sut.prefetchComponent(newPath: .init(url: url, shouldPrefetch: true)) let result2 = dependencies.cacheManager?.getReference(identifiedBy: url) //Then @@ -95,24 +95,24 @@ final class BeaglePrefetchHelperTests: XCTestCase { Navigate.openNativeRoute(.init(route: path, data: data)), Navigate.resetApplication(.declarative(Screen(child: container))), - Navigate.resetApplication(.remote(.init(url: .value(path), shouldPrefetch: true))), - Navigate.resetApplication(.remote(.init(url: .value(path), shouldPrefetch: false))), + Navigate.resetApplication(.remote(.init(url: path, shouldPrefetch: true))), + Navigate.resetApplication(.remote(.init(url: path, shouldPrefetch: false))), Navigate.resetStack(.declarative(Screen(child: container))), - Navigate.resetStack(.remote(.init(url: .value(path), shouldPrefetch: true))), - Navigate.resetStack(.remote(.init(url: .value(path), shouldPrefetch: false))), + Navigate.resetStack(.remote(.init(url: path, shouldPrefetch: true))), + Navigate.resetStack(.remote(.init(url: path, shouldPrefetch: false))), Navigate.pushStack(.declarative(Screen(child: container))), - Navigate.pushStack(.remote(.init(url: .value(path), shouldPrefetch: true))), - Navigate.pushStack(.remote(.init(url: .value(path), shouldPrefetch: false))), + Navigate.pushStack(.remote(.init(url: path, shouldPrefetch: true))), + Navigate.pushStack(.remote(.init(url: path, shouldPrefetch: false))), Navigate.pushStack(.declarative(Screen(child: container)), controllerId: "customId"), - Navigate.pushStack(.remote(.init(url: .value(path), shouldPrefetch: true)), controllerId: "customId"), - Navigate.pushStack(.remote(.init(url: .value(path), shouldPrefetch: false)), controllerId: "customId"), + Navigate.pushStack(.remote(.init(url: path, shouldPrefetch: true)), controllerId: "customId"), + Navigate.pushStack(.remote(.init(url: path, shouldPrefetch: false)), controllerId: "customId"), Navigate.pushView(.declarative(Screen(child: container))), - Navigate.pushView(.remote(.init(url: .value(path), shouldPrefetch: true))), - Navigate.pushView(.remote(.init(url: .value(path), shouldPrefetch: false))), + Navigate.pushView(.remote(.init(url: path, shouldPrefetch: true))), + Navigate.pushView(.remote(.init(url: path, shouldPrefetch: false))), Navigate.popStack, Navigate.popView, diff --git a/iOS/Sources/Beagle/Sources/Components/Button+Renderable/Tests/ButtonTests.swift b/iOS/Sources/Beagle/Sources/Components/Button+Renderable/Tests/ButtonTests.swift index 8d9c00acdd..c16c28a473 100644 --- a/iOS/Sources/Beagle/Sources/Components/Button+Renderable/Tests/ButtonTests.swift +++ b/iOS/Sources/Beagle/Sources/Components/Button+Renderable/Tests/ButtonTests.swift @@ -76,7 +76,7 @@ final class ButtonTests: XCTestCase { controller.dependencies = BeagleScreenDependencies(preFetchHelper: prefetch) let navigatePath = "path-to-prefetch" - let navigate = Navigate.pushStack(.remote(.init(url: .value(navigatePath)))) + let navigate = Navigate.pushStack(.remote(.init(url: navigatePath))) let button = Button(text: "prefetch", onPress: [navigate]) // When diff --git a/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Image+Renderable.swift b/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Image+Renderable.swift index bbc2f963c3..3b729780cd 100644 --- a/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Image+Renderable.swift +++ b/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Image+Renderable.swift @@ -20,22 +20,47 @@ import BeagleSchema extension Image: Widget { public func toView(renderer: BeagleRenderer) -> UIView { - let image = UIImageView(frame: .zero) - image.clipsToBounds = true - image.contentMode = (mode ?? .fitCenter).toUIKit() - var token: RequestToken? + let image = BeagleImageView(with: mode) + + switch path { + case .value(let path): + observeFields(path, renderer, image) + case .expression: + observePath(renderer, image) + } + + return image + } + private func observeFields(_ path: Image.ImagePath, _ renderer: BeagleRenderer, _ image: BeagleImageView) { + switch path { + case .local(let mobileId): + let expression: Expression = "\(mobileId)" + renderer.observe(expression, andUpdateManyIn: image) { mobileId in + guard let mobileId = mobileId else { return } + self.setImageFromAsset(named: mobileId, bundle: renderer.controller.dependencies.appBundle, imageView: image) + } + case .remote(let remote): + let expression: Expression = "\(remote.url)" + renderer.observe(expression, andUpdateManyIn: image) { url in + guard let url = url else { return } + image.token?.cancel() + image.token = self.setRemoteImage(from: url, placeholder: remote.placeholder, imageView: image, renderer: renderer) + } + } + } + + private func observePath(_ renderer: BeagleRenderer, _ image: BeagleImageView) { renderer.observe(path, andUpdateManyIn: image) { path in - token?.cancel() + image.token?.cancel() switch path { case .local(let mobileId): self.setImageFromAsset(named: mobileId, bundle: renderer.controller.dependencies.appBundle, imageView: image) case .remote(let remote): - token = self.setRemoteImage(from: remote.url, placeholder: remote.placeholder, imageView: image, renderer: renderer) + image.token = self.setRemoteImage(from: remote.url, placeholder: remote.placeholder, imageView: image, renderer: renderer) case .none: () } } - return image } private func setImageFromAsset(named: String, bundle: Bundle, imageView: UIImageView) { @@ -68,3 +93,17 @@ extension Image: Widget { } } } + +private class BeagleImageView: UIImageView { + var token: RequestToken? + + init(with mode: ImageContentMode?) { + super.init(frame: .zero) + clipsToBounds = true + contentMode = (mode ?? .fitCenter).toUIKit() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Tests/ImageTests.swift b/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Tests/ImageTests.swift index c46fe3e33e..bf01a610fc 100644 --- a/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Tests/ImageTests.swift +++ b/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Tests/ImageTests.swift @@ -91,17 +91,17 @@ class ImageTests: XCTestCase { func testCancelRequest() { //Given - let image = Image("@{img.path}") + let image = Image(.remote(.init(url: "@{url}"))) let dependency = BeagleDependencies() let imageDownloader = ImageDownloaderStub(imageResult: .success(Data())) dependency.imageDownloader = imageDownloader let container = Container(children: [image]) let controller = BeagleScreenViewController(viewModel: .init(screenType:.declarative(container.toScreen()), dependencies: dependency)) - let action = SetContext(contextId: "img", path: "path", value: ["_beagleImagePath_": "local", "mobileId": "shuttle"]) + let action = SetContext(contextId: "url", value: "www.com.br") let view = image.toView(renderer: controller.renderer) //When - view.setContext(Context(id: "img", value: ["path": ["_beagleImagePath_": "remote", "url": "www.com.br"]])) + view.setContext(Context(id: "url", value: "www.beagle.com.br")) controller.bindings.config() action.execute(controller: controller, origin: view) @@ -111,7 +111,7 @@ class ImageTests: XCTestCase { func testInvalidURL() { // Given - let component = Image(.value(.remote(.init(url: "www.com")))) + let component = Image(.remote(.init(url: "www.com"))) // When let imageView = renderer.render(component) as? UIImageView @@ -122,7 +122,7 @@ class ImageTests: XCTestCase { func testPlaceholder() { // Given - let component = Image(.value(.remote(.init(url: "www.com", placeholder: "test_image_square-x")))) + let component = Image(.remote(.init(url: "www.com", placeholder: "test_image_square-x"))) // When let placeholderView = renderer.render(component) as? UIImageView @@ -134,7 +134,7 @@ class ImageTests: XCTestCase { func testImageLeak() { // Given - let component = Image("@{img.path}", mode: .fitXY) + let component = Image(.remote(.init(url: "@{img.path}")), mode: .fitXY) let controller = BeagleScreenViewController(viewModel: .init(screenType:.declarative(component.toScreen()), dependencies: BeagleDependencies())) var view = component.toView(renderer: controller.renderer) @@ -147,6 +147,42 @@ class ImageTests: XCTestCase { // Then XCTAssertNil(weakView) } + + func testImageWithPathCancelRequest() { + //Given + let image = Image("@{img.path}") + let dependency = BeagleDependencies() + let imageDownloader = ImageDownloaderStub(imageResult: .success(Data())) + dependency.imageDownloader = imageDownloader + let container = Container(children: [image]) + let controller = BeagleScreenViewController(viewModel: .init(screenType:.declarative(container.toScreen()), dependencies: dependency)) + let action = SetContext(contextId: "img", path: "path", value: ["_beagleImagePath_": "local", "mobileId": "shuttle"]) + let view = image.toView(renderer: controller.renderer) + + //When + view.setContext(Context(id: "img", value: ["path": ["_beagleImagePath_": "remote", "url": "www.com.br"]])) + controller.bindings.config() + action.execute(controller: controller, origin: view) + + // Then + XCTAssertTrue(imageDownloader.token.didCallCancel) + } + + func testLocalImageWithContext() { + //Given + let container = Container( + children: [ + Image(.local("@{mobileId}")) + ], + context: Context(id: "mobileId", value: "test_image_square-x") + ) + + //When + let controller = BeagleScreenViewController(viewModel: .init(screenType:.declarative(container.toScreen()), dependencies: dependencies)) + + // Then + assertSnapshotImage(controller.view, size: ImageSize.custom(CGSize(width: 100, height: 100))) + } } private struct ImageDownloaderMock: ImageDownloader { diff --git a/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Tests/__Snapshots__/ImageTests/testLocalImageWithContext.1.png b/iOS/Sources/Beagle/Sources/Components/Image+Renderable/Tests/__Snapshots__/ImageTests/testLocalImageWithContext.1.png new file mode 100644 index 0000000000000000000000000000000000000000..f09d22cb27de9fed9eb0593759a0503e37cf88cf GIT binary patch literal 7670 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 literal 0 HcmV?d00001 diff --git a/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/NavigationBar+UIKit.swift b/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/NavigationBar+UIKit.swift index a55b216777..b106d38cd9 100644 --- a/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/NavigationBar+UIKit.swift +++ b/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/NavigationBar+UIKit.swift @@ -40,11 +40,7 @@ extension NavigationBarItem { self.controller = controller super.init() if let localImage = barItem.image { - image = UIImage( - named: localImage, - in: controller.dependencies.appBundle, - compatibleWith: nil - )?.withRenderingMode(.alwaysOriginal) + handleContextOnNavigationBarImage(icon: localImage) accessibilityHint = barItem.text } else { title = barItem.text @@ -55,6 +51,23 @@ extension NavigationBarItem { ViewConfigurator.applyAccessibility(barItem.accessibility, to: self) } + private func handleContextOnNavigationBarImage(icon: String) { + let expression: Expression = "\(icon)" + let renderer = controller?.renderer + + // Since `BeagleScreenViewController` creates a different view hierarchy, to get the correct hierarchy we need to use the `view` from our `controller`. + guard case .view(let view) = controller?.content else { return } + + renderer?.observe(expression, andUpdateManyIn: view) { icon in + guard let icon = icon else { return } + self.image = UIImage( + named: icon, + in: self.controller?.dependencies.appBundle, + compatibleWith: nil + )?.withRenderingMode(.alwaysOriginal) + } + } + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/Tests/ScreenComponentTests.swift b/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/Tests/ScreenComponentTests.swift index 699c0a81b6..b62ec8c46d 100644 --- a/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/Tests/ScreenComponentTests.swift +++ b/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/Tests/ScreenComponentTests.swift @@ -92,6 +92,30 @@ final class ScreenComponentTests: XCTestCase { assertSnapshotImage(viewController, size: .custom(CGSize(width: 300, height: 200))) } + func testNavigationBarItemWithContextOnImage() { + // Given + let dependencies = BeagleDependencies() + dependencies.appBundle = Bundle(for: ScreenComponentTests.self) + + let barItem = NavigationBarItem(image: "@{image}", text: "", action: ActionDummy()) + + let screen = Screen( + safeArea: SafeArea.all, + navigationBar: .init(title: "title", showBackButton: true, navigationBarItems: [barItem]), + child: Text("test"), + context: Context(id: "image", value: "shuttle") + ) + + // When + let controller = BeagleScreenViewController(viewModel: .init( + screenType: .declarative(screen), + dependencies: dependencies + )) + + // Then + assertSnapshotImage(controller.view, size: .custom(CGSize(width: 150, height: 80))) + } + func test_action_shouldBeTriggered() { // Given let action = ActionSpy() @@ -116,7 +140,7 @@ final class ScreenComponentTests: XCTestCase { controller.dependencies = BeagleScreenDependencies(preFetchHelper: prefetch) let navigatePath = "button-item-prefetch" - let navigate = Navigate.pushView(.remote(.init(url: .value(navigatePath), shouldPrefetch: true))) + let navigate = Navigate.pushView(.remote(.init(url: navigatePath, shouldPrefetch: true))) let barItem = NavigationBarItem(text: "Item", action: navigate) let screen = ScreenComponent( navigationBar: NavigationBar(title: "Prefetch", navigationBarItems: [barItem]), diff --git a/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/Tests/__Snapshots__/ScreenComponentTests/testNavigationBarItemWithContextOnImage.1.png b/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Screen/Tests/__Snapshots__/ScreenComponentTests/testNavigationBarItemWithContextOnImage.1.png new file mode 100644 index 0000000000000000000000000000000000000000..35ace7d5897f74beb86685ecc321978f82ba33de GIT binary patch literal 5839 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 KHmK5bi~bMKELa!- literal 0 HcmV?d00001 diff --git a/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Style/StyleViewConfigurator.swift b/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Style/StyleViewConfigurator.swift index df9d4f64f0..5dd3260499 100644 --- a/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Style/StyleViewConfigurator.swift +++ b/iOS/Sources/Beagle/Sources/Components/Layout+Renderable/Style/StyleViewConfigurator.swift @@ -80,17 +80,15 @@ final class StyleViewConfigurator: StyleViewConfiguratorProtocol { func markDirty() { view?.yoga.markDirty() - var view: UIView? = self.view + var view = self.view while let currentView = view { - if !currentView.yoga.isEnabled { - currentView.superview?.invalidateIntrinsicContentSize() - currentView.setNeedsLayout() - break + if !(currentView.superview?.yoga.isEnabled ?? false) { + view?.setNeedsLayout() } view = view?.superview } } - + // MARK: - Private Methods private func applyYogaProperties(from style: Style, to layout: YGLayout) { diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/ContainerIndicatorView.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/ContainerIndicatorView.swift deleted file mode 100644 index a6fa593b85..0000000000 --- a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/ContainerIndicatorView.swift +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA - * - * 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 UIKit - -final class ContainerIndicatorView: UIView { - lazy var indicatorView: UIView = { - let view = UIView() - view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - view.backgroundColor = .black - return view - }() - - override init(frame: CGRect) { - super.init(frame: frame) - setupView() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setupView() { - addSubview(indicatorView) - } -} diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBar+Renderable.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBar+Renderable.swift index 604eb15d94..4347b01729 100644 --- a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBar+Renderable.swift +++ b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBar+Renderable.swift @@ -20,24 +20,21 @@ import BeagleSchema extension TabBar: ServerDrivenComponent { public func toView(renderer: BeagleRenderer) -> UIView { - let view = TabBarUIComponent(model: .init(tabIndex: 0, tabBarItems: items)) - + let tabBarScroll = TabBarUIComponent(model: .init(tabBarItems: items, styleId: styleId, renderer: renderer)) + if let currentTab = currentTab { - renderer.observe(currentTab, andUpdateManyIn: view) { + renderer.observe(currentTab, andUpdateManyIn: tabBarScroll) { if let tab = $0 { - view.scrollTo(page: tab) + tabBarScroll.model.tabIndex = tab + tabBarScroll.scrollTo(page: tab) } } } - - view.onTabSelection = { tab in - renderer.controller.execute(actions: self.onTabSelection, with: "onTabSelection", and: .int(tab), origin: view) + + tabBarScroll.onTabSelection = { tab in + renderer.controller.execute(actions: self.onTabSelection, with: "onTabSelection", and: .int(tab), origin: tabBarScroll) } - if let styleId = styleId { - view.beagle.applyStyle(for: view as UIView, styleId: styleId, with: renderer.controller) - } - - return view + return tabBarScroll } } diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarCollectionViewCell.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarCollectionViewCell.swift deleted file mode 100644 index 2bdc233f8e..0000000000 --- a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarCollectionViewCell.swift +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA - * - * 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 UIKit -import BeagleSchema - -extension TabBarCollectionViewCell { - struct Model { - var selectedTextColor: UIColor? - var unselectedTextColor: UIColor? - var selectedIconColor: UIColor? - var unselectedIconColor: UIColor? - } -} - -final class TabBarCollectionViewCell: UICollectionViewCell { - - // MARK: - UIComponents - - lazy var stackView: UIStackView = { - let stack = UIStackView() - stack.axis = .vertical - stack.distribution = .fill - stack.alignment = .center - stack.spacing = 5 - stack.translatesAutoresizingMaskIntoConstraints = false - return stack - }() - - lazy var icon: UIImageView = { - let icon = UIImageView() - icon.invalidateIntrinsicContentSize() - icon.contentMode = .scaleAspectFit - icon.translatesAutoresizingMaskIntoConstraints = false - return icon - }() - - lazy var title: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - var model: Model? - - override var isSelected: Bool { - didSet { - setupSelectionAppearance() - } - } - - // MARK: - Initialization - - override init(frame: CGRect) { - super.init(frame: frame) - contentView.addSubview(stackView) - stackView.anchorTo(superview: contentView) - stackView.addArrangedSubview(icon) - stackView.addArrangedSubview(title) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup - - func setupTab(with tab: TabBarItem) { - switch tab.itemContentType { - case let .both(iconName, text): - icon.heightAnchor.constraint(lessThanOrEqualToConstant: 30).isActive = true - title.text = text - icon.image = model?.selectedIconColor == nil ? UIImage(named: iconName): UIImage(named: iconName)?.withRenderingMode(.alwaysTemplate) - title.font = UIFont.systemFont(ofSize: 13) - icon.isHidden = false - title.isHidden = false - - case .icon(let iconName): - icon.widthAnchor.constraint(lessThanOrEqualToConstant: 35).isActive = true - icon.image = model?.selectedIconColor == nil ? UIImage(named: iconName): UIImage(named: iconName)?.withRenderingMode(.alwaysTemplate) - icon.isHidden = false - title.isHidden = true - - case .title(let text): - title.isHidden = false - icon.isHidden = true - title.sizeToFit() - title.text = text - case .none: - title.isHidden = true - icon.isHidden = true - } - setupSelectionAppearance() - } - - private func setupSelectionAppearance() { - guard let model = model else { return } - switch styleVerification(model: model) { - case .both: - title.textColor = isSelected ? model.selectedTextColor : model.unselectedTextColor - icon.tintColor = isSelected ? model.selectedIconColor : model.unselectedIconColor - case .icon: - icon.tintColor = isSelected ? model.selectedIconColor : model.unselectedIconColor - title.textColor = isSelected ? .black : .gray - case .text: - title.textColor = isSelected ? model.selectedTextColor : model.unselectedTextColor - icon.tintColor = isSelected ? .black : .gray - default: - title.textColor = isSelected ? .black : .gray - icon.tintColor = isSelected ? .black : .gray - } - } - - private func styleVerification(model: Model) -> StyleEnabler { - switch (model.selectedIconColor, model.unselectedIconColor, model.selectedTextColor, model.unselectedTextColor) { - case let (selectedIconColor?, unselectedIconColor?, selectedTextColor?, unselectedTextColor?): - return .both(iconSelectedColor: selectedIconColor, - iconUnselectedColor: unselectedIconColor, - textSelectedColor: selectedTextColor, - textUnselectedColor: unselectedTextColor) - case let (selectedIconColor?, unselectedIconColor?, _, _): - return .icon(selectedIconColor, unselectedIconColor) - case let (_, _, selectedTextColor?, unselectedTextColor?): - return .text(selectedTextColor, unselectedTextColor) - default: - return .none - } - } - - private enum StyleEnabler { - case icon(UIColor, UIColor) - case text(UIColor, UIColor) - case both(iconSelectedColor: UIColor, iconUnselectedColor: UIColor, textSelectedColor: UIColor, textUnselectedColor: UIColor) - case none - } -} diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarItemUIComponent.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarItemUIComponent.swift new file mode 100644 index 0000000000..20f46b5c3e --- /dev/null +++ b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarItemUIComponent.swift @@ -0,0 +1,137 @@ +/* + * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA + * + * 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 UIKit +import BeagleSchema + +final class TabBarItemUIComponent: UIView { + + // MARK: - UIComponents + + private lazy var icon: UIImageView = { + let icon = UIImageView() + icon.invalidateIntrinsicContentSize() + icon.contentMode = .scaleAspectFit + return icon + }() + + private lazy var title: UILabel = { + let label = UILabel() + label.sizeToFit() + label.textAlignment = .center + return label + }() + + // MARK: - Properties + + var index: Int? + var theme: TabBarTheme? { + didSet { + setupSelectionAppearance() + } + } + + var isSelected: Bool? { + didSet { + setupSelectionAppearance() + } + } + + private var renderer: BeagleRenderer? + + // MARK: - Initialization + + init( + index: Int, + renderer: BeagleRenderer + ) { + super.init(frame: .zero) + self.renderer = renderer + self.index = index + addSubview(icon) + addSubview(title) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup + + func setupTab(with tab: TabBarItem) { + switch tab.itemContentType { + + case let .both(iconName, text): + title.text = text + title.font = .systemFont(ofSize: 14) + + handleContextOnImage(iconName: iconName) + + title.style.setup(Style().display(.flex)) + icon.style.setup(Style() + .display(.flex) + .size(Size().width(30).height(30)) + ) + + case .icon(let iconName): + + handleContextOnImage(iconName: iconName) + + title.style.setup(Style().display(.none)) + icon.style.setup(Style() + .display(.flex) + .size(Size().width(35).height(35)) + ) + + case .title(let text): + title.text = text + title.font = .systemFont(ofSize: 16) + + title.style.setup(Style().display(.flex).flex(Flex().alignSelf(.center))) + icon.style.setup(Style().display(.none)) + + case .none: + title.style.setup(Style().display(.none)) + icon.style.setup(Style().display(.none)) + } + } + + private func setupSelectionAppearance() { + let appearanceColor = getSelectedAppearanceColors() + title.textColor = appearanceColor.textColor + icon.tintColor = appearanceColor.iconColor + } + + private func getSelectedAppearanceColors() -> (textColor: UIColor, iconColor: UIColor) { + guard let theme = theme, let isSelected = isSelected else { return (.black, .black) } + if isSelected { + return (theme.selectedTextColor ?? UIColor.black, theme.selectedIconColor ?? UIColor.black) + } + return (theme.unselectedTextColor ?? UIColor.gray, theme.unselectedIconColor ?? UIColor.gray) + } + + private func handleContextOnImage(iconName: String) { + let expression: Expression = "\(iconName)" + + renderer?.observe(expression, andUpdateManyIn: self) { icon in + if let icon = icon { + self.icon.image = self.theme?.selectedIconColor == nil ? + UIImage(named: icon, in: self.renderer?.controller.dependencies.appBundle, compatibleWith: nil) : + UIImage(named: icon, in: self.renderer?.controller.dependencies.appBundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) + } + } + } +} diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarUIComponent.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarUIComponent.swift index ea6f2a826f..40edce6141 100644 --- a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarUIComponent.swift +++ b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/TabBarUIComponent.swift @@ -17,60 +17,47 @@ import UIKit import BeagleSchema + +struct TabBarTheme { + var selectedTextColor: UIColor? + var unselectedTextColor: UIColor? + var selectedIconColor: UIColor? + var unselectedIconColor: UIColor? +} -final class TabBarUIComponent: UIView { +final class TabBarUIComponent: UIScrollView { // MARK: - Model struct Model { - var tabIndex: Int + var tabIndex: Int? var tabBarItems: [TabBarItem] - var selectedTextColor: UIColor? - var unselectedTextColor: UIColor? - var selectedIconColor: UIColor? - var unselectedIconColor: UIColor? + var styleId: String? + var renderer: BeagleRenderer } - - // MARK: - Properties - private var shouldScrollToCurrentTab = true - private var shouldAnimateOnCellDisplay = false - private var containerWidthConstraint: NSLayoutConstraint? - private let tabBarPreferedHeight: CGFloat = 65 - private let collectionViewCellHeight: CGFloat = 55 + // MARK: - Properties + private var shouldCreateTabItemsView = true private let tabItemMinimumHorizontalMargin: CGFloat = 40 private let tabItemIconMinimunWidth: CGFloat = 75 var model: Model - + var tabItemViews = [Int: TabBarItemUIComponent]() var onTabSelection: ((_ tab: Int) -> Void)? - + // MARK: - UI - lazy var collectionView: UICollectionView = { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .horizontal - layout.minimumInteritemSpacing = 0 - layout.minimumLineSpacing = 0 - let collection = UICollectionView( - frame: CGRect(), - collectionViewLayout: layout - ) - collection.backgroundColor = .clear - collection.register(TabBarCollectionViewCell.self, forCellWithReuseIdentifier: TabBarCollectionViewCell.className) - collection.translatesAutoresizingMaskIntoConstraints = false - collection.showsHorizontalScrollIndicator = false - collection.dataSource = self - collection.delegate = self - return collection + private lazy var contentView: UIView = { + let view = UIView() + return view }() - lazy var containerIndicator: ContainerIndicatorView = { - let view = ContainerIndicatorView() - view.translatesAutoresizingMaskIntoConstraints = false + lazy var indicatorView: UIView = { + let view = UIView() + view.backgroundColor = .black return view }() - + // MARK: - Initialization init( @@ -78,8 +65,8 @@ final class TabBarUIComponent: UIView { ) { self.model = model super.init(frame: .zero) - setupLayout() - scrollTo(page: 0) + setupScrollView() + setupContentView() } @available(*, unavailable) @@ -87,66 +74,173 @@ final class TabBarUIComponent: UIView { fatalError("init(coder:) has not been implemented") } + override func layoutSubviews() { + super.layoutSubviews() + if let contentView = subviews.first { + contentSize = contentView.frame.size + resetTabItemsStyle() + + // Creates tabItems only after view it's already in superview hierarchy + if superview != nil && shouldCreateTabItemsView { + setupTabBarItems() + setupIndicatorViewStyle(for: tabItemViews[0]) + style.applyLayout() + scrollTo(page: model.tabIndex ?? 0) + } + } + } + // MARK: - Layout - override var frame: CGRect { - didSet { - if collectionView.visibleCells.count != 0, shouldScrollToCurrentTab { - scrollTo(page: model.tabIndex) - shouldScrollToCurrentTab.toggle() - } + private func resetTabItemsStyle() { + tabItemViews.forEach { key, item in + let size = getContentSize(forItem: model.tabBarItems[key].itemContentType) + setupTabBarItemStyle(for: item, with: size.width) + } + guard let selectedTabItem = tabItemViews[model.tabIndex ?? 0] else { return } + setupIndicatorViewStyle(for: selectedTabItem) + model.renderer.controller.setNeedsLayout(component: self) + } + + private func setupScrollView() { + showsHorizontalScrollIndicator = false + showsVerticalScrollIndicator = false + style.setup(Style( + size: Size().height(65), flex: Flex().flexDirection(.row).shrink(0)) + ) + } + + private func setupContentView() { + contentView.style.setup(Style( + flex: Flex(flexDirection: .row, grow: 0, shrink: 0)) + ) + contentView.addSubview(indicatorView) + addSubview(contentView) + } + + func setupTabBarItems() { + var index = 0 + shouldCreateTabItemsView = false + model.tabBarItems.forEach { item in + let size = getContentSize(forItem: item.itemContentType) + let itemView = createTabBarItemsView(with: item, index: index) + setupTabBarItemStyle(for: itemView, with: size.width) + tabItemViews[index] = itemView + index += 1 + contentView.addSubview(itemView) } + + if let styleId = model.styleId { + beagle.applyStyle(for: self as UIView, styleId: styleId, with: model.renderer.controller) + } + } + + private func createTabBarItemsView(with item: TabBarItem, index: Int) -> TabBarItemUIComponent { + let itemView = TabBarItemUIComponent(index: index, renderer: model.renderer) + itemView.setupTab(with: item) + + let tap = UITapGestureRecognizer(target: self, action: #selector(didSelectTabItem(sender:))) + itemView.addGestureRecognizer(tap) + itemView.isUserInteractionEnabled = true + return itemView + } + + @objc func didSelectTabItem(sender: UITapGestureRecognizer) { + guard let tabItem = sender.view as? TabBarItemUIComponent, let index = tabItem.index else { return } + model.tabIndex = index + setupTabBarItemsTheme(for: index) + onTabSelection?(index) + } + + private func setupTabBarItemsTheme(for currentIndex: Int) { + tabItemViews.forEach { _, item in + item.isSelected = currentIndex == item.index + } + } +} + +// MARK: - Style +private extension TabBarUIComponent { + func setupTabBarItemStyle(for item: TabBarItemUIComponent, with width: CGFloat) { + item.style.setup( + Style( + size: Size().height(62).width(.init(value: Double(width), type: .real)), + position: EdgeValue().left(5), + flex: Flex() + .alignItems(.center) + .justifyContent(.spaceEvenly)) + ) } - override func sizeThatFits(_ size: CGSize) -> CGSize { - .init(width: size.width, height: tabBarPreferedHeight) + func setupIndicatorViewStyle(for selectedItem: TabBarItemUIComponent?) { + guard let selectedItem = selectedItem else { return } + indicatorView.style.setup( + Style( + size: Size().height(3).width(.init(value: Double(selectedItem.bounds.width), type: .real)), + position: EdgeValue(left: UnitValue(value: Double(selectedItem.frame.origin.x), type: .real)), + positionType: .absolute, + flex: Flex().alignSelf(.flexEnd) + ) + ) } + +} + +// MARK: - TabBarItem Size + +private extension TabBarUIComponent { - private lazy var tabBarItensFreeHorizontalSpace: CGFloat = { + func getTabBarItensFreeHorizontalSpace() -> CGFloat { let tabBarItems = model.tabBarItems let tabBarItemsAvailableSpace = frame.width let tabItensRequiredSpace = tabBarItems.reduce(0) { result, item -> CGFloat in if let title = item.title { - return result + getCellMinimumWidth(for: title) + return result + getItemMinimumWidth(for: title) } return result + tabItemIconMinimunWidth } return tabItensRequiredSpace <= tabBarItemsAvailableSpace ? (tabBarItemsAvailableSpace - tabItensRequiredSpace) / CGFloat(tabBarItems.count) : 0 - }() + } - private func getCellMinimumWidth(for text: String) -> CGFloat { + func getItemMinimumWidth(for text: String) -> CGFloat { let label = UILabel() label.numberOfLines = 1 label.text = text return label.intrinsicContentSize.width + tabItemMinimumHorizontalMargin } + + func getContentSize(forItem item: TabBarItem.ItemContentType) -> CGSize { + switch item { + case .both(_, let title): + let minimumCellWidth = getItemMinimumWidth(for: title) + let width = minimumCellWidth <= tabItemIconMinimunWidth ? tabItemIconMinimunWidth : minimumCellWidth + return getContentSize(forWidth: width) + case .title(let title): + return getContentSize(forWidth: getItemMinimumWidth(for: title)) + default: + return getContentSize(forWidth: tabItemIconMinimunWidth) + } + } - private func setupLayout() { - addSubview(collectionView) - collectionView.anchor(top: topAnchor, left: leftAnchor, right: rightAnchor) - collectionView.heightAnchor.constraint(lessThanOrEqualToConstant: tabBarPreferedHeight).isActive = true - collectionView.addSubview(containerIndicator) - collectionView.bringSubviewToFront(containerIndicator.indicatorView) - - containerIndicator.anchor(bottom: collectionView.bottomAnchor, bottomConstant: -tabBarPreferedHeight, heightConstant: 3) - containerWidthConstraint = NSLayoutConstraint(item: containerIndicator.indicatorView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100) - containerWidthConstraint?.isActive = true + func getContentSize(forWidth width: CGFloat) -> CGSize { + CGSize(width: width + getTabBarItensFreeHorizontalSpace(), height: 62) } } // MARK: - Animation private extension TabBarUIComponent { - private func moveIndicatorView(to cell: UICollectionViewCell?) { - guard let cell = cell else { return } + private func moveIndicatorView(to tabItem: TabBarItemUIComponent) { UIView.animate( withDuration: 0.2, delay: 0, options: .curveLinear, animations: { - self.containerIndicator.indicatorView.frame.origin.x = cell.frame.origin.x - self.containerWidthConstraint?.constant = cell.frame.width - self.layoutIfNeeded() + self.setupIndicatorViewStyle(for: tabItem) + + // TODO: setNeedLayout should call layoutIfNeeded + self.model.renderer.controller.setNeedsLayout(component: self) + self.model.renderer.controller.view.layoutIfNeeded() } ) } @@ -157,72 +251,14 @@ private extension TabBarUIComponent { extension TabBarUIComponent { func scrollTo(page: Int) { model.tabIndex = page - let indexPath = IndexPath(item: page, section: 0) - collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredVertically) - collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) - guard let cell = collectionView.cellForItem(at: indexPath) else { - shouldAnimateOnCellDisplay = true - return - } - moveIndicatorView(to: cell) - } -} - -// MARK: - UICollection View Delegate and DataSource Extension - -extension TabBarUIComponent: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { - - func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if indexPath.row == model.tabIndex, shouldAnimateOnCellDisplay { - moveIndicatorView(to: cell) - shouldAnimateOnCellDisplay.toggle() - } - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return model.tabBarItems.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let item = model.tabBarItems[indexPath.row] - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TabBarCollectionViewCell.className, for: indexPath) - guard let tabBarCell = cell as? TabBarCollectionViewCell else { return cell } + guard let view = tabItemViews[page] else { return } - tabBarCell.model = TabBarCollectionViewCell.Model( - selectedTextColor: model.selectedTextColor, - unselectedTextColor: model.unselectedTextColor, - selectedIconColor: model.selectedIconColor, - unselectedIconColor: model.unselectedIconColor + let visibleRect = CGRect( + origin: CGPoint(x: max(0, view.center.x - (frame.width / 2)), y: 0), + size: bounds.size ) - tabBarCell.setupTab(with: item) - - return tabBarCell - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return getCellSize(forContent: model.tabBarItems[indexPath.row].itemContentType) - } - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let index = Int(indexPath.row) - scrollTo(page: index) - onTabSelection?(index) - } - - private func getCellSize(forContent content: TabBarItem.ItemContentType) -> CGSize { - switch content { - case .both(_, let title): - let minimumCellWidth = getCellMinimumWidth(for: title) - let width = minimumCellWidth <= tabItemIconMinimunWidth ? tabItemIconMinimunWidth : minimumCellWidth - return getCellSize(forWidth: width) - case .title(let title): - return getCellSize(forWidth: getCellMinimumWidth(for: title)) - default: - return getCellSize(forWidth: tabItemIconMinimunWidth) - } - } - - private func getCellSize(forWidth width: CGFloat) -> CGSize { - CGSize(width: width + tabBarItensFreeHorizontalSpace, height: collectionViewCellHeight) + scrollRectToVisible(visibleRect, animated: true) + setupTabBarItemsTheme(for: page) + moveIndicatorView(to: view) } } diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarCollectionViewCellTests.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarCollectionViewCellTests.swift deleted file mode 100644 index 5c500d81ea..0000000000 --- a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarCollectionViewCellTests.swift +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA - * - * 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 XCTest -@testable import Beagle -import BeagleSchema - -final class TabBarCollectionViewCellTests: XCTestCase { - - func test_setupShouldSetTabItems() { - // Given - let sut = TabBarCollectionViewCell(frame: .zero) - - // When - sut.setupTab(with: TabBarItem(icon: "icon", title: "Tab")) - - let innerComponentView = Mirror(reflecting: sut).children.first - - // Then - XCTAssert(innerComponentView?.value is UIStackView) - XCTAssert(sut.contentView.subviews.count == 1) - } - - func test_setupShouldSetTabItemsWithIconOnly() { - // Given - let sut = TabBarCollectionViewCell(frame: .zero) - - // When - sut.setupTab(with: TabBarItem(icon: "icon")) - - let innerComponentView = Mirror(reflecting: sut).children.first - let stackView = innerComponentView?.value as? UIStackView - - // Then - XCTAssertNotNil(stackView) - XCTAssert(stackView?.subviews[0].isHidden == false) - - } - - func test_setupShouldSetTabItemsWithTitleOnly() { - // Given - let sut = TabBarCollectionViewCell(frame: .zero) - - // When - sut.setupTab(with: TabBarItem(title: "Tab 1")) - - let innerComponentView = Mirror(reflecting: sut).children.first - let stackView = innerComponentView?.value as? UIStackView - - // Then - XCTAssertNotNil(stackView) - XCTAssert(stackView?.subviews[1].isHidden == false) - } - -} diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarTests.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarTests.swift index 5b858fa9ce..afe8f9f08b 100644 --- a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarTests.swift +++ b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarTests.swift @@ -21,16 +21,95 @@ import SnapshotTesting import BeagleSchema class TabBarTests: XCTestCase { - - func test_viewWithTabBar() { - let tabBar = TabBar(items: [ - TabBarItem(title: "TAB 1"), - TabBarItem(title: "TAB 2"), - TabBarItem(title: "TAB 3") - ]) - - let screen = Beagle.screen(.declarative(tabBar.toScreen())) - assertSnapshotImage(screen) + + var dependencies: BeagleDependencies { + // swiftlint:disable implicit_getter + get { + let dependency = BeagleDependencies() + dependency.appBundle = Bundle(for: TabBarTests.self) + return dependency + } } + lazy var controller = BeagleControllerStub(dependencies: dependencies) + lazy var renderer = BeagleRenderer(controller: controller) + + private let imageSize = ImageSize.custom(CGSize(width: 150, height: 80)) + + func testCurrentTabWithContext() { + // Given + let screen = Screen( + child: TabBar( + items: [ + TabBarItem(icon: "shuttle"), + TabBarItem(icon: "shuttle", title: "Tab 2") + ], + currentTab: "@{tab}"), + context: Context(id: "tab", value: 1) + ) + + // When + let controller = BeagleScreenViewController(viewModel: .init( + screenType: .declarative(screen), + dependencies: dependencies + )) + + // Then + assertSnapshotImage(controller.view, size: imageSize) + } + + func testImageWithContext() { + // Given + let screen = Screen( + child: TabBar( + items: [ + TabBarItem(icon: "@{image}"), + TabBarItem(icon: "@{image}", title: "Tab 2") + ]), + context: Context(id: "image", value: "shuttle") + ) + + // When + let controller = BeagleScreenViewController(viewModel: .init( + screenType: .declarative(screen), + dependencies: dependencies + )) + + // Then + assertSnapshotImage(controller.view, size: imageSize) + } + + func testTabSelection() { + // Given + let index = 1 + var didCalledOnTabSelection = false + var passedIndex: Int? + + let sut = TabBarUIComponent(model: .init( + tabIndex: 0, + tabBarItems: [ + TabBarItem(icon: "shuttle"), + TabBarItem(icon: "shuttle", title: "Tab 2") + ], + renderer: renderer) + ) + + sut.setupTabBarItems() + + // When + sut.onTabSelection = { index in + didCalledOnTabSelection = true + passedIndex = index + } + + guard let gestureRecognizer = sut.tabItemViews[index]?.gestureRecognizers?.first as? UITapGestureRecognizer else { + XCTFail("TabBarItem of index 1 has no gesture recognizer.") + return + } + sut.didSelectTabItem(sender: gestureRecognizer) + + // Then + XCTAssert(didCalledOnTabSelection) + XCTAssert(passedIndex == index) + } } diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarUIComponentTests.swift b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarUIComponentTests.swift deleted file mode 100644 index 6fb584ad11..0000000000 --- a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/TabBarUIComponentTests.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -/* - * Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA - * - * 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 XCTest -@testable import Beagle -import BeagleSchema - -class TabBarUIComponentTests: XCTestCase { - - private lazy var model = TabBarUIComponent.Model( - tabIndex: 0, - tabBarItems: [ - TabBarItem(icon: "beagle", title: "Tab 1"), - TabBarItem(icon: "beagle", title: "Tab 2") - ] - ) - - private lazy var sut = TabBarUIComponent(model: model) - - func test_didSelectItemAt_shouldCallOnTabSelectionClosure() { - // Given - let collectionView = sut.collectionView - let index = 1 - let indexPath = IndexPath(item: index, section: 0) - var didCalledOnTabSelection = false - var passedIndex: Int? - - // When - sut.onTabSelection = { index in - didCalledOnTabSelection = true - passedIndex = index - } - - sut.collectionView(collectionView, didSelectItemAt: indexPath) - - // Then - XCTAssert(didCalledOnTabSelection) - XCTAssert(passedIndex == index) - } - -} diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/__Snapshots__/TabBarTests/testCurrentTabWithContext.1.png b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/__Snapshots__/TabBarTests/testCurrentTabWithContext.1.png new file mode 100644 index 0000000000000000000000000000000000000000..85c3b10b8d193a21e2b190f64fa4b4bd3042d513 GIT binary patch literal 13352 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 b9fFgOj-P&5(-IHtT|pWuI?6Rlh_L?xWhd>B literal 0 HcmV?d00001 diff --git a/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/__Snapshots__/TabBarTests/test_viewWithTabBar.1.png b/iOS/Sources/Beagle/Sources/Components/TabBar+Renderable/Tests/__Snapshots__/TabBarTests/test_viewWithTabBar.1.png deleted file mode 100644 index 357af406f0f53b16ca4ee7789e8ef5b9772df60f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21480 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ΠJJzj z#_#MhDBWBdP3g7q zwG`(NMwoy{I`CGh0OFTBD%emjZtYL`k|IlKLt2a~=&ZvN&R`XOfQ*|PNJUe&hvyXz z#P}I~6NVR1L=DuqVIP#Pvm2k7nhb!Ew7%_;@WR=rNSy~J*mtmcgTKMy4(Zy=^qKpx z*7nINYGgHOkR@u62i;SgO)Wq5ON=Cbu4SxwTlTZvOo1u0NtC5+J9YV&CHRmYJ&F*I zA|Gid^=BP+XJB#V*zrnRS(7(KqjaZ}$}|s`1cb6+%F~hCx!of$P2**xs2DbTYr?>H zJHNLw)-f)8{12oO+AB?JxMYp;_g!1~Z=LB6-?;XN`dNqNOw>Nk2Qm@*!L{a)<*N22 zNrwOne}co0P1EIZf7E&+X(f?XsbJL1>Ck#n|!!Fk`M#wk?QB2IS z!e3TdsBj{>w0Ro~wiY!sg<+P2$~X2Fhs5wa1Ycst7ye*+Aj)2dE__FabCbr_k;XNI z`kuX&fQL<`FtIu9nm?$BKje!&p*7EAP+J>&(2Mv16^yVBw@c5nrB#*wrW{eepa~syjSQ#FZVSB z*<~0vR^zBy)&4s|PMq4gPNs|pV#dv^Yl4PmUhR7Gt@wuI6}Oo78Rk7#0IWjt^cS2I7N7s&Y%v45L0V2GABm{E3&Wwrjmb@ATmTH$RX z(~f9KYOpN=JnS-&n!{et%3YoGQ?d|vy;m>Z)?X|vk1@$P``3og{tgYFTm&eYaQ1Xc zN%-8$S`6;Ii5mBg1+5?TaHfCryX*9Tbv$PI1NrrM$!>ONw)S8F0FmLjrw=*C70w`) zkC#A&XJff<6lveM8oY?2?%m z+yBVo`+=NkJZ>+|XGkd#(zt5{>ujGb=_eBuM~10kQXb&TtH zy=CP`R;V7}fhI|JygyzoU*L{$*|y}`M+WKH(zUF5!*3TqUL9VQoE)+y>flEP$t#}2 zc`lDce7w5#`*AXlweGk1R&EJG{@DiX5LBtvH2$NFhJ4$sS*QFr=TP$8Auof}cZGE( z-2tY6<6UywUqP1$PcEOc+P-y;IA9Zm)xn0i@*_E&?fM@htt4xet>YE)*iyPf8M)t= zkzi$3FZm@W`)S`bM>X_o**)zK{r|^puf(&KCOGb5Hs)Bl)rd#OE7P8VbLgYb0NoPk zc)`#D22T)=fQakE8YPG+L6{CEA^> zr4e9m0p=E9ZUN>N;6fF+_6M$RdV%o}ToV7k7X7DDz>XG+g(8KdLm*zut=m|p_<>&l z+i>gxjW_&PlALW3qwePMkH0Peu;niDHo^JNdq?JNPtO0m=Oki{7wf=|qaVm!VEL3h z=JZ*n?l|Y{<`^p>hKXN zf|(YWn1U%b$VdQC5sZvrWCSB4$U%Z^Do8$mV(7t~5zHAuHWg%3K{gd!X9PSUNEU!( f!T%-;!qjI+=E}Z=d}sYPm|MS#-op52@`?Wfy&=gH diff --git a/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabView+Renderable.swift b/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabView+Renderable.swift index 73c033bfa1..4325675a2c 100644 --- a/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabView+Renderable.swift +++ b/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabView+Renderable.swift @@ -20,7 +20,7 @@ import BeagleSchema extension TabView: ServerDrivenComponent { public func toView(renderer: BeagleRenderer) -> UIView { - let model = TabViewUIComponent.Model(tabIndex: 0, tabViewItems: children) + let model = TabViewUIComponent.Model(tabIndex: 0, tabViewItems: children, renderer: renderer) let tabView = TabViewUIComponent(model: model, renderer: renderer) tabView.style.setup(Style(size: Size().width(100%), flex: Flex().grow(1))) diff --git a/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabViewUIComponent.swift b/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabViewUIComponent.swift index b4d9e1d623..7d646875b4 100644 --- a/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabViewUIComponent.swift +++ b/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/TabViewUIComponent.swift @@ -25,6 +25,7 @@ extension TabViewUIComponent { var unselectedTextColor: UIColor? var selectedIconColor: UIColor? var unselectedIconColor: UIColor? + var renderer: BeagleRenderer // sourcery:inline:auto:TabViewUIComponent.Model.Init init( @@ -33,7 +34,8 @@ extension TabViewUIComponent { selectedTextColor: UIColor? = nil, unselectedTextColor: UIColor? = nil, selectedIconColor: UIColor? = nil, - unselectedIconColor: UIColor? = nil + unselectedIconColor: UIColor? = nil, + renderer: BeagleRenderer ) { self.tabIndex = tabIndex self.tabViewItems = tabViewItems @@ -41,6 +43,7 @@ extension TabViewUIComponent { self.unselectedTextColor = unselectedTextColor self.selectedIconColor = selectedIconColor self.unselectedIconColor = unselectedIconColor + self.renderer = renderer } // sourcery:end } @@ -59,6 +62,7 @@ final class TabViewUIComponent: UIView { indicatorView: nil, controller: renderer.controller ) + view.style.setup(Style(flex: Flex().grow(1))) view.translatesAutoresizingMaskIntoConstraints = false return view } @@ -75,10 +79,7 @@ final class TabViewUIComponent: UIView { model: .init( tabIndex: model.tabIndex, tabBarItems: model.tabViewItems.map { TabBarItem(icon: $0.icon, title: $0.title) }, - selectedTextColor: model.selectedTextColor, - unselectedTextColor: model.unselectedTextColor, - selectedIconColor: model.selectedIconColor, - unselectedIconColor: model.unselectedIconColor + renderer: model.renderer ) ) self.contentView = Self.contentView(items: model.tabViewItems, renderer: renderer) @@ -97,6 +98,7 @@ final class TabViewUIComponent: UIView { tabBar.onTabSelection = { [weak self] index in guard let self = self else { return } self.contentView.swipeToPage(at: index) + self.tabBar.scrollTo(page: index) } } diff --git a/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/Tests/TabViewUIComponentTests.swift b/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/Tests/TabViewUIComponentTests.swift index d64561913a..cc571715a4 100644 --- a/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/Tests/TabViewUIComponentTests.swift +++ b/iOS/Sources/Beagle/Sources/Components/TabView+Renderable/Tests/TabViewUIComponentTests.swift @@ -21,6 +21,9 @@ import BeagleSchema final class TabViewUIComponentTests: XCTestCase { // MARK: - Variables + private lazy var controller = BeagleControllerStub() + private lazy var renderer = BeagleRenderer(controller: controller) + private lazy var component = TabView(children: [ TabItem(icon: "beagle", title: "Tab 1", child: Container(children: [ @@ -38,7 +41,7 @@ final class TabViewUIComponentTests: XCTestCase { ) ]) - private lazy var model = TabViewUIComponent.Model(tabIndex: 0, tabViewItems: component.children) + private lazy var model = TabViewUIComponent.Model(tabIndex: 0, tabViewItems: component.children, renderer: renderer) private lazy var sut = TabViewUIComponent(model: model, renderer: .init(controller: controllerStub)) @@ -93,11 +96,20 @@ final class TabViewUIComponentTests: XCTestCase { } func test_whenChangedTabs_shouldChangeCurrentPage() { - let tabBar = sut.tabBar - tabBar.collectionView(tabBar.collectionView, didSelectItemAt: IndexPath(item: 1, section: 0)) + // Given + sut.tabBar.setupTabBarItems() + let tabItem = sut.tabBar.tabItemViews[1] + + guard let gestureRecognizer = tabItem?.gestureRecognizers?.first as? UITapGestureRecognizer else { + XCTFail("TabItem of index 1 has no gesture recognizer.") + return + } + + // When + sut.tabBar.didSelectTabItem(sender: gestureRecognizer) + + // Then XCTAssert(sut.contentView.model.currentPage == 1) - tabBar.collectionView(tabBar.collectionView, didSelectItemAt: IndexPath(item: 0, section: 0)) - XCTAssert(sut.contentView.model.currentPage == 0) } } diff --git a/iOS/Sources/Beagle/Sources/ContextExpression/Tests/UIView+ContextTests.swift b/iOS/Sources/Beagle/Sources/ContextExpression/Tests/UIView+ContextTests.swift index bf642afdc5..a7cf33cadd 100644 --- a/iOS/Sources/Beagle/Sources/ContextExpression/Tests/UIView+ContextTests.swift +++ b/iOS/Sources/Beagle/Sources/ContextExpression/Tests/UIView+ContextTests.swift @@ -143,10 +143,10 @@ final class UIViewContextTests: XCTestCase { // Given var view: UIView? = UIView() weak var weakReference = view - + let contextId = "context" view?.setContext(Context(id: contextId, value: .empty)) - + let binding = Binding(context: contextId, path: .init(nodes: [])) let singleExpression = SingleExpression.value(.binding(binding)) let multipleExpression = MultipleExpression(nodes: [.expression(singleExpression)]) diff --git a/iOS/Sources/Beagle/Sources/Logger/Tests/BeagleLoggerTests.swift b/iOS/Sources/Beagle/Sources/Logger/Tests/BeagleLoggerTests.swift index 27e2e45e82..4b5141e7ae 100644 --- a/iOS/Sources/Beagle/Sources/Logger/Tests/BeagleLoggerTests.swift +++ b/iOS/Sources/Beagle/Sources/Logger/Tests/BeagleLoggerTests.swift @@ -42,7 +42,7 @@ class BeagleLoggerTests: XCTestCase { Log.form(.keyDuplication(data: ["key": "value"])), Log.navigation(.cantPopToAlreadyCurrentScreen(identifier: "identifier")), - Log.navigation(.didReceiveAction(Navigate.pushView(.remote(.init(url: .value(path)))))), + Log.navigation(.didReceiveAction(Navigate.pushView(.remote(.init(url: path))))), Log.navigation(.didReceiveAction(Navigate.openNativeRoute(.init(route: path)))), Log.navigation(.didReceiveAction(Navigate.openNativeRoute(.init(route: path, data: ["key": "value"])))), Log.navigation(.errorTryingToPopScreenOnNavigatorWithJustOneScreen), diff --git a/iOS/Sources/Beagle/Sources/Renderer/BeagleScreenViewController.swift b/iOS/Sources/Beagle/Sources/Renderer/BeagleScreenViewController.swift index f6e384b7db..fc8d95e7aa 100644 --- a/iOS/Sources/Beagle/Sources/Renderer/BeagleScreenViewController.swift +++ b/iOS/Sources/Beagle/Sources/Renderer/BeagleScreenViewController.swift @@ -194,7 +194,7 @@ public class BeagleScreenViewController: BeagleController { } } - // MARK: - + // MARK: - Update View fileprivate func updateView(state: ViewModel.State) { switch state { @@ -249,6 +249,9 @@ public class BeagleScreenViewController: BeagleController { extension BeagleControllerProtocol where Self: UIViewController { public func setNeedsLayout(component: UIView) { dependencies.style(component).markDirty() + if let beagleView = view.superview as? BeagleView { + beagleView.invalidateIntrinsicContentSize() + } viewIfLoaded?.setNeedsLayout() } } diff --git a/iOS/Sources/Beagle/Sources/Theme/Theme.swift b/iOS/Sources/Beagle/Sources/Theme/Theme.swift index 5814f7fb48..cbc8ebb6fb 100644 --- a/iOS/Sources/Beagle/Sources/Theme/Theme.swift +++ b/iOS/Sources/Beagle/Sources/Theme/Theme.swift @@ -77,11 +77,16 @@ public struct BeagleStyle { return { guard let tabBar = $0 as? TabBarUIComponent else { return } tabBar.backgroundColor = backgroundColor - tabBar.containerIndicator.indicatorView.backgroundColor = indicatorColor - tabBar.model.selectedTextColor = selectedTextColor - tabBar.model.unselectedTextColor = unselectedTextColor - tabBar.model.selectedIconColor = selectedIconColor - tabBar.model.unselectedIconColor = unselectedIconColor + tabBar.indicatorView.backgroundColor = indicatorColor + tabBar.tabItemViews.forEach { _, item in + item.theme = TabBarTheme( + selectedTextColor: selectedTextColor, + unselectedTextColor: unselectedTextColor, + selectedIconColor: selectedIconColor, + unselectedIconColor: unselectedIconColor + ) + } + } } diff --git a/iOS/Sources/Beagle/Sources/Theme/ThemeTests.swift b/iOS/Sources/Beagle/Sources/Theme/ThemeTests.swift index 1164653a90..acd140f877 100644 --- a/iOS/Sources/Beagle/Sources/Theme/ThemeTests.swift +++ b/iOS/Sources/Beagle/Sources/Theme/ThemeTests.swift @@ -105,11 +105,15 @@ final class ThemeTests: XCTestCase { func test_tabViewWithStyle_shouldReturnAFunctionThatChangesTabViewStyle() { // Given + + let controller = BeagleControllerStub() + let renderer = BeagleRenderer(controller: controller) + let backgroundColor: UIColor = .clear let indicatorColor: UIColor = .blue let tabItem = TabBarItem(title: "Tab") let tabBar = TabBarUIComponent( - model: TabBarUIComponent.Model(tabIndex: 0, tabBarItems: [tabItem, tabItem]) + model: TabBarUIComponent.Model(tabIndex: 0, tabBarItems: [tabItem, tabItem], renderer: renderer) ) // When @@ -117,6 +121,6 @@ final class ThemeTests: XCTestCase { //Then XCTAssertEqual(backgroundColor, tabBar.backgroundColor) - XCTAssertEqual(indicatorColor, tabBar.containerIndicator.indicatorView.backgroundColor) + XCTAssertEqual(indicatorColor, tabBar.indicatorView.backgroundColor) } }