diff --git a/ISICkonto.xcodeproj/project.pbxproj b/ISICkonto.xcodeproj/project.pbxproj index 127c60b..1c3e36e 100644 --- a/ISICkonto.xcodeproj/project.pbxproj +++ b/ISICkonto.xcodeproj/project.pbxproj @@ -7,21 +7,69 @@ objects = { /* Begin PBXBuildFile section */ + 0E22A7D82243B9E30079353F /* BalanceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E22A7D72243B9E30079353F /* BalanceViewModel.swift */; }; + 0E22A7DB2243D94B0079353F /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E22A7DA2243D94B0079353F /* LoginView.swift */; }; + 0E22A7DD2243D96D0079353F /* BalanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E22A7DC2243D96D0079353F /* BalanceView.swift */; }; + 0E22A7E02243D98E0079353F /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E22A7DF2243D98E0079353F /* AppView.swift */; }; + 0E22A7E22243DD5F0079353F /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E22A7E12243DD5F0079353F /* AppViewController.swift */; }; + 0E2D753E22452DB400A01BD5 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2D753D22452DB400A01BD5 /* UIImage.swift */; }; + 0E3111DC223DB30000DFD24E /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3111DB223DB30000DFD24E /* LoginViewModel.swift */; }; + 0E3111DE223E54EF00DFD24E /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3111DD223E54EF00DFD24E /* UIView.swift */; }; + 0E3111E22240086600DFD24E /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3111E12240086600DFD24E /* UIFont.swift */; }; + 0E3111E422400B4200DFD24E /* BindingOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3111E322400B4200DFD24E /* BindingOperators.swift */; }; 0E429665220CAFDD0093EC63 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E429664220CAFDD0093EC63 /* AppDelegate.swift */; }; - 0E429667220CAFDD0093EC63 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E429666220CAFDD0093EC63 /* ViewController.swift */; }; - 0E42966A220CAFDD0093EC63 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E429668220CAFDD0093EC63 /* Main.storyboard */; }; 0E42966C220CAFDF0093EC63 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E42966B220CAFDF0093EC63 /* Assets.xcassets */; }; 0E42966F220CAFDF0093EC63 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E42966D220CAFDF0093EC63 /* LaunchScreen.storyboard */; }; + 0E4A230F2244E69100CF1A10 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4A230E2244E69100CF1A10 /* AppViewModel.swift */; }; + 0E6E4448223B047D009E5514 /* AppFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6E4447223B047D009E5514 /* AppFlowController.swift */; }; + 0E6E444D223B0758009E5514 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6E444C223B0758009E5514 /* LoginViewController.swift */; }; + 0E6E444F223B0765009E5514 /* BalanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6E444E223B0765009E5514 /* BalanceViewController.swift */; }; + 0E6E4453223B080B009E5514 /* FormField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6E4452223B080B009E5514 /* FormField.swift */; }; + 0E6E4459223B0955009E5514 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E6E445B223B0955009E5514 /* Localizable.strings */; }; + 0E6E445E223B09E2009E5514 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6E445D223B09E2009E5514 /* String.swift */; }; + 0E6E4466223B0C6A009E5514 /* Cabin-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E6E4462223B0C6A009E5514 /* Cabin-Medium.ttf */; }; + 0E6E4467223B0C6A009E5514 /* Cabin-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E6E4463223B0C6A009E5514 /* Cabin-Regular.ttf */; }; + 0E6E4468223B0C6A009E5514 /* Cabin-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E6E4464223B0C6A009E5514 /* Cabin-Bold.ttf */; }; + 0E6E4469223B0C6A009E5514 /* Cabin-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E6E4465223B0C6A009E5514 /* Cabin-SemiBold.ttf */; }; + 0E6E446C223B105A009E5514 /* WebScrapingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6E446B223B105A009E5514 /* WebScrapingService.swift */; }; + 0E6E446E223B1157009E5514 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6E446D223B1157009E5514 /* Result.swift */; }; + 5FD71D6E33DF02D17C232A49 /* Pods_ISICkonto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C42D5D950A0EEBA2C1D47D44 /* Pods_ISICkonto.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 0E22A7D72243B9E30079353F /* BalanceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceViewModel.swift; sourceTree = ""; }; + 0E22A7DA2243D94B0079353F /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + 0E22A7DC2243D96D0079353F /* BalanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceView.swift; sourceTree = ""; }; + 0E22A7DF2243D98E0079353F /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; + 0E22A7E12243DD5F0079353F /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = ""; }; + 0E2D753D22452DB400A01BD5 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; + 0E3111DB223DB30000DFD24E /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; + 0E3111DD223E54EF00DFD24E /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; + 0E3111E12240086600DFD24E /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; + 0E3111E322400B4200DFD24E /* BindingOperators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingOperators.swift; sourceTree = ""; }; 0E429661220CAFDD0093EC63 /* ISICkonto.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ISICkonto.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0E429664220CAFDD0093EC63 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 0E429666220CAFDD0093EC63 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 0E429669220CAFDD0093EC63 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 0E42966B220CAFDF0093EC63 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0E42966E220CAFDF0093EC63 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 0E429670220CAFDF0093EC63 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0E4A230E2244E69100CF1A10 /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = ""; }; + 0E6E4447223B047D009E5514 /* AppFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFlowController.swift; sourceTree = ""; }; + 0E6E444C223B0758009E5514 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 0E6E444E223B0765009E5514 /* BalanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceViewController.swift; sourceTree = ""; }; + 0E6E4452223B080B009E5514 /* FormField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormField.swift; sourceTree = ""; }; + 0E6E4454223B08BF009E5514 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/LaunchScreen.strings; sourceTree = ""; }; + 0E6E445A223B0955009E5514 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 0E6E445C223B0959009E5514 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; + 0E6E445D223B09E2009E5514 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; + 0E6E4462223B0C6A009E5514 /* Cabin-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cabin-Medium.ttf"; sourceTree = ""; }; + 0E6E4463223B0C6A009E5514 /* Cabin-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cabin-Regular.ttf"; sourceTree = ""; }; + 0E6E4464223B0C6A009E5514 /* Cabin-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cabin-Bold.ttf"; sourceTree = ""; }; + 0E6E4465223B0C6A009E5514 /* Cabin-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cabin-SemiBold.ttf"; sourceTree = ""; }; + 0E6E446B223B105A009E5514 /* WebScrapingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebScrapingService.swift; sourceTree = ""; }; + 0E6E446D223B1157009E5514 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 67F6C21F247D9D74D7C28545 /* Pods-ISICkonto.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ISICkonto.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ISICkonto/Pods-ISICkonto.debug.xcconfig"; sourceTree = ""; }; + C42D5D950A0EEBA2C1D47D44 /* Pods_ISICkonto.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ISICkonto.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EE8D4AEA4C262BB7AAD9A92A /* Pods-ISICkonto.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ISICkonto.release.xcconfig"; path = "Pods/Target Support Files/Pods-ISICkonto/Pods-ISICkonto.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -29,17 +77,39 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5FD71D6E33DF02D17C232A49 /* Pods_ISICkonto.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0E22A7D92243D9250079353F /* Views */ = { + isa = PBXGroup; + children = ( + 0E22A7DA2243D94B0079353F /* LoginView.swift */, + 0E22A7DC2243D96D0079353F /* BalanceView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 0E22A7DE2243D9780079353F /* Base */ = { + isa = PBXGroup; + children = ( + 0E22A7DF2243D98E0079353F /* AppView.swift */, + 0E22A7E12243DD5F0079353F /* AppViewController.swift */, + 0E4A230E2244E69100CF1A10 /* AppViewModel.swift */, + ); + path = Base; + sourceTree = ""; + }; 0E429658220CAFDD0093EC63 = { isa = PBXGroup; children = ( 0E429663220CAFDD0093EC63 /* ISICkonto */, 0E429662220CAFDD0093EC63 /* Products */, + D054AAA82D6C9D541E7BAB16 /* Pods */, + 6AAF3F4A18EA8351529CEBB4 /* Frameworks */, ); sourceTree = ""; }; @@ -54,9 +124,12 @@ 0E429663220CAFDD0093EC63 /* ISICkonto */ = { isa = PBXGroup; children = ( + 0E22A7DE2243D9780079353F /* Base */, + 0E6E446A223B1041009E5514 /* Services */, + 0E6E4461223B0C4F009E5514 /* Fonts */, + 0E6E4450223B07D7009E5514 /* Support */, + 0E6E4449223B0712009E5514 /* Flow */, 0E429664220CAFDD0093EC63 /* AppDelegate.swift */, - 0E429666220CAFDD0093EC63 /* ViewController.swift */, - 0E429668220CAFDD0093EC63 /* Main.storyboard */, 0E42966B220CAFDF0093EC63 /* Assets.xcassets */, 0E42966D220CAFDF0093EC63 /* LaunchScreen.storyboard */, 0E429670220CAFDF0093EC63 /* Info.plist */, @@ -64,6 +137,103 @@ path = ISICkonto; sourceTree = ""; }; + 0E6E4449223B0712009E5514 /* Flow */ = { + isa = PBXGroup; + children = ( + 0E6E4447223B047D009E5514 /* AppFlowController.swift */, + 0E6E444A223B0729009E5514 /* ViewControllers */, + 0E6E444B223B0737009E5514 /* ViewModels */, + 0E22A7D92243D9250079353F /* Views */, + ); + path = Flow; + sourceTree = ""; + }; + 0E6E444A223B0729009E5514 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 0E6E444C223B0758009E5514 /* LoginViewController.swift */, + 0E6E444E223B0765009E5514 /* BalanceViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + 0E6E444B223B0737009E5514 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 0E3111DB223DB30000DFD24E /* LoginViewModel.swift */, + 0E22A7D72243B9E30079353F /* BalanceViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 0E6E4450223B07D7009E5514 /* Support */ = { + isa = PBXGroup; + children = ( + 0E6E445F223B09E7009E5514 /* Extensions */, + 0E6E4451223B07FA009E5514 /* UIComponents */, + 0E6E445B223B0955009E5514 /* Localizable.strings */, + 0E6E446D223B1157009E5514 /* Result.swift */, + 0E3111E322400B4200DFD24E /* BindingOperators.swift */, + ); + path = Support; + sourceTree = ""; + }; + 0E6E4451223B07FA009E5514 /* UIComponents */ = { + isa = PBXGroup; + children = ( + 0E6E4452223B080B009E5514 /* FormField.swift */, + ); + path = UIComponents; + sourceTree = ""; + }; + 0E6E445F223B09E7009E5514 /* Extensions */ = { + isa = PBXGroup; + children = ( + 0E6E445D223B09E2009E5514 /* String.swift */, + 0E3111DD223E54EF00DFD24E /* UIView.swift */, + 0E3111E12240086600DFD24E /* UIFont.swift */, + 0E2D753D22452DB400A01BD5 /* UIImage.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 0E6E4461223B0C4F009E5514 /* Fonts */ = { + isa = PBXGroup; + children = ( + 0E6E4464223B0C6A009E5514 /* Cabin-Bold.ttf */, + 0E6E4462223B0C6A009E5514 /* Cabin-Medium.ttf */, + 0E6E4463223B0C6A009E5514 /* Cabin-Regular.ttf */, + 0E6E4465223B0C6A009E5514 /* Cabin-SemiBold.ttf */, + ); + name = Fonts; + path = "New Group"; + sourceTree = ""; + }; + 0E6E446A223B1041009E5514 /* Services */ = { + isa = PBXGroup; + children = ( + 0E6E446B223B105A009E5514 /* WebScrapingService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 6AAF3F4A18EA8351529CEBB4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C42D5D950A0EEBA2C1D47D44 /* Pods_ISICkonto.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D054AAA82D6C9D541E7BAB16 /* Pods */ = { + isa = PBXGroup; + children = ( + 67F6C21F247D9D74D7C28545 /* Pods-ISICkonto.debug.xcconfig */, + EE8D4AEA4C262BB7AAD9A92A /* Pods-ISICkonto.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -71,9 +241,11 @@ isa = PBXNativeTarget; buildConfigurationList = 0E429673220CAFDF0093EC63 /* Build configuration list for PBXNativeTarget "ISICkonto" */; buildPhases = ( + A00FA82B19915203700F7299 /* [CP] Check Pods Manifest.lock */, 0E42965D220CAFDD0093EC63 /* Sources */, 0E42965E220CAFDD0093EC63 /* Frameworks */, 0E42965F220CAFDD0093EC63 /* Resources */, + EEBF80EB30C7AA88550A46A0 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -106,6 +278,7 @@ knownRegions = ( en, Base, + cs, ); mainGroup = 0E429658220CAFDD0093EC63; productRefGroup = 0E429662220CAFDD0093EC63 /* Products */; @@ -122,41 +295,127 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0E6E4467223B0C6A009E5514 /* Cabin-Regular.ttf in Resources */, + 0E6E4468223B0C6A009E5514 /* Cabin-Bold.ttf in Resources */, 0E42966F220CAFDF0093EC63 /* LaunchScreen.storyboard in Resources */, + 0E6E4459223B0955009E5514 /* Localizable.strings in Resources */, 0E42966C220CAFDF0093EC63 /* Assets.xcassets in Resources */, - 0E42966A220CAFDD0093EC63 /* Main.storyboard in Resources */, + 0E6E4469223B0C6A009E5514 /* Cabin-SemiBold.ttf in Resources */, + 0E6E4466223B0C6A009E5514 /* Cabin-Medium.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + A00FA82B19915203700F7299 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ISICkonto-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EEBF80EB30C7AA88550A46A0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ISICkonto/Pods-ISICkonto-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", + "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", + "${BUILT_PRODUCTS_DIR}/RxAlamofire/RxAlamofire.framework", + "${BUILT_PRODUCTS_DIR}/RxAtomic/RxAtomic.framework", + "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", + "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", + "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", + "${BUILT_PRODUCTS_DIR}/SwiftKeychainWrapper/SwiftKeychainWrapper.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAlamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAtomic.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftKeychainWrapper.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ISICkonto/Pods-ISICkonto-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 0E42965D220CAFDD0093EC63 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0E429667220CAFDD0093EC63 /* ViewController.swift in Sources */, + 0E3111DE223E54EF00DFD24E /* UIView.swift in Sources */, + 0E6E445E223B09E2009E5514 /* String.swift in Sources */, + 0E4A230F2244E69100CF1A10 /* AppViewModel.swift in Sources */, + 0E6E4453223B080B009E5514 /* FormField.swift in Sources */, + 0E6E444D223B0758009E5514 /* LoginViewController.swift in Sources */, + 0E22A7E02243D98E0079353F /* AppView.swift in Sources */, 0E429665220CAFDD0093EC63 /* AppDelegate.swift in Sources */, + 0E6E4448223B047D009E5514 /* AppFlowController.swift in Sources */, + 0E22A7DD2243D96D0079353F /* BalanceView.swift in Sources */, + 0E3111E22240086600DFD24E /* UIFont.swift in Sources */, + 0E3111E422400B4200DFD24E /* BindingOperators.swift in Sources */, + 0E22A7E22243DD5F0079353F /* AppViewController.swift in Sources */, + 0E22A7DB2243D94B0079353F /* LoginView.swift in Sources */, + 0E6E444F223B0765009E5514 /* BalanceViewController.swift in Sources */, + 0E22A7D82243B9E30079353F /* BalanceViewModel.swift in Sources */, + 0E6E446E223B1157009E5514 /* Result.swift in Sources */, + 0E2D753E22452DB400A01BD5 /* UIImage.swift in Sources */, + 0E6E446C223B105A009E5514 /* WebScrapingService.swift in Sources */, + 0E3111DC223DB30000DFD24E /* LoginViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - 0E429668220CAFDD0093EC63 /* Main.storyboard */ = { + 0E42966D220CAFDF0093EC63 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( - 0E429669220CAFDD0093EC63 /* Base */, + 0E42966E220CAFDF0093EC63 /* Base */, + 0E6E4454223B08BF009E5514 /* cs */, ); - name = Main.storyboard; + name = LaunchScreen.storyboard; sourceTree = ""; }; - 0E42966D220CAFDF0093EC63 /* LaunchScreen.storyboard */ = { + 0E6E445B223B0955009E5514 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 0E42966E220CAFDF0093EC63 /* Base */, + 0E6E445A223B0955009E5514 /* en */, + 0E6E445C223B0959009E5514 /* cs */, ); - name = LaunchScreen.storyboard; + name = Localizable.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ @@ -166,6 +425,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -227,6 +487,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -280,6 +541,7 @@ }; 0E429674220CAFDF0093EC63 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 67F6C21F247D9D74D7C28545 /* Pods-ISICkonto.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; @@ -297,6 +559,7 @@ }; 0E429675220CAFDF0093EC63 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EE8D4AEA4C262BB7AAD9A92A /* Pods-ISICkonto.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; diff --git a/ISICkonto.xcworkspace/contents.xcworkspacedata b/ISICkonto.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..dd8889a --- /dev/null +++ b/ISICkonto.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ISICkonto.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ISICkonto.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ISICkonto.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ISICkonto.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ISICkonto.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/ISICkonto.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/ISICkonto/AppDelegate.swift b/ISICkonto/AppDelegate.swift index 31bfa1c..68819f0 100644 --- a/ISICkonto/AppDelegate.swift +++ b/ISICkonto/AppDelegate.swift @@ -7,40 +7,73 @@ // import UIKit +import SVProgressHUD @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - + var window: UIWindow? - - + var appFlowController: AppFlowController? + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + + setUpGlobalAppearance() + + window = UIWindow(frame: UIScreen.main.bounds) + + let navigationController = UINavigationController() + window?.rootViewController = navigationController + appFlowController = AppFlowController(with: navigationController) + appFlowController?.launch() + window?.makeKeyAndVisible() + return true } - + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } - + func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - + func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. } - + func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - - + + func setUpGlobalAppearance() { + + // for UINavigationBar + UINavigationBar.appearance().setBackgroundImage(UIImage(), for: UIBarMetrics.default) + UINavigationBar.appearance().shadowImage = UIImage() + UINavigationBar.appearance().isTranslucent = true + UINavigationBar.appearance().backgroundColor = .clear + UINavigationBar.appearance().tintColor = .white + UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white, .font: UIFont.cabinRegular(size: 20)] + UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white, .font: UIFont.cabinMedium(size: 17)], for: [.normal, .highlighted]) + + // for SVProgressHUD + SVProgressHUD.setFont(.cabinRegular(size: 15)) + SVProgressHUD.setBackgroundColor(.white) + SVProgressHUD.setForegroundColor(UIColor(red: 55/255, green: 131/255, blue: 160/255, alpha: 1)) + SVProgressHUD.setDefaultMaskType(SVProgressHUDMaskType.gradient) + SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 150)) + SVProgressHUD.setRingRadius(40) + SVProgressHUD.setRingThickness(5) + SVProgressHUD.setImageViewSize(CGSize(width: 80, height: 80)) + } + } diff --git a/ISICkonto/Assets.xcassets/accountIcon.imageset/Contents.json b/ISICkonto/Assets.xcassets/accountIcon.imageset/Contents.json new file mode 100644 index 0000000..849d4e0 --- /dev/null +++ b/ISICkonto/Assets.xcassets/accountIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "accountIcon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ISICkonto/Assets.xcassets/accountIcon.imageset/accountIcon@2x.png b/ISICkonto/Assets.xcassets/accountIcon.imageset/accountIcon@2x.png new file mode 100644 index 0000000..fa6ddec Binary files /dev/null and b/ISICkonto/Assets.xcassets/accountIcon.imageset/accountIcon@2x.png differ diff --git a/ISICkonto/Assets.xcassets/accountIcon@2x.png b/ISICkonto/Assets.xcassets/accountIcon@2x.png new file mode 100644 index 0000000..fa6ddec Binary files /dev/null and b/ISICkonto/Assets.xcassets/accountIcon@2x.png differ diff --git a/ISICkonto/Assets.xcassets/logo.imageset/Contents.json b/ISICkonto/Assets.xcassets/logo.imageset/Contents.json new file mode 100644 index 0000000..8463aca --- /dev/null +++ b/ISICkonto/Assets.xcassets/logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "logo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ISICkonto/Assets.xcassets/logo.imageset/logo.png b/ISICkonto/Assets.xcassets/logo.imageset/logo.png new file mode 100644 index 0000000..012c6b1 Binary files /dev/null and b/ISICkonto/Assets.xcassets/logo.imageset/logo.png differ diff --git a/ISICkonto/Assets.xcassets/reloadIcon.imageset/Contents.json b/ISICkonto/Assets.xcassets/reloadIcon.imageset/Contents.json new file mode 100644 index 0000000..bd3ad31 --- /dev/null +++ b/ISICkonto/Assets.xcassets/reloadIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "reloadIcon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ISICkonto/Assets.xcassets/reloadIcon.imageset/reloadIcon@2x.png b/ISICkonto/Assets.xcassets/reloadIcon.imageset/reloadIcon@2x.png new file mode 100644 index 0000000..3817c2f Binary files /dev/null and b/ISICkonto/Assets.xcassets/reloadIcon.imageset/reloadIcon@2x.png differ diff --git a/ISICkonto/Base.lproj/LaunchScreen.storyboard b/ISICkonto/Base.lproj/LaunchScreen.storyboard index bfa3612..a489912 100644 --- a/ISICkonto/Base.lproj/LaunchScreen.storyboard +++ b/ISICkonto/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,11 @@ - - + + + + + - + + @@ -13,7 +17,21 @@ - + + + + + + + + + + + + + + + @@ -22,4 +40,7 @@ + + + diff --git a/ISICkonto/Base.lproj/Main.storyboard b/ISICkonto/Base.lproj/Main.storyboard deleted file mode 100644 index f1bcf38..0000000 --- a/ISICkonto/Base.lproj/Main.storyboard +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ISICkonto/Base/AppView.swift b/ISICkonto/Base/AppView.swift new file mode 100644 index 0000000..c1233be --- /dev/null +++ b/ISICkonto/Base/AppView.swift @@ -0,0 +1,48 @@ +// +// AppView.swift +// ISICkonto +// +// Created by Vendula Švastalová on 21/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import UIKit +import SVProgressHUD + +class AppView: UIView { + + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + createConstraints() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupUI() + createConstraints() + } + + func setupUI() { + + } + + func createConstraints() { + + } + + func showSuccess(with message: String) { + SVProgressHUD.showSuccess(withStatus: message) + SVProgressHUD.dismiss(withDelay: 1.0) + } + + func showError(with message: String) { + SVProgressHUD.showError(withStatus: message) + SVProgressHUD.dismiss(withDelay: 1.0) + } + + func showLoading(with message: String) { + SVProgressHUD.show(withStatus: message) + } + +} diff --git a/ISICkonto/Base/AppViewController.swift b/ISICkonto/Base/AppViewController.swift new file mode 100644 index 0000000..919fc57 --- /dev/null +++ b/ISICkonto/Base/AppViewController.swift @@ -0,0 +1,54 @@ +// +// AppViewController.swift +// ISICkonto +// +// Created by Vendula Švastalová on 21/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import UIKit +import RxSwift + +class AppViewController: UIViewController { + var vm: VM! + var v: V { return view as! V } + var disposeBag = DisposeBag() + + init(vm: VM) { + self.vm = vm + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + bindToViewModel(vm: vm) + } + + override func loadView() { + super.loadView() + setView() + } + + + func setView() { + + } + + private func bindToViewModel(vm: VM) { + setupOutputBindings(to: vm) + setupInputBindings(from: vm) + } + + func setupInputBindings(from vm: VM) { + + } + + func setupOutputBindings(to vm: VM) { + + } +} diff --git a/ISICkonto/Base/AppViewModel.swift b/ISICkonto/Base/AppViewModel.swift new file mode 100644 index 0000000..55ab371 --- /dev/null +++ b/ISICkonto/Base/AppViewModel.swift @@ -0,0 +1,13 @@ +// +// AppViewModel.swift +// ISICkonto +// +// Created by Vendula Švastalová on 22/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation + +class AppViewModel { + +} diff --git a/ISICkonto/Flow/AppFlowController.swift b/ISICkonto/Flow/AppFlowController.swift new file mode 100644 index 0000000..d2a6950 --- /dev/null +++ b/ISICkonto/Flow/AppFlowController.swift @@ -0,0 +1,53 @@ +// +// AppFlowController.swift +// ISICkonto +// +// Created by Vendula Švastalová on 14/02/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import UIKit +import RxCocoa +import RxSwift + +protocol AppFlowControllerProtocol { + var navigationController: UINavigationController { get } +} + +class AppFlowController : AppFlowControllerProtocol { + + let navigationController: UINavigationController + + init(with navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func launch() { + if true { + showLoginViewController() + } else { + + } + } + + func showLoginViewController() { + let loginViewModel = LoginViewModel() + let loginViewController = LogInViewController(vm: loginViewModel) + navigationController.show(loginViewController, sender: self) + + loginViewController.onLoginSuccess = { [unowned self] in + self.pushBalanceViewController() + } + } + + func pushBalanceViewController() { + let balanceViewModel = BalanceViewModel() + let balanceViewController = BalanceViewController(vm: balanceViewModel) + navigationController.pushViewController(balanceViewController, animated: true) + + balanceViewController.onPopBalanceViewController = { [unowned self] in + self.navigationController.popViewController(animated: true) + } + } +} diff --git a/ISICkonto/Flow/ViewControllers/BalanceViewController.swift b/ISICkonto/Flow/ViewControllers/BalanceViewController.swift new file mode 100644 index 0000000..9e1111c --- /dev/null +++ b/ISICkonto/Flow/ViewControllers/BalanceViewController.swift @@ -0,0 +1,44 @@ +// +// BalanceViewController.swift +// ISICkonto +// +// Created by Vendula Švastalová on 14/02/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import UIKit +import SVProgressHUD +import RxSwift + +class BalanceViewController: AppViewController { + + var onPopBalanceViewController: () -> () = { } + + override func setView() { + view = BalanceView() + } + + override func setupOutputBindings(to vm: BalanceViewModel) { + (v.refreshButton >>> vm.refreshAction).disposed(by: disposeBag) + + v.refreshButton.rx.tap.bind { [unowned self] in + self.v.showLoading(with: "Loading balance...".localized) + }.disposed(by: disposeBag) + + v.logOutButton.rx.tap.bind { [unowned self] in + self.onPopBalanceViewController() + }.disposed(by: disposeBag) + } + + override func setupInputBindings(from vm: BalanceViewModel) { + (vm.balance --> { [unowned self] in + if $0 != "..." { + SVProgressHUD.dismiss(withDelay: 1.0) + } else { + self.v.showError(with: "Failed".localized) + } + self.v.balanceLabel.text = $0 + " Kč" + }).disposed(by: disposeBag) + } +} diff --git a/ISICkonto/Flow/ViewControllers/LoginViewController.swift b/ISICkonto/Flow/ViewControllers/LoginViewController.swift new file mode 100644 index 0000000..2ecfc81 --- /dev/null +++ b/ISICkonto/Flow/ViewControllers/LoginViewController.swift @@ -0,0 +1,61 @@ +// +// ViewController.swift +// ISICkonto +// +// Created by Vendula Švastalová on 07/02/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import UIKit +import SnapKit +import RxCocoa +import RxSwift +import SVProgressHUD + +class LogInViewController: AppViewController { + + var onLoginSuccess: () -> () = { } + + override func setView() { + view = LoginView() + } + + override func viewDidLoad() { + super.viewDidLoad() + + v.constraintSmallLogo() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + v.animateLogo() + } + + override func setupOutputBindings(to vm: LoginViewModel) { + (v.usernameTextField >>> vm.username).disposed(by: disposeBag) + (v.passwordTextField >>> vm.password).disposed(by: disposeBag) + (v.loginButton >>> vm.loginAction).disposed(by: disposeBag) + + v.loginButton.rx.tap.bind { [unowned self] in + self.v.showLoading(with: "Loading balance...".localized) + }.disposed(by: disposeBag) + } + + override func setupInputBindings(from vm: LoginViewModel) { + (vm.isLoginSuccess --> { [unowned self] success in + if !success { + self.v.showError(with: "Wrong credentials".localized) + } else { + self.v.showSuccess(with: "Success!".localized) + self.onLoginSuccess() + } + self.v.usernameTextField.deactivate() + self.v.passwordTextField.deactivate() + }).disposed(by: disposeBag) + + (vm.isLoginEnabled --> { [unowned self] in + self.v.loginButton.set(alpha: $0 ? 1.0 : 0.2) + }).disposed(by: disposeBag) + } +} diff --git a/ISICkonto/Flow/ViewModels/BalanceViewModel.swift b/ISICkonto/Flow/ViewModels/BalanceViewModel.swift new file mode 100644 index 0000000..77f9ca8 --- /dev/null +++ b/ISICkonto/Flow/ViewModels/BalanceViewModel.swift @@ -0,0 +1,58 @@ +// +// BalanceViewModel.swift +// ISICkonto +// +// Created by Vendula Švastalová on 21/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import RxSwift +import Kanna +import SwiftKeychainWrapper +import SVProgressHUD + +class BalanceViewModel: AppViewModel { + var refreshAction: Variable = Variable(()) + + var disposeBag = DisposeBag() + + var balance: Observable { + return resultRequestBalancePage.map { [weak self] in + guard let strongSelf = self, let balancePage = $0.element, $0.info == .loggedIn else { return "..." } + return strongSelf.scrapeBalance(from: balancePage) + } + } + + private var requestBalance: Observable { + return refreshAction.asObservable().map { [weak self] in + guard let strongSelf = self else { return (username: "", password: "") } + return strongSelf.getCredentials() } + } + + private var resultRequestBalancePage: Observable> { + return requestBalance.flatMapLatest { [weak self] (credentials) -> Observable> in + return (self?.request(credentials: credentials))! + } + } + + private func request(credentials: Credentials) -> Observable> { + return WebScrapingService.authenticate(credentials).request() + } + + private func scrapeBalance(from html: String) -> String { + let accountHtml = try? HTML(html: html, encoding: .utf8) + guard let information = accountHtml?.xpath("//td") else { return "..." } + var balance = information[4].text! + balance.removeLast(4) + print(balance) + return balance + } + + private func getCredentials() -> Credentials { + let username = KeychainWrapper.standard.string(forKey: "username") + let password = KeychainWrapper.standard.string(forKey: "password") + + return (username: username ?? "", password: password ?? "") + } +} diff --git a/ISICkonto/Flow/ViewModels/LoginViewModel.swift b/ISICkonto/Flow/ViewModels/LoginViewModel.swift new file mode 100644 index 0000000..febdaa9 --- /dev/null +++ b/ISICkonto/Flow/ViewModels/LoginViewModel.swift @@ -0,0 +1,54 @@ +// +// LoginViewModel.swift +// ISICkonto +// +// Created by Vendula Švastalová on 16/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import RxCocoa +import RxSwift +import SVProgressHUD +import SwiftKeychainWrapper + +class LoginViewModel: AppViewModel { + + var username: Variable = Variable("") + var password: Variable = Variable("") + var loginAction: Variable = Variable(()) + + var isLoginEnabled: Observable { + return Observable.combineLatest(username.asObservable(), password.asObservable()) { !$0.isEmpty && !$1.isEmpty } + } + + var isLoginSuccess: Observable { + return resultRequestLogin.map { [weak self] in + guard let strongSelf = self, $0.info == .loggedIn else { return false } + strongSelf.save(credentials: (username: strongSelf.username.value, password: strongSelf.password.value)) + return true + }.filter{ $0 } + } + + private var credentials: Observable { + return loginAction.asObservable().withLatestFrom(isLoginEnabled).filter { $0 }.map { [weak self] _ -> Credentials in + return Credentials(username: (self?.username.value)!, password: (self?.password.value)!) + } + } + + private var resultRequestLogin: Observable> { + return credentials.flatMapLatest { [weak self] (credentials) -> Observable> in + return (self?.request(credentials: credentials))! + } + } + + private func request(credentials: Credentials) -> Observable> { + return WebScrapingService.authenticate(credentials).request() + } + + private func save(credentials: Credentials) { + KeychainWrapper.standard.set(credentials.username, forKey: "username") + KeychainWrapper.standard.set(credentials.password, forKey: "password") + } + +} diff --git a/ISICkonto/Flow/Views/BalanceView.swift b/ISICkonto/Flow/Views/BalanceView.swift new file mode 100644 index 0000000..37537d7 --- /dev/null +++ b/ISICkonto/Flow/Views/BalanceView.swift @@ -0,0 +1,73 @@ +// +// BalanceView.swift +// ISICkonto +// +// Created by Vendula Švastalová on 21/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import UIKit + +class BalanceView: AppView { + + let balanceLabel: UILabel = UILabel() + let textLabel: UILabel = UILabel() + let refreshButton: UIButton = UIButton() + let logOutButton: UIButton = UIButton() + let buttonView: UIView = UIView() + + + override func setupUI() { + + backgroundColor = UIColor(red: 93/255, green: 149/255, blue: 170/255, alpha: 1) + + balanceLabel.textAlignment = .center + balanceLabel.font = .cabinBold(size: 85) + balanceLabel.textColor = UIColor.white + + textLabel.textAlignment = .center + textLabel.font = .cabinRegular(size: 25) + textLabel.textColor = UIColor(red: 182/255, green: 220/255, blue: 252/255, alpha: 1) + textLabel.text = "Your balance is".localized + + refreshButton.setImage(UIImage(id: .reloadIcon), for: .normal) + + logOutButton.setImage(UIImage(id: .accountIcon), for: .normal) + + buttonView.addSubview(refreshButton) + buttonView.addSubview(logOutButton) + addSubview(buttonView) + addSubview(balanceLabel) + addSubview(textLabel) + + } + + override func createConstraints() { + textLabel.snp.makeConstraints { make in + make.bottom.equalTo(balanceLabel.snp.top).offset(-10) + make.centerX.equalTo(balanceLabel) + } + + balanceLabel.snp.makeConstraints { make in + make.center.equalTo(self) + make.leading.equalTo(10) + make.trailing.equalTo(-10) + } + + buttonView.snp.makeConstraints { make in + make.centerX.equalTo(self) + make.top.equalTo(balanceLabel.snp.bottom).offset(20) + make.width.equalTo(150) + } + + refreshButton.snp.makeConstraints { make in + make.leading.top.bottom.equalTo(buttonView) + make.height.width.equalTo(50) + } + + logOutButton.snp.makeConstraints { make in + make.trailing.top.bottom.equalTo(buttonView) + make.width.height.equalTo(50) + } + } +} diff --git a/ISICkonto/Flow/Views/LoginView.swift b/ISICkonto/Flow/Views/LoginView.swift new file mode 100644 index 0000000..15c3a3d --- /dev/null +++ b/ISICkonto/Flow/Views/LoginView.swift @@ -0,0 +1,88 @@ +// +// LoginView.swift +// ISICkonto +// +// Created by Vendula Švastalová on 21/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import UIKit + +class LoginView: AppView { + + let logo: UIImageView = UIImageView() + let usernameTextField: FormField = FormField() + let passwordTextField: FormField = FormField() + let loginButton: UIButton = UIButton() + + override func setupUI() { + backgroundColor = UIColor(red: 93/255, green: 149/255, blue: 170/255, alpha: 1) + + logo.image = UIImage(id: .logo) + + passwordTextField.textContentType = UITextContentType.password + passwordTextField.isSecureTextEntry = true + passwordTextField.placeholder = "password".localized + + usernameTextField.textContentType = UITextContentType.username + usernameTextField.placeholder = "username".localized + + loginButton.setTitle("Log In".localized, for: .normal) + loginButton.setTitleColor(UIColor(red: 86/255, green: 129/255, blue: 154/255, alpha: 1), for: .normal) + loginButton.titleLabel?.font = .cabinRegular(size: 17) + loginButton.backgroundColor = UIColor.white + loginButton.layer.cornerRadius = 5.0 + + addSubview(usernameTextField) + addSubview(passwordTextField) + addSubview(loginButton) + addSubview(logo) + } + + override func createConstraints() { + + usernameTextField.snp.makeConstraints { make in + make.leading.equalTo(self).offset(20) + make.trailing.equalTo(self).offset(-20) + make.height.equalTo(50) + make.top.equalTo(logo.snp.bottom).offset(30) + } + + passwordTextField.snp.makeConstraints { make in + make.leading.equalTo(self).offset(20) + make.trailing.equalTo(self).offset(-20) + make.height.equalTo(50) + make.top.equalTo(usernameTextField.snp.bottom).offset(20) + } + + loginButton.snp.makeConstraints { make in + make.leading.equalTo(self).offset(20) + make.trailing.equalTo(self).offset(-20) + make.height.equalTo(50) + make.top.equalTo(passwordTextField.snp.bottom).offset(20) + } + } + + func constraintSmallLogo() { + logo.snp.makeConstraints { [unowned self] make in + make.leading.equalTo(self).offset(50) + make.trailing.equalTo(self).offset(-50) + make.height.equalTo(logo.snp.width) + make.center.equalTo(self.snp.center) + } + } + + func animateLogo() { + logo.snp.removeConstraints() + logo.snp.makeConstraints { [unowned self] make in + make.leading.equalTo(self).offset(100) + make.trailing.equalTo(self).offset(-100) + make.height.equalTo(logo.snp.width) + make.top.equalTo(self.safeAreaLayoutGuide).offset(30) + } + UIView.animate(withDuration: 1) { [unowned self] in + self.layoutIfNeeded() + } + } + +} diff --git a/ISICkonto/Fonts/Cabin/Cabin-Bold.ttf b/ISICkonto/Fonts/Cabin/Cabin-Bold.ttf new file mode 100755 index 0000000..a7e2f73 Binary files /dev/null and b/ISICkonto/Fonts/Cabin/Cabin-Bold.ttf differ diff --git a/ISICkonto/Fonts/Cabin/Cabin-Medium.ttf b/ISICkonto/Fonts/Cabin/Cabin-Medium.ttf new file mode 100755 index 0000000..5541d91 Binary files /dev/null and b/ISICkonto/Fonts/Cabin/Cabin-Medium.ttf differ diff --git a/ISICkonto/Fonts/Cabin/Cabin-Regular.ttf b/ISICkonto/Fonts/Cabin/Cabin-Regular.ttf new file mode 100755 index 0000000..a76c2e7 Binary files /dev/null and b/ISICkonto/Fonts/Cabin/Cabin-Regular.ttf differ diff --git a/ISICkonto/Fonts/Cabin/Cabin-SemiBold.ttf b/ISICkonto/Fonts/Cabin/Cabin-SemiBold.ttf new file mode 100755 index 0000000..079c073 Binary files /dev/null and b/ISICkonto/Fonts/Cabin/Cabin-SemiBold.ttf differ diff --git a/ISICkonto/Info.plist b/ISICkonto/Info.plist index 16be3b6..5dfea23 100644 --- a/ISICkonto/Info.plist +++ b/ISICkonto/Info.plist @@ -22,12 +22,19 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 + UIStatusBarStyle + UIStatusBarStyleLightContent + UIAppFonts + + Cabin-Bold.ttf + Cabin-Regular.ttf + Cabin-Medium.ttf + Cabin-SemiBold.ttf + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -41,5 +48,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIViewControllerBasedStatusBarAppearance + diff --git a/ISICkonto/New Group/Cabin-Bold.ttf b/ISICkonto/New Group/Cabin-Bold.ttf new file mode 100755 index 0000000..a7e2f73 Binary files /dev/null and b/ISICkonto/New Group/Cabin-Bold.ttf differ diff --git a/ISICkonto/New Group/Cabin-Medium.ttf b/ISICkonto/New Group/Cabin-Medium.ttf new file mode 100755 index 0000000..5541d91 Binary files /dev/null and b/ISICkonto/New Group/Cabin-Medium.ttf differ diff --git a/ISICkonto/New Group/Cabin-Regular.ttf b/ISICkonto/New Group/Cabin-Regular.ttf new file mode 100755 index 0000000..a76c2e7 Binary files /dev/null and b/ISICkonto/New Group/Cabin-Regular.ttf differ diff --git a/ISICkonto/New Group/Cabin-SemiBold.ttf b/ISICkonto/New Group/Cabin-SemiBold.ttf new file mode 100755 index 0000000..079c073 Binary files /dev/null and b/ISICkonto/New Group/Cabin-SemiBold.ttf differ diff --git a/ISICkonto/Services/WebScrapingService.swift b/ISICkonto/Services/WebScrapingService.swift new file mode 100644 index 0000000..96915d6 --- /dev/null +++ b/ISICkonto/Services/WebScrapingService.swift @@ -0,0 +1,106 @@ +// +// WebScrapingService.swift +// ISICkonto +// +// Created by Vendula Švastalová on 04/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import Alamofire +import RxSwift +import RxAlamofire +import RxCocoa +import Kanna +import SwiftKeychainWrapper + +// move this to bundle as constants +let agataSuzUrl: String = "https://agata.suz.cvut.cz/secure/index.php" +let sibbolethUrl: String = "https://idp2.civ.cvut.cz/idp/shibboleth" + +typealias Credentials = (username: String, password: String) + +enum WebScrapingService { + case authenticate( _ requestData : Credentials) + + func request() -> Observable>{ + switch self { + case .authenticate(let requestData): + return requestSsoLogin(credentials: requestData) + } + } + + private func requestAgataSuz() -> Observable> { + return RxAlamofire + .requestString(.get, agataSuzUrl) + .map { (response, html) -> Result in + + guard let url = response.url?.absoluteString else { return Result.resultFailed("requesting agata failed") } + guard let responseUrl = URLComponents(string: url) else { return Result.resultFailed("creating responseUrl failed") } + guard let hostUrl = responseUrl.host else { return Result.resultFailed("host not found") } + + if hostUrl.range(of: "agata.suz.cvut.cz") != nil { + return Result.resultLoggedIn(element: html) + } + + /* if we have not got straight to agata account we have to proceed with SSO Login */ + var ssoLoginUrl = URLComponents(string: (responseUrl.queryItems?.first(where: { $0.name == "return"})!.value)!) + + /* creating url for SSO Login request */ + ssoLoginUrl?.queryItems?.append( URLQueryItem(name: "entityID", value: sibbolethUrl) ) + ssoLoginUrl?.queryItems?.append( (responseUrl.queryItems?.first(where: { $0.name == "filter" }))! ) + ssoLoginUrl?.queryItems?.append( (responseUrl.queryItems?.first(where: { $0.name == "lang" }))! ) + + return Result.resultSuccess("succeeded", element: (ssoLoginUrl?.url?.absoluteString)!) + } + } + + private func requestSsoLogin(credentials: Credentials) -> Observable> { + /* requesting agata page, checking for success and possily chaining it with SSO Login requests */ + + return requestAgataSuz() + .flatMapLatest { (agataResult) -> Observable> in + + if agataResult.info != .success { return Observable.of(agataResult) } + + return RxAlamofire + .requestString(.get, agataResult.element!) + .flatMapLatest { (response, _) -> Observable> in + + let params = ["j_username": credentials.username, "j_password": credentials.password, "_eventId_proceed": ""] + + return RxAlamofire + .requestString(.post, (response.url?.absoluteString)!, parameters: params) + .flatMapLatest { (response2,str) -> Observable> in + + /* using Kanna to parse HTML */ + guard let sessionHtml = try? HTML(html: str, encoding: .utf8) else { return Observable.of(Result.resultFailed()) } + + /* if the resulting site contains Stale request in the title the session has expired */ + let staleRequest = sessionHtml.xpath("//title").first?.content + if staleRequest != nil && staleRequest!.contains("Stale request") { return Observable.of(Result.resultFailed("Stale request")) } + + /* if the resulting page contains an error message the user has entered wrong credentials */ + let possibleError = sessionHtml.xpath("//p[@class='error-message']").first?.content + if possibleError != nil && possibleError!.contains("Login failed.") { return Observable.of(Result.resultFailed("Wrong credentials")) } + + /* creating parameters and POSTing them to actionUrl to finish the SSO Login request */ + var parameters: [String: String] = [:] + sessionHtml.xpath("//input[@type='hidden']").forEach { element in + parameters[element["name"]!] = element["value"]! + } + if parameters.isEmpty { return Observable.of(Result.resultFailed("parameters empty")) } + + let actionUrl = sessionHtml.xpath("//form[@method='post']").first!["action"] + + return RxAlamofire + .requestString(.post, actionUrl!, parameters: parameters) + .map { return Result.resultLoggedIn(element: $1) } + } + } + } + } +} + + + diff --git a/ISICkonto/Support/BindingOperators.swift b/ISICkonto/Support/BindingOperators.swift new file mode 100644 index 0000000..f931829 --- /dev/null +++ b/ISICkonto/Support/BindingOperators.swift @@ -0,0 +1,27 @@ +// +// BindingOperators.swift +// ISICkonto +// +// Created by Vendula Švastalová on 18/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import UIKit +import RxSwift +import RxCocoa + +infix operator >>> +infix operator --> + +func >>> (button: UIButton, to: Variable) -> Disposable { + return button.rx.tap.bind(to: to) +} + +func >>> (textField: UITextField, to: Variable) -> Disposable { + return textField.rx.text.orEmpty.asObservable().bind(to: to) +} + +func --> (observable: Observable, closure: @escaping (Element) -> ()) -> Disposable { + return observable.subscribe(onNext: { closure($0) }) +} diff --git a/ISICkonto/Support/Extensions/String.swift b/ISICkonto/Support/Extensions/String.swift new file mode 100644 index 0000000..e104c37 --- /dev/null +++ b/ISICkonto/Support/Extensions/String.swift @@ -0,0 +1,15 @@ +// +// String.swift +// ISICkonto +// +// Created by Vendula Švastalová on 15/02/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation + +extension String { + var localized: String { + return NSLocalizedString(self, comment: "") + } +} diff --git a/ISICkonto/Support/Extensions/UIFont.swift b/ISICkonto/Support/Extensions/UIFont.swift new file mode 100644 index 0000000..39e4fa0 --- /dev/null +++ b/ISICkonto/Support/Extensions/UIFont.swift @@ -0,0 +1,24 @@ +// +// UIFont.swift +// ISICkonto +// +// Created by Vendula Švastalová on 18/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import UIKit + +extension UIFont { + class func cabinRegular(size: CGFloat) -> UIFont { + return UIFont(name: "Cabin-Regular", size: size)! + } + + class func cabinBold(size: CGFloat) -> UIFont { + return UIFont(name: "Cabin-Bold", size: size)! + } + + class func cabinMedium(size: CGFloat) -> UIFont { + return UIFont(name: "Cabin-Medium", size: size)! + } +} diff --git a/ISICkonto/Support/Extensions/UIImage.swift b/ISICkonto/Support/Extensions/UIImage.swift new file mode 100644 index 0000000..36b7df1 --- /dev/null +++ b/ISICkonto/Support/Extensions/UIImage.swift @@ -0,0 +1,22 @@ +// +// UIImage.swift +// ISICkonto +// +// Created by Vendula Švastalová on 22/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import UIKit + +extension UIImage { + enum ImageId: String { + case logo + case reloadIcon + case accountIcon + } + + convenience init(id: ImageId) { + self.init(named: id.rawValue)! + } +} diff --git a/ISICkonto/Support/Extensions/UIView.swift b/ISICkonto/Support/Extensions/UIView.swift new file mode 100644 index 0000000..8181acd --- /dev/null +++ b/ISICkonto/Support/Extensions/UIView.swift @@ -0,0 +1,18 @@ +// +// UIView.swift +// ISICkonto +// +// Created by Vendula Švastalová on 17/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation +import UIKit + +extension UIView { + func set(alpha: CGFloat, duration: TimeInterval = 0.2) { + UIView.transition(with: self, duration: duration, animations: { [weak self] in + self?.alpha = alpha + }) + } +} diff --git a/ISICkonto/Support/Result.swift b/ISICkonto/Support/Result.swift new file mode 100644 index 0000000..cb5d73c --- /dev/null +++ b/ISICkonto/Support/Result.swift @@ -0,0 +1,24 @@ +// +// Result.swift +// ISICkonto +// +// Created by Vendula Švastalová on 13/03/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import Foundation + +enum ResultInformation { + case inProgress, success, failure, loggedIn +} + +struct Result { + let info: ResultInformation + let message: String? + let element: Element? + + static func resultFailed(_ message: String? = nil) -> Result { return Result(info: .failure, message: message, element: nil) } + static func resultSuccess(_ message: String? = nil, element: Element) -> Result { return Result(info: .success, message: message, element: element) } + static func resultInProgress() -> Result { return Result(info: .inProgress, message: nil, element: nil) } + static func resultLoggedIn(_ message: String? = nil, element: Element) -> Result { return Result(info: .loggedIn, message: nil, element: element) } +} diff --git a/ISICkonto/Support/UIComponents/FormField.swift b/ISICkonto/Support/UIComponents/FormField.swift new file mode 100644 index 0000000..c7f03d4 --- /dev/null +++ b/ISICkonto/Support/UIComponents/FormField.swift @@ -0,0 +1,51 @@ +// +// FormField.swift +// ISICkonto +// +// Created by Vendula Švastalová on 15/02/2019. +// Copyright © 2019 Vendula Švastalová. All rights reserved. +// + +import UIKit + +class FormField: UITextField { + + override init(frame: CGRect) { + super.init(frame: frame) + + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + setup() + } + + private func setup() { + self.backgroundColor = UIColor(red: 74/255, green: 130/255, blue: 157/255, alpha: 1) + self.layer.cornerRadius = 5.0 + + self.textColor = UIColor.white + self.tintColor = UIColor.white + + self.font = .cabinRegular(size: 17) + } + + override func placeholderRect(forBounds bounds: CGRect) -> CGRect { + return bounds.insetBy(dx: 10, dy: 0) + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + return bounds.insetBy(dx: 10, dy: 0) + } + + override func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.insetBy(dx: 10, dy: 0) + } + + func deactivate() { + text = "" + resignFirstResponder() + } +} diff --git a/ISICkonto/Support/cs.lproj/Localizable.strings b/ISICkonto/Support/cs.lproj/Localizable.strings new file mode 100644 index 0000000..a605443 --- /dev/null +++ b/ISICkonto/Support/cs.lproj/Localizable.strings @@ -0,0 +1,17 @@ +/* + Localizable.strings + ISICkonto + + Created by Vendula Švastalová on 15/02/2019. + Copyright © 2019 Vendula Švastalová. All rights reserved. + */ + +"User" = "Uživatel"; +"Log In" = "Přihlásit se"; +"username" = "uživatelské jméno"; +"password" = "heslo"; +"Your balance is" = "Na účtu máš"; +"Success!" = "Jsi přihlášen!"; +"Wrong credentials" = "Nesprávné údaje"; +"Loading balance..." = "Načítání stavu..."; +"Failed to refresh" = "Nezdařeno"; diff --git a/ISICkonto/Support/en.lproj/Localizable.strings b/ISICkonto/Support/en.lproj/Localizable.strings new file mode 100644 index 0000000..27a9721 --- /dev/null +++ b/ISICkonto/Support/en.lproj/Localizable.strings @@ -0,0 +1,17 @@ +/* + Localizable.strings + ISICkonto + + Created by Vendula Švastalová on 15/02/2019. + Copyright © 2019 Vendula Švastalová. All rights reserved. + */ + +"User" = "User"; +"Log In" = "Log In"; +"username" = "username"; +"password" = "password"; +"Your balance is" = "Your balance is"; +"Success!" = "Success!"; +"Wrong credentials" = "Wrong credentials"; +"Loading balance..." = "Loading balance..."; +"Failed to refresh" = "Failed to refresh"; diff --git a/ISICkonto/ViewController.swift b/ISICkonto/ViewController.swift deleted file mode 100644 index 44c8d51..0000000 --- a/ISICkonto/ViewController.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// ViewController.swift -// ISICkonto -// -// Created by Vendula Švastalová on 07/02/2019. -// Copyright © 2019 Vendula Švastalová. All rights reserved. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - } - - -} - diff --git a/ISICkonto/cs.lproj/LaunchScreen.strings b/ISICkonto/cs.lproj/LaunchScreen.strings new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ISICkonto/cs.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..913348d --- /dev/null +++ b/Podfile @@ -0,0 +1,15 @@ +platform :ios, '10.0' + +target 'ISICkonto' do + # Comment the next line if you're not using Swift and don't want to use dynamic frameworks + use_frameworks! + + # Pods for ISICkonto + pod 'SnapKit', '~> 4.2' #constraints + pod 'Kanna', '~> 4.0' #html parser + pod 'RxAlamofire', '~> 4.3' #reactive wrapper to Alamofire for http networking + pod 'RxSwift', '~> 4.4' + pod 'RxCocoa', '~> 4.4' + pod 'SwiftKeychainWrapper', '~> 3.2' #keychain wrapper + pod 'SVProgressHUD', '~> 2.2' #progress hud +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..72b3c4e --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,52 @@ +PODS: + - Alamofire (4.8.1) + - Kanna (4.0.2) + - RxAlamofire (4.3.0): + - RxAlamofire/Core (= 4.3.0) + - RxAlamofire/Core (4.3.0): + - Alamofire (~> 4.5) + - RxSwift (~> 4) + - RxAtomic (4.4.1) + - RxCocoa (4.4.1): + - RxSwift (~> 4.0) + - RxSwift (4.4.1): + - RxAtomic (~> 4.4) + - SnapKit (4.2.0) + - SVProgressHUD (2.2.5) + - SwiftKeychainWrapper (3.2.0) + +DEPENDENCIES: + - Kanna (~> 4.0) + - RxAlamofire (~> 4.3) + - RxCocoa (~> 4.4) + - RxSwift (~> 4.4) + - SnapKit (~> 4.2) + - SVProgressHUD (~> 2.2) + - SwiftKeychainWrapper (~> 3.2) + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - Alamofire + - Kanna + - RxAlamofire + - RxAtomic + - RxCocoa + - RxSwift + - SnapKit + - SVProgressHUD + - SwiftKeychainWrapper + +SPEC CHECKSUMS: + Alamofire: 16ce2c353fb72865124ddae8a57c5942388f4f11 + Kanna: c60b61d72554bf04ed1ee074f9b379855cab4892 + RxAlamofire: 09624d0f2d48ed8b686e4eb4cf68e28cbd2df556 + RxAtomic: f8d6adc1ccb87a767811269e4875887bc74dbf19 + RxCocoa: 2f35a76bf8887872e28a1914112395b11b8e0e64 + RxSwift: 92fcf68dfef21f3e2ab1965363d9e7b3d787597e + SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a + SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 + SwiftKeychainWrapper: 82c4c02cdc36f9a24f7edfcaab3e336401caa1a1 + +PODFILE CHECKSUM: 9dce246af002670f9fe3789328a14bccc62e0b79 + +COCOAPODS: 1.6.1