diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..8a78a69d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+*.DS_Store
+*.xcuserstate
+build/
+DerivedData/
+*.pbxproj
+*.xcworkspace/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+*.moved-aside
+*.xccheckout
+*.xcscmblueprint
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+timeline.xctimeline
+playground.xcworkspace
+.build/
+Pods/
+Carthage/Build
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots/**/*.png
+fastlane/test_output
+Breakpoints_v2.xcbkptlist
+*.xcodeproj/*
+/Santander/Santander.xcodeproj
\ No newline at end of file
diff --git a/Santander/Gemfile b/Santander/Gemfile
new file mode 100644
index 00000000..ea0699a4
--- /dev/null
+++ b/Santander/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org'
+
+gem 'cocoapods', '~> 1.6.1'
\ No newline at end of file
diff --git a/Santander/Gemfile.lock b/Santander/Gemfile.lock
new file mode 100644
index 00000000..c47d26fe
--- /dev/null
+++ b/Santander/Gemfile.lock
@@ -0,0 +1,76 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.0)
+ activesupport (4.2.11.1)
+ i18n (~> 0.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.3, >= 0.3.4)
+ tzinfo (~> 1.1)
+ atomos (0.1.3)
+ claide (1.0.2)
+ cocoapods (1.6.2)
+ activesupport (>= 4.0.2, < 5)
+ claide (>= 1.0.2, < 2.0)
+ cocoapods-core (= 1.6.2)
+ cocoapods-deintegrate (>= 1.0.2, < 2.0)
+ cocoapods-downloader (>= 1.2.2, < 2.0)
+ cocoapods-plugins (>= 1.0.0, < 2.0)
+ cocoapods-search (>= 1.0.0, < 2.0)
+ cocoapods-stats (>= 1.0.0, < 2.0)
+ cocoapods-trunk (>= 1.3.1, < 2.0)
+ cocoapods-try (>= 1.1.0, < 2.0)
+ colored2 (~> 3.1)
+ escape (~> 0.0.4)
+ fourflusher (>= 2.2.0, < 3.0)
+ gh_inspector (~> 1.0)
+ molinillo (~> 0.6.6)
+ nap (~> 1.0)
+ ruby-macho (~> 1.4)
+ xcodeproj (>= 1.8.1, < 2.0)
+ cocoapods-core (1.6.2)
+ activesupport (>= 4.0.2, < 6)
+ fuzzy_match (~> 2.0.4)
+ nap (~> 1.0)
+ cocoapods-deintegrate (1.0.4)
+ cocoapods-downloader (1.2.2)
+ cocoapods-plugins (1.0.0)
+ nap
+ cocoapods-search (1.0.0)
+ cocoapods-stats (1.1.0)
+ cocoapods-trunk (1.3.1)
+ nap (>= 0.8, < 2.0)
+ netrc (~> 0.11)
+ cocoapods-try (1.1.0)
+ colored2 (3.1.2)
+ concurrent-ruby (1.1.5)
+ escape (0.0.4)
+ fourflusher (2.2.0)
+ fuzzy_match (2.0.4)
+ gh_inspector (1.1.3)
+ i18n (0.9.5)
+ concurrent-ruby (~> 1.0)
+ minitest (5.11.3)
+ molinillo (0.6.6)
+ nanaimo (0.2.6)
+ nap (1.1.0)
+ netrc (0.11.0)
+ ruby-macho (1.4.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.5)
+ thread_safe (~> 0.1)
+ xcodeproj (1.9.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.2.6)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ cocoapods (~> 1.6.1)
+
+BUNDLED WITH
+ 2.0.2
diff --git a/Santander/Podfile b/Santander/Podfile
new file mode 100644
index 00000000..47de9cd8
--- /dev/null
+++ b/Santander/Podfile
@@ -0,0 +1,19 @@
+use_frameworks!
+inhibit_all_warnings!
+platform :ios, '9.0'
+
+def app_pods
+ pod 'SnapKit'
+ pod 'Eureka'
+ pod 'TLCustomMask'
+ pod 'Moya', '~> 13.0'
+ pod 'JGProgressHUD', '~> 2.0'
+end
+
+target 'Santander' do
+ app_pods
+end
+
+target 'SantanderUnitTests' do
+ app_pods
+end
diff --git a/Santander/Podfile.lock b/Santander/Podfile.lock
new file mode 100644
index 00000000..d0101bfa
--- /dev/null
+++ b/Santander/Podfile.lock
@@ -0,0 +1,42 @@
+PODS:
+ - Alamofire (4.8.2)
+ - Eureka (4.3.1)
+ - JGProgressHUD (2.0.4)
+ - Moya (13.0.1):
+ - Moya/Core (= 13.0.1)
+ - Moya/Core (13.0.1):
+ - Alamofire (~> 4.1)
+ - Result (~> 4.1)
+ - Result (4.1.0)
+ - SnapKit (4.2.0)
+ - TLCustomMask (2.0.0)
+
+DEPENDENCIES:
+ - Eureka
+ - JGProgressHUD (~> 2.0)
+ - Moya (~> 13.0)
+ - SnapKit
+ - TLCustomMask
+
+SPEC REPOS:
+ https://github.com/cocoapods/specs.git:
+ - Alamofire
+ - Eureka
+ - JGProgressHUD
+ - Moya
+ - Result
+ - SnapKit
+ - TLCustomMask
+
+SPEC CHECKSUMS:
+ Alamofire: ae5c501addb7afdbb13687d7f2f722c78734c2d3
+ Eureka: 28ea296f06710f6745266b71f17862048941a32d
+ JGProgressHUD: 62658b14e72cccf179efc7a13bcb54d30b92fc22
+ Moya: f4a4b80ff2f8a4ffc208dfb31cd91636622fee6e
+ Result: bd966fac789cc6c1563440b348ab2598cc24d5c7
+ SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a
+ TLCustomMask: 32bcd873851167c2d2a3726b048766b737873710
+
+PODFILE CHECKSUM: 96e7b800b5a7b93d749bdfce5e50a90371f01bdb
+
+COCOAPODS: 1.6.2
diff --git a/Santander/README.md b/Santander/README.md
new file mode 100644
index 00000000..b1523495
--- /dev/null
+++ b/Santander/README.md
@@ -0,0 +1,37 @@
+# Setup Guide
+
+## Requirements
+
+- iOS 9.0+
+- Xcode 10.0+
+- Swift 5.0
+
+## Development Dependencies
+
+In order to work in this project, some dependencies are needed and are also described below.
+
+- [bundler](https://bundler.io) - Bundler encapsulates the project environment by locking our ruby dependencies versions.
+- [xcodegen](https://github.com/yonaskolb/XcodeGen) - Generates the xcodeproj you'll be working on with the proper folder structure and specs.
+
+## Building Santander
+
+1. Install [Xcode developer tools](https://developer.apple.com/xcode/downloads/) from Apple.
+1. Clone the repository:
+ ```shell
+ git clone git@github.com:orlandoamorim/TesteiOS.git
+ ```
+1. Pull in the project dependencies:
+ ### If you're running the project for the first time run this
+ ```shell
+ sh ./bootstrap.sh --install-bundler
+ ```
+
+ ### else run this
+ ```shell
+ sh ./bootstrap.sh
+ ```
+1. Open `Santander.xcworkspace` in Xcode.
+
+# Info
+
+ - Company: [Zup](https://www.zup.com.br/)
\ No newline at end of file
diff --git a/Santander/Santander/Application/AppDelegate.swift b/Santander/Santander/Application/AppDelegate.swift
new file mode 100644
index 00000000..1d372bcf
--- /dev/null
+++ b/Santander/Santander/Application/AppDelegate.swift
@@ -0,0 +1,23 @@
+//
+// AppDelegate.swift
+// Santander
+//
+// Created by Orlando Amorim on 10/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ window = UIWindow(frame: UIScreen.main.bounds)
+ window?.makeKeyAndVisible()
+ window?.rootViewController = UINavigationController(rootViewController: ContainerViewController())
+ return true
+ }
+}
+
diff --git a/Santander/Santander/Application/Propeties/Info.plist b/Santander/Santander/Application/Propeties/Info.plist
new file mode 100644
index 00000000..4f639edf
--- /dev/null
+++ b/Santander/Santander/Application/Propeties/Info.plist
@@ -0,0 +1,50 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIAppFonts
+
+ DINPro-Medium.otf
+ DINPro-Regular.otf
+ DINPro-Light.otf
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortraitUpsideDown
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/Santander/Santander/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..d8db8d65
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "83.5x83.5",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "size" : "1024x1024",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/Contents.json b/Santander/Santander/Assets/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..da4a164c
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/arrow-icon.imageset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/arrow-icon.imageset/Contents.json
new file mode 100644
index 00000000..04165373
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/arrow-icon.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "arrow-icon.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "compression-type" : "automatic",
+ "preserves-vector-representation" : true
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/arrow-icon.imageset/arrow-icon.pdf b/Santander/Santander/Assets/Assets.xcassets/arrow-icon.imageset/arrow-icon.pdf
new file mode 100644
index 00000000..487b393b
Binary files /dev/null and b/Santander/Santander/Assets/Assets.xcassets/arrow-icon.imageset/arrow-icon.pdf differ
diff --git a/Santander/Santander/Assets/Assets.xcassets/checkmark-selected-icon.imageset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/checkmark-selected-icon.imageset/Contents.json
new file mode 100644
index 00000000..825d1dab
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/checkmark-selected-icon.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "checkmark-selected-icon.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "compression-type" : "automatic",
+ "preserves-vector-representation" : true
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/checkmark-selected-icon.imageset/checkmark-selected-icon.pdf b/Santander/Santander/Assets/Assets.xcassets/checkmark-selected-icon.imageset/checkmark-selected-icon.pdf
new file mode 100644
index 00000000..66ac3e60
Binary files /dev/null and b/Santander/Santander/Assets/Assets.xcassets/checkmark-selected-icon.imageset/checkmark-selected-icon.pdf differ
diff --git a/Santander/Santander/Assets/Assets.xcassets/checkmark-unselected-icon.imageset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/checkmark-unselected-icon.imageset/Contents.json
new file mode 100644
index 00000000..1a475609
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/checkmark-unselected-icon.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "checkmark-unselected-icon.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "compression-type" : "automatic",
+ "preserves-vector-representation" : true
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/checkmark-unselected-icon.imageset/checkmark-unselected-icon.pdf b/Santander/Santander/Assets/Assets.xcassets/checkmark-unselected-icon.imageset/checkmark-unselected-icon.pdf
new file mode 100644
index 00000000..71e334d1
Binary files /dev/null and b/Santander/Santander/Assets/Assets.xcassets/checkmark-unselected-icon.imageset/checkmark-unselected-icon.pdf differ
diff --git a/Santander/Santander/Assets/Assets.xcassets/clear-icon.imageset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/clear-icon.imageset/Contents.json
new file mode 100644
index 00000000..672b7e06
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/clear-icon.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "clear-icon.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "compression-type" : "automatic",
+ "preserves-vector-representation" : true
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/clear-icon.imageset/clear-icon.pdf b/Santander/Santander/Assets/Assets.xcassets/clear-icon.imageset/clear-icon.pdf
new file mode 100644
index 00000000..a9885067
Binary files /dev/null and b/Santander/Santander/Assets/Assets.xcassets/clear-icon.imageset/clear-icon.pdf differ
diff --git a/Santander/Santander/Assets/Assets.xcassets/download-icon.imageset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/download-icon.imageset/Contents.json
new file mode 100644
index 00000000..64ada0ab
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/download-icon.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "download-icon.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "compression-type" : "automatic",
+ "preserves-vector-representation" : true
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/download-icon.imageset/download-icon.pdf b/Santander/Santander/Assets/Assets.xcassets/download-icon.imageset/download-icon.pdf
new file mode 100644
index 00000000..6d6e6929
Binary files /dev/null and b/Santander/Santander/Assets/Assets.xcassets/download-icon.imageset/download-icon.pdf differ
diff --git a/Santander/Santander/Assets/Assets.xcassets/separator-line-arrow-icon.imageset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/separator-line-arrow-icon.imageset/Contents.json
new file mode 100644
index 00000000..c6fac41f
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/separator-line-arrow-icon.imageset/Contents.json
@@ -0,0 +1,17 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "separator-line-icon.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "compression-type" : "automatic",
+ "template-rendering-intent" : "original",
+ "preserves-vector-representation" : true
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/separator-line-arrow-icon.imageset/separator-line-icon.pdf b/Santander/Santander/Assets/Assets.xcassets/separator-line-arrow-icon.imageset/separator-line-icon.pdf
new file mode 100644
index 00000000..ac0c6a7f
Binary files /dev/null and b/Santander/Santander/Assets/Assets.xcassets/separator-line-arrow-icon.imageset/separator-line-icon.pdf differ
diff --git a/Santander/Santander/Assets/Assets.xcassets/share-icon.imageset/Contents.json b/Santander/Santander/Assets/Assets.xcassets/share-icon.imageset/Contents.json
new file mode 100644
index 00000000..c7a6a6e0
--- /dev/null
+++ b/Santander/Santander/Assets/Assets.xcassets/share-icon.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "share-icon.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "compression-type" : "automatic",
+ "preserves-vector-representation" : true
+ }
+}
\ No newline at end of file
diff --git a/Santander/Santander/Assets/Assets.xcassets/share-icon.imageset/share-icon.pdf b/Santander/Santander/Assets/Assets.xcassets/share-icon.imageset/share-icon.pdf
new file mode 100644
index 00000000..5617f428
Binary files /dev/null and b/Santander/Santander/Assets/Assets.xcassets/share-icon.imageset/share-icon.pdf differ
diff --git a/Santander/Santander/Assets/Base.lproj/LaunchScreen.storyboard b/Santander/Santander/Assets/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..bfa36129
--- /dev/null
+++ b/Santander/Santander/Assets/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Santander/Santander/Assets/Fonts/DINPro-Light.otf b/Santander/Santander/Assets/Fonts/DINPro-Light.otf
new file mode 100755
index 00000000..8a7f085a
Binary files /dev/null and b/Santander/Santander/Assets/Fonts/DINPro-Light.otf differ
diff --git a/Santander/Santander/Assets/Fonts/DINPro-Medium.otf b/Santander/Santander/Assets/Fonts/DINPro-Medium.otf
new file mode 100755
index 00000000..b4608d06
Binary files /dev/null and b/Santander/Santander/Assets/Fonts/DINPro-Medium.otf differ
diff --git a/Santander/Santander/Assets/Fonts/DINPro-Regular.otf b/Santander/Santander/Assets/Fonts/DINPro-Regular.otf
new file mode 100755
index 00000000..84d57abb
Binary files /dev/null and b/Santander/Santander/Assets/Fonts/DINPro-Regular.otf differ
diff --git a/Santander/Santander/Library/Eureka Rules/RulePhoneNumber.swift b/Santander/Santander/Library/Eureka Rules/RulePhoneNumber.swift
new file mode 100644
index 00000000..7cafb4d4
--- /dev/null
+++ b/Santander/Santander/Library/Eureka Rules/RulePhoneNumber.swift
@@ -0,0 +1,39 @@
+//
+// RulePhoneNumber.swift
+// Santander
+//
+// Created by Orlando Amorim on 13/08/19.
+//
+
+import Eureka
+
+public class RulePhoneNumber: RuleType {
+
+ public var regExpr: String
+ public var id: String?
+ public var validationError: ValidationError
+ public var allowsEmpty = true
+
+ public init(regExpr: String = "^(\\([0-9]{2}\\))\\s([9]{1})?([0-9]{4})-([0-9]{4})$",
+ allowsEmpty: Bool = true,
+ msg: String = "Field value should be a valid phone number!",
+ id: String? = nil) {
+ self.validationError = ValidationError(msg: msg)
+ self.regExpr = regExpr
+ self.allowsEmpty = allowsEmpty
+ self.id = id
+ }
+
+ public func isValid(value: String?) -> ValidationError? {
+ if let value = value, !value.isEmpty {
+ let predicate = NSPredicate(format: "SELF MATCHES %@", regExpr)
+ guard predicate.evaluate(with: value) else {
+ return validationError
+ }
+ return nil
+ } else if !allowsEmpty {
+ return validationError
+ }
+ return nil
+ }
+}
diff --git a/Santander/Santander/Library/Extensions/MoyaProvider.swift b/Santander/Santander/Library/Extensions/MoyaProvider.swift
new file mode 100644
index 00000000..06c6051f
--- /dev/null
+++ b/Santander/Santander/Library/Extensions/MoyaProvider.swift
@@ -0,0 +1,28 @@
+//
+// MoyaProvider.swift
+// Santander
+//
+// Created by Orlando Amorim on 17/08/19.
+//
+
+import Moya
+
+extension MoyaProvider {
+ @discardableResult
+ func request(_ target: Target, decodeType: T.Type, result: @escaping (Result) -> Void) -> Cancellable? {
+ return request(target) { requestResult in
+ switch requestResult {
+ case .success(let response):
+ let decoder = JSONDecoder()
+ do {
+ let decodableObject = try decoder.decode(T.self, from: response.data)
+ result(.success(decodableObject))
+ } catch let error {
+ result(.failure(error))
+ }
+ case .failure(let error):
+ result(.failure(error))
+ }
+ }
+ }
+}
diff --git a/Santander/Santander/Library/Extensions/String.swift b/Santander/Santander/Library/Extensions/String.swift
new file mode 100644
index 00000000..b8ce853b
--- /dev/null
+++ b/Santander/Santander/Library/Extensions/String.swift
@@ -0,0 +1,16 @@
+//
+// String.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+import Eureka
+
+extension String: SectionHeaderFooterRenderable {
+ public func viewForItem() -> HeaderFooterViewRepresentable {
+ return HeaderFooterView(stringLiteral: self)
+ }
+}
diff --git a/Santander/Santander/Library/Extensions/UIColor.swift b/Santander/Santander/Library/Extensions/UIColor.swift
new file mode 100644
index 00000000..ac071f38
--- /dev/null
+++ b/Santander/Santander/Library/Extensions/UIColor.swift
@@ -0,0 +1,45 @@
+//
+// UIColor.swift
+// Santander
+//
+// Created by Orlando Amorim on 10/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+
+extension UIColor {
+
+ struct Santander {
+ static let monza = UIColor(hexString: "#DA0101")
+ static let guardsmanRed = UIColor(hexString: "#C80404")
+ static let apricot = UIColor(hexString: "#EB7676")
+ static let silverChalice = UIColor(hexString: "#ACACAC")
+ static let havelockBlue = UIColor(hexString: "#6DA1DF")
+ static let gallery = UIColor(hexString: "#EFEEED")
+ static let torchRed = UIColor(hexString: "#FF1F1F")
+ static let sushi = UIColor(hexString: "#65BE30")
+ static let gray = UIColor(hexString: "#7E7E7E")
+ static let mineShaft = UIColor(hexString: "#333333")
+ static let pastelGreen = UIColor(hexString: "#74DA61")
+ static let emerald = UIColor(hexString: "#4AC16C")
+ static let lightningYellow = UIColor(hexString: "#FFC011")
+ static let burningOrange = UIColor(hexString: "#FF742C")
+ static let redOrange = UIColor(hexString: "#FF3634")
+ static let cloudy = UIColor(hexString: "#AFA9A3")
+ }
+
+ convenience init(hexString: String, alpha: CGFloat? = 1.0) {
+ var hexInt: UInt32 = 0
+ let scanner = Scanner(string: hexString)
+ scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#")
+ scanner.scanHexInt32(&hexInt)
+
+ let red = CGFloat((hexInt & 0xff0000) >> 16) / 255.0
+ let green = CGFloat((hexInt & 0xff00) >> 8) / 255.0
+ let blue = CGFloat((hexInt & 0xff) >> 0) / 255.0
+ let alpha = alpha!
+
+ self.init(red: red, green: green, blue: blue, alpha: alpha)
+ }
+}
diff --git a/Santander/Santander/Library/Extensions/UIFont.swift b/Santander/Santander/Library/Extensions/UIFont.swift
new file mode 100644
index 00000000..c99e642c
--- /dev/null
+++ b/Santander/Santander/Library/Extensions/UIFont.swift
@@ -0,0 +1,26 @@
+//
+// UIFont.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit.UIFont
+
+extension UIFont {
+
+ enum SantanderFontType {
+ case medium
+ case regular
+ case light
+ }
+
+ static func santander(type: SantanderFontType = .regular, with size: CGFloat) -> UIFont {
+ switch type {
+ case .medium: return UIFont(name: "DINPro-Medium", size: size)!
+ case .regular: return UIFont(name: "DINPro-Regular", size: size)!
+ case .light: return UIFont(name: "DINPro-Light", size: size)!
+ }
+ }
+}
diff --git a/Santander/Santander/Library/Extensions/UIStackView.swift b/Santander/Santander/Library/Extensions/UIStackView.swift
new file mode 100644
index 00000000..662ba337
--- /dev/null
+++ b/Santander/Santander/Library/Extensions/UIStackView.swift
@@ -0,0 +1,18 @@
+//
+// UIStackView.swift
+// Santander
+//
+// Created by Orlando Amorim on 10/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit.UIStackView
+
+extension UIStackView {
+ func removeAllArrangedSubviews() {
+ arrangedSubviews.forEach({
+ removeArrangedSubview($0)
+ $0.removeFromSuperview()
+ })
+ }
+}
diff --git a/Santander/Santander/Library/Extensions/UIView.swift b/Santander/Santander/Library/Extensions/UIView.swift
new file mode 100644
index 00000000..53afae0e
--- /dev/null
+++ b/Santander/Santander/Library/Extensions/UIView.swift
@@ -0,0 +1,23 @@
+//
+// UIView.swift
+// Santander
+//
+// Created by Orlando Amorim on 14/08/19.
+//
+
+import UIKit
+
+extension UIView {
+ func round(corners: UIRectCorner, radius: CGFloat) {
+ let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+ let mask = CAShapeLayer()
+ mask.path = path.cgPath
+ self.layer.mask = mask
+ }
+
+ @available(iOS 11.0, *)
+ func round(corners: CACornerMask, radius: CGFloat) {
+ layer.maskedCorners = corners
+ layer.cornerRadius = radius
+ }
+}
diff --git a/Santander/Santander/Library/Extensions/ViewController.swift b/Santander/Santander/Library/Extensions/ViewController.swift
new file mode 100644
index 00000000..c6ecec41
--- /dev/null
+++ b/Santander/Santander/Library/Extensions/ViewController.swift
@@ -0,0 +1,18 @@
+//
+// ViewController.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+
+extension UIViewController {
+ func showAlert(title: String, message: String?) {
+ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
+ let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
+ alert.addAction(okAction)
+ present(alert, animated: true, completion: nil)
+ }
+}
diff --git a/Santander/Santander/Library/Protocols/SectionHeaderFooterRenderable.swift b/Santander/Santander/Library/Protocols/SectionHeaderFooterRenderable.swift
new file mode 100644
index 00000000..cbb78db5
--- /dev/null
+++ b/Santander/Santander/Library/Protocols/SectionHeaderFooterRenderable.swift
@@ -0,0 +1,19 @@
+//
+// SectionHeaderFooterRenderable.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Eureka
+
+// MARK: Section Header Footer Renderable Protocol
+
+/**
+ * Protocol used to set headers and footers to sections.
+ * Can be set with a view or a String
+ */
+public protocol SectionHeaderFooterRenderable {
+ func viewForItem() -> HeaderFooterViewRepresentable
+}
diff --git a/Santander/Santander/Library/Protocols/Service.swift b/Santander/Santander/Library/Protocols/Service.swift
new file mode 100644
index 00000000..a98234ef
--- /dev/null
+++ b/Santander/Santander/Library/Protocols/Service.swift
@@ -0,0 +1,13 @@
+//
+// Service.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+
+protocol Service {
+ typealias ResultCompletion = (_ result: Result) -> ()
+}
diff --git a/Santander/Santander/Models/Contact/Contact.swift b/Santander/Santander/Models/Contact/Contact.swift
new file mode 100644
index 00000000..d3241c00
--- /dev/null
+++ b/Santander/Santander/Models/Contact/Contact.swift
@@ -0,0 +1,18 @@
+//
+// Contact.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+
+struct ContactForm: Decodable {
+
+ let cells: [FormCell]
+
+ private enum CodingKeys: String, CodingKey {
+ case cells
+ }
+}
diff --git a/Santander/Santander/Models/Contact/FieldType.swift b/Santander/Santander/Models/Contact/FieldType.swift
new file mode 100644
index 00000000..8ec69bea
--- /dev/null
+++ b/Santander/Santander/Models/Contact/FieldType.swift
@@ -0,0 +1,57 @@
+//
+// FieldType.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+
+enum FieldType: Int, Decodable {
+ case text = 1
+ case phone = 2
+ case email = 3
+
+ enum FieldTypeError: Error {
+ case decoding(String)
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+
+ if let typeInt = try? container.decode(Int.self), let type = FieldType(rawValue: typeInt) {
+ self = type
+ return
+ }
+
+ if let typeString = try? container.decode(String.self), let type = try? FieldType(type: typeString) {
+ self = type
+ return
+ }
+
+ throw FieldTypeError.decoding("The value \(container.codingPath) does not match with any type")
+ }
+
+ var tag: String {
+ switch self {
+ case .text:
+ return "text"
+ case .phone:
+ return "phone"
+ case .email:
+ return "email"
+ }
+ }
+}
+
+extension FieldType {
+ init(type: String) throws {
+ switch type {
+ case "telnumber":
+ self = .phone
+ default:
+ throw FieldTypeError.decoding("The value \(type) does not match with any type")
+ }
+ }
+}
diff --git a/Santander/Santander/Models/Contact/FormCell.swift b/Santander/Santander/Models/Contact/FormCell.swift
new file mode 100644
index 00000000..1ddbe96e
--- /dev/null
+++ b/Santander/Santander/Models/Contact/FormCell.swift
@@ -0,0 +1,47 @@
+//
+// Cell.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+struct FormCell: Decodable {
+ let id: Int
+ let type: FormCellType
+ let message: String
+ let fieldType: FieldType?
+ var isHidden: Bool
+ let topSpacing: CGFloat
+ let fieldToPresent: Int?
+ let isRequired: Bool
+
+ private enum CodingKeys: String, CodingKey {
+ case id
+ case type
+ case message
+ case fieldType = "typefield"
+ case isHidden = "hidden"
+ case topSpacing
+ case fieldToPresent = "show"
+ case isRequired = "required"
+ }
+
+ var tag: String {
+ switch type {
+ case .field:
+ return fieldType != nil ? "field-\(fieldType!.tag)" : "field"
+ case .text:
+ return "text"
+ case .image:
+ return "image"
+ case .checkbox:
+ return "checkbox"
+ case .send:
+ return "send"
+ }
+ }
+}
diff --git a/Santander/Santander/Models/Contact/FormCellType.swift b/Santander/Santander/Models/Contact/FormCellType.swift
new file mode 100644
index 00000000..4737e8d2
--- /dev/null
+++ b/Santander/Santander/Models/Contact/FormCellType.swift
@@ -0,0 +1,17 @@
+//
+// CellType.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+
+enum FormCellType: Int, Decodable {
+ case field = 1
+ case text = 2
+ case image = 3
+ case checkbox = 4
+ case send = 5
+}
diff --git a/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+DownInfo.swift b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+DownInfo.swift
new file mode 100644
index 00000000..5b3ea7d3
--- /dev/null
+++ b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+DownInfo.swift
@@ -0,0 +1,15 @@
+//
+// Funds+DownInfo.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import Foundation
+
+extension FundsScreen {
+ struct DownInfo: Decodable {
+ let name: String
+ let data: String?
+ }
+}
diff --git a/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+Info.swift b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+Info.swift
new file mode 100644
index 00000000..fe2bdf33
--- /dev/null
+++ b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+Info.swift
@@ -0,0 +1,15 @@
+//
+// Funds+Info.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import Foundation
+
+extension FundsScreen {
+ struct Info: Decodable {
+ let name: String
+ let data: String
+ }
+}
diff --git a/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+MoreInfo+Percentages.swift b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+MoreInfo+Percentages.swift
new file mode 100644
index 00000000..9e064d99
--- /dev/null
+++ b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+MoreInfo+Percentages.swift
@@ -0,0 +1,20 @@
+//
+// FundsMoreInfoPercentages.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import Foundation
+
+extension FundsScreen.MoreInfo {
+ struct Percentages: Decodable {
+ let fund: Double
+ let cdi: Double
+
+ private enum CodingKeys: String, CodingKey {
+ case fund
+ case cdi = "CDI"
+ }
+ }
+}
diff --git a/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+MoreInfo.swift b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+MoreInfo.swift
new file mode 100644
index 00000000..7c30cff5
--- /dev/null
+++ b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+MoreInfo.swift
@@ -0,0 +1,44 @@
+//
+// FundPercentages.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import Foundation
+
+extension FundsScreen {
+ struct MoreInfo: Decodable {
+ let month: Percentages
+ let year: Percentages
+ let twelveMonths: Percentages
+
+ enum CodingKeys: String, CodingKey, CaseIterable {
+ case month
+ case year
+ case twelveMonths = "12months"
+ }
+
+ static func title(for key: CodingKeys) -> String {
+ switch key {
+ case .month:
+ return "No mês"
+ case .year:
+ return "No Ano"
+ case .twelveMonths:
+ return "12 meses"
+ }
+ }
+
+ func value(for key: CodingKeys) -> Percentages {
+ switch key {
+ case .month:
+ return month
+ case .year:
+ return year
+ case .twelveMonths:
+ return twelveMonths
+ }
+ }
+ }
+}
diff --git a/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+Risk.swift b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+Risk.swift
new file mode 100644
index 00000000..44590f3f
--- /dev/null
+++ b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen+Risk.swift
@@ -0,0 +1,33 @@
+//
+// FundsRisk.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+
+extension FundsScreen {
+ enum Risk: Int, CaseIterable, Decodable {
+ case one = 1
+ case two = 2
+ case three = 3
+ case four = 4
+ case five = 5
+
+ var color: UIColor {
+ switch self {
+ case .one:
+ return UIColor.Santander.pastelGreen
+ case .two:
+ return UIColor.Santander.emerald
+ case .three:
+ return UIColor.Santander.lightningYellow
+ case .four:
+ return UIColor.Santander.burningOrange
+ case .five:
+ return UIColor.Santander.redOrange
+ }
+ }
+ }
+}
diff --git a/Santander/Santander/Models/Investment/FundsScreen/FundsScreen.swift b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen.swift
new file mode 100644
index 00000000..45127b6d
--- /dev/null
+++ b/Santander/Santander/Models/Investment/FundsScreen/FundsScreen.swift
@@ -0,0 +1,21 @@
+//
+// FundScreen.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import Foundation
+
+struct FundsScreen: Decodable {
+ let title: String
+ let fundName: String
+ let whatIs: String
+ let definition: String
+ let riskTitle: String
+ let risk: Risk
+ let infoTitle: String
+ let moreInfo: MoreInfo
+ let info: [Info]
+ let downInfo: [DownInfo]
+}
diff --git a/Santander/Santander/Networking/Endpoints/ContactTarget.swift b/Santander/Santander/Networking/Endpoints/ContactTarget.swift
new file mode 100644
index 00000000..e342a0cf
--- /dev/null
+++ b/Santander/Santander/Networking/Endpoints/ContactTarget.swift
@@ -0,0 +1,49 @@
+//
+// ContactTarget.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Moya
+
+enum ContactTarget {
+ case getForm
+}
+
+extension ContactTarget: TargetType {
+
+ var baseURL: URL {
+ return try! ServerRoutes.baseRoute.asURL()
+ }
+
+ var path: String {
+ switch self {
+ case .getForm:
+ return ServerRoutes.Contact.getForm
+ }
+ }
+
+ var method: Moya.Method {
+ switch self {
+ case .getForm:
+ return .get
+ }
+ }
+
+ var task: Task {
+ switch self {
+ case .getForm:
+ return .requestPlain
+ }
+ }
+
+ var sampleData: Data {
+ return "".data(using: .utf8)!
+ }
+
+ var headers: [String : String]? {
+ return nil
+ }
+}
diff --git a/Santander/Santander/Networking/Endpoints/FundsTarget.swift b/Santander/Santander/Networking/Endpoints/FundsTarget.swift
new file mode 100644
index 00000000..8b91d21b
--- /dev/null
+++ b/Santander/Santander/Networking/Endpoints/FundsTarget.swift
@@ -0,0 +1,49 @@
+//
+// FundsTarget.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Moya
+
+enum InvestmentTarget {
+ case getFunds
+}
+
+extension InvestmentTarget: TargetType {
+
+ var baseURL: URL {
+ return try! ServerRoutes.baseRoute.asURL()
+ }
+
+ var path: String {
+ switch self {
+ case .getFunds:
+ return ServerRoutes.Investment.getFunds
+ }
+ }
+
+ var method: Moya.Method {
+ switch self {
+ case .getFunds:
+ return .get
+ }
+ }
+
+ var task: Task {
+ switch self {
+ case .getFunds:
+ return .requestPlain
+ }
+ }
+
+ var sampleData: Data {
+ return "".data(using: .utf8)!
+ }
+
+ var headers: [String : String]? {
+ return nil
+ }
+}
diff --git a/Santander/Santander/Networking/Requests/Contact/ContactFormDataRequest.swift b/Santander/Santander/Networking/Requests/Contact/ContactFormDataRequest.swift
new file mode 100644
index 00000000..601993c0
--- /dev/null
+++ b/Santander/Santander/Networking/Requests/Contact/ContactFormDataRequest.swift
@@ -0,0 +1,15 @@
+//
+// ContactFormDataRequest.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+
+struct ContactFormDataRequest {
+ var name: String?
+ var email: String?
+ var phone: String?
+}
diff --git a/Santander/Santander/Networking/Routes/ServerRoutes.swift b/Santander/Santander/Networking/Routes/ServerRoutes.swift
new file mode 100644
index 00000000..8668580a
--- /dev/null
+++ b/Santander/Santander/Networking/Routes/ServerRoutes.swift
@@ -0,0 +1,24 @@
+//
+// ServerRoutes.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+
+struct ServerRoutes {
+
+ static let baseRoute = "https://floating-mountain-50292.herokuapp.com/"
+
+ // Contact
+ struct Contact {
+ static let getForm = "cells.json"
+ }
+
+ // Investment
+ struct Investment {
+ static let getFunds = "fund.json"
+ }
+}
diff --git a/Santander/Santander/Networking/Services/Contact/ContactAPI.swift b/Santander/Santander/Networking/Services/Contact/ContactAPI.swift
new file mode 100644
index 00000000..bf780d87
--- /dev/null
+++ b/Santander/Santander/Networking/Services/Contact/ContactAPI.swift
@@ -0,0 +1,28 @@
+//
+// ContactAPI.swift
+// Santander
+//
+// Created by Orlando Amorim on 12/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+import Moya
+
+class ContactAPI: ContactStoreProtocol {
+
+ private let provider = MoyaProvider()
+ var cancelable: Cancellable?
+
+ func getForm(result: @escaping (Result) -> Void) {
+ cancelable?.cancel()
+ cancelable = provider.request(.getForm, decodeType: ContactForm.self) { responseResult in
+ switch responseResult {
+ case .success(let contactForm):
+ result(.success(contactForm))
+ case .failure(let error):
+ result(.failure(error))
+ }
+ }
+ }
+}
diff --git a/Santander/Santander/Networking/Services/Investment/InvestmentAPI.swift b/Santander/Santander/Networking/Services/Investment/InvestmentAPI.swift
new file mode 100644
index 00000000..2e5bcce0
--- /dev/null
+++ b/Santander/Santander/Networking/Services/Investment/InvestmentAPI.swift
@@ -0,0 +1,27 @@
+//
+// InvestmentAPI.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Moya
+
+class InvestmentAPI: InvestmentStoreProtocol {
+
+ private let provider = MoyaProvider()
+ var cancelable: Cancellable?
+
+ func getFunds(result: @escaping (Result) -> Void) {
+ cancelable?.cancel()
+ cancelable = provider.request(.getFunds, decodeType: Investment.Funds.Response.self) { responseResult in
+ switch responseResult {
+ case .success(let funds):
+ result(.success(funds))
+ case .failure(let error):
+ result(.failure(error))
+ }
+ }
+ }
+}
diff --git a/Santander/Santander/Scenes/Contact/ContactInteractor.swift b/Santander/Santander/Scenes/Contact/ContactInteractor.swift
new file mode 100644
index 00000000..6fb3d9ae
--- /dev/null
+++ b/Santander/Santander/Scenes/Contact/ContactInteractor.swift
@@ -0,0 +1,53 @@
+//
+// ContactInteractor.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright (c) 2019 Santander. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+protocol ContactBusinessLogic {
+ func getForm()
+ func sendForm(data: Contact.Form.Request)
+}
+
+protocol ContactDataStore {
+
+}
+
+class ContactInteractor: ContactBusinessLogic, ContactDataStore {
+
+ var presenter: ContactPresentationLogic?
+ var worker: ContactWorker = ContactWorker(contactStore: ContactAPI())
+
+ // MARK: Get Form
+ func getForm() {
+ worker.getForm { [weak self] result in
+ guard let self = self, let presenter = self.presenter else {
+ return
+ }
+ switch result {
+ case .success(let contactForm):
+ presenter.presentForm(contactForm)
+ case .failure(let error):
+ presenter.presentError(error)
+ }
+ }
+ }
+
+ // MARK: Send Form
+ func sendForm(data: Contact.Form.Request) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
+ guard let self = self, let presenter = self.presenter else {
+ return
+ }
+ presenter.presentSuccess()
+ }
+ }
+}
diff --git a/Santander/Santander/Scenes/Contact/ContactModels.swift b/Santander/Santander/Scenes/Contact/ContactModels.swift
new file mode 100644
index 00000000..b984f735
--- /dev/null
+++ b/Santander/Santander/Scenes/Contact/ContactModels.swift
@@ -0,0 +1,28 @@
+//
+// ContactModels.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright (c) 2019 Santander. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+enum Contact {
+ // MARK: Use cases
+ enum Form {
+ struct Request {
+ var sendFormData: ContactFormDataRequest
+ }
+ struct Response {
+ var result: Result
+ }
+ struct ViewModel {
+
+ }
+ }
+}
diff --git a/Santander/Santander/Scenes/Contact/ContactPresenter.swift b/Santander/Santander/Scenes/Contact/ContactPresenter.swift
new file mode 100644
index 00000000..d7b3a8ba
--- /dev/null
+++ b/Santander/Santander/Scenes/Contact/ContactPresenter.swift
@@ -0,0 +1,39 @@
+//
+// ContactPresenter.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright (c) 2019 Santander. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+protocol ContactPresentationLogic {
+ func presentForm(_ form: ContactForm)
+ func presentError(_ error: Error)
+ func presentSuccess()
+}
+
+class ContactPresenter: ContactPresentationLogic {
+
+ weak var viewController: ContactDisplayLogic?
+
+ // MARK: Present Form
+ func presentForm(_ form: ContactForm) {
+ viewController?.displayForm(form)
+ }
+
+ // MARK: Present Error
+ func presentError(_ error: Error) {
+ viewController?.displayError(error.localizedDescription)
+ }
+
+ // MARK: Present Success
+ func presentSuccess() {
+ viewController?.displaySuccess()
+ }
+}
diff --git a/Santander/Santander/Scenes/Contact/ContactRouter.swift b/Santander/Santander/Scenes/Contact/ContactRouter.swift
new file mode 100644
index 00000000..3b304d77
--- /dev/null
+++ b/Santander/Santander/Scenes/Contact/ContactRouter.swift
@@ -0,0 +1,41 @@
+//
+// ContactRouter.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright (c) 2019 Santander. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+@objc protocol ContactRoutingLogic {
+ //func routeToSomewhere(segue: UIStoryboardSegue?)
+}
+
+protocol ContactDataPassing {
+ var dataStore: ContactDataStore? { get }
+}
+
+class ContactRouter: NSObject, ContactRoutingLogic, ContactDataPassing {
+ weak var viewController: ContactViewController?
+ var dataStore: ContactDataStore?
+
+ // MARK: Routing
+
+ // MARK: Navigation
+//
+// func navigateToSomewhere(source: ContactViewController, destination: SomewhereViewController) {
+// source.show(destination, sender: nil)
+// }
+//
+ // MARK: Passing data
+
+ //func passDataToSomewhere(source: ContactDataStore, destination: inout SomewhereDataStore)
+ //{
+ // destination.name = source.name
+ //}
+}
diff --git a/Santander/Santander/Scenes/Contact/ContactViewController.swift b/Santander/Santander/Scenes/Contact/ContactViewController.swift
new file mode 100644
index 00000000..9ba5852a
--- /dev/null
+++ b/Santander/Santander/Scenes/Contact/ContactViewController.swift
@@ -0,0 +1,338 @@
+//
+// ContactViewController.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright (c) 2019 Santander. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+import Eureka
+import JGProgressHUD
+
+protocol ContactDisplayLogic: class {
+ func displayForm(_ form: ContactForm)
+ func displayError(_ error: String)
+ func displaySuccess()
+}
+
+class ContactViewController: SantanderBaseFormViewController {
+
+ var interactor: ContactBusinessLogic?
+ var router: (NSObjectProtocol & ContactRoutingLogic & ContactDataPassing)?
+ var sendFormRequest = ContactFormDataRequest(name: nil, email: nil, phone: nil)
+
+ private let progressHud: JGProgressHUD = {
+ let progressHud = JGProgressHUD(style: .light)
+ progressHud.textLabel.text = "Carregando..."
+ return progressHud
+ }()
+
+ private var sendButton: SantanderButton = {
+ var button = SantanderButton()
+ button.setTitle("Enviar", for: .normal)
+ return button
+ }()
+
+ private var successView: SuccessView = {
+ var view = SuccessView()
+ view.alpha = 0.0
+ view.isUserInteractionEnabled = true
+ return view
+ }()
+
+ private var isResetingForm: Bool = false
+
+ // MARK: Object lifecycle
+ override init() {
+ super.init()
+ setup()
+ }
+
+ // MARK: Setup
+ private func setup() {
+ let viewController = self
+ let interactor = ContactInteractor()
+ let presenter = ContactPresenter()
+ let router = ContactRouter()
+ viewController.interactor = interactor
+ viewController.router = router
+ interactor.presenter = presenter
+ presenter.viewController = viewController
+ router.viewController = viewController
+ router.dataStore = interactor
+ }
+
+ // MARK: View lifecycle
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupView()
+ getForm()
+ }
+
+ // MARK: Setup View
+ private func setupView() {
+ tableView.keyboardDismissMode = .interactive
+ addSuccssView()
+ setupSuccessView()
+ }
+
+ private func addSuccssView() {
+ view.addSubview(successView)
+ successView.snp.makeConstraints { make in
+ make.edges.equalToSuperview()
+ }
+ }
+
+ private func setupSuccessView() {
+ successView.onTap { [weak self] in
+ guard let self = self else {
+ return
+ }
+ self.dismissSuccessView()
+ }
+ }
+
+ func getForm() {
+ progressHud.show(in: view)
+ interactor?.getForm()
+ }
+
+ func sendForm() {
+ guard form.validate(includeHidden: false, includeDisabled: false).isEmpty else {
+ return
+ }
+ tableView.endEditing(true)
+ progressHud.show(in: view)
+ let requestData = Contact.Form.Request(sendFormData: sendFormRequest)
+ interactor?.sendForm(data: requestData)
+ }
+}
+
+extension ContactViewController: ContactDisplayLogic {
+
+ func displayForm(_ form: ContactForm) {
+ progressHud.dismiss()
+ self.form.removeAll()
+ let rows = form.cells.compactMap({ makeRow(with: $0) })
+ makeSection(rows: rows)
+ }
+
+ func displayError(_ error: String) {
+ progressHud.dismiss()
+ showAlert(title: "Atenção", message: error)
+ }
+
+ func displaySuccess() {
+ isResetingForm = true
+ form.rows.forEach({ $0.baseValue = nil })
+ form.rows.forEach({ $0.reload() })
+ form.cleanValidationErrors()
+ isResetingForm = false
+
+ progressHud.dismiss()
+ presentSuccessView()
+ }
+}
+
+// MARK: Factory
+extension ContactViewController {
+
+ private func makeRow(with cell: FormCell) -> BaseRow? {
+ switch cell.type {
+ case .field:
+ if let fieldType = cell.fieldType {
+ switch fieldType {
+ case .text:
+ return makeNameRow(tag: cell.id, title: cell.message, isHidden: cell.isHidden, isRequired: cell.isRequired, topSpacing: cell.topSpacing)
+ case .phone:
+ return makePhoneRow(tag: cell.id, title: cell.message, isHidden: cell.isHidden, isRequired: cell.isRequired, topSpacing: cell.topSpacing)
+ case .email:
+ return makeEmailRow(tag: cell.id, title: cell.message, isHidden: cell.isHidden, isRequired: cell.isRequired, topSpacing: cell.topSpacing)
+ }
+ }
+ case .text:
+ return nil
+ case .image:
+ return nil
+ case .checkbox:
+ return makeCheckboxRow(tag: cell.id, title: cell.message, isHidden: cell.isHidden, isRequired: cell.isRequired, topSpacing: cell.topSpacing, fieldToPresent: cell.fieldToPresent)
+ case .send:
+ return makeSendButtonRow(tag: cell.id, title: cell.message, isHidden: cell.isHidden, isRequired: cell.isRequired, topSpacing: cell.topSpacing)
+ }
+ return nil
+ }
+
+ private func makeNameRow(tag: Int, title: String, isHidden: Bool, isRequired: Bool, topSpacing: CGFloat) -> BaseRow {
+ let row = TextFloatLabelRow(tag: String(tag))
+ row.title = title
+ row.hidden = Condition(booleanLiteral: isHidden)
+ row.cell.topSpacing = topSpacing
+ if isRequired {
+ row.add(rule: RuleRequired())
+ }
+ row.validationOptions = .validatesAlways
+
+ row.onRowValidationChanged { [weak self] cell, row in
+ guard let self = self, !self.isResetingForm else {
+ return
+ }
+ cell.floatLabelTextField.borderColor = row.isValid ? UIColor.Santander.sushi : UIColor.Santander.torchRed
+ }
+
+ row.onChange { [weak self] row in
+ guard let self = self else {
+ return
+ }
+ self.sendFormRequest.name = row.value
+
+ if row.value == nil {
+ row.cell.floatLabelTextField.borderColor = UIColor.Santander.gallery
+ }
+ }
+ return row
+ }
+
+ private func makeEmailRow(tag: Int, title: String, isHidden: Bool, isRequired: Bool, topSpacing: CGFloat) -> BaseRow {
+ let row = EmailFloatLabelRow(tag: String(tag))
+ row.title = title
+ row.hidden = Condition(booleanLiteral: isHidden)
+ row.cell.topSpacing = topSpacing
+ if isRequired {
+ row.add(rule: RuleRequired())
+ }
+ row.add(rule: RuleEmail())
+ row.validationOptions = .validatesAlways
+
+ row.onRowValidationChanged { [weak self] cell, row in
+ guard let self = self, !self.isResetingForm else {
+ return
+ }
+ cell.floatLabelTextField.borderColor = row.isValid ? UIColor.Santander.sushi : UIColor.Santander.torchRed
+ }
+
+
+ row.onChange { [weak self] row in
+ guard let self = self else {
+ return
+ }
+ self.sendFormRequest.email = row.value
+
+ if row.value == nil {
+ row.cell.floatLabelTextField.borderColor = UIColor.Santander.gallery
+ }
+ }
+ return row
+ }
+
+ private func makePhoneRow(tag: Int, title: String, isHidden: Bool, isRequired: Bool, topSpacing: CGFloat) -> BaseRow {
+ let row = PhoneFloatLabelRow(tag: String(tag))
+ row.title = title
+ row.hidden = Condition(booleanLiteral: isHidden)
+ row.cell.topSpacing = topSpacing
+ if isRequired {
+ row.add(rule: RuleRequired())
+ }
+ row.add(rule: RulePhoneNumber())
+ row.validationOptions = .validatesAlways
+
+ row.onRowValidationChanged { [weak self] cell, row in
+ guard let self = self, !self.isResetingForm else {
+ return
+ }
+ cell.floatLabelTextField.borderColor = row.isValid ? UIColor.Santander.sushi : UIColor.Santander.torchRed
+ }
+
+ row.onChange { [weak self] row in
+ guard let self = self else {
+ return
+ }
+ self.sendFormRequest.phone = row.value
+
+ if row.value == nil {
+ row.cell.floatLabelTextField.borderColor = UIColor.Santander.gallery
+ }
+ }
+ return row
+ }
+
+ private func makeCheckboxRow(tag: Int, title: String, isHidden: Bool, isRequired: Bool, topSpacing: CGFloat, fieldToPresent: Int? = nil) -> BaseRow {
+ let row = ViewRow(tag: String(tag))
+ var state: CheckmarkButton.State = .unselected
+ if let fieldToPresent = fieldToPresent, let row = self.form.rowBy(tag: String(fieldToPresent)) {
+ state = row.isHidden ? .unselected : .selected
+ }
+
+ let checkmarkButton = CheckmarkButton(text: title, state: state, frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 21.0))
+ checkmarkButton.onTap { [weak self] state in
+ guard let fieldToPresent = fieldToPresent, let self = self, let row = self.form.rowBy(tag: String(fieldToPresent)) else {
+ return
+ }
+ let isHidden = state == .unselected
+ let condition = Condition(booleanLiteral: isHidden)
+ row.hidden = condition
+ row.validationOptions = .validatesOnDemand
+ row.baseValue = nil
+ row.evaluateHidden()
+ row.validationOptions = .validatesAlways
+ }
+
+ row.cellSetup { cell, row in
+ cell.view = checkmarkButton
+
+ cell.viewTopMargin = topSpacing
+ cell.viewLeftMargin = 40.0
+ cell.viewRightMargin = 40.0
+ cell.viewBottomMargin = 0.0
+ }
+
+ return row
+ }
+
+ private func makeSendButtonRow(tag: Int, title: String, isHidden: Bool, isRequired: Bool, topSpacing: CGFloat) -> BaseRow {
+ let row = ViewRow(tag: String(tag))
+ row.value = "Enviar"
+
+ let sendButton = SantanderButton(title: "Enviar", frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 50.0))
+
+ row.cellSetup { cell, row in
+ cell.view = sendButton
+ cell.viewTopMargin = topSpacing
+ cell.viewLeftMargin = 30.0
+ cell.viewRightMargin = 30.0
+ cell.viewBottomMargin = 0.0
+ }
+
+ sendButton.onTap { [weak self] button in
+ guard let self = self else {
+ return
+ }
+ row.section?.form?.validate()
+ self.sendForm()
+ }
+
+ return row
+ }
+}
+
+extension ContactViewController {
+ private func presentSuccessView() {
+ UIView.animate(withDuration: 0.2, animations: { [weak self] in
+ guard let self = self else { return }
+ self.tableView.alpha = 0.0
+ self.successView.alpha = 1.0
+ })
+ }
+
+ private func dismissSuccessView() {
+ UIView.animate(withDuration: 0.2, animations: { [weak self] in
+ guard let self = self else { return }
+ self.tableView.alpha = 1.0
+ self.successView.alpha = 0.0
+ })
+ }
+}
diff --git a/Santander/Santander/Scenes/Contact/ContactWorker.swift b/Santander/Santander/Scenes/Contact/ContactWorker.swift
new file mode 100644
index 00000000..bb1ff688
--- /dev/null
+++ b/Santander/Santander/Scenes/Contact/ContactWorker.swift
@@ -0,0 +1,32 @@
+//
+// ContactWorker.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright (c) 2019 Santander. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+import Moya
+
+protocol ContactStoreProtocol {
+ func getForm(result: @escaping (Result) -> Void)
+}
+
+class ContactWorker {
+
+ private let provider = MoyaProvider()
+ var contactStore: ContactStoreProtocol
+
+ init(contactStore: ContactStoreProtocol) {
+ self.contactStore = contactStore
+ }
+
+ func getForm(result: @escaping (Result) -> Void) {
+ contactStore.getForm(result: result)
+ }
+}
diff --git a/Santander/Santander/Scenes/Container/ContainerViewController.swift b/Santander/Santander/Scenes/Container/ContainerViewController.swift
new file mode 100644
index 00000000..87edee16
--- /dev/null
+++ b/Santander/Santander/Scenes/Container/ContainerViewController.swift
@@ -0,0 +1,139 @@
+//
+// ContainerViewController.swift
+// Santander
+//
+// Created by Orlando Amorim on 10/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+import SnapKit
+
+class ContainerViewController: UIViewController {
+
+ //MARK: - Views
+ private var containedSegmentedView: SegmentedControl = {
+ var segmentedControl = SegmentedControl()
+ segmentedControl.set(buttons: [("Investimento", true), ("Contato", false)])
+ return segmentedControl
+ }()
+
+ private lazy var investmentViewController: InvestmentViewController = {
+ let viewController = InvestmentViewController()
+ return viewController
+ }()
+
+ private lazy var contactViewController: ContactViewController = {
+ let viewController = ContactViewController()
+ return viewController
+ }()
+
+ private lazy var shareButton: UIBarButtonItem = {
+ let button = UIBarButtonItem()
+ button.image = UIImage(named: "share-icon")
+ return button
+ }()
+
+ enum ContainerType: Int {
+ case funds = 0
+ case contact = 1
+
+ var title: String {
+ switch self {
+ case .funds:
+ return "Investimento"
+ case .contact:
+ return "Contato"
+ }
+ }
+ }
+
+ //MARK: - Vars
+ private var selectedType: ContainerType = .funds
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupView()
+ setupSelectionHandler()
+ select(type: .funds)
+ }
+
+ private func setupView() {
+ view.backgroundColor = .white
+ addContainedSegmentedView()
+ setupNavigationController()
+ }
+
+ private func setupNavigationController() {
+ guard let navigationController = navigationController else {
+ return
+ }
+ navigationController.navigationBar.titleTextAttributes =
+ [.foregroundColor: UIColor.Santander.mineShaft,
+ .font: UIFont.santander(type: .medium, with: 16.0)]
+ if #available(iOS 11.0, *) {
+ navigationController.navigationBar.prefersLargeTitles = false
+ }
+ navigationController.navigationBar.backgroundColor = .black
+ navigationController.navigationBar.shadowImage = UIImage()
+ navigationController.navigationBar.isTranslucent = false
+ navigationController.view.backgroundColor = .white
+ navigationController.navigationBar.tintColor = UIColor.Santander.monza
+ }
+
+ private func addContainedSegmentedView() {
+ view.addSubview(containedSegmentedView)
+ containedSegmentedView.snp.makeConstraints { make in
+ make.height.equalTo(57.0)
+ make.leading.trailing.equalToSuperview()
+ if #available(iOS 11.0, *) {
+ make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom)
+ } else {
+ make.bottom.equalToSuperview()
+ }
+ }
+ }
+
+ private func setupSelectionHandler() {
+ containedSegmentedView.onTap { [weak self] index in
+ guard let self = self, let type = ContainerType(rawValue: index) else {
+ return
+ }
+ self.select(type: type)
+ }
+ }
+
+ private func select(type: ContainerType) {
+ let viewControllers = [investmentViewController, contactViewController]
+ removeChildVc(viewControllers[type.rawValue])
+ addChildVc(viewControllers[type.rawValue])
+ title = type.title
+ selectedType = type
+ managerNavigationButton(type: type)
+ }
+
+ private func addChildVc(_ child: UIViewController) {
+ addChild(child)
+ view.insertSubview(child.view, belowSubview: containedSegmentedView)
+ child.view.snp.makeConstraints { (make) in
+ make.top.leading.trailing.equalToSuperview()
+ make.bottom.equalTo(containedSegmentedView.snp.top)
+ }
+ child.didMove(toParent: self)
+ }
+
+ private func removeChildVc(_ child: UIViewController) {
+ child.willMove(toParent: self)
+ child.removeFromParent()
+ child.view.removeFromSuperview()
+ }
+
+ private func managerNavigationButton(type: ContainerType) {
+ switch type {
+ case .funds:
+ navigationItem.setRightBarButtonItems([shareButton], animated: false)
+ case .contact:
+ navigationItem.setRightBarButtonItems([], animated: false)
+ }
+ }
+}
diff --git a/Santander/Santander/Scenes/Investment/InvestmentInteractor.swift b/Santander/Santander/Scenes/Investment/InvestmentInteractor.swift
new file mode 100644
index 00000000..18414cea
--- /dev/null
+++ b/Santander/Santander/Scenes/Investment/InvestmentInteractor.swift
@@ -0,0 +1,45 @@
+//
+// InvestmentInteractor.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+// Copyright (c) 2019 ___ORGANIZATIONNAME___. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+protocol InvestmentBusinessLogic {
+ func getFunds()
+}
+
+protocol InvestmentDataStore {
+
+ //var name: String { get set }
+}
+
+class InvestmentInteractor: InvestmentBusinessLogic, InvestmentDataStore {
+
+ var presenter: InvestmentPresentationLogic?
+ var worker: InvestmentWorker = InvestmentWorker(investmentStore: InvestmentAPI())
+
+ // MARK: Do something
+
+ // MARK: - Get Funds
+ func getFunds() {
+ worker.getFunds { [weak self] result in
+ guard let self = self, let presenter = self.presenter else {
+ return
+ }
+ switch result {
+ case .success(let response):
+ presenter.presentScreen(response: response)
+ case .failure(let error):
+ presenter.presentError(error)
+ }
+ }
+ }
+}
diff --git a/Santander/Santander/Scenes/Investment/InvestmentModels.swift b/Santander/Santander/Scenes/Investment/InvestmentModels.swift
new file mode 100644
index 00000000..8ae5bfa7
--- /dev/null
+++ b/Santander/Santander/Scenes/Investment/InvestmentModels.swift
@@ -0,0 +1,31 @@
+//
+// InvestmentModels.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+// Copyright (c) 2019 ___ORGANIZATIONNAME___. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+enum Investment {
+
+ // MARK: Use cases
+ enum Funds {
+ struct Request {
+
+ }
+
+ struct Response: Decodable {
+ let screen: FundsScreen
+ }
+
+ struct ViewModel {
+ let screen: FundsScreen
+ }
+ }
+}
diff --git a/Santander/Santander/Scenes/Investment/InvestmentPresenter.swift b/Santander/Santander/Scenes/Investment/InvestmentPresenter.swift
new file mode 100644
index 00000000..8ebdd119
--- /dev/null
+++ b/Santander/Santander/Scenes/Investment/InvestmentPresenter.swift
@@ -0,0 +1,34 @@
+//
+// InvestmentPresenter.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+// Copyright (c) 2019 ___ORGANIZATIONNAME___. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+protocol InvestmentPresentationLogic {
+ func presentScreen(response: Investment.Funds.Response)
+ func presentError(_ error: Error)
+}
+
+class InvestmentPresenter: InvestmentPresentationLogic {
+
+ weak var viewController: InvestmentDisplayLogic?
+
+ // MARK: Present Screen
+ func presentScreen(response: Investment.Funds.Response) {
+ let viewModel = Investment.Funds.ViewModel(screen: response.screen)
+ viewController?.displayScreen(viewModel: viewModel)
+ }
+
+ // MARK: Present Error
+ func presentError(_ error: Error) {
+ viewController?.displayError(error.localizedDescription)
+ }
+}
diff --git a/Santander/Santander/Scenes/Investment/InvestmentRouter.swift b/Santander/Santander/Scenes/Investment/InvestmentRouter.swift
new file mode 100644
index 00000000..4fb2ded4
--- /dev/null
+++ b/Santander/Santander/Scenes/Investment/InvestmentRouter.swift
@@ -0,0 +1,58 @@
+//
+// InvestmentRouter.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+// Copyright (c) 2019 ___ORGANIZATIONNAME___. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+
+@objc protocol InvestmentRoutingLogic {
+ //func routeToSomewhere(segue: UIStoryboardSegue?)
+}
+
+protocol InvestmentDataPassing {
+ var dataStore: InvestmentDataStore? { get }
+}
+
+class InvestmentRouter: NSObject, InvestmentRoutingLogic, InvestmentDataPassing {
+
+ weak var viewController: InvestmentViewController?
+ var dataStore: InvestmentDataStore?
+
+ // MARK: Routing
+
+ //func routeToSomewhere(segue: UIStoryboardSegue?)
+ //{
+ // if let segue = segue {
+ // let destinationVC = segue.destination as! SomewhereViewController
+ // var destinationDS = destinationVC.router!.dataStore!
+ // passDataToSomewhere(source: dataStore!, destination: &destinationDS)
+ // } else {
+ // let storyboard = UIStoryboard(name: "Main", bundle: nil)
+ // let destinationVC = storyboard.instantiateViewController(withIdentifier: "SomewhereViewController") as! SomewhereViewController
+ // var destinationDS = destinationVC.router!.dataStore!
+ // passDataToSomewhere(source: dataStore!, destination: &destinationDS)
+ // navigateToSomewhere(source: viewController!, destination: destinationVC)
+ // }
+ //}
+
+ // MARK: Navigation
+
+ //func navigateToSomewhere(source: InvestmentViewController, destination: SomewhereViewController)
+ //{
+ // source.show(destination, sender: nil)
+ //}
+
+ // MARK: Passing data
+
+ //func passDataToSomewhere(source: InvestmentDataStore, destination: inout SomewhereDataStore)
+ //{
+ // destination.name = source.name
+ //}
+}
diff --git a/Santander/Santander/Scenes/Investment/InvestmentViewController.swift b/Santander/Santander/Scenes/Investment/InvestmentViewController.swift
new file mode 100644
index 00000000..d402c9b1
--- /dev/null
+++ b/Santander/Santander/Scenes/Investment/InvestmentViewController.swift
@@ -0,0 +1,263 @@
+//
+// InvestmentViewController.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+// Copyright (c) 2019 ___ORGANIZATIONNAME___. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+import Eureka
+import JGProgressHUD
+import SafariServices
+
+protocol InvestmentDisplayLogic: class {
+ func displayScreen(viewModel: Investment.Funds.ViewModel)
+ func displayError(_ error: String)
+}
+
+class InvestmentViewController: SantanderBaseFormViewController, InvestmentDisplayLogic {
+
+ private let progressHud: JGProgressHUD = {
+ let progressHud = JGProgressHUD(style: .light)
+ progressHud.textLabel.text = "Carregando..."
+ return progressHud
+ }()
+
+ var interactor: InvestmentBusinessLogic?
+ var router: (NSObjectProtocol & InvestmentRoutingLogic & InvestmentDataPassing)?
+
+ // MARK: Object lifecycle
+ override init() {
+ super.init()
+ setup()
+ }
+
+ // MARK: Setup
+ private func setup() {
+ let viewController = self
+ let interactor = InvestmentInteractor()
+ let presenter = InvestmentPresenter()
+ let router = InvestmentRouter()
+ viewController.interactor = interactor
+ viewController.router = router
+ interactor.presenter = presenter
+ presenter.viewController = viewController
+ router.viewController = viewController
+ router.dataStore = interactor
+ }
+
+ // MARK: View lifecycle
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ getFunds()
+ }
+
+ func getFunds() {
+ progressHud.show(in: view)
+ interactor?.getFunds()
+ }
+
+ func displayScreen(viewModel: Investment.Funds.ViewModel) {
+ progressHud.dismiss()
+ form.removeAll()
+ var rows: [BaseRow] = []
+ rows.append(makeFundNameRow(title: viewModel.screen.title, name: viewModel.screen.fundName))
+ rows.append(makeSeparatorArrowLineRow())
+ rows.append(makeWhatIsRow(title: viewModel.screen.whatIs, detail: viewModel.screen.definition))
+ rows.append(makeRiskViewRow(title: viewModel.screen.riskTitle, risk: viewModel.screen.risk))
+ rows.append(makeMoreInfoTitleRow(title: viewModel.screen.infoTitle))
+ rows.append(makeMoreInfoPercentagesViewRow(moreInfo: viewModel.screen.moreInfo))
+ rows.append(makeSeparatorLineRow())
+
+ viewModel.screen.info.forEach { info in
+ rows.append(makeMoreInfoRow(title: info.name, value: info.data))
+ }
+ viewModel.screen.downInfo.forEach { downInfo in
+ rows.append(makeMoreInfoDownloadRow(title: downInfo.name))
+ }
+ rows.append(makeInvestButtonRow())
+ // Make the section
+ makeSection(rows: rows)
+ }
+
+ func displayError(_ error: String) {
+ progressHud.dismiss()
+ showAlert(title: "Atenção", message: error)
+ }
+
+}
+
+// MARK: Factory
+extension InvestmentViewController {
+
+ private func makeFundNameRow(title: String, name: String) -> BaseRow {
+ let labelRow = TitleSubtitleRow()
+ labelRow.title = title
+ labelRow.value = name
+
+ labelRow.cellUpdate { cell, row in
+ cell.titleLabel.font = UIFont.santander(type: .medium, with: 14.0)
+ cell.titleLabel.textAlignment = .center
+ cell.titleLabel.textColor = UIColor.Santander.gray
+ cell.titleLabel.numberOfLines = 0
+
+ cell.subtitleLabel.font = UIFont.santander(type: .medium, with: 28.0)
+ cell.subtitleLabel.textAlignment = .center
+ cell.subtitleLabel.textColor = UIColor.Santander.mineShaft
+ cell.subtitleLabel.numberOfLines = 0
+ }
+ return labelRow
+ }
+
+ private func makeSeparatorArrowLineRow() -> BaseRow {
+ let row = ViewRow()
+ let separatorLineImageView = UIImageView(image: UIImage(named: "separator-line-arrow-icon"))
+
+ row.cellSetup { cell, row in
+ cell.view = separatorLineImageView
+ cell.viewTopMargin = 21.0
+ cell.viewLeftMargin = 30.0
+ cell.viewRightMargin = 30.0
+ cell.viewBottomMargin = 14.0
+ }
+ return row
+ }
+
+ private func makeWhatIsRow(title: String, detail: String) -> BaseRow {
+ let labelRow = TitleSubtitleRow()
+ labelRow.title = title
+ labelRow.value = detail
+
+ labelRow.cellUpdate { cell, row in
+ cell.titleLabel.font = UIFont.santander(type: .medium, with: 16.0)
+ cell.titleLabel.textAlignment = .center
+ cell.titleLabel.textColor = UIColor.Santander.gray
+ cell.titleLabel.numberOfLines = 0
+
+ cell.subtitleLabel.font = UIFont.santander(type: .light, with: 16.0)
+ cell.subtitleLabel.textAlignment = .center
+ cell.subtitleLabel.textColor = UIColor.Santander.gray
+ cell.subtitleLabel.numberOfLines = 0
+ }
+ return labelRow
+ }
+
+ private func makeRiskViewRow(title: String, risk: FundsScreen.Risk) -> BaseRow {
+ let row = ViewRow()
+ let fundRiskView = FundRiskView(text: title, risk: risk, frame: CGRect(x: 0, y: 0, width: view.frame.width - 38.0 - 38.0, height: 77.0))
+
+ row.cellSetup { cell, row in
+ cell.view = fundRiskView
+ cell.viewTopMargin = 35.0
+ cell.viewLeftMargin = 38.0
+ cell.viewRightMargin = 38.0
+ cell.viewBottomMargin = 46.0
+ }
+ return row
+ }
+
+ private func makeMoreInfoTitleRow(title: String) -> BaseRow {
+ let labelRow = LabelRow()
+ labelRow.title = title
+
+ labelRow.cellSetup { (cell, row) in
+ guard let textLabel = cell.textLabel else {
+ return
+ }
+ textLabel.snp.makeConstraints({ make in
+ make.top.bottom.equalToSuperview()
+ make.leading.trailing.equalToSuperview().inset(34.0)
+ })
+ }
+
+ labelRow.cellUpdate { cell, row in
+ guard let textLabel = cell.textLabel else {
+ return
+ }
+ textLabel.font = UIFont.santander(type: .medium, with: 16.0)
+ textLabel.textAlignment = .center
+ textLabel.textColor = UIColor.Santander.gray
+ textLabel.numberOfLines = 0
+ }
+ return labelRow
+ }
+
+ private func makeMoreInfoPercentagesViewRow(moreInfo: FundsScreen.MoreInfo) -> BaseRow {
+ let row = ViewRow()
+ let moreInfoView = MoreInfoPercentagesView(moreInfo: moreInfo, frame: CGRect(x: 0, y: 0, width: view.frame.width - 30.0 - 30.0, height: 130.0))
+
+ row.cellSetup { cell, row in
+ cell.view = moreInfoView
+ cell.viewTopMargin = 19.0
+ cell.viewLeftMargin = 30.0
+ cell.viewRightMargin = 30.0
+ cell.viewBottomMargin = 0.0
+ }
+ return row
+ }
+
+ private func makeSeparatorLineRow() -> BaseRow {
+ let row = ViewRow()
+ let separatorLineView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width - 30.0 - 30.0, height: 1.0))
+ separatorLineView.backgroundColor = UIColor.Santander.cloudy.withAlphaComponent(0.2)
+
+ row.cellSetup { cell, row in
+ cell.view = separatorLineView
+ cell.viewTopMargin = 21.0
+ cell.viewLeftMargin = 30.0
+ cell.viewRightMargin = 30.0
+ cell.viewBottomMargin = 17.0
+ }
+ return row
+ }
+
+ private func makeMoreInfoRow(title: String, value: String) -> BaseRow {
+ let row = MoreInfoRow()
+ row.title = title
+ row.value = value
+ return row
+ }
+
+ private func makeMoreInfoDownloadRow(title: String) -> BaseRow {
+ let row = DownloadInfoRow()
+ row.title = title
+
+ row.cell.downloadButton.onTap { [weak self] in
+ guard let self = self, let url = URL(string: "https://www.google.com") else {
+ return
+ }
+ let safariViewController = SFSafariViewController(url: url)
+ // safariVC.delegate = self
+ self.present(safariViewController, animated: true, completion: nil)
+ }
+
+ row.onDonwloadButtonTap { [weak self] in
+ guard let self = self, let url = URL(string: "https://www.google.com") else {
+ return
+ }
+ let safariViewController = SFSafariViewController(url: url)
+ self.present(safariViewController, animated: true, completion: nil)
+ }
+
+ return row
+ }
+
+ private func makeInvestButtonRow() -> BaseRow {
+ let row = ViewRow()
+ let sendButton = SantanderButton(title: "Investir", frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 50.0))
+
+ row.cellSetup { cell, row in
+ cell.view = sendButton
+ cell.viewTopMargin = 45.0
+ cell.viewLeftMargin = 30.0
+ cell.viewRightMargin = 30.0
+ cell.viewBottomMargin = 40.0
+ }
+ return row
+ }
+}
diff --git a/Santander/Santander/Scenes/Investment/InvestmentWorker.swift b/Santander/Santander/Scenes/Investment/InvestmentWorker.swift
new file mode 100644
index 00000000..bb694458
--- /dev/null
+++ b/Santander/Santander/Scenes/Investment/InvestmentWorker.swift
@@ -0,0 +1,33 @@
+//
+// InvestmentWorker.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+// Copyright (c) 2019 ___ORGANIZATIONNAME___. All rights reserved.
+//
+// This file was generated by the Clean Swift Xcode Templates so
+// you can apply clean architecture to your iOS and Mac projects,
+// see http://clean-swift.com
+//
+
+import UIKit
+import Moya
+
+protocol InvestmentStoreProtocol {
+ func getFunds(result: @escaping (Result) -> Void)
+}
+
+class InvestmentWorker {
+
+ private let provider = MoyaProvider()
+
+ var investmentStore: InvestmentStoreProtocol
+
+ init(investmentStore: InvestmentStoreProtocol) {
+ self.investmentStore = investmentStore
+ }
+
+ func getFunds(result: @escaping (Result) -> Void) {
+ investmentStore.getFunds(result: result)
+ }
+}
diff --git a/Santander/Santander/Scenes/SantanderBaseFormViewController.swift b/Santander/Santander/Scenes/SantanderBaseFormViewController.swift
new file mode 100644
index 00000000..775a9ec3
--- /dev/null
+++ b/Santander/Santander/Scenes/SantanderBaseFormViewController.swift
@@ -0,0 +1,65 @@
+//
+// SantanderBaseFormViewController.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Foundation
+import Eureka
+
+class SantanderBaseFormViewController: FormViewController {
+
+ init() {
+ super.init(style: .grouped)
+ }
+
+ @available(*, unavailable)
+ override init(style: UITableView.Style) {
+ super.init(style: style)
+ }
+
+ @available(*, unavailable)
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+ super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupView()
+ }
+
+ private func setupView() {
+ view.backgroundColor = .white
+ tableView.backgroundColor = .white
+ tableView.separatorStyle = .none
+ }
+
+ // MARK: - Fix animations
+ override func insertAnimation(forRows rows: [BaseRow]) -> UITableView.RowAnimation { return .fade }
+ override func deleteAnimation(forRows rows: [BaseRow]) -> UITableView.RowAnimation { return .fade }
+ override func reloadAnimation(oldRows: [BaseRow], newRows: [BaseRow]) -> UITableView.RowAnimation { return .fade }
+ override func insertAnimation(forSections sections: [Section]) -> UITableView.RowAnimation { return .fade }
+ override func deleteAnimation(forSections sections: [Section]) -> UITableView.RowAnimation { return .fade }
+ override func reloadAnimation(oldSections: [Section], newSections: [Section]) -> UITableView.RowAnimation { return .fade }
+}
+
+extension SantanderBaseFormViewController {
+
+ @discardableResult
+ func makeSection(with tag: String? = nil, header: SectionHeaderFooterRenderable? = nil, footer: SectionHeaderFooterRenderable? = nil, rows: [BaseRow]) -> Section {
+ let section = Section()
+ section.tag = tag
+ section.header = header?.viewForItem()
+ section.footer = footer?.viewForItem()
+ section.append(contentsOf: rows)
+ form.append(section)
+ return section
+ }
+}
diff --git a/Santander/Santander/Views/Buttons/CheckmarkButton.swift b/Santander/Santander/Views/Buttons/CheckmarkButton.swift
new file mode 100644
index 00000000..cf82b55b
--- /dev/null
+++ b/Santander/Santander/Views/Buttons/CheckmarkButton.swift
@@ -0,0 +1,129 @@
+//
+// CheckmarkButton.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+import Eureka
+
+class CheckmarkButton: UIView {
+ // MARK: - Dependencies
+ enum State {
+ case selected
+ case unselected
+
+ var image: UIImage {
+ switch self {
+ case .selected:
+ return UIImage(named: "checkmark-selected-icon")!
+ case .unselected:
+ return UIImage(named: "checkmark-unselected-icon")!
+ }
+ }
+ }
+
+ private(set) var state: State
+
+ // MARK: - Views
+ private var checkmarkImage: UIImageView = {
+ var imageView = UIImageView()
+ return imageView
+ }()
+
+ private var textLabel: UILabel = {
+ var label = UILabel()
+ label.font = UIFont.santander(type: .regular, with: 16.0)
+ label.textColor = UIColor.Santander.silverChalice
+ return label
+ }()
+
+ // MARK: - Vars
+ typealias ButtonBlock = (State) -> Void
+ private var block: ButtonBlock?
+
+ init(text: String, state: State = .unselected, frame: CGRect = .zero) {
+ self.state = state
+ super.init(frame: frame)
+ textLabel.text = text
+ setupView()
+ setupTapGesture()
+ update(to: state)
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ addCheckmarkButton()
+ addTextLabel()
+ }
+
+ private func addCheckmarkButton() {
+ addSubview(checkmarkImage)
+ checkmarkImage.snp.makeConstraints { make in
+ make.centerY.equalToSuperview()
+ make.leading.equalToSuperview()
+ make.height.width.equalTo(19.0)
+ }
+ }
+
+ private func addTextLabel() {
+ addSubview(textLabel)
+ textLabel.snp.makeConstraints { [weak self] make in
+ guard let self = self else {
+ return
+ }
+ make.centerY.equalTo(self.checkmarkImage.snp.centerY)
+ make.leading.equalTo(self.checkmarkImage.snp.trailing).inset(-9.0)
+ make.trailing.equalToSuperview()
+ }
+ }
+
+ // MARK: - Actions
+
+ private func setupTapGesture() {
+ let tap = UITapGestureRecognizer(target: self, action: #selector(self.onTapAction))
+ addGestureRecognizer(tap)
+ }
+
+ func onTap(_ block: @escaping ButtonBlock) {
+ self.block = block
+ }
+
+ @objc private func onTapAction() {
+ let newState = state == State.unselected ? State.selected : State.unselected
+ update(to: newState)
+ block?(newState)
+ }
+
+ private func update(to state: State) {
+ self.state = state
+ checkmarkImage.image = state.image
+ }
+}
+
+extension CheckmarkButton: SectionHeaderFooterRenderable {
+ public func viewForItem() -> HeaderFooterViewRepresentable {
+ self.translatesAutoresizingMaskIntoConstraints = false
+ var footerView = HeaderFooterView(.class)
+ footerView.onSetupView = { [weak self] view, _ in
+ guard let self = self else { return }
+ view.addSubview(self)
+ NSLayoutConstraint.activate([
+ self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
+ self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
+ self.topAnchor.constraint(equalTo: view.topAnchor, constant: 47.0),
+ self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
+ ])
+ }
+ footerView.height = {
+ 47.0 + 21.0
+ }
+ return footerView
+ }
+}
diff --git a/Santander/Santander/Views/Buttons/DownloadButton.swift b/Santander/Santander/Views/Buttons/DownloadButton.swift
new file mode 100644
index 00000000..b6d8ca8d
--- /dev/null
+++ b/Santander/Santander/Views/Buttons/DownloadButton.swift
@@ -0,0 +1,68 @@
+//
+// DownloadButton.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Eureka
+
+class DownloadButton: UIButton {
+
+ // MARK: - Vars
+ typealias ButtonBlock = () -> Void
+ private var block: ButtonBlock? {
+ didSet {
+ self.addTarget(self, action: #selector(onTapAction), for: .touchUpInside)
+ }
+ }
+
+ override init(frame: CGRect = .zero) {
+ super.init(frame: frame)
+ setupView()
+ setupTapGesture()
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ setupDownloadImageButton()
+ setupTitleLabel()
+ setupEdges()
+ }
+
+ private func setupDownloadImageButton() {
+ setImage(UIImage(named: "download-icon"), for: .normal)
+ imageView?.contentMode = .scaleAspectFit
+ }
+
+ private func setupTitleLabel() {
+ setTitle("Baixar", for: .normal)
+ setTitleColor(UIColor.Santander.monza, for: .normal)
+ titleLabel?.font = UIFont.santander(type: .regular, with: 14.0)
+ }
+
+ private func setupEdges() {
+ let insetAmount: CGFloat = 8.0 / 2
+ imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
+ titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
+ }
+
+ // MARK: - Actions
+ private func setupTapGesture() {
+ let tap = UITapGestureRecognizer(target: self, action: #selector(self.onTapAction))
+ addGestureRecognizer(tap)
+ }
+
+ func onTap(_ block: @escaping ButtonBlock) {
+ self.block = block
+ }
+
+ @objc private func onTapAction() {
+ block?()
+ }
+}
diff --git a/Santander/Santander/Views/Buttons/SantanderButton.swift b/Santander/Santander/Views/Buttons/SantanderButton.swift
new file mode 100644
index 00000000..f294f580
--- /dev/null
+++ b/Santander/Santander/Views/Buttons/SantanderButton.swift
@@ -0,0 +1,89 @@
+//
+// SantanderButton.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+import Eureka
+
+class SantanderButton: UIButton {
+
+ typealias ButtonBlock = (SantanderButton) -> Void
+
+ private var block: ButtonBlock? {
+ didSet {
+ self.addTarget(self, action: #selector(onTapAction(sender:)), for: .touchUpInside)
+ }
+ }
+
+ var defaultBackgroundColor: UIColor = UIColor.Santander.monza
+ var selectedBackgroundColor: UIColor = UIColor.Santander.apricot
+
+ init(title: String? = nil, frame: CGRect = .zero) {
+ super.init(frame: frame)
+ setTitle(title, for: .normal)
+ setupStyle()
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupStyle() {
+ layer.cornerRadius = 25.0
+ setTitleColor(UIColor.white, for: .normal)
+ backgroundColor = defaultBackgroundColor
+ guard let titleLabel = titleLabel else { return }
+ titleLabel.font = UIFont.santander(type: .medium, with: 16.0)
+ titleLabel.textAlignment = .center
+ }
+
+ // MARK: - Highlighted
+ override var isHighlighted: Bool {
+ didSet {
+ invalidateHighlightedAppearance()
+ }
+ }
+
+ private func invalidateHighlightedAppearance() {
+ UIView.animate(withDuration: 0.25, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: { [weak self] in
+ guard let self = self else { return }
+ self.backgroundColor = self.isHighlighted ? self.selectedBackgroundColor : self.defaultBackgroundColor
+ self.transform = self.isHighlighted ? CGAffineTransform(scaleX: 0.9, y: 0.9) : .identity
+ })
+ }
+
+ // MARK: - Actions
+ func onTap(_ block: @escaping ButtonBlock) {
+ self.block = block
+ }
+
+ @objc func onTapAction(sender: SantanderButton) {
+ self.block?(sender)
+ }
+}
+
+extension SantanderButton: SectionHeaderFooterRenderable {
+ public func viewForItem() -> HeaderFooterViewRepresentable {
+ self.translatesAutoresizingMaskIntoConstraints = false
+ var footerView = HeaderFooterView(.class)
+ footerView.onSetupView = { [weak self] view, _ in
+ guard let self = self else { return }
+ view.addSubview(self)
+ NSLayoutConstraint.activate([
+ self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30.0),
+ self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30.0),
+ self.topAnchor.constraint(equalTo: view.topAnchor, constant: 45.0),
+ self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40.0)
+ ])
+ }
+ footerView.height = {
+ 45.0 + 50.0 + 40.0
+ }
+ return footerView
+ }
+}
diff --git a/Santander/Santander/Views/Custom/FloatLabelTextField.swift b/Santander/Santander/Views/Custom/FloatLabelTextField.swift
new file mode 100644
index 00000000..e5f99c9b
--- /dev/null
+++ b/Santander/Santander/Views/Custom/FloatLabelTextField.swift
@@ -0,0 +1,218 @@
+//
+// FloatLabelTextField.swift
+// FloatLabelFields
+//
+// Created by Fahim Farook on 28/11/14.
+// Copyright (c) 2014 RookSoft Ltd. All rights reserved.
+//
+// Original Concept by Matt D. Smith
+// http://dribbble.com/shots/1254439--GIF-Mobile-Form-Interaction?list=users
+//
+// Objective-C version by Jared Verdi
+// https://github.com/jverdi/JVFloatLabeledTextField
+//
+
+import UIKit
+
+@IBDesignable public class FloatLabelTextField: UITextField {
+
+ let animationDuration = 0.3
+ var title = UILabel()
+ let border = CALayer()
+
+ // MARK: - Properties
+ override public var accessibilityLabel: String! {
+ get {
+ if text?.isEmpty ?? true {
+ return title.text
+ } else {
+ return text
+ }
+ }
+ set {
+ self.accessibilityLabel = newValue
+ }
+ }
+
+ override public var placeholder: String? {
+ didSet {
+ title.text = placeholder
+ title.sizeToFit()
+ }
+ }
+
+ override public var attributedPlaceholder: NSAttributedString? {
+ didSet {
+ title.text = attributedPlaceholder?.string
+ title.sizeToFit()
+ }
+ }
+
+ var titleFont: UIFont = .systemFont(ofSize: 12.0) {
+ didSet {
+ title.font = titleFont
+ title.sizeToFit()
+ }
+ }
+
+ @IBInspectable var hintYPadding: CGFloat = 0.0
+
+ @IBInspectable var titleYPadding: CGFloat = 0.0 {
+ didSet {
+ var r = title.frame
+ r.origin.y = titleYPadding
+ title.frame = r
+ }
+ }
+
+ @IBInspectable var titleTextColour: UIColor = .gray {
+ didSet {
+ if !isFirstResponder {
+ title.textColor = titleTextColour
+ }
+ }
+ }
+
+ @IBInspectable var titleActiveTextColour: UIColor! {
+ didSet {
+ if isFirstResponder {
+ title.textColor = titleActiveTextColour
+ }
+ }
+ }
+
+ @IBInspectable var borderColor: UIColor? {
+ didSet {
+ border.borderColor = borderColor?.cgColor
+ }
+ }
+
+ @IBInspectable var borderWidth: CGFloat = 0.5 {
+ didSet {
+ border.borderWidth = borderWidth
+ }
+ }
+
+ // MARK: - Init
+ required public init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setup()
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setup()
+ }
+
+ // MARK: - Overrides
+ override public func layoutSubviews() {
+ super.layoutSubviews()
+ setTitlePositionForTextAlignment()
+ let isResp = isFirstResponder
+ if isResp && !(text?.isEmpty ?? true) {
+ title.textColor = titleActiveTextColour
+ } else {
+ title.textColor = titleTextColour
+ }
+ // Should we show or hide the title label?
+ if text?.isEmpty ?? true {
+ // Hide
+ hideTitle(isResp)
+ } else {
+ // Show
+ showTitle(isResp)
+ }
+ border.frame = CGRect(x: 0, y: self.frame.size.height - borderWidth, width: self.frame.size.width, height: self.frame.size.height)
+ }
+
+ override public func textRect(forBounds bounds: CGRect) -> CGRect {
+ var r = super.textRect(forBounds: bounds)
+ if !(text?.isEmpty ?? true) {
+ var top = ceil(title.font.lineHeight + hintYPadding)
+ top = min(top, maxTopInset())
+ r = r.inset(by: UIEdgeInsets(top: top, left: 0.0, bottom: 0.0, right: 0.0))
+ }
+ return r.integral
+ }
+
+ override public func editingRect(forBounds bounds: CGRect) -> CGRect {
+ var r = super.editingRect(forBounds: bounds)
+ if !(text?.isEmpty ?? true) {
+ var top = ceil(title.font.lineHeight + hintYPadding)
+ top = min(top, maxTopInset())
+ r = r.inset(by: UIEdgeInsets(top: top, left: 0.0, bottom: 0.0, right: 0.0))
+ }
+ return r.integral
+ }
+
+ override public func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
+ var r = super.clearButtonRect(forBounds: bounds)
+ if !(text?.isEmpty ?? true) {
+ var top = ceil(title.font.lineHeight + hintYPadding)
+ top = min(top, maxTopInset())
+ r = CGRect(x: r.origin.x, y: r.origin.y + (top * 0.5), width: r.size.width, height: r.size.height)
+ }
+ return r.integral
+ }
+
+ // MARK: - Private Methods
+ private func setup() {
+ borderStyle = .none
+ titleActiveTextColour = tintColor
+ // Set up title label
+ title.alpha = 0.0
+ title.font = titleFont
+ title.textColor = titleTextColour
+ if let str = placeholder, !str.isEmpty {
+ title.text = str
+ title.sizeToFit()
+ }
+ if let str = attributedPlaceholder, !str.string.isEmpty {
+ title.attributedText = str
+ title.sizeToFit()
+ }
+ self.addSubview(title)
+
+ border.borderColor = borderColor?.cgColor
+ border.borderWidth = borderWidth
+ self.layer.addSublayer(border)
+ self.layer.masksToBounds = true
+ }
+
+ private func maxTopInset() -> CGFloat {
+ return max(0, floor(bounds.size.height - (font?.lineHeight ?? 0) - 4.0))
+ }
+
+ private func setTitlePositionForTextAlignment() {
+ let r = textRect(forBounds: bounds)
+ var x = r.origin.x
+ if textAlignment == .center {
+ x = r.origin.x + (r.size.width * 0.5) - title.frame.size.width
+ } else if textAlignment == .right {
+ x = r.origin.x + r.size.width - title.frame.size.width
+ }
+ title.frame = CGRect(x: x, y: title.frame.origin.y, width: title.frame.size.width, height: title.frame.size.height)
+ }
+
+ private func showTitle(_ animated: Bool) {
+ let dur = animated ? animationDuration : 0
+ UIView.animate(withDuration: dur, delay: 0, options: [.beginFromCurrentState, .curveEaseOut], animations: {
+ // Animation
+ self.title.alpha = 1.0
+ var r = self.title.frame
+ r.origin.y = self.titleYPadding
+ self.title.frame = r
+ })
+ }
+
+ private func hideTitle(_ animated: Bool) {
+ let dur = animated ? animationDuration : 0
+ UIView.animate(withDuration: dur, delay: 0, options: [.beginFromCurrentState, .curveEaseIn], animations: {
+ // Animation
+ self.title.alpha = 0.0
+ var r = self.title.frame
+ r.origin.y = self.title.font.lineHeight + self.hintYPadding
+ self.title.frame = r
+ })
+ }
+}
diff --git a/Santander/Santander/Views/Custom/FundRiskView.swift b/Santander/Santander/Views/Custom/FundRiskView.swift
new file mode 100644
index 00000000..cb2ef123
--- /dev/null
+++ b/Santander/Santander/Views/Custom/FundRiskView.swift
@@ -0,0 +1,144 @@
+//
+// FundRiskView.swift
+// Santander
+//
+// Created by Orlando Amorim on 14/08/19.
+//
+
+import UIKit
+import SnapKit
+
+class FundRiskView: UIView {
+
+ static let arrowWidth: CGFloat = 13.0
+
+ let risk: FundsScreen.Risk
+
+ var textLabel: UILabel = {
+ let label = UILabel()
+ label.font = UIFont.santander(type: .medium, with: 16.0)
+ label.textColor = UIColor.Santander.gray
+ label.textAlignment = .center
+ return label
+ }()
+
+ var arrowImageView: UIImageView = {
+ let imageView = UIImageView(image: UIImage(named: "arrow-icon"))
+ imageView.contentMode = .scaleAspectFill
+ return imageView
+ }()
+
+ var stackView: UIStackView = {
+ let stackView = UIStackView()
+ stackView.axis = .horizontal
+ stackView.alignment = .bottom
+ stackView.distribution = .fillEqually
+ stackView.spacing = 1
+ return stackView
+ }()
+
+ var arrowLeadingConstraint: Constraint?
+
+ init(text: String, risk: FundsScreen.Risk, frame: CGRect = .zero) {
+ self.risk = risk
+ super.init(frame: frame)
+ textLabel.text = text
+ setupView()
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ setupArrow()
+ }
+
+ private func setupView() {
+ addTextLabel()
+ addStackView()
+ addArrowImageView()
+ setupArrow()
+ setupRiskLevel()
+ }
+
+ private func addTextLabel() {
+ addSubview(textLabel)
+ textLabel.snp.makeConstraints { make in
+ make.top.leading.trailing.equalToSuperview()
+ make.height.equalTo(34.0)
+ }
+ }
+
+ private func addArrowImageView() {
+ addSubview(arrowImageView)
+ arrowImageView.snp.makeConstraints { make in
+ make.top.equalTo(textLabel.snp.bottom).offset(15.5)
+ make.height.equalTo(8.0)
+ make.width.equalTo(FundRiskView.arrowWidth)
+ arrowLeadingConstraint = make.leading.equalToSuperview().inset(0.0).constraint
+ make.bottom.equalTo(stackView.snp.top).offset(-6.5)
+ }
+ }
+
+ private func addStackView() {
+ addSubview(stackView)
+ stackView.snp.makeConstraints { make in
+ make.height.equalTo(10.0)
+ make.bottom.leading.trailing.equalToSuperview()
+ }
+ }
+
+ private func setupRiskLevel() {
+ stackView.removeAllArrangedSubviews()
+ FundsScreen.Risk.allCases.forEach { risk in
+ let riskView = makeRiskView(for: risk)
+ stackView.addArrangedSubview(riskView)
+ riskView.snp.makeConstraints { make in
+ if self.risk == risk {
+ make.top.equalToSuperview()
+ make.bottom.equalTo(self.snp.bottom)
+ } else {
+ make.bottom.equalTo(self.snp.bottom).inset(1.0)
+ make.height.equalTo(6.0)
+ }
+ }
+ }
+ }
+
+ private func setupArrow() {
+ let arrowMiddleWidth = FundRiskView.arrowWidth / 2
+ let riskLevelWidth = frame.size.width / CGFloat(FundsScreen.Risk.allCases.count)
+ let middleRiskLevelWidth = riskLevelWidth / CGFloat(2)
+ let arrowPosition = ((riskLevelWidth * CGFloat(risk.rawValue)) - middleRiskLevelWidth) - arrowMiddleWidth
+ arrowLeadingConstraint?.update(inset: arrowPosition)
+ }
+}
+
+// MARK: - Factory
+extension FundRiskView {
+ private func makeRiskView(for risk: FundsScreen.Risk) -> UIView {
+ let view = UIView()
+ view.tag = risk.rawValue
+ view.backgroundColor = risk.color
+
+ let radius: CGFloat = risk == self.risk ? 5.0 : 3.0
+ if risk == .one {
+ if #available(iOS 11.0, *) {
+ view.round(corners: [.layerMinXMinYCorner, .layerMinXMaxYCorner], radius: radius)
+ } else {
+ view.round(corners: [.bottomLeft, .topLeft], radius: radius)
+ }
+ }
+ if risk == .five {
+ if #available(iOS 11.0, *) {
+ view.round(corners: [.layerMaxXMaxYCorner, .layerMaxXMinYCorner], radius: radius)
+ } else {
+ view.round(corners: [.topRight, .bottomRight], radius: radius)
+ }
+ }
+ return view
+ }
+}
diff --git a/Santander/Santander/Views/Custom/MoreInfoPercentagesView.swift b/Santander/Santander/Views/Custom/MoreInfoPercentagesView.swift
new file mode 100644
index 00000000..97382eee
--- /dev/null
+++ b/Santander/Santander/Views/Custom/MoreInfoPercentagesView.swift
@@ -0,0 +1,133 @@
+//
+// MoreInfoPercentagesView.swift
+// Santander
+//
+// Created by Orlando Amorim on 14/08/19.
+//
+
+import UIKit
+import SnapKit
+
+class MoreInfoPercentagesView: UIView {
+
+ let moreInfo: FundsScreen.MoreInfo
+
+ init(moreInfo: FundsScreen.MoreInfo, frame: CGRect = .zero) {
+ self.moreInfo = moreInfo
+ super.init(frame: frame)
+ setupView()
+ makeView()
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ isUserInteractionEnabled = true
+ backgroundColor = .white
+ }
+}
+
+// MARK: - Factory
+extension MoreInfoPercentagesView {
+
+ private func makeView() {
+ let header = makeHeader()
+ addSubview(header)
+ header.snp.makeConstraints { make in
+ make.top.leading.trailing.equalToSuperview()
+ make.height.equalTo(32.0)
+ }
+
+ var lastPercentagesView: UIView?
+ let allCases = FundsScreen.MoreInfo.CodingKeys.allCases
+ allCases.forEach { key in
+ let percentagesView = makePercentagesView(with: key, percentages: moreInfo.value(for: key))
+ addSubview(percentagesView)
+ percentagesView.snp.makeConstraints { make in
+ if let lastPercentagesView = lastPercentagesView {
+ make.top.equalTo(lastPercentagesView.snp.bottom)
+ } else {
+ make.top.equalTo(header.snp.bottom).inset(-2.0)
+ }
+ make.leading.trailing.equalToSuperview()
+ make.height.equalTo(32.0)
+ if let last = allCases.last, last == key {
+ make.bottom.equalToSuperview()
+ }
+ }
+ lastPercentagesView = percentagesView
+ }
+ }
+
+ private func makeHeader() -> UIView {
+ let headerView = UIView()
+
+ let cdiTitleLabel = makeTitleLabel(with: "CDI", textAlignment: .right)
+ headerView.addSubview(cdiTitleLabel)
+ cdiTitleLabel.snp.makeConstraints { make in
+ make.trailing.equalToSuperview()
+ make.centerY.equalToSuperview()
+ make.height.equalTo(32.0)
+ }
+
+ let fundTitleLabel = makeTitleLabel(with: "Fundo", textAlignment: .right)
+ headerView.addSubview(fundTitleLabel)
+ fundTitleLabel.snp.makeConstraints { make in
+ make.trailing.equalToSuperview().inset(102.0)
+ make.centerY.equalToSuperview()
+ make.height.equalTo(32.0)
+ }
+ return headerView
+ }
+
+ private func makePercentagesView(with key: FundsScreen.MoreInfo.CodingKeys, percentages: FundsScreen.MoreInfo.Percentages) -> UIView {
+ let percentageView = UIView()
+
+ let titleLabel = makeTitleLabel(with: FundsScreen.MoreInfo.title(for: key), textAlignment: .left)
+ percentageView.addSubview(titleLabel)
+ titleLabel.snp.makeConstraints { make in
+ make.leading.equalToSuperview()
+ make.centerY.equalToSuperview()
+ make.height.equalTo(32.0)
+ }
+
+ let cdiValueLabel = makeValueLabel(with: "\(percentages.cdi)%", textAlignment: .right)
+ percentageView.addSubview(cdiValueLabel)
+ cdiValueLabel.snp.makeConstraints { make in
+ make.trailing.equalToSuperview()
+ make.centerY.equalToSuperview()
+ make.height.equalTo(32.0)
+ }
+
+ let fundValueLabel = makeValueLabel(with: "\(percentages.fund)%", textAlignment: .right)
+ percentageView.addSubview(fundValueLabel)
+ fundValueLabel.snp.makeConstraints { make in
+ make.trailing.equalToSuperview().inset(102.0)
+ make.centerY.equalToSuperview()
+ make.height.equalTo(32.0)
+ }
+
+ return percentageView
+ }
+
+ private func makeTitleLabel(with text: String, textAlignment: NSTextAlignment = .left) -> UILabel {
+ let label = UILabel()
+ label.text = text
+ label.font = UIFont.santander(type: .regular, with: 14.0)
+ label.textColor = UIColor.Santander.silverChalice
+ label.textAlignment = textAlignment
+ return label
+ }
+
+ private func makeValueLabel(with text: String, textAlignment: NSTextAlignment = .right) -> UILabel {
+ let label = UILabel()
+ label.text = text
+ label.font = UIFont.santander(type: .regular, with: 14.0)
+ label.textColor = UIColor.Santander.mineShaft
+ label.textAlignment = textAlignment
+ return label
+ }
+}
diff --git a/Santander/Santander/Views/Custom/SegmentedControl.swift b/Santander/Santander/Views/Custom/SegmentedControl.swift
new file mode 100644
index 00000000..76da09c0
--- /dev/null
+++ b/Santander/Santander/Views/Custom/SegmentedControl.swift
@@ -0,0 +1,142 @@
+//
+// SegmentedControl.swift
+// Santander
+//
+// Created by Orlando Amorim on 10/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+import SnapKit
+
+class SegmentedControl: UIView {
+
+ typealias Button = (title: String, isSelected: Bool)
+ typealias ButtonBlock = (_ selectedIndex: Int) -> Void
+ private var block: ButtonBlock?
+
+ private var buttons: [UIButton] = []
+ private var selectorView: UIView!
+ private var selectedIndex: Int = 0
+
+ var textColor: UIColor = .white
+ var selectedColor: UIColor = UIColor.Santander.guardsmanRed
+ var unselectedColor: UIColor = UIColor.Santander.monza
+
+ var stackView: UIStackView = {
+ let stackView = UIStackView()
+ stackView.axis = .horizontal
+ stackView.alignment = .fill
+ stackView.distribution = .fillEqually
+ return stackView
+ }()
+
+ init() {
+ super.init(frame: .zero)
+ setupView()
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ updateSelectorView()
+ }
+
+ private func setupView() {
+ backgroundColor = .white
+ setupStackView()
+ setupSelectorView()
+ }
+
+ func set(buttons: [Button]) {
+ updateView(with: buttons)
+ }
+
+ func onTap(_ block: @escaping ButtonBlock) {
+ self.block = block
+ }
+
+ @objc func onTapAction(sender: UIButton) {
+ guard let index = buttons.firstIndex(of: sender) else {
+ return
+ }
+ didSelected(index: index)
+ }
+
+ func didSelected(index: Int) {
+ buttons.forEach({ $0.backgroundColor = unselectedColor })
+ let selectorPosition = frame.width / CGFloat(self.buttons.count) * CGFloat(index)
+ UIView.animate(withDuration: 0.3) { [weak self] in
+ guard let self = self else {
+ return
+ }
+ self.block?(index)
+ self.selectedIndex = index
+ self.buttons[index].backgroundColor = self.selectedColor
+ self.selectorView.frame.origin.x = selectorPosition
+ }
+ }
+}
+
+// MARK: - Factory
+extension SegmentedControl {
+
+ private func updateView(with buttons: [Button]) {
+ create(with: buttons)
+ updateStackView()
+ updateSelectorView()
+ }
+
+ private func create(with buttons: [Button]) {
+ self.buttons.removeAll()
+ var selectedIndex = 0
+ buttons.enumerated().forEach { [weak self] index, button in
+ if let self = self {
+ let newButton = self.createButton(with: button)
+ selectedIndex = button.isSelected ? index : selectedIndex
+ self.buttons.append(newButton)
+ }
+ }
+ didSelected(index: selectedIndex)
+ }
+
+ private func createButton(with button: Button) -> UIButton {
+ let newButton = UIButton(type: .system)
+ newButton.setTitle(button.title, for: .normal)
+ newButton.addTarget(self, action: #selector(onTapAction(sender:)), for: .touchUpInside)
+ newButton.setTitleColor(textColor, for: .normal)
+ newButton.backgroundColor = button.isSelected ? selectedColor : unselectedColor
+ newButton.titleLabel?.font = UIFont.santander(type: .medium, with: 16.0)
+ return newButton
+ }
+
+ private func updateSelectorView() {
+ let selectorPosition = frame.width / CGFloat(self.buttons.count) * CGFloat(selectedIndex)
+ let selectorWidth = frame.width / CGFloat(self.buttons.count)
+ selectorView.frame = CGRect(x: selectorPosition, y: 0.0, width: selectorWidth.isNaN ? 0.0 : selectorWidth, height: 2)
+ }
+
+ private func setupSelectorView() {
+ let selectorWidth = frame.width / CGFloat(self.buttons.count)
+ selectorView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: selectorWidth.isNaN ? 0.0 : selectorWidth, height: 2))
+ selectorView.backgroundColor = unselectedColor
+ addSubview(selectorView)
+ }
+
+ private func updateStackView() {
+ stackView.removeAllArrangedSubviews()
+ buttons.forEach({ stackView.addArrangedSubview($0) })
+ }
+
+ private func setupStackView() {
+ addSubview(stackView)
+ stackView.snp.makeConstraints { make in
+ make.top.equalToSuperview().inset(2.0)
+ make.leading.trailing.bottom.equalToSuperview()
+ }
+ }
+}
diff --git a/Santander/Santander/Views/Custom/SuccessView.swift b/Santander/Santander/Views/Custom/SuccessView.swift
new file mode 100644
index 00000000..56d93072
--- /dev/null
+++ b/Santander/Santander/Views/Custom/SuccessView.swift
@@ -0,0 +1,88 @@
+//
+// SuccessView.swift
+// Santander
+//
+// Created by Orlando Amorim on 13/08/19.
+//
+
+import UIKit
+import SnapKit
+
+class SuccessView: UIView {
+
+ typealias ButtonBlock = () -> Void
+ private var block: ButtonBlock? {
+ didSet {
+ sendNewMessage.addTarget(self, action: #selector(onTapAction), for: .touchUpInside)
+ }
+ }
+
+ private var infoLabel: UILabel = {
+ let label = UILabel()
+ label.numberOfLines = 0
+ label.textAlignment = .center
+ return label
+ }()
+
+ private var sendNewMessage: UIButton = {
+ let button = UIButton()
+ button.setTitle("Enviar nova mensagem", for: .normal)
+ button.setTitleColor(UIColor.Santander.monza, for: .normal)
+ button.titleLabel?.font = UIFont.santander(type: .medium, with: 16.0)
+ return button
+ }()
+
+ init() {
+ super.init(frame: .zero)
+ setupView()
+ setupInfoLabel()
+ }
+
+ @available(*, unavailable)
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ isUserInteractionEnabled = true
+ addInfoLabel()
+ addSendNewButton()
+ backgroundColor = .white
+ }
+
+ private func addInfoLabel() {
+ addSubview(infoLabel)
+ infoLabel.snp.makeConstraints { make in
+ make.center.equalToSuperview()
+ }
+ }
+
+ private func addSendNewButton() {
+ addSubview(sendNewMessage)
+ sendNewMessage.snp.makeConstraints { make in
+ make.centerX.equalToSuperview()
+ make.bottom.equalToSuperview().inset(87.0)
+ }
+ }
+
+ private func setupInfoLabel() {
+ let firstAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.Santander.gray,
+ .font: UIFont.santander(type: .medium, with: 14.0)]
+ let secondAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.Santander.mineShaft,
+ .font: UIFont.santander(type: .medium, with: 28.0)]
+
+ let infoText = NSMutableAttributedString(string: "Obrigado!\n", attributes: firstAttributes)
+ let secondString = NSAttributedString(string: "Mensagem enviada\ncom sucesso :)", attributes: secondAttributes)
+ infoText.append(secondString)
+ infoLabel.attributedText = infoText
+ }
+
+ // MARK: - Actions
+ func onTap(_ block: @escaping ButtonBlock) {
+ self.block = block
+ }
+
+ @objc private func onTapAction() {
+ self.block?()
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/DownloadInfoRow/DownloadInfoCell.swift b/Santander/Santander/Views/Eureka Custom Rows/DownloadInfoRow/DownloadInfoCell.swift
new file mode 100644
index 00000000..802556eb
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/DownloadInfoRow/DownloadInfoCell.swift
@@ -0,0 +1,58 @@
+//
+// DownloadInfoCell.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Eureka
+
+public class DownloadInfoCell: Cell, CellType {
+
+ let titleLabel: UILabel = {
+ let label = UILabel()
+ label.font = UIFont.santander(type: .regular, with: 14.0)
+ label.textAlignment = .left
+ label.textColor = UIColor.Santander.silverChalice
+ return label
+ }()
+
+ var downloadButton: DownloadButton = {
+ var button = DownloadButton()
+ return button
+ }()
+
+ // MARK: - Views
+ public override func update() {
+ titleLabel.text = row.title
+ }
+
+ public override func setup() {
+ super.setup()
+ selectionStyle = .none
+ height = { 32.0 }
+ setupView()
+ }
+
+ private func setupView() {
+ addTitleLabel()
+ addDownloadButton()
+ }
+
+ private func addTitleLabel() {
+ contentView.addSubview(titleLabel)
+ titleLabel.snp.makeConstraints({ make in
+ make.centerY.equalToSuperview()
+ make.leading.equalToSuperview().inset(30.0)
+ })
+ }
+
+ private func addDownloadButton() {
+ contentView.addSubview(downloadButton)
+ downloadButton.snp.makeConstraints { make in
+ make.centerY.equalToSuperview()
+ make.trailing.equalToSuperview().inset(30.0)
+ }
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/DownloadInfoRow/DownloadInfoRow.swift b/Santander/Santander/Views/Eureka Custom Rows/DownloadInfoRow/DownloadInfoRow.swift
new file mode 100644
index 00000000..de4e05d2
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/DownloadInfoRow/DownloadInfoRow.swift
@@ -0,0 +1,22 @@
+//
+// DownloadInfoRow.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Eureka
+
+final class DownloadInfoRow: Row>, RowType {
+
+ // MARK: - Actions
+ func onDonwloadButtonTap(_ block: @escaping DownloadButton.ButtonBlock) {
+ cell.downloadButton.onTap(block)
+ }
+
+ required public init(tag: String?) {
+ super.init(tag: tag)
+ displayValueFor = nil
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/EmailFloatLabelRow.swift b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/EmailFloatLabelRow.swift
new file mode 100644
index 00000000..10078eb5
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/EmailFloatLabelRow.swift
@@ -0,0 +1,33 @@
+//
+// EmailFloatLabelRow.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Eureka
+
+public class EmailFloatLabelCell: _FloatLabelCell, CellType {
+
+ required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ }
+
+ required public init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func setup() {
+ super.setup()
+ textField?.autocorrectionType = .no
+ textField?.autocapitalizationType = .none
+ textField?.keyboardType = .emailAddress
+ }
+}
+
+public final class EmailFloatLabelRow: FloatFieldRow, RowType {
+ public required init(tag: String?) {
+ super.init(tag: tag)
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/FloatFieldRow.swift b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/FloatFieldRow.swift
new file mode 100644
index 00000000..dcd6f76f
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/FloatFieldRow.swift
@@ -0,0 +1,267 @@
+//
+// FloatFieldRow.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import UIKit
+import Eureka
+import TLCustomMask
+import SnapKit
+
+// MARK: - FloatLabelCell
+
+public class _FloatLabelCell: Cell, UITextFieldDelegate, TextFieldCell where T: Equatable, T: InputTypeInitiable {
+
+ public var textField: UITextField! { return floatLabelTextField }
+
+ public enum MaskType {
+ case phone
+ case none
+ }
+
+ private lazy var clearButton: UIButton = {
+ let button = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
+ button.addTarget(self, action: #selector(toggleClearButton), for: .touchUpInside)
+ return button
+ }()
+
+ private lazy var customMask = TLCustomMask()
+
+ public var clearImage: (on: UIImage?, off: UIImage?) {
+ didSet {
+ setClearButtonImage()
+ }
+ }
+
+ public var customMaskType: MaskType = .none {
+ didSet {
+ if customMaskType == .none {
+ customMask.formattingPattern = ""
+ }
+ }
+ }
+
+ public var topSpacing: CGFloat = 0.0 {
+ didSet {
+ if let topConstraint = topConstraint {
+ topConstraint.update(inset: topSpacing)
+ }
+ }
+ }
+ private var topConstraint: Constraint?
+
+ required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ }
+
+ required public init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ lazy public var floatLabelTextField: FloatLabelTextField = { [unowned self] in
+ let floatTextField = FloatLabelTextField()
+ floatTextField.translatesAutoresizingMaskIntoConstraints = false
+ floatTextField.titleFont = UIFont.santander(type: .regular, with: 11.0)
+ floatTextField.font = UIFont.santander(type: .medium, with: 18.0)
+ floatTextField.delegate = self
+ floatTextField.addTarget(self, action: #selector(_FloatLabelCell.textFieldDidChange(_:)), for: .editingChanged)
+ floatTextField.titleTextColour = UIColor.Santander.silverChalice
+ floatTextField.titleActiveTextColour = UIColor.Santander.silverChalice
+ floatTextField.borderWidth = 1
+ floatTextField.borderColor = UIColor.Santander.gallery
+ floatTextField.tintColor = UIColor.Santander.havelockBlue
+ return floatTextField
+ }()
+
+ open override func setup() {
+ super.setup()
+ height = { [weak self] in
+ guard let self = self else {
+ return 0.0
+ }
+ return 47.0 + self.topSpacing
+ }
+ selectionStyle = .none
+ setupClearButton()
+ setupFloatLabelTextField()
+ }
+
+ private func setupFloatLabelTextField() {
+ contentView.addSubview(floatLabelTextField)
+ floatLabelTextField.snp.makeConstraints { [weak self] make in
+ guard let self = self else {
+ return
+ }
+ self.topConstraint = make.top.equalToSuperview().inset(self.topSpacing).constraint
+ make.leading.trailing.equalToSuperview().inset(40.0)
+ make.bottom.equalToSuperview()
+ }
+ }
+
+ open override func update() {
+ super.update()
+ textLabel?.text = nil
+ detailTextLabel?.text = nil
+ floatLabelTextField.attributedPlaceholder = NSAttributedString(string: row.title ?? "", attributes: [.foregroundColor: UIColor.Santander.silverChalice,
+ .font: UIFont.santander(type: .regular, with: 16.0)])
+ floatLabelTextField.text = row.displayValueFor?(row.value)
+ floatLabelTextField.isEnabled = !row.isDisabled
+ floatLabelTextField.alpha = row.isDisabled ? 0.6 : 1
+ setClearButtonImage()
+ }
+
+ private func setupClearButton() {
+ clearImage = (on: UIImage(named: "clear-icon"), off: nil)
+ textField.clearButtonMode = .never
+ floatLabelTextField.rightViewMode = .always
+ floatLabelTextField.rightView = clearButton
+ }
+
+ @objc
+ private func toggleClearButton() {
+ textField.text = customMask.formatString(string: "")
+ row.value = nil
+ row.updateCell()
+ setClearButtonImage()
+ }
+
+ private func setClearButtonImage() {
+ let image = textField.text != nil ? (textField.text!.isEmpty ? clearImage.off : clearImage.on) : clearImage.off
+ clearButton.setImage(image, for: .normal)
+ clearButton.setImage(image, for: .highlighted)
+ }
+
+ /// Returns the value withou mask
+ func cleanText() -> String {
+ return customMask.cleanText
+ }
+
+ open override func cellCanBecomeFirstResponder() -> Bool {
+ return !row.isDisabled && floatLabelTextField.canBecomeFirstResponder
+ }
+
+ open override func cellBecomeFirstResponder(withDirection direction: Direction) -> Bool {
+ return floatLabelTextField.becomeFirstResponder()
+ }
+
+ open override func cellResignFirstResponder() -> Bool {
+ return floatLabelTextField.resignFirstResponder()
+ }
+
+ @objc public func textFieldDidChange(_ textField: UITextField) {
+ guard let textValue = textField.text else {
+ row.value = nil
+ return
+ }
+ if let fieldRow = row as? FormatterConformance, let formatter = fieldRow.formatter {
+ if fieldRow.useFormatterDuringInput {
+ let value: AutoreleasingUnsafeMutablePointer = AutoreleasingUnsafeMutablePointer.init(UnsafeMutablePointer.allocate(capacity: 1))
+ let errorDesc: AutoreleasingUnsafeMutablePointer? = nil
+ if formatter.getObjectValue(value, for: textValue, errorDescription: errorDesc) {
+ row.value = value.pointee as? T
+ if var selStartPos = textField.selectedTextRange?.start {
+ let oldVal = textField.text
+ textField.text = row.displayValueFor?(row.value)
+ if let f = formatter as? FormatterProtocol {
+ selStartPos = f.getNewPosition(forPosition: selStartPos, inTextInput: textField, oldValue: oldVal, newValue: textField.text)
+ }
+ textField.selectedTextRange = textField.textRange(from: selStartPos, to: selStartPos)
+ }
+ return
+ }
+ } else {
+ let value: AutoreleasingUnsafeMutablePointer = AutoreleasingUnsafeMutablePointer.init(UnsafeMutablePointer.allocate(capacity: 1))
+ let errorDesc: AutoreleasingUnsafeMutablePointer? = nil
+ if formatter.getObjectValue(value, for: textValue, errorDescription: errorDesc) {
+ row.value = value.pointee as? T
+ }
+ return
+ }
+ }
+ guard !textValue.isEmpty else {
+ row.value = nil
+ return
+ }
+ guard let newValue = T.init(string: textValue) else {
+ row.value = nil
+ return
+ }
+ row.value = nil
+ row.value = newValue
+ row.updateCell()
+ }
+
+ // MARK: - Helpers
+
+ private func displayValue(useFormatter: Bool) -> String? {
+ guard let v = row.value else { return nil }
+ if let formatter = (row as? FormatterConformance)?.formatter, useFormatter {
+ return textField?.isFirstResponder == true ? formatter.editingString(for: v) : formatter.string(for: v)
+ }
+ let text = String(describing: v)
+ if customMaskType != .none {
+ return customMask.formatString(string: text)
+ } else {
+ return text
+ }
+ }
+
+ // MARK: - TextFieldDelegate
+
+ public func textFieldDidBeginEditing(_ textField: UITextField) {
+ formViewController()?.beginEditing(of: self)
+ if let fieldRowConformance = row as? FormatterConformance, fieldRowConformance.formatter != nil, fieldRowConformance.useFormatterOnDidBeginEditing ?? fieldRowConformance.useFormatterDuringInput {
+ textField.text = displayValue(useFormatter: true)
+ } else {
+ textField.text = displayValue(useFormatter: false)
+ }
+ }
+
+ public func textFieldDidEndEditing(_ textField: UITextField) {
+ formViewController()?.endEditing(of: self)
+ formViewController()?.textInputDidEndEditing(textField, cell: self)
+ textFieldDidChange(textField)
+ textField.text = displayValue(useFormatter: (row as? FormatterConformance)?.formatter != nil)
+ }
+
+ public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ setClearButtonImage()
+ switch customMaskType {
+ case .phone:
+ return managePhoneMask(textField, shouldChangeCharactersIn: range, replacementString: string)
+ default:
+ return true
+ }
+ }
+}
+
+extension _FloatLabelCell {
+ private func managePhoneMask(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ guard let text = textField.text as NSString? else { return true }
+ var newText = text.replacingCharacters(in: range, with: string)
+ if newText.count >= 15 {
+ customMask.formattingPattern = "($$) $$$$$-$$$$"
+ } else {
+ customMask.formattingPattern = "($$) $$$$-$$$$"
+ }
+ newText = customMask.formatString(string: newText)
+ guard let newValue = T.init(string: newText) else {
+ row.value = nil
+ return false
+ }
+ row.value = newValue
+ textField.text = newText
+ setClearButtonImage()
+ return false
+ }
+}
+
+// MARK: - FloatLabelRow
+open class FloatFieldRow: FormatteableRow where Cell: BaseCell, Cell: TextFieldCell {
+ public required init(tag: String?) {
+ super.init(tag: tag)
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/PhoneFloatLabelRow.swift b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/PhoneFloatLabelRow.swift
new file mode 100644
index 00000000..7cf91105
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/PhoneFloatLabelRow.swift
@@ -0,0 +1,32 @@
+//
+// PhoneFloatLabelRow.swift
+// Santander
+//
+// Created by Orlando Amorim on 13/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Eureka
+
+public class PhoneFloatLabelCell : _FloatLabelCell, CellType {
+
+ required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ }
+
+ required public init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func setup() {
+ super.setup()
+ customMaskType = .phone
+ textField?.keyboardType = .phonePad
+ }
+}
+
+public final class PhoneFloatLabelRow: FloatFieldRow, RowType {
+ public required init(tag: String?) {
+ super.init(tag: tag)
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/TextFloatLabelCell.swift b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/TextFloatLabelCell.swift
new file mode 100644
index 00000000..61e42b60
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/FloatLabelRow/TextFloatLabelCell.swift
@@ -0,0 +1,33 @@
+//
+// TextFloatLabelCell.swift
+// Santander
+//
+// Created by Orlando Amorim on 11/08/19.
+// Copyright © 2019 Santander. All rights reserved.
+//
+
+import Eureka
+
+public class TextFloatLabelCell: _FloatLabelCell, CellType {
+
+ required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ }
+
+ required public init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func setup() {
+ super.setup()
+ textField?.autocorrectionType = .default
+ textField?.autocapitalizationType = .sentences
+ textField?.keyboardType = .default
+ }
+}
+
+public final class TextFloatLabelRow: FloatFieldRow, RowType {
+ public required init(tag: String?) {
+ super.init(tag: tag)
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/MoreInfoRow/MoreInfoCell.swift b/Santander/Santander/Views/Eureka Custom Rows/MoreInfoRow/MoreInfoCell.swift
new file mode 100644
index 00000000..d53fb1da
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/MoreInfoRow/MoreInfoCell.swift
@@ -0,0 +1,62 @@
+//
+// MoreInfoCell.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Eureka
+
+public class MoreInfoCell: Cell, CellType {
+
+ let titleLabel: UILabel = {
+ let label = UILabel()
+ label.font = UIFont.santander(type: .regular, with: 14.0)
+ label.textAlignment = .left
+ label.textColor = UIColor.Santander.silverChalice
+ return label
+ }()
+
+ let detailLabel: UILabel = {
+ let label = UILabel()
+ label.font = UIFont.santander(type: .regular, with: 14.0)
+ label.textAlignment = .right
+ label.textColor = UIColor.Santander.mineShaft
+ return label
+ }()
+
+ // MARK: - Views
+ public override func update() {
+ titleLabel.text = row.title
+ detailLabel.text = row.value
+ }
+
+ public override func setup() {
+ super.setup()
+ selectionStyle = .none
+ height = { 32.0 }
+ setupView()
+ }
+
+ private func setupView() {
+ addTitleLabel()
+ addDetailLabel()
+ }
+
+ private func addTitleLabel() {
+ contentView.addSubview(titleLabel)
+ titleLabel.snp.makeConstraints({ make in
+ make.centerY.equalToSuperview()
+ make.leading.equalToSuperview().inset(30.0)
+ })
+ }
+
+ private func addDetailLabel() {
+ contentView.addSubview(detailLabel)
+ detailLabel.snp.makeConstraints({ make in
+ make.centerY.equalToSuperview()
+ make.trailing.equalToSuperview().inset(30.0)
+ })
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/MoreInfoRow/MoreInfoRow.swift b/Santander/Santander/Views/Eureka Custom Rows/MoreInfoRow/MoreInfoRow.swift
new file mode 100644
index 00000000..7e0b4355
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/MoreInfoRow/MoreInfoRow.swift
@@ -0,0 +1,16 @@
+//
+// MoreInfoRow.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Eureka
+
+final class MoreInfoRow: Row, RowType {
+ required public init(tag: String?) {
+ super.init(tag: tag)
+ displayValueFor = nil
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/TitleValueRow/TitleSubtitleCell.swift b/Santander/Santander/Views/Eureka Custom Rows/TitleValueRow/TitleSubtitleCell.swift
new file mode 100644
index 00000000..0f89987f
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/TitleValueRow/TitleSubtitleCell.swift
@@ -0,0 +1,60 @@
+//
+// TitleSubtitleCell.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Eureka
+
+public class TitleSubtitleCell: Cell, CellType {
+
+ let titleLabel: UILabel = {
+ let label = UILabel()
+ label.textAlignment = .center
+ return label
+ }()
+
+ let subtitleLabel: UILabel = {
+ let label = UILabel()
+ label.textAlignment = .center
+ return label
+ }()
+
+ // MARK: - Views
+ public override func update() {
+ titleLabel.text = row.title
+ subtitleLabel.text = row.value
+ }
+
+ public override func setup() {
+ super.setup()
+ selectionStyle = .none
+ setupView()
+ }
+
+ private func setupView() {
+ addTitleLabel()
+ addSubtitleLabel()
+ }
+
+ private func addTitleLabel() {
+ contentView.addSubview(titleLabel)
+ titleLabel.snp.makeConstraints({ make in
+ make.top.centerX.equalToSuperview()
+ make.leading.trailing.equalToSuperview().inset(30.0)
+ })
+ }
+
+ private func addSubtitleLabel() {
+ contentView.addSubview(subtitleLabel)
+ subtitleLabel.snp.makeConstraints({ make in
+ make.centerX.equalToSuperview()
+ make.top.equalTo(titleLabel.snp.bottom).inset(-10.0)
+ make.leading.trailing.equalTo(titleLabel)
+ make.bottom.equalToSuperview()
+ })
+ }
+}
+
diff --git a/Santander/Santander/Views/Eureka Custom Rows/TitleValueRow/TitleSubtitleRow.swift b/Santander/Santander/Views/Eureka Custom Rows/TitleValueRow/TitleSubtitleRow.swift
new file mode 100644
index 00000000..c50442c6
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/TitleValueRow/TitleSubtitleRow.swift
@@ -0,0 +1,16 @@
+//
+// TitleSubtitleRow.swift
+// Santander
+//
+// Created by Orlando Amorim on 15/08/19.
+//
+
+import UIKit
+import Eureka
+
+final class TitleSubtitleRow: Row, RowType {
+ required public init(tag: String?) {
+ super.init(tag: tag)
+ displayValueFor = nil
+ }
+}
diff --git a/Santander/Santander/Views/Eureka Custom Rows/ViewRow.swift b/Santander/Santander/Views/Eureka Custom Rows/ViewRow.swift
new file mode 100644
index 00000000..ea095194
--- /dev/null
+++ b/Santander/Santander/Views/Eureka Custom Rows/ViewRow.swift
@@ -0,0 +1,158 @@
+//
+// ViewRow.swift
+// CustomViewRow
+//
+// Created by Mark Alldritt on 2017-09-13.
+// Copyright © 2017 Late Night Software Ltd. All rights reserved.
+//
+import UIKit
+import Eureka
+
+
+public class ViewCell : Cell, CellType {
+
+ public var view : ViewType?
+
+ public var viewRightMargin = CGFloat(15.0)
+ public var viewLeftMargin = CGFloat(15.0)
+ public var viewTopMargin = CGFloat(1.0)
+ public var viewBottomMargin = CGFloat(1.0)
+
+ public var titleLeftMargin = CGFloat(15.0)
+ public var titleRightMargin = CGFloat(5.0)
+ public var titleTopMargin = CGFloat(12.0)
+ public var titleBottomMargin = CGFloat(4.0)
+
+ public var titleLabel : UILabel?
+
+ private var notificationObserver : NSObjectProtocol?
+
+ required public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+ backgroundColor = UIColor.white
+ }
+
+ required public init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ deinit {
+ if let notificationObserver = notificationObserver {
+ NotificationCenter.default.removeObserver(notificationObserver)
+ }
+ }
+
+ open override func setup() {
+ super.setup()
+
+ titleLabel = UILabel(frame: CGRect.zero)
+ titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+ contentView.addSubview(titleLabel!)
+
+ // Provide a default row height calculation based on the height of the assigned view.
+ height = {
+ if self.titleLabel!.text == nil || self.titleLabel!.text == "" {
+ return ceil((self.view?.frame.height ?? 0) + self.viewTopMargin + self.viewBottomMargin)
+ }
+ else {
+ let titleHeight = ceil(self.titleLabel!.sizeThatFits(CGSize(width: self.contentView.frame.width - self.titleLeftMargin - self.titleRightMargin, height: 9999.0)).height)
+
+ return ceil(titleHeight + self.titleTopMargin + self.titleBottomMargin + (self.view?.frame.height ?? 0.0) + self.viewTopMargin + self.viewBottomMargin)
+ }
+ }
+
+ notificationObserver = NotificationCenter.default.addObserver(forName: UIContentSizeCategory.didChangeNotification,
+ object: nil,
+ queue: nil,
+ using: { [weak self] (note) in
+ self?.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+ self?.setNeedsLayout()
+ })
+
+ selectionStyle = .none
+ }
+
+ open override func didSelect() {
+ }
+
+
+ open override func layoutSubviews() {
+ super.layoutSubviews()
+
+ // This could be done with autolayout, but this seems simpler...
+ let contentFrame = contentView.frame
+
+ if titleLabel!.text == nil || titleLabel!.text == "" {
+ titleLabel!.frame = CGRect.zero
+ view?.frame = CGRect(x: viewLeftMargin,
+ y: viewTopMargin,
+ width: contentFrame.width - viewLeftMargin - viewRightMargin,
+ height: contentFrame.height - viewTopMargin - viewBottomMargin)
+ }
+ else {
+ let titleHeight = ceil(titleLabel!.sizeThatFits(CGSize(width: contentFrame.width - titleLeftMargin - titleRightMargin, height: 9999.0)).height)
+ let titleFrame = CGRect(x: titleLeftMargin,
+ y: titleTopMargin,
+ width: contentFrame.width - titleLeftMargin - titleRightMargin,
+ height: titleHeight)
+ let viewFrame = CGRect(x: viewLeftMargin,
+ y: titleFrame.maxY + titleBottomMargin + viewTopMargin,
+ width: contentFrame.width - viewLeftMargin - viewRightMargin,
+ height: ceil(contentFrame.height - titleFrame.maxY - titleBottomMargin - viewTopMargin - viewBottomMargin))
+
+ titleLabel!.frame = titleFrame
+ view?.frame = viewFrame
+ }
+ }
+
+}
+
+// MARK: ViewRow
+open class _ViewRow: Row > {
+
+ override open func updateCell() {
+ // NOTE: super.updateCell() deliberatly not called.
+
+ // Deal with the case where the caller did not add their custom view to the containerView in a
+ // backwards compatible manner.
+ if let view = cell.view,
+ view.superview != cell.contentView {
+ view.removeFromSuperview()
+ cell.contentView.addSubview(view)
+ }
+ cell.titleLabel?.text = title
+ }
+
+ required public init(tag: String?) {
+ super.init(tag: tag)
+ displayValueFor = nil
+ }
+}
+
+// ViewRow class with value type specialization. When/if Swift allows default values for generics this can be folded
+// into the ViewRow class.
+public final class ViewRowGeneric: _ViewRow, RowType {
+
+ public var view: ViewType? { // provide a convience accessor for the view
+ return cell.view
+ }
+
+ required public init(tag: String?) {
+ super.init(tag: tag)
+ }
+
+}
+
+// legacy ViewRow class without value type specialization
+public final class ViewRow : _ViewRow, RowType {
+
+ public var view: ViewType? { // provide a convience accessor for the view
+ return cell.view
+ }
+
+ required public init(tag: String?) {
+ super.init(tag: tag)
+ }
+
+}
diff --git a/Santander/SantanderUnitTests/Info.plist b/Santander/SantanderUnitTests/Info.plist
new file mode 100644
index 00000000..6c40a6cd
--- /dev/null
+++ b/Santander/SantanderUnitTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/Santander/SantanderUnitTests/Library/Eureka Rules/StringExtensionsTests.swift b/Santander/SantanderUnitTests/Library/Eureka Rules/StringExtensionsTests.swift
new file mode 100644
index 00000000..901eeac7
--- /dev/null
+++ b/Santander/SantanderUnitTests/Library/Eureka Rules/StringExtensionsTests.swift
@@ -0,0 +1,74 @@
+//
+// StringExtensionsTests.swift
+// SantanderTests
+//
+// Created by Orlando Amorim on 17/08/19.
+//
+
+import XCTest
+@testable import Santander
+
+class StringExtensionsTests: XCTestCase {
+
+ // Normally, it can be argued that force unwrapping (!) should
+ // be avoided, but in unit tests it can be a good idea for
+ // properties (only!) in order to avoid unnecessary boilerplate.
+ private var rule: RulePhoneNumber!
+
+ override func setUp() {
+ super.setUp()
+ rule = RulePhoneNumber()
+ }
+
+ override func tearDown() {
+ rule = nil
+ super.tearDown()
+ }
+
+ // MARK: - Valid numbers allowing empty
+ func testRuleValidPhoneNumberAllowingEmpty() {
+ rule.allowsEmpty = true
+
+ XCTAssertNil(rule.isValid(value: "(31) 4633-9632"), "Is valid number without digit 9")
+ XCTAssertNil(rule.isValid(value: "(31) 99633-9632"), "Is valid number with digit 9")
+ XCTAssertNil(rule.isValid(value: ""), "Is valid empty number")
+ }
+
+ // MARK: - Inalid numbers allowing empty
+ func testRuleInvalidPhoneNumberAllowingEmpty() {
+ rule.allowsEmpty = true
+
+ XCTAssertNotNil(rule.isValid(value: "(31) 9484-82080"), "Is invalid phone number with 5 digits after -")
+ XCTAssertNotNil(rule.isValid(value: "(11)9678-0750"), "Is invalid phone number without spacing after )")
+ XCTAssertNotNil(rule.isValid(value: "(021 12) 91212-2124"), "Is invalid phone number with carrier code")
+ XCTAssertNotNil(rule.isValid(value: "(12) 9 1212-1212"), "Is invalid phone number with spacing between the 9 digit and the rest of number")
+ XCTAssertNotNil(rule.isValid(value: "1212-1124"), "Is invalid phone number without the state code")
+ XCTAssertNotNil(rule.isValid(value: "319484-8208"), "Is invalid phone number without () between the state code")
+ XCTAssertNotNil(rule.isValid(value: "3194848208"), "Is invalid phone number without () between the state code and - between numbers")
+ XCTAssertNotNil(rule.isValid(value: " "), "Is invalid phone number with space")
+ }
+
+
+ // MARK: - Valid numbers not allowing empty
+ func testRuleValidPhoneNumberNotAllowingEmpty() {
+ rule.allowsEmpty = false
+
+ XCTAssertNil(rule.isValid(value: "(31) 4633-9632"), "Is valid number without digit 9")
+ XCTAssertNil(rule.isValid(value: "(31) 99633-9632"), "Is valid number with digit 9")
+ }
+
+ // MARK: - Invalid numbers not allowing empty
+ func testRuleInvalidPhoneNumberNotAllowingEmpty() {
+ rule.allowsEmpty = false
+
+ XCTAssertNotNil(rule.isValid(value: "(31) 9484-82080"), "Is invalid phone number with 5 digits after -")
+ XCTAssertNotNil(rule.isValid(value: "(11)9678-0750"), "Is invalid phone number without spacing after )")
+ XCTAssertNotNil(rule.isValid(value: "(021 12) 91212-2124"), "Is invalid phone number with carrier code")
+ XCTAssertNotNil(rule.isValid(value: "(12) 9 1212-1212"), "Is invalid phone number with spacing between the 9 digit and the rest of number")
+ XCTAssertNotNil(rule.isValid(value: "1212-1124"), "Is invalid phone number without the state code")
+ XCTAssertNotNil(rule.isValid(value: "319484-8208"), "Is invalid phone number without () between the state code")
+ XCTAssertNotNil(rule.isValid(value: "3194848208"), "Is invalid phone number without () between the state code and - between numbers")
+ XCTAssertNotNil(rule.isValid(value: " "), "Is invalid phone number with space")
+ XCTAssertNotNil(rule.isValid(value: ""), "Is invalid empty number")
+ }
+}
diff --git a/Santander/SantanderUnitTests/Scenes/Contact/ContactInteractorTests.swift b/Santander/SantanderUnitTests/Scenes/Contact/ContactInteractorTests.swift
new file mode 100644
index 00000000..ea01a0b4
--- /dev/null
+++ b/Santander/SantanderUnitTests/Scenes/Contact/ContactInteractorTests.swift
@@ -0,0 +1,126 @@
+//
+// ContactInteractorTests.swift
+// SantanderUnitTests
+//
+// Created by Orlando Amorim on 21/08/19.
+//
+
+import XCTest
+@testable import Santander
+
+class ContactInteractorTests: XCTestCase {
+
+ // MARK: - Interactor
+ var interactor: ContactInteractor!
+
+ // MARK: - Test lifecycle
+ override func setUp() {
+ super.setUp()
+ setupIntercator()
+ }
+
+ override func tearDown() {
+ interactor = nil
+ super.tearDown()
+ }
+
+ // MARK: - Test setup
+ func setupIntercator() {
+ interactor = ContactInteractor()
+ }
+
+ class ContactPresentationLogicSpy: ContactPresentationLogic {
+
+ // MARK: Method call expectations
+ var presentFormCalled = false
+ var presentErrorCalled = false
+ var presentSuccessCalled = false
+
+ // MARK: Spied methods
+ func presentForm(_ form: ContactForm) {
+ presentFormCalled = true
+ }
+
+ func presentError(_ error: Error) {
+ presentErrorCalled = true
+ }
+
+ func presentSuccess() {
+ presentSuccessCalled = true
+ }
+ }
+
+ class ContactWorkerSpy: ContactWorker {
+
+ var shouldReturnSuccess: Bool = true
+
+ // MARK: Method call expectations
+ var getFormCalled = false
+
+ override func getForm(result: @escaping (Result) -> Void) {
+ getFormCalled = true
+ if shouldReturnSuccess {
+ let cells = [TestData.ContactForm.emailCell]
+ let contactForm = ContactForm(cells: cells)
+ result(.success(contactForm))
+ } else {
+ result(.failure(TestError.contact))
+ }
+ }
+ }
+
+ func testFetchContactsShouldAskContactsWorkerToGetFormAndPresenterToFormatSuccessResult() {
+ // Given
+ let contactPresentationLogicSpy = ContactPresentationLogicSpy()
+ interactor.presenter = contactPresentationLogicSpy
+ let contactWorkerSpy = ContactWorkerSpy(contactStore: ContactAPI())
+ interactor.worker = contactWorkerSpy
+
+ // When
+ interactor.getForm()
+
+ // Then
+ XCTAssert(contactWorkerSpy.getFormCalled, "ContactInteractor() should ask ContactWorker to fetch form")
+ XCTAssert(contactPresentationLogicSpy.presentFormCalled, "ContactInteractor() should ask presenter to present form")
+ }
+
+ func testFetchContactsShouldAskContactsWorkerToGetFormAndPresenterToFormatErrorResult() {
+ // Given
+ let contactPresentationLogicSpy = ContactPresentationLogicSpy()
+ interactor.presenter = contactPresentationLogicSpy
+ let contactWorkerSpy = ContactWorkerSpy(contactStore: ContactAPI())
+ contactWorkerSpy.shouldReturnSuccess = false
+ interactor.worker = contactWorkerSpy
+
+ // When
+ interactor.getForm()
+
+ // Then
+ XCTAssert(contactWorkerSpy.getFormCalled, "ContactInteractor() should ask ContactWorker to fetch form")
+ XCTAssert(contactPresentationLogicSpy.presentErrorCalled, "ContactInteractor() should ask presenter to present error")
+ }
+
+
+ func testSendContactsFormShouldCallPresenterToShowSuccess() {
+ // Given
+ let contactPresentationLogicSpy = ContactPresentationLogicSpy()
+ interactor.presenter = contactPresentationLogicSpy
+ let contactWorkerSpy = ContactWorkerSpy(contactStore: ContactAPI())
+ interactor.worker = contactWorkerSpy
+
+ // When
+ let contactFormDataRequest = ContactFormDataRequest(name: "Teste", email: "test@test.com", phone: "(41) 99734-2345")
+ let request = Contact.Form.Request(sendFormData: contactFormDataRequest)
+ interactor.sendForm(data: request)
+
+ let expectation = XCTestExpectation(description: "The interactor will sendForm and wait 3 seconds to call presenter")
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+ expectation.fulfill()
+ }
+
+ wait(for: [expectation], timeout: 3)
+
+ // Then
+ XCTAssert(contactPresentationLogicSpy.presentSuccessCalled, "ContactInteractor() should ask presenter to show success")
+ }
+}
diff --git a/Santander/SantanderUnitTests/Scenes/Contact/ContactPresenterTests.swift b/Santander/SantanderUnitTests/Scenes/Contact/ContactPresenterTests.swift
new file mode 100644
index 00000000..df23fb12
--- /dev/null
+++ b/Santander/SantanderUnitTests/Scenes/Contact/ContactPresenterTests.swift
@@ -0,0 +1,112 @@
+//
+// ContactPresenterTests.swift
+// SantanderUnitTests
+//
+// Created by Orlando Amorim on 22/08/19.
+//
+
+import XCTest
+@testable import Santander
+
+class ContactPresenterTests: XCTestCase {
+
+ // MARK: - Interactor
+ var presenter: ContactPresenter!
+
+ // MARK: - Test lifecycle
+ override func setUp() {
+ super.setUp()
+ setupPresenter()
+ }
+
+ override func tearDown() {
+ presenter = nil
+ super.tearDown()
+ }
+
+ // MARK: - Test setup
+ func setupPresenter() {
+ presenter = ContactPresenter()
+ }
+
+ class ContactDisplayLogicSpy: ContactDisplayLogic {
+
+ // MARK: Method call expectations
+ var displayFormCalled = false
+ var displayErrorCalled = false
+ var displaySuccessCalled = false
+
+ // MARK: Argument expectations
+ var form: ContactForm!
+ var error: String!
+
+ // MARK: Spied methods
+ func displayForm(_ form: ContactForm) {
+ displayFormCalled = true
+ self.form = form
+ }
+
+ func displayError(_ error: String) {
+ displayErrorCalled = true
+ self.error = error
+ }
+
+ func displaySuccess() {
+ displaySuccessCalled = true
+ }
+ }
+
+ // MARK: - Tests
+
+ func testPresenterPresentFormShouldAskViewControllerToDisplayForm() {
+ // Given
+ let contactDisplayLogicSpy = ContactDisplayLogicSpy()
+ presenter.viewController = contactDisplayLogicSpy
+
+ // When
+ let cells = [TestData.ContactForm.emailCell]
+ let contactForm = ContactForm(cells: cells)
+ presenter.presentForm(contactForm)
+
+ // Then
+ XCTAssert(contactDisplayLogicSpy.displayFormCalled, "Presenting fetched contact form should ask view controller to display them")
+ }
+
+ func testPresenterPresentErrorShouldAskViewControllerToDisplayError() {
+ // Given
+ let contactDisplayLogicSpy = ContactDisplayLogicSpy()
+ presenter.viewController = contactDisplayLogicSpy
+
+ // When
+ let error = TestError.contact
+ presenter.presentError(error)
+
+ // Then
+ XCTAssert(contactDisplayLogicSpy.displayErrorCalled, "Presenting present error should ask view controller to display them")
+ }
+
+ func testPresenterPresentSuccessShouldAskViewControllerToDisplaySuccess() {
+ // Given
+ let contactDisplayLogicSpy = ContactDisplayLogicSpy()
+ presenter.viewController = contactDisplayLogicSpy
+
+ // When
+ presenter.presentSuccess()
+
+ // Then
+ XCTAssert(contactDisplayLogicSpy.displaySuccessCalled, "Presenting present success should ask view controller to display them")
+ }
+
+ func testPresentPresentErrorShouldFormatErrorForDisplay() {
+ // Given
+ let contactDisplayLogicSpy = ContactDisplayLogicSpy()
+ presenter.viewController = contactDisplayLogicSpy
+
+ // When
+ let error = TestError.contact
+ presenter.presentError(error)
+
+ // Then
+ XCTAssertEqual(contactDisplayLogicSpy.error, error.localizedDescription, "Presenting should properly format error")
+ }
+}
diff --git a/Santander/SantanderUnitTests/Scenes/Investment/InvestmentInteractorTests.swift b/Santander/SantanderUnitTests/Scenes/Investment/InvestmentInteractorTests.swift
new file mode 100644
index 00000000..60ce0234
--- /dev/null
+++ b/Santander/SantanderUnitTests/Scenes/Investment/InvestmentInteractorTests.swift
@@ -0,0 +1,97 @@
+//
+// InvestmentInteractorTests.swift
+// SantanderUnitTests
+//
+// Created by Orlando Amorim on 23/08/19.
+//
+
+import XCTest
+@testable import Santander
+
+class InvestmentInteractorTests: XCTestCase {
+
+ // MARK: - Interactor
+ var interactor: InvestmentInteractor!
+
+ // MARK: - Test lifecycle
+ override func setUp() {
+ super.setUp()
+ setupIntercator()
+ }
+
+ override func tearDown() {
+ interactor = nil
+ super.tearDown()
+ }
+
+ // MARK: - Test setup
+ func setupIntercator() {
+ interactor = InvestmentInteractor()
+ }
+
+ class InvestmentPresentationLogicSpy: InvestmentPresentationLogic {
+
+ // MARK: Method call expectations
+ var presentScreenCalled = false
+ var presentErrorCalled = false
+
+ // MARK: Spied methods
+ func presentScreen(response: Investment.Funds.Response) {
+ presentScreenCalled = true
+ }
+
+ func presentError(_ error: Error) {
+ presentErrorCalled = true
+ }
+ }
+
+ class InvestmentWorkerSpy: InvestmentWorker {
+
+ var shouldReturnSuccess: Bool = true
+
+ // MARK: Method call expectations
+ var getFundsCalled = false
+
+ override func getFunds(result: @escaping (Result) -> Void) {
+ getFundsCalled = true
+ if shouldReturnSuccess {
+ let screen = TestData.Investment.screen
+ let response = Investment.Funds.Response(screen: screen)
+ result(.success(response))
+ } else {
+ result(.failure(TestError.investment))
+ }
+ }
+ }
+
+ func testFetchContactsShouldAskContactsWorkerToGetFormAndPresenterToFormatSuccessResult() {
+ // Given
+ let investmentPresentationLogicSpy = InvestmentPresentationLogicSpy()
+ interactor.presenter = investmentPresentationLogicSpy
+ let investmentWorkerSpy = InvestmentWorkerSpy(investmentStore: InvestmentAPI())
+ interactor.worker = investmentWorkerSpy
+
+ // When
+ interactor.getFunds()
+
+ // Then
+ XCTAssert(investmentWorkerSpy.getFundsCalled, "InvestmentInteractor() should ask InvestmentWorker to fetch funds")
+ XCTAssert(investmentPresentationLogicSpy.presentScreenCalled, "InvestmentInteractor() should ask presenter to present screen")
+ }
+
+ func testFetchContactsShouldAskContactsWorkerToGetFormAndPresenterToFormatErrorResult() {
+ // Given
+ let investmentPresentationLogicSpy = InvestmentPresentationLogicSpy()
+ interactor.presenter = investmentPresentationLogicSpy
+ let investmentWorkerSpy = InvestmentWorkerSpy(investmentStore: InvestmentAPI())
+ investmentWorkerSpy.shouldReturnSuccess = false
+ interactor.worker = investmentWorkerSpy
+
+ // When
+ interactor.getFunds()
+
+ // Then
+ XCTAssert(investmentWorkerSpy.getFundsCalled, "InvestmentInteractor() should ask InvestmentWorker to fetch funds")
+ XCTAssert(investmentPresentationLogicSpy.presentErrorCalled, "InvestmentInteractor() should ask presenter to present error")
+ }
+}
diff --git a/Santander/SantanderUnitTests/Scenes/Investment/InvestmentPresenterTests.swift b/Santander/SantanderUnitTests/Scenes/Investment/InvestmentPresenterTests.swift
new file mode 100644
index 00000000..022c1649
--- /dev/null
+++ b/Santander/SantanderUnitTests/Scenes/Investment/InvestmentPresenterTests.swift
@@ -0,0 +1,94 @@
+//
+// InvestmentPresenterTests.swift
+// SantanderUnitTests
+//
+// Created by Orlando Amorim on 23/08/19.
+//
+
+import XCTest
+@testable import Santander
+
+class InvestmentPresenterTests: XCTestCase {
+
+ // MARK: - Interactor
+ var presenter: InvestmentPresenter!
+
+ // MARK: - Test lifecycle
+ override func setUp() {
+ super.setUp()
+ setupPresenter()
+ }
+
+ override func tearDown() {
+ presenter = nil
+ super.tearDown()
+ }
+
+ // MARK: - Test setup
+ func setupPresenter() {
+ presenter = InvestmentPresenter()
+ }
+
+ class InvestmentDisplayLogicSpy: InvestmentDisplayLogic {
+
+ // MARK: Method call expectations
+ var displayScreenCalled = false
+ var displayErrorCalled = false
+
+ // MARK: Argument expectations
+ var viewModel: Investment.Funds.ViewModel!
+ var error: String!
+
+ // MARK: Spied methods
+ func displayScreen(viewModel: Investment.Funds.ViewModel) {
+ displayScreenCalled = true
+ self.viewModel = viewModel
+ }
+
+ func displayError(_ error: String) {
+ displayErrorCalled = true
+ self.error = error
+ }
+ }
+
+ // MARK: - Tests
+
+ func testPresenterPresentFormShouldAskViewControllerToDisplayForm() {
+ // Given
+ let investmentDisplayLogicSpy = InvestmentDisplayLogicSpy()
+ presenter.viewController = investmentDisplayLogicSpy
+
+ // When
+ let screen = TestData.Investment.screen
+ let response = Investment.Funds.Response(screen: screen)
+ presenter.presentScreen(response: response)
+
+ // Then
+ XCTAssert(investmentDisplayLogicSpy.displayScreenCalled, "Presenting fetched screen should ask view controller to display them")
+ }
+
+ func testPresenterPresentErrorShouldAskViewControllerToDisplayError() {
+ // Given
+ let investmentDisplayLogicSpy = InvestmentDisplayLogicSpy()
+ presenter.viewController = investmentDisplayLogicSpy
+
+ // When
+ presenter.presentError(TestError.investment)
+
+ // Then
+ XCTAssert(investmentDisplayLogicSpy.displayErrorCalled, "Presenting present error should ask view controller to display them")
+ }
+
+ func testPresentPresentErrorShouldFormatErrorForDisplay() {
+ // Given
+ let investmentDisplayLogicSpy = InvestmentDisplayLogicSpy()
+ presenter.viewController = investmentDisplayLogicSpy
+
+ // When
+ let error = TestError.investment
+ presenter.presentError(error)
+
+ // Then
+ XCTAssertEqual(investmentDisplayLogicSpy.error, error.localizedDescription, "Presenting should properly format error")
+ }
+}
diff --git a/Santander/SantanderUnitTests/TestData/TestData.swift b/Santander/SantanderUnitTests/TestData/TestData.swift
new file mode 100644
index 00000000..ccc4975b
--- /dev/null
+++ b/Santander/SantanderUnitTests/TestData/TestData.swift
@@ -0,0 +1,46 @@
+//
+// TestData.swift
+// SantanderUnitTests
+//
+// Created by Orlando Amorim on 22/08/19.
+//
+
+import Foundation
+@testable import Santander
+
+struct TestData {
+ struct ContactForm {
+ static let emailCell = FormCell(id: 2, type: .field, message: "Email", fieldType: .email, isHidden: true, topSpacing: 35.0, fieldToPresent: nil, isRequired: true)
+ }
+
+ struct Investment {
+ static let moreInfo = FundsScreen.MoreInfo(month: FundsScreen.MoreInfo.Percentages(fund: 0.3, cdi: 0.3),
+ year: FundsScreen.MoreInfo.Percentages(fund: 13.01, cdi: 12.08),
+ twelveMonths: FundsScreen.MoreInfo.Percentages(fund: 17.9, cdi: 17.6))
+
+ static let info = [FundsScreen.Info(name: "Taxa de administração", data: "0,50%"),
+ FundsScreen.Info(name: "Aplicação inicial", data: "R$ 10.000,00"),
+ FundsScreen.Info(name: "Movimentação mínima", data: "R$ 1.000,00"),
+ FundsScreen.Info(name: "Saldo mínimo", data: "R$ 5.000,00"),
+ FundsScreen.Info(name: "Resgate (valor bruto)", data: "D+0"),
+ FundsScreen.Info(name: "Cota (valor bruto)", data: "D+1"),
+ FundsScreen.Info(name: "Pagamento (valor bruto)", data: "D+2")]
+
+ static let downInfo = [FundsScreen.DownInfo(name: "Essenciais", data: nil),
+ FundsScreen.DownInfo(name: "Desempenho", data: nil),
+ FundsScreen.DownInfo(name: "Complementares", data: nil),
+ FundsScreen.DownInfo(name: "Regulamento", data: nil),
+ FundsScreen.DownInfo(name: "Adesão", data: nil)]
+
+ static let screen = FundsScreen(title: "Fundos de investimento",
+ fundName: "Vinci Valorem FI Multimercado",
+ whatIs: "O que é?",
+ definition: "O Fundo tem por objetivo proporcionar aos seus cotistas rentabilidade no longo prazo através de investimentos.",
+ riskTitle: "Grau de risco do investimento",
+ risk: .four,
+ infoTitle: "Mais informações sobre o investimento",
+ moreInfo: moreInfo,
+ info: info,
+ downInfo: downInfo)
+ }
+}
diff --git a/Santander/SantanderUnitTests/TestError/TestError.swift b/Santander/SantanderUnitTests/TestError/TestError.swift
new file mode 100644
index 00000000..772c7748
--- /dev/null
+++ b/Santander/SantanderUnitTests/TestError/TestError.swift
@@ -0,0 +1,23 @@
+//
+// TestError.swift
+// SantanderUnitTests
+//
+// Created by Orlando Amorim on 23/08/19.
+//
+
+import Foundation
+
+enum TestError: Error, LocalizedError {
+
+ case contact
+ case investment
+
+ public var errorDescription: String? {
+ switch self {
+ case .contact:
+ return NSLocalizedString("Contact Error", comment: "Contact Error")
+ case .investment:
+ return NSLocalizedString("Investment Error", comment: "Investment Error")
+ }
+ }
+}
diff --git a/Santander/bootstrap.sh b/Santander/bootstrap.sh
new file mode 100644
index 00000000..842446e5
--- /dev/null
+++ b/Santander/bootstrap.sh
@@ -0,0 +1,11 @@
+if [ "$1" == "--install-bundler" ]; then
+ bundle install
+fi
+
+if [ "$1" == "--force" ]; then
+ rm -rf "${HOME}/Library/Caches/CocoaPods"
+ rm -rf "`pwd`/Pods/"
+fi
+
+xcodegen
+bundle exec pod install
\ No newline at end of file
diff --git a/Santander/project.yml b/Santander/project.yml
new file mode 100644
index 00000000..749fe611
--- /dev/null
+++ b/Santander/project.yml
@@ -0,0 +1,25 @@
+name: Santander
+options:
+ bundleIdPrefix: br.com.santander
+targets:
+ Santander:
+ type: application
+ platform: iOS
+ deploymentTarget: 9.0
+ sources: Santander
+ scheme:
+ environmentVariables:
+ OS_ACTIVITY_MODE: disable
+ gatherCoverageData: true
+ testTargets:
+ - SantanderUnitTests
+ settings:
+ SWIFT_VERSION: 5.0
+ TARGETED_DEVICE_FAMILY: 1
+ SantanderUnitTests:
+ type: bundle.unit-test
+ platform: iOS
+ sources: SantanderUnitTests
+ settings:
+ SWIFT_VERSION: 5.0
+ TEST_HOST: $(BUILT_PRODUCTS_DIR)/Santander.app/Santander
\ No newline at end of file
diff --git a/teste_app.sketch b/teste_app.sketch
index 1047c7cb..4b9df99d 100644
Binary files a/teste_app.sketch and b/teste_app.sketch differ
|