├── .gitignore ├── .semaphore ├── deployment.yml ├── master_deployment.yml └── semaphore.yml ├── .swiftlint.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── Swift-Base.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Swift-Base.xcscheme │ └── Swift-BaseStaging.xcscheme ├── Swift-Base.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Swift-Base ├── AppDelegate │ └── AppDelegate.swift ├── Configuration │ ├── Fonts.swift │ ├── Generated │ │ └── .gitkeep │ ├── Keys │ │ ├── Production │ │ │ └── Configuration.swift │ │ └── Staging │ │ │ └── Configuration.swift │ └── Services.swift ├── GraphQL │ ├── API │ │ └── API.swift │ ├── DefaultGraphQLAPIClient.swift │ ├── Fragments │ │ ├── CurrentUserFragment.graphql │ │ └── UserFragment.graphql │ ├── GraphQLAPIClient.swift │ ├── Interceptors │ │ ├── AuthorizationInterceptor.swift │ │ ├── ErrorHandlerInterseptor.swift │ │ ├── LogInterceptor.swift │ │ ├── ParameterInterceptor.swift │ │ └── RefreshTokenInterceptor.swift │ ├── Mutations │ │ ├── RequestPasswordRecovery.graphql │ │ ├── SignIn.graphql │ │ ├── SignOut.graphql │ │ ├── SignUp.graphql │ │ ├── UpdateToken.graphql │ │ └── UpdateUser.graphql │ ├── Provider │ │ └── NetworkInterceptorProvider.swift │ ├── PublishersExtension.swift │ ├── Queries │ │ └── FetchCurrentUser.graphql │ ├── Schemas │ │ └── schema.json │ └── UpdateTokenGraphQLAPIClient.swift ├── Managers │ ├── Access Manager │ │ ├── AccessManager.swift │ │ └── DefaultAccessManager.swift │ ├── DefaultManagersModule.swift │ ├── Keychain Manager │ │ ├── DefaultKeychainManager.swift │ │ └── KeychainManager.swift │ ├── ManagersModule.swift │ └── User Defaults Manager │ │ ├── DefaultUserDefaultsManager.swift │ │ └── UserDefaultsManager.swift ├── Models │ ├── Cache │ │ ├── Cache.swift │ │ └── Swift-Base.xcdatamodeld │ │ │ ├── .xccurrentversion │ │ │ └── Swift-Base.xcdatamodel │ │ │ └── contents │ ├── CacheManagers │ │ ├── CurrentUserCacheManager │ │ │ ├── CurrentUserCacheManager.swift │ │ │ └── DefaultCurrentUserCacheManager.swift │ │ └── UserCacheManager │ │ │ ├── DefaultUserCacheManager.swift │ │ │ └── UserCacheManager.swift │ ├── CurrentUser.swift │ └── User.swift ├── Resources │ ├── Images.xcassets │ │ ├── AppIcon-Staging.appiconset │ │ │ ├── 1024.png │ │ │ ├── 120-1.png │ │ │ ├── 120.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40-1.png │ │ │ ├── 40-2.png │ │ │ ├── 40.png │ │ │ ├── 58-1.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 76.png │ │ │ ├── 80-1.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 120-1.png │ │ │ ├── 120.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40-1.png │ │ │ ├── 40-2.png │ │ │ ├── 40.png │ │ │ ├── 58-1.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 76.png │ │ │ ├── 80-1.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ └── Contents.json │ └── Localization │ │ ├── Base.lproj │ │ └── Localizable.strings │ │ └── ru.lproj │ │ └── Localizable.strings ├── Services │ ├── AuthorizationService │ │ ├── AuthorizationService.swift │ │ └── DefaultAuthorizationService.swift │ ├── CurrentUserService │ │ ├── CurrentUserService.swift │ │ └── DefaultCurrentUserService.swift │ └── FirebaseAnalyticsService │ │ ├── AnalyticKeys.swift │ │ ├── DefaultFirebaseAnalyticsService.swift │ │ └── FirebaseAnalyticsService.swift ├── Stroyboards │ ├── Base.lproj │ │ ├── Main_Storyboard.storyboard │ │ └── Main_iPad.storyboard │ ├── LaunchScreen.storyboard │ └── ru.lproj │ │ ├── Main_Storyboard.strings │ │ └── Main_iPad.strings ├── Supporting Files │ ├── GoogleService-Info.plist │ └── Info.plist ├── Tools │ ├── Error │ │ └── MyError.swift │ ├── Event │ │ ├── Event.swift │ │ ├── EventConnection.swift │ │ ├── EventListener.swift │ │ └── EventListenerConnection.swift │ ├── Extensions │ │ ├── ArrayExtension.swift │ │ ├── CGPointExtension.swift │ │ ├── CGRectExtension.swift │ │ ├── CGSizeExtension.swift │ │ ├── CollectionExtension.swift │ │ ├── DataExtension.swift │ │ ├── DateFormatterExtension.swift │ │ ├── FloatingPointExtension.swift │ │ ├── NSLayoutConstraintExtension.swift │ │ ├── StringExtension.swift │ │ ├── UIColorExtension.swift │ │ ├── UIEdgeInsets.swift │ │ └── UIImageExtension.swift │ ├── ImageLoader │ │ ├── DefaultImageLoader.swift │ │ └── ImageLoader.swift │ ├── Log │ │ ├── Log.swift │ │ ├── LogConsolePrinter.swift │ │ ├── LogFilePrinter.swift │ │ └── LogPrinter.swift │ ├── Protocols │ │ ├── DictionaryReceiver.swift │ │ ├── KeyboardHandler.swift │ │ └── KeyboardScrollableHandler.swift │ ├── Storage │ │ ├── CoreData │ │ │ ├── CoreDataStorageContext.swift │ │ │ ├── CoreDataStorageExtension.swift │ │ │ ├── CoreDataStorageManager.swift │ │ │ └── CoreDataStorageModel.swift │ │ ├── StorageContext.swift │ │ ├── StorageContextObserver.swift │ │ ├── StorageContextType.swift │ │ ├── StorageFetchRequest.swift │ │ ├── StorageManager.swift │ │ ├── StorageModel.swift │ │ └── StorageObject.swift │ ├── ViewControllers │ │ ├── LoggedNavigationController.swift │ │ ├── LoggedPageViewController.swift │ │ ├── LoggedTabBarController.swift │ │ ├── LoggedTableViewController.swift │ │ ├── LoggedViewController.swift │ │ ├── MessagePresenting.swift │ │ └── TransparentNavigationController.swift │ ├── Views │ │ ├── Button.swift │ │ ├── IconTextField.swift │ │ ├── RoundImageView.swift │ │ ├── RoundView.swift │ │ ├── RoundedImageView.swift │ │ ├── RoundedView.swift │ │ ├── TextField.swift │ │ └── TextView.swift │ └── WebService │ │ ├── UIWebActivityIndicator.swift │ │ ├── WebActivityIndicator.swift │ │ ├── WebError.swift │ │ ├── WebErrorBase.swift │ │ ├── WebHandler.swift │ │ ├── WebMethod.swift │ │ ├── WebMultiPart.swift │ │ ├── WebMultiPartDataItem.swift │ │ ├── WebMultiPartItem.swift │ │ ├── WebRequest.swift │ │ ├── WebRequestEncoding.swift │ │ ├── WebService.swift │ │ ├── WebStatusCode.swift │ │ └── WebURLRequestAdapter.swift └── ViewControllers │ └── ViewController.swift ├── Swift-BaseStaging └── Swift-BaseStaging.plist ├── Swift-BaseTests ├── Info.plist └── Swift_BaseTests.swift ├── fastlane ├── Appfile ├── Deliverfile ├── Fastfile ├── Matchfile ├── Pluginfile ├── README.md └── report.xml └── rename-project.command /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS X files 2 | .DS_Store 3 | .DS_Store? 4 | .Trashes 5 | .Spotlight-V100 6 | *.swp 7 | 8 | ## Xcode build files 9 | DerivedData/ 10 | build/ 11 | 12 | ## Xcode private settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.xccheckout 26 | *.moved-aside 27 | *.xcuserstate 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | ## Swift Package Manager 37 | .build/ 38 | 39 | ## Playgrounds 40 | timeline.xctimeline 41 | playground.xcworkspace 42 | 43 | ## R.swift 44 | *.generated.swift 45 | 46 | ## Cocoapods 47 | /Pods -------------------------------------------------------------------------------- /.semaphore/deployment.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: Swift-Base 3 | 4 | agent: 5 | machine: 6 | type: a1-standard-4 7 | os_image: macos-xcode11 8 | 9 | blocks: 10 | - name: Deploy 11 | task: 12 | env_vars: 13 | - name: LANG 14 | value: en_US.UTF-8 15 | secrets: 16 | - name: ios-base-swift-secrets 17 | prologue: 18 | commands: 19 | - chmod 0600 ~/.ssh/id_rsa_semaphoreci 20 | - ssh-add ~/.ssh/id_rsa_semaphoreci 21 | 22 | - checkout 23 | - cache restore 24 | - curl -sL firebase.tools | bash 25 | - bundle install --path vendor/bundle 26 | - bundle exec fastlane update_plugins 27 | - cache store 28 | jobs: 29 | - name: Staging Deploy 30 | commands: 31 | - bundle exec xcversion select 11.2.1 32 | - bundle exec fastlane deploy_staging 33 | - name: Production Deploy 34 | commands: 35 | - bundle exec xcversion select 11.2.1 36 | - bundle exec fastlane deploy_production -------------------------------------------------------------------------------- /.semaphore/master_deployment.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: Swift-Base 3 | 4 | agent: 5 | machine: 6 | type: a1-standard-4 7 | os_image: macos-xcode11 8 | 9 | blocks: 10 | - name: Master Deploy 11 | task: 12 | env_vars: 13 | - name: LANG 14 | value: en_US.UTF-8 15 | secrets: 16 | - name: ios-base-swift-secrets 17 | prologue: 18 | commands: 19 | - chmod 0600 ~/.ssh/id_rsa_semaphoreci 20 | - ssh-add ~/.ssh/id_rsa_semaphoreci 21 | 22 | - checkout 23 | - cache restore 24 | - curl -sL firebase.tools | bash 25 | - bundle install --path vendor/bundle 26 | - bundle exec fastlane update_plugins 27 | - cache store 28 | jobs: 29 | - name: Staging Deploy 30 | commands: 31 | - bundle exec xcversion select 11.2.1 32 | - bundle exec fastlane deploy_staging -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: Swift-Base 3 | 4 | agent: 5 | machine: 6 | type: a1-standard-4 7 | os_image: macos-xcode11 8 | 9 | blocks: 10 | - name: Build app 11 | task: 12 | env_vars: 13 | - name: LANG 14 | value: en_US.UTF-8 15 | secrets: 16 | - name: ios-base-swift-secrets 17 | prologue: 18 | commands: 19 | - chmod 0600 ~/.ssh/id_rsa_semaphoreci 20 | - ssh-add ~/.ssh/id_rsa_semaphoreci 21 | 22 | - checkout 23 | - cache restore 24 | - bundle install --path vendor/bundle 25 | - bundle exec fastlane update_plugins 26 | - cache store 27 | jobs: 28 | - name: Build 29 | commands: 30 | - bundle exec xcversion select 11.5 31 | - bundle exec fastlane build 32 | 33 | - artifact push job Swift-Base.ipa 34 | promotions: 35 | - name: Deployment 36 | pipeline_file: deployment.yml 37 | auto_promote: 38 | when: "result = 'passed' and branch =~ '^release'" 39 | - name: Master Deployment 40 | pipeline_file: master_deployment.yml 41 | auto_promote: 42 | when: "result = 'passed' and branch = 'master'" -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem "cocoapods" 5 | gem "xcode-install" 6 | 7 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 8 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Flatstack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | inhibit_all_warnings! 2 | use_frameworks! 3 | 4 | def includeCommonPods 5 | # Analytics 6 | pod 'Firebase/Analytics' 7 | pod 'Firebase/Crashlytics' 8 | 9 | # Libraries 10 | pod 'SwiftLint' 11 | pod 'R.swift' 12 | pod 'Kingfisher', '~> 5.0' 13 | pod 'Apollo' 14 | end 15 | 16 | target 'Swift-Base' do 17 | platform :ios, '11.0' 18 | 19 | includeCommonPods 20 | 21 | target 'Swift-BaseTests' do 22 | inherit! :search_paths 23 | end 24 | end 25 | 26 | target 'Swift-BaseStaging' do 27 | platform :ios, '11.0' 28 | 29 | includeCommonPods 30 | end 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://flatstack-opensource.semaphoreci.com/badges/ios-base-swift.svg?style=shields) 2 | 3 | # ios-base-swift 4 | 5 | iOS swift template application. 6 | 7 | ## Get started 8 | 9 | To setup project follow a few simple steps: 10 | 1. [Download][1]. 11 | 2. [Rename project][2]. 12 | 4. [Configure CocoaPods][4]. 13 | 5. [Configure Fabric/Crashlytics][5]. 14 | 6. [Edit README.md][6]. 15 | 16 | ### Download 17 | Download prject as `zip` file. 18 | 19 | ### Rename project 20 | run `rename-project.command script` 21 | 22 | ### Configure CocoaPods 23 | * Find Podfile in project. 24 | * Uncomment, add or remove pods. 25 | * Then run in terminal: 26 | 27 | ```sh 28 | pod install 29 | ``` 30 | 31 | ### Configure Fabric/Crashlytics 32 | * [Download Fabric desktop application][13] 33 | * Check **Bundle ID** in project build settings for all schemes. 34 | * Create new organization in Fabric/Crashlytics. 35 | * Add applications to this organization for all schemes. 36 | * [!] Do not forget change **Run Script** in settings of the project. 37 | 38 | ### Configure TestFlight 39 | * Go to [iTunes Connect][20] 40 | * Create applications for Staging and Release 41 | 42 | ### Edit README.md 43 | * Edit `README.md`. 44 | 45 | ### CI 46 | 47 | #### Creating certificates and provisioning profiles 48 | * Go to [Developer Center][14]. 49 | * Choose ```Certificates -> All``` and create Developer and Distribution certificates. 50 | * Choose ```Identifiers -> App IDs``` and create new two bundles for Staging and Release. 51 | * Choose ```Devices -> All``` and add all devices for testing. 52 | * Choose ```Provisioning Profiles -> All``` and create 6 provisioning profiles for Staging ```Debug Staging```, ```Release Staging```, ```AppStore Staging``` and for Release ```Debug```, ```Release```, ```AppStore```. 53 | * Download the 2 certificates and the 6 provisioning profiles. 54 | * Copy the files to ```/Certs``` path. 55 | * Encrypting the files via ```encrypt.sh``` 56 | 57 | #### Setting fastlane scripts 58 | 59 | * We use [bundler][16] for third-party gems. Set it like [here][17]. 60 | * Find AppFile in the project and set ```team_id``` from developer program. (for private repos) 61 | * Find Deliverfile in the project and set ```username``` like apple id. (for private repos) 62 | * Open the Terminal, write ```fastlane fastlane-credentials add --username YOUR_APPLE_ID``` and save your password to [CredentialManager][18]. It need that you can send new builds locally without input of the password everytime. 63 | * Find Fastfile in the project and set: 64 | - ```fastlane_version```, ```info_plist_path``` - required settings of the project. 65 | - ```CRASHLYTICS_GROUPS``` - testers aliases of Fabric/Crashlytics. Example: "group1, group2". (for private repos) 66 | - ```BUNDLE_ID_APPSTORE```, ```BUNDLE_ID_APPSTORE_STAGING``` - for TestFlight. 67 | - Check ```SCHEME_STAGING```, ```SCHEME_APPSTORE```, ```SCHEME_APPSTORE_STAGING``` - shared schemes. 68 | * [More info][15] 69 | 70 | #### Setting [Travis][19] 71 | 72 | Add on the CI environment variables: 73 | * ```APP_NAME``` - your the name of the app (can be open) 74 | * ```ENCRYPTION_SECRET``` - secret key of certificates and provisioning profiles (must be private) 75 | * ```DEVELOP_BRANCH``` and ```DEVELOP_LANE``` - setting developer branch and fastlane name (can be open) 76 | * ```RELEASE_BRANCH``` and ```RELEASE_LANE``` - setting release branch and fastlane name (can be open) 77 | * ```FASTLANE_TEAM_ID```, ```FASTLANE_USER``` and ```FASTLANE_PASSWORD``` - teaapple id for deploying to testflight (must be private) 78 | * ```CRASHLYTICS_TOKEN```, ```CRASHLYTICS_SECRET``` and ```CRASHLYTICS_GROUPS``` - API key and Build secret of Fabric for deploying to Fabric/Crashlytics (must be private) 79 | 80 | ## License 81 | ios-base-swift is released under the MIT license. See [LICENSE][7] for details. 82 | 83 | 84 | [][11] 85 | 86 | [1]: #download 87 | [2]: #rename-project 88 | [3]: #configure-mogenerator 89 | [4]: #configure-cocoapods 90 | [5]: #configure-fabric-crashlytics 91 | [6]: #edit-readme-md 92 | [7]: LICENSE 93 | [8]: http://github.com/nikitafomin 94 | [9]: https://github.com/NikolaevSergey 95 | [10]: https://github.com/VladimirGoncharov 96 | [11]: http://www.flatstack.com 97 | [12]: https://github.com/fs/ios-base-swift/graphs/contributors 98 | [13]: https://get.fabric.io 99 | [14]: https://developer.apple.com/ 100 | [15]: https://docs.fastlane.tools/getting-started/ios/setup/ 101 | [16]: https://bundler.io/ 102 | [17]: https://docs.fastlane.tools/getting-started/ios/setup/#use-a-gemfile 103 | [18]: https://docs.fastlane.tools/advanced/#adding-credentials 104 | [19]: https://travis-ci.org 105 | [20]: https://itunesconnect.apple.com 106 | -------------------------------------------------------------------------------- /Swift-Base.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Swift-Base.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Swift-Base.xcodeproj/xcshareddata/xcschemes/Swift-Base.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 78 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Swift-Base.xcodeproj/xcshareddata/xcschemes/Swift-BaseStaging.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 57 | 59 | 65 | 66 | 67 | 68 | 74 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Swift-Base.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Swift-Base.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Swift-Base/AppDelegate/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Swift-Base 4 | // 5 | // Created by FS Flatstack on 02/10/2014. 6 | // Copyright © 2014 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | import UserNotifications 12 | import Firebase 13 | 14 | @UIApplicationMain 15 | class AppDelegate: UIResponder, UIApplicationDelegate { 16 | 17 | // MARK: - Instance Properties 18 | 19 | var window: UIWindow? 20 | 21 | // MARK: - Instance Methods 22 | 23 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 24 | self.setupProject() 25 | 26 | return true 27 | } 28 | 29 | func applicationWillResignActive(_ application: UIApplication) { } 30 | 31 | func applicationDidEnterBackground(_ application: UIApplication) { } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { } 36 | 37 | func applicationWillTerminate(_ application: UIApplication) { } 38 | 39 | // MARK: - Remote notifications 40 | 41 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 42 | self.saveRemoteNotificationTokenData(application, deviceToken: deviceToken) 43 | } 44 | 45 | func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { } 46 | 47 | func requestForRemoteNotifications () { 48 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { [weak self] granted, error in 49 | guard granted else { 50 | return 51 | } 52 | 53 | self?.getNotificationSettings() 54 | }) 55 | } 56 | } 57 | 58 | // MARK: - Settings 59 | 60 | extension AppDelegate { 61 | 62 | // MARK: - Instance Methods 63 | 64 | private func setupProject() { 65 | FirebaseApp.configure() 66 | 67 | #if RELEASE 68 | Log.i(LogConsolePrinter.shared) 69 | #else 70 | Log.i(LogConsolePrinter.shared) 71 | #endif 72 | } 73 | } 74 | 75 | // MARK: - Push notifications 76 | 77 | extension AppDelegate { 78 | 79 | // MARK: - Instance Methods 80 | 81 | private func saveRemoteNotificationTokenData(_ application: UIApplication, deviceToken: Data) { 82 | let tokenParts = deviceToken.map { data in 83 | String(format: "%02.2hhx", data) 84 | } 85 | 86 | let token = tokenParts.joined() 87 | 88 | Log.i("saveRemoteNotificationTokenData(token: \(token))") 89 | } 90 | 91 | private func getNotificationSettings() { 92 | UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { settings in 93 | guard settings.authorizationStatus == .authorized else { 94 | return 95 | } 96 | 97 | DispatchQueue.main.async { 98 | UIApplication.shared.registerForRemoteNotifications() 99 | } 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Swift-Base/Configuration/Fonts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fonts.swift 3 | // Swift-Base 4 | // 5 | // Created by Timur Shafigullin on 07/03/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum Fonts { 12 | 13 | // MARK: - Type Methods 14 | 15 | static func regular(ofSize size: CGFloat) -> UIFont { 16 | return UIFont.systemFont(ofSize: size, weight: .regular) 17 | } 18 | 19 | static func medium(ofSize size: CGFloat) -> UIFont { 20 | return UIFont.systemFont(ofSize: size, weight: .medium) 21 | } 22 | 23 | static func bold(ofSize size: CGFloat) -> UIFont { 24 | return UIFont.systemFont(ofSize: size, weight: .bold) 25 | } 26 | 27 | static func semibold(ofSize size: CGFloat) -> UIFont { 28 | return UIFont.systemFont(ofSize: size, weight: .semibold) 29 | } 30 | 31 | static func light(ofSize size: CGFloat) -> UIFont { 32 | return UIFont.systemFont(ofSize: size, weight: .light) 33 | } 34 | 35 | static func thin(ofSize size: CGFloat) -> UIFont { 36 | return UIFont.systemFont(ofSize: size, weight: .thin) 37 | } 38 | 39 | static func heavy(ofSize size: CGFloat) -> UIFont { 40 | return UIFont.systemFont(ofSize: size, weight: .heavy) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Swift-Base/Configuration/Generated/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Configuration/Generated/.gitkeep -------------------------------------------------------------------------------- /Swift-Base/Configuration/Keys/Production/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // Swift-Base 4 | // 5 | // Created by Timur Shafigullin on 09/07/2020. 6 | // Copyright © 2020 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Configuration { 12 | 13 | // MARK: - Type Properties 14 | 15 | static let apiServerBaseURL = URL(string: "https://Swift-Base.com")! 16 | static let graphQLURL = URL(string: "https://rails-base-graphql-api.herokuapp.com/graphql")! 17 | } 18 | -------------------------------------------------------------------------------- /Swift-Base/Configuration/Keys/Staging/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // Swift-Base 4 | // 5 | // Created by Timur Shafigullin on 09/07/2020. 6 | // Copyright © 2020 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Configuration { 12 | 13 | // MARK: - Type Properties 14 | 15 | static let apiServerBaseURL = URL(string: "https://Swift-Base.staging.com")! 16 | static let graphQLURL = URL(string: "https://rails-base-graphql-api.herokuapp.com/graphql")! 17 | } 18 | -------------------------------------------------------------------------------- /Swift-Base/Configuration/Services.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Services.swift 3 | // Swift-Base 4 | // 5 | // Created by Elina Batyrova on 28/03/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @available(iOS 13.0, *) 12 | enum Services { 13 | 14 | // MARK: - Type Properties 15 | 16 | static func authorizationService() -> AuthorizationService { 17 | return DefaultAuthorizationService() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/DefaultGraphQLAPIClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultGraphQLAPIClient.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | class DefaultGraphQLAPIClient: GraphQLAPIClient { 15 | 16 | // MARK: - GraphQLAPIClient Properties 17 | 18 | static let shared: GraphQLAPIClient = DefaultGraphQLAPIClient(accessManager: DefaultManagersModule().accessManager) 19 | 20 | // MARK: - 21 | 22 | private let accessManager: AccessManager 23 | 24 | // MARK: - Instance Properties 25 | 26 | private lazy var client: ApolloClient = { 27 | let cache = InMemoryNormalizedCache() 28 | let store = ApolloStore(cache: cache) 29 | let interceptorProvider = NetworkInterceptorProvider(accessManager: self.accessManager, store: store, isUpdateToken: false) 30 | let requestChainNetworkTransport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, endpointURL: Configuration.graphQLURL) 31 | return ApolloClient(networkTransport: requestChainNetworkTransport, store: store) 32 | }() 33 | 34 | // MARK: - Initializers 35 | 36 | init(accessManager: AccessManager) { 37 | self.accessManager = accessManager 38 | } 39 | 40 | // MARK: - GraphQLAPIClient Methods 41 | 42 | func fetchPublisher(query: Query, cachePolicy: CachePolicy, queue: DispatchQueue, contextIdentifier: UUID? = nil) -> Publishers.ApolloFetch { 43 | let config = Publishers.ApolloFetchConfiguration(client: self.client, query: query, cachePolicy: cachePolicy, contextIdentifier: contextIdentifier, queue: queue) 44 | return Publishers.ApolloFetch(with: config) 45 | } 46 | 47 | func performPublisher(mutation: Mutation, queue: DispatchQueue) -> Publishers.ApolloPerform { 48 | let config = Publishers.ApolloPerformConfiguration(client: self.client, mutation: mutation, queue: queue) 49 | return Publishers.ApolloPerform(with: config) 50 | } 51 | 52 | func uploadPublisher(operation: Operation, queue: DispatchQueue, files: [GraphQLFile]) -> Publishers.ApolloUpload { 53 | let config = Publishers.ApolloUploadConfiguration(client: self.client, operation: operation, files: files, queue: queue) 54 | return Publishers.ApolloUpload(with: config) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Fragments/CurrentUserFragment.graphql: -------------------------------------------------------------------------------- 1 | fragment currentUserFragment on CurrentUser { 2 | id, 3 | email, 4 | firstName, 5 | lastName, 6 | avatarUrl 7 | } 8 | 9 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Fragments/UserFragment.graphql: -------------------------------------------------------------------------------- 1 | fragment userFragment on User { 2 | id, 3 | email, 4 | firstName, 5 | lastName, 6 | avatarUrl 7 | } 8 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/GraphQLAPIClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GraphQLAPIClient.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | protocol GraphQLAPIClient { 15 | 16 | // MARK: - Instance Properties 17 | 18 | static var shared: GraphQLAPIClient { get } 19 | 20 | // MARK: - Instance Methods 21 | 22 | func fetchPublisher(query: Query, cachePolicy: CachePolicy, queue: DispatchQueue, contextIdentifier: UUID?) -> Publishers.ApolloFetch 23 | func performPublisher(mutation: Mutation, queue: DispatchQueue) -> Publishers.ApolloPerform 24 | func uploadPublisher(operation: Operation, queue: DispatchQueue, files: [GraphQLFile]) -> Publishers.ApolloUpload 25 | } 26 | 27 | // MARK: - 28 | 29 | @available(iOS 13.0, *) 30 | extension GraphQLAPIClient { 31 | 32 | // MARK: - Insatnсe Methods 33 | 34 | func performPublisher(mutation: Mutation, queue: DispatchQueue = .main) -> Publishers.ApolloPerform { 35 | return self.performPublisher(mutation: mutation, queue: queue) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Interceptors/AuthorizationInterceptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthorizationInterceptor.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20.05.2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | 12 | class AuthorizationInterceptor: ApolloInterceptor { 13 | 14 | // MARK: - Instance Properties 15 | 16 | private let accessManager: AccessManager 17 | private let isUpdateToken: Bool 18 | 19 | // MARK: - Initalizers 20 | 21 | init(accessManager: AccessManager, isUpdateToken: Bool) { 22 | self.accessManager = accessManager 23 | self.isUpdateToken = isUpdateToken 24 | } 25 | 26 | // MARK: - ApolloInterceptor Methods 27 | 28 | func interceptAsync(chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) where Operation: GraphQLOperation { 29 | if self.isUpdateToken { 30 | if let token = self.accessManager.refreshToken { 31 | request.addHeader(name: "Authorization", value: "Bearer \(token)") 32 | } 33 | } else { 34 | if let token = self.accessManager.token { 35 | request.addHeader(name: "Authorization", value: "Bearer \(token)") 36 | } 37 | } 38 | 39 | chain.proceedAsync(request: request, response: response, completion: completion) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Interceptors/ErrorHandlerInterseptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorHandlerInterseptor.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | 12 | class ErrorHandlerInterseptor: ApolloInterceptor { 13 | 14 | // MARK: - ApolloInterceptor Methods 15 | 16 | func interceptAsync(chain: RequestChain, 17 | request: HTTPRequest, 18 | response: HTTPResponse?, 19 | completion: @escaping (Result, Error>) -> Void) where Operation: GraphQLOperation { 20 | if let result = response?.parsedResponse { 21 | if result.data?.resultMap == nil { 22 | let error: NSError = { 23 | if let errors = result.errors, !errors.isEmpty { 24 | return NSError() 25 | } else { 26 | return NSError() 27 | } 28 | }() 29 | chain.handleErrorAsync(error, 30 | request: request, 31 | response: response, 32 | completion: completion) 33 | } else { 34 | chain.proceedAsync(request: request, 35 | response: response, 36 | completion: completion) 37 | } 38 | } else { 39 | chain.proceedAsync(request: request, 40 | response: response, 41 | completion: completion) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Interceptors/LogInterceptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogInterceptor.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | 12 | class LogInterceptor: ApolloInterceptor { 13 | 14 | // MARK: - ApolloInterceptor Methods 15 | 16 | func interceptAsync(chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) where Operation: GraphQLOperation { 17 | Log.i(request) 18 | 19 | if let response = response { 20 | if let parsedResponseData = response.parsedResponse?.data { 21 | Log.i(parsedResponseData) 22 | } else { 23 | Log.e("No response data") 24 | } 25 | } else { 26 | Log.e("No response") 27 | } 28 | 29 | chain.proceedAsync(request: request, response: response, completion: completion) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Interceptors/ParameterInterceptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParameterInterceptor.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20.05.2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | 12 | class ParameterInterceptor: ApolloInterceptor { 13 | 14 | // MARK: - Nested Types 15 | 16 | private enum Keys { 17 | 18 | // MARK: - Type Properties 19 | 20 | static let deviceSource = "device_source" 21 | static let deviceVersion = "device_version" 22 | static let appVersion = "app_version" 23 | } 24 | 25 | // MARK: - ApolloInterceptor Methods 26 | 27 | func interceptAsync(chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) where Operation: GraphQLOperation { 28 | let deviceVersion = UIDevice.current.systemVersion 29 | 30 | var queryItems = [URLQueryItem(name: Keys.deviceSource, value: "iOS"), 31 | URLQueryItem(name: Keys.deviceVersion, value: deviceVersion)] 32 | 33 | if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { 34 | queryItems.append(URLQueryItem(name: Keys.appVersion, value: appVersion)) 35 | } 36 | 37 | let graphQLEndpoint = request.graphQLEndpoint 38 | var urlComponents = URLComponents(string: graphQLEndpoint.absoluteString)! 39 | 40 | urlComponents.queryItems = queryItems 41 | request.graphQLEndpoint = urlComponents.url! 42 | 43 | chain.proceedAsync(request: request, response: response, completion: completion) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Interceptors/RefreshTokenInterceptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshTokenInterceptor.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20.05.2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | class RefreshTokenInterceptor: ApolloInterceptor { 15 | 16 | // MARK: - Nested Types 17 | 18 | private enum JSONKeys { 19 | 20 | // MARK: - Type Properties 21 | 22 | static let profile = "me" 23 | static let status = "status" 24 | } 25 | 26 | // MARK: - Instance Properties 27 | 28 | private let accessManager: AccessManager 29 | private let authorizationService: AuthorizationService = Services.authorizationService() 30 | 31 | private var refreshTokenSubscriber: Combine.Cancellable? 32 | 33 | // MARK: - Initalizers 34 | 35 | init(accessManager: AccessManager) { 36 | self.accessManager = accessManager 37 | } 38 | 39 | // MARK: - Instance Methods 40 | 41 | private func refreshToken(chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) where Operation: GraphQLOperation { 42 | if !(self.accessManager.refreshToken?.isEmpty ?? true) { 43 | self.refreshTokenSubscriber = self.authorizationService.refreshToken()? 44 | .sink { [weak self] completionResult in 45 | switch completionResult { 46 | case .failure(let error): 47 | print(error) 48 | chain.handleErrorAsync(error, request: request, response: response, completion: completion) 49 | self?.authorizationService.signOut() 50 | 51 | case .finished: 52 | print("FINISHED") 53 | break 54 | } 55 | } receiveValue: { accessToken in 56 | print(accessToken) 57 | request.addHeader(name: "Authorization", value: "Bearer \(accessToken)") 58 | chain.retry(request: request, completion: completion) 59 | } 60 | } else { 61 | chain.handleErrorAsync(MyError(title: "Error", message: "Sorry, you email or password is incorrect. Please try again."), request: request, response: response, completion: completion) 62 | } 63 | } 64 | 65 | // MARK: - ApolloInterceptor Methods 66 | 67 | func interceptAsync(chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) where Operation: GraphQLOperation { 68 | if let errors = response?.parsedResponse?.errors { 69 | for error in errors { 70 | if let errorCode = error.extensions?[JSONKeys.status] as? Int, errorCode == 401 { 71 | self.refreshToken(chain: chain, request: request, response: response, completion: completion) 72 | 73 | return 74 | } 75 | } 76 | } else if let resultMap = response?.parsedResponse?.data?.resultMap { 77 | if resultMap.keys.contains(JSONKeys.profile) && !(resultMap[JSONKeys.profile] is ResultMap) { 78 | self.refreshToken(chain: chain, request: request, response: response, completion: completion) 79 | 80 | return 81 | } 82 | } 83 | 84 | chain.proceedAsync(request: request, response: response, completion: completion) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Mutations/RequestPasswordRecovery.graphql: -------------------------------------------------------------------------------- 1 | mutation requestPasswordRecovery($input: RequestPasswordRecoveryInput!) { 2 | requestPasswordRecovery(input: $input) { 3 | detail 4 | message 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Mutations/SignIn.graphql: -------------------------------------------------------------------------------- 1 | mutation signin($input: SignInInput!) { 2 | signin(input: $input) { 3 | me { 4 | ...currentUserFragment 5 | } 6 | accessToken 7 | refreshToken 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Mutations/SignOut.graphql: -------------------------------------------------------------------------------- 1 | mutation signout($input: SignOutInput!) { 2 | signout(input: $input) { 3 | message 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Mutations/SignUp.graphql: -------------------------------------------------------------------------------- 1 | mutation signup($input: SignUpInput!) { 2 | signup(input: $input) { 3 | me { 4 | ...currentUserFragment 5 | } 6 | accessToken 7 | refreshToken 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Mutations/UpdateToken.graphql: -------------------------------------------------------------------------------- 1 | mutation updateToken { 2 | updateToken { 3 | me { 4 | ...currentUserFragment 5 | } 6 | accessToken 7 | refreshToken 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Mutations/UpdateUser.graphql: -------------------------------------------------------------------------------- 1 | mutation updateUser($input: UpdateUserInput!) { 2 | updateUser(input: $input) { 3 | me { 4 | ...currentUserFragment 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Provider/NetworkInterceptorProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkInterceptorProvider.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | 12 | @available(iOS 13.0, *) 13 | class NetworkInterceptorProvider: LegacyInterceptorProvider { 14 | 15 | // MARK: - Insatnce Properties 16 | 17 | private let accessManager: AccessManager 18 | private let isUpdateToken: Bool 19 | 20 | // MARK: - Initializers 21 | 22 | init(accessManager: AccessManager, store: ApolloStore, isUpdateToken: Bool) { 23 | self.accessManager = accessManager 24 | self.isUpdateToken = isUpdateToken 25 | 26 | super.init(store: store) 27 | } 28 | 29 | // MARK: - InterceptorProvider Methods 30 | 31 | override func interceptors(for operation: Operation) -> [ApolloInterceptor] where Operation: GraphQLOperation { 32 | var interceptors = super.interceptors(for: operation) 33 | let authorizationInterceptor = AuthorizationInterceptor(accessManager: self.accessManager, isUpdateToken: self.isUpdateToken) 34 | interceptors.insert(authorizationInterceptor, at: 0) 35 | let parameterInterceptor = ParameterInterceptor() 36 | interceptors.append(parameterInterceptor) 37 | if !self.isUpdateToken { 38 | let refreshTokenInterceptor = RefreshTokenInterceptor(accessManager: self.accessManager) 39 | interceptors.append(refreshTokenInterceptor) 40 | } 41 | let logInterceptor = LogInterceptor() 42 | interceptors.append(logInterceptor) 43 | 44 | let errorHandlerInterceptor = ErrorHandlerInterseptor() 45 | interceptors.append(errorHandlerInterceptor) 46 | 47 | return interceptors 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/Queries/FetchCurrentUser.graphql: -------------------------------------------------------------------------------- 1 | query FetchCurrentUser { 2 | me { 3 | ...currentUserFragment 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Swift-Base/GraphQL/UpdateTokenGraphQLAPIClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateTokenGraphQLAPIClient.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | class UpdateTokenGraphQLAPIClient: GraphQLAPIClient { 15 | 16 | // MARK: - GraphQLAPIClient Properties 17 | 18 | static let shared: GraphQLAPIClient = UpdateTokenGraphQLAPIClient(accessManager: DefaultManagersModule().accessManager) 19 | 20 | // MARK: - 21 | 22 | private let accessManager: AccessManager 23 | 24 | // MARK: - Instance Properties 25 | 26 | private lazy var client: ApolloClient = { 27 | let cache = InMemoryNormalizedCache() 28 | let store = ApolloStore(cache: cache) 29 | let interceptorProvider = NetworkInterceptorProvider(accessManager: self.accessManager, store: store, isUpdateToken: true) 30 | let requestChainNetworkTransport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, endpointURL: Configuration.graphQLURL) 31 | return ApolloClient(networkTransport: requestChainNetworkTransport, store: store) 32 | }() 33 | 34 | // MARK: - Initializers 35 | 36 | init(accessManager: AccessManager) { 37 | self.accessManager = accessManager 38 | } 39 | 40 | // MARK: - GraphQLAPIClient Methods 41 | 42 | func fetchPublisher(query: Query, cachePolicy: CachePolicy, queue: DispatchQueue, contextIdentifier: UUID? = nil) -> Publishers.ApolloFetch { 43 | let config = Publishers.ApolloFetchConfiguration(client: self.client, query: query, cachePolicy: cachePolicy, contextIdentifier: contextIdentifier, queue: queue) 44 | return Publishers.ApolloFetch(with: config) 45 | } 46 | 47 | func performPublisher(mutation: Mutation, queue: DispatchQueue) -> Publishers.ApolloPerform { 48 | let config = Publishers.ApolloPerformConfiguration(client: self.client, mutation: mutation, queue: queue) 49 | return Publishers.ApolloPerform(with: config) 50 | } 51 | 52 | func uploadPublisher(operation: Operation, queue: DispatchQueue, files: [GraphQLFile]) -> Publishers.ApolloUpload { 53 | let config = Publishers.ApolloUploadConfiguration(client: self.client, operation: operation, files: files, queue: queue) 54 | return Publishers.ApolloUpload(with: config) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Swift-Base/Managers/Access Manager/AccessManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol AccessManager { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var token: String? { get set } 16 | var refreshToken: String? { get set } 17 | var expiryDate: Date? { get set } 18 | 19 | // MARK: - Instance Methods 20 | 21 | func resetAccess() 22 | } 23 | -------------------------------------------------------------------------------- /Swift-Base/Managers/Access Manager/DefaultAccessManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultAccessManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto 11 | 12 | struct DefaultAccessManager: AccessManager { 13 | 14 | // MARK: - Nested Types 15 | 16 | private enum Constants { 17 | 18 | // MARK: - Type Properties 19 | 20 | static let tokenKey = "AccessToken" 21 | static let refreshTokenKey = "RefreshToken" 22 | static let expiryDateKey = "AccessExpiryDate" 23 | } 24 | 25 | // MARK: - Instance Properties 26 | 27 | let keychainManager: KeychainManager 28 | 29 | // MARK: - 30 | 31 | var token: String? { 32 | get { 33 | return self.keychainManager.string(forKey: Constants.tokenKey) 34 | } 35 | 36 | set { 37 | if let token = newValue { 38 | self.keychainManager.save(string: token, forKey: Constants.tokenKey) 39 | } else { 40 | self.keychainManager.removeObject(forKey: Constants.tokenKey) 41 | } 42 | } 43 | } 44 | 45 | var refreshToken: String? { 46 | get { 47 | return self.keychainManager.string(forKey: Constants.refreshTokenKey) 48 | } 49 | 50 | set { 51 | if let token = newValue { 52 | self.keychainManager.save(string: token, forKey: Constants.refreshTokenKey) 53 | } else { 54 | self.keychainManager.removeObject(forKey: Constants.refreshTokenKey) 55 | } 56 | } 57 | } 58 | 59 | var expiryDate: Date? { 60 | get { 61 | if let expiryTimeInterval = self.keychainManager.object(forKey: Constants.expiryDateKey) as? NSNumber { 62 | return Date(timeIntervalSinceReferenceDate: expiryTimeInterval.doubleValue) 63 | } else { 64 | return nil 65 | } 66 | } 67 | 68 | set { 69 | if let expiryTimeInterval = newValue?.timeIntervalSinceReferenceDate { 70 | self.keychainManager.save(double: expiryTimeInterval, forKey: Constants.expiryDateKey) 71 | } else { 72 | self.keychainManager.removeObject(forKey: Constants.expiryDateKey) 73 | } 74 | } 75 | } 76 | 77 | // MARK: - Instance Methods 78 | 79 | func resetAccess() { 80 | self.keychainManager.removeObject(forKey: Constants.refreshTokenKey) 81 | self.keychainManager.removeObject(forKey: Constants.tokenKey) 82 | self.keychainManager.removeObject(forKey: Constants.expiryDateKey) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Swift-Base/Managers/DefaultManagersModule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultManagersModule.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DefaultManagersModule: ManagersModule { 12 | 13 | // MARK: - Type Properties 14 | 15 | lazy var accessManager: AccessManager = DefaultAccessManager(keychainManager: self.keyhainManager) 16 | lazy var keyhainManager: KeychainManager = DefaultKeychainManager(serviceName: Bundle.main.bundleIdentifier!) 17 | lazy var userDefaultsManager: UserDefaultsManager = DefaultUserDefaultsManager() 18 | } 19 | -------------------------------------------------------------------------------- /Swift-Base/Managers/Keychain Manager/DefaultKeychainManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultKeychainManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DefaultKeychainManager: KeychainManager { 12 | 13 | // MARK: - Instance Properties 14 | 15 | let serviceName: String 16 | 17 | // MARK: - KeychainManager Methods 18 | 19 | @discardableResult 20 | func string(forKey key: String) -> String? { 21 | guard let data = self.data(forKey: key) else { 22 | return nil 23 | } 24 | 25 | return String(data: data, encoding: .utf8) 26 | } 27 | 28 | @discardableResult 29 | func object(forKey key: String) -> NSCoding? { 30 | guard let data = data(forKey: key) else { 31 | return nil 32 | } 33 | 34 | return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSCoding 35 | } 36 | 37 | @discardableResult 38 | func data(forKey key: String) -> Data? { 39 | var queryDictionary: [String: Any] = [:] 40 | var result: AnyObject? 41 | 42 | self.configure(queryDictionary: &queryDictionary, for: key) 43 | 44 | queryDictionary[kSecMatchLimit as String] = kSecMatchLimitOne 45 | queryDictionary[kSecReturnData as String] = kCFBooleanTrue 46 | 47 | let status = withUnsafeMutablePointer(to: &result, { pointer in 48 | SecItemCopyMatching(queryDictionary as CFDictionary, UnsafeMutablePointer(pointer)) 49 | }) 50 | 51 | return status == noErr ? result as? Data : nil 52 | } 53 | 54 | @discardableResult 55 | func save(string: String, forKey key: String) -> Bool { 56 | if let data = string.data(using: .utf8) { 57 | return self.save(data: data, forKey: key) 58 | } else { 59 | return false 60 | } 61 | } 62 | 63 | @discardableResult 64 | func save(double: Double, forKey key: String) -> Bool { 65 | return self.save(object: NSNumber(value: double), forKey: key) 66 | } 67 | 68 | @discardableResult 69 | func save(object: NSCoding, forKey key: String) -> Bool { 70 | guard let data = try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false) else { 71 | fatalError() 72 | } 73 | 74 | return self.save(data: data, forKey: key) 75 | } 76 | 77 | @discardableResult 78 | func save(data: Data, forKey key: String) -> Bool { 79 | var queryDictionary: [String: Any] = [:] 80 | 81 | self.configure(queryDictionary: &queryDictionary, for: key) 82 | 83 | queryDictionary[kSecValueData as String] = data 84 | 85 | let status = SecItemAdd(queryDictionary as CFDictionary, nil) 86 | 87 | switch status { 88 | case errSecSuccess: 89 | return true 90 | 91 | case errSecDuplicateItem: 92 | return self.update(data, for: key) 93 | 94 | default: 95 | return true 96 | } 97 | } 98 | 99 | @discardableResult 100 | func removeObject(forKey key: String) -> Bool { 101 | var queryDictionary: [String: Any] = [:] 102 | 103 | self.configure(queryDictionary: &queryDictionary, for: key) 104 | 105 | let status = SecItemDelete(queryDictionary as CFDictionary) 106 | 107 | return status == errSecSuccess 108 | } 109 | 110 | @discardableResult 111 | func removeAllData() -> Bool { 112 | var queryDictionary: [String: Any] = [:] 113 | 114 | self.configure(queryDictionary: &queryDictionary) 115 | 116 | let status = SecItemDelete(queryDictionary as CFDictionary) 117 | 118 | return status == errSecSuccess 119 | } 120 | 121 | // MARK: - Instance Properties 122 | 123 | private func update(_ value: Data, for key: String) -> Bool { 124 | var queryDictionary: [String: Any] = [:] 125 | 126 | self.configure(queryDictionary: &queryDictionary, for: key) 127 | 128 | let updateDictionary = [(kSecValueData as String): value] 129 | 130 | let status = SecItemUpdate(queryDictionary as CFDictionary, updateDictionary as CFDictionary) 131 | 132 | return status == errSecSuccess 133 | } 134 | 135 | private func configure(queryDictionary: inout [String: Any], for key: String? = nil) { 136 | queryDictionary[kSecClass as String] = kSecClassGenericPassword 137 | queryDictionary[kSecAttrService as String] = self.serviceName as AnyObject? 138 | 139 | if let key = key { 140 | queryDictionary[kSecAttrAccount as String] = key as AnyObject? 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Swift-Base/Managers/Keychain Manager/KeychainManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol KeychainManager { 12 | 13 | // MARK: - Instance Properties 14 | 15 | @discardableResult 16 | func string(forKey key: String) -> String? 17 | 18 | @discardableResult 19 | func object(forKey key: String) -> NSCoding? 20 | 21 | @discardableResult 22 | func data(forKey key: String) -> Data? 23 | 24 | @discardableResult 25 | func save(string: String, forKey key: String) -> Bool 26 | 27 | @discardableResult 28 | func save(double: Double, forKey key: String) -> Bool 29 | 30 | @discardableResult 31 | func save(object: NSCoding, forKey key: String) -> Bool 32 | 33 | @discardableResult 34 | func save(data: Data, forKey key: String) -> Bool 35 | 36 | @discardableResult 37 | func removeObject(forKey key: String) -> Bool 38 | 39 | @discardableResult 40 | func removeAllData() -> Bool 41 | } 42 | -------------------------------------------------------------------------------- /Swift-Base/Managers/ManagersModule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManagersModule.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ManagersModule { 12 | 13 | // MARK: - Type Properties 14 | 15 | var accessManager: AccessManager { get } 16 | var keyhainManager: KeychainManager { get } 17 | var userDefaultsManager: UserDefaultsManager { get } 18 | } 19 | -------------------------------------------------------------------------------- /Swift-Base/Managers/User Defaults Manager/DefaultUserDefaultsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultUserDefaultsManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DefaultUserDefaultsManager: UserDefaultsManager { 12 | 13 | // MARK: - Nested Types 14 | 15 | private enum Constants { 16 | 17 | // MARK: - Type Properties 18 | 19 | static let deviceTokenKey = "DeviceToken" 20 | } 21 | 22 | // MARK: - Instance Properties 23 | 24 | let userDefaults = UserDefaults.standard 25 | 26 | // MARK: - 27 | 28 | var deviceToken: String? { 29 | get { 30 | return self.userDefaults.string(forKey: Constants.deviceTokenKey) 31 | } 32 | 33 | set { 34 | if let token = newValue { 35 | self.userDefaults.set(token, forKey: Constants.deviceTokenKey) 36 | } else { 37 | self.userDefaults.removeObject(forKey: Constants.deviceTokenKey) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Swift-Base/Managers/User Defaults Manager/UserDefaultsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol UserDefaultsManager { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var deviceToken: String? { get set } 16 | } 17 | -------------------------------------------------------------------------------- /Swift-Base/Models/Cache/Cache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cache.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 23/11/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Cache { 12 | 13 | // MARK: - Type Properties 14 | 15 | static let storageModel: StorageModel = CoreDataStorageModel(identifier: "Swift-Base") 16 | 17 | // MARK: - Instance Properties 18 | 19 | lazy var currentUserCacheManager: CurrentUserCacheManager = DefaultCurrentUserCacheManager(storageModel: Cache.storageModel) 20 | lazy var userCacheManager: UserCacheManager = DefaultUserCacheManager(storageModel: Cache.storageModel) 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Models/Cache/Swift-Base.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | Swift-Base.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Swift-Base/Models/Cache/Swift-Base.xcdatamodeld/Swift-Base.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift-Base/Models/CacheManagers/CurrentUserCacheManager/CurrentUserCacheManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentUserCacheManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 24/11/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol CurrentUserCacheManager { 12 | 13 | // MARK: - Instance Methods 14 | 15 | @discardableResult 16 | func createOrUpdate(from me: UserFragment, context: StorageContext) -> CurrentUser 17 | func createOrUpdate(from me: CurrentUserFragment, context: StorageContext) -> CurrentUser 18 | func save() 19 | func save(context: StorageContext) 20 | func currentUser() -> CurrentUser? 21 | func remove() 22 | } 23 | -------------------------------------------------------------------------------- /Swift-Base/Models/CacheManagers/CurrentUserCacheManager/DefaultCurrentUserCacheManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultCurrentUserCacheManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 24/11/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DefaultCurrentUserCacheManager: CurrentUserCacheManager { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var storageModel: StorageModel! 16 | 17 | // MARK: - Instance Methods 18 | 19 | @discardableResult 20 | func createOrUpdate(from me: UserFragment, context: StorageContext) -> CurrentUser { 21 | var currentUser: CurrentUser! 22 | let predicate = NSPredicate(format: "id == %@", me.id) 23 | currentUser = createOrUpdateUser(from: predicate, context: context) 24 | currentUser.id = me.id 25 | currentUser.firstName = me.firstName 26 | currentUser.lastName = me.lastName 27 | currentUser.avatarURLRaw = me.avatarUrl 28 | 29 | return currentUser 30 | } 31 | 32 | @discardableResult 33 | func createOrUpdate(from me: CurrentUserFragment, context: StorageContext) -> CurrentUser { 34 | var currentUser: CurrentUser! 35 | let predicate = NSPredicate(format: "id == %@", me.id) 36 | currentUser = createOrUpdateUser(from: predicate, context: context) 37 | currentUser.id = me.id 38 | currentUser.firstName = me.firstName 39 | currentUser.lastName = me.lastName 40 | currentUser.avatarURLRaw = me.avatarUrl 41 | 42 | return currentUser 43 | } 44 | 45 | func save() { 46 | self.storageModel.viewContext.save() 47 | } 48 | 49 | func save(context: StorageContext) { 50 | context.save() 51 | } 52 | 53 | func currentUser() -> CurrentUser? { 54 | return self.storageModel.viewContext.manager.fetch(CurrentUser.self, sortDescriptors: []).first 55 | } 56 | 57 | func remove() { 58 | self.storageModel.viewContext.manager.clear(CurrentUser.self, sortDescriptors: []) 59 | } 60 | 61 | private func createOrUpdateUser(from predicate: NSPredicate, context: StorageContext) -> CurrentUser { 62 | if let existingCurrentUser = context.manager.first(CurrentUser.self, sortDescriptors: [], predicate: predicate) { 63 | return existingCurrentUser 64 | } else { 65 | guard let newCurrentUser = context.manager.append(CurrentUser.self) else { 66 | fatalError("CurrentUser can't be initialized") 67 | } 68 | return newCurrentUser 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Swift-Base/Models/CacheManagers/UserCacheManager/DefaultUserCacheManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultUserCacheManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 24/11/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DefaultUserCacheManager: UserCacheManager { 12 | 13 | // MARK: - Instance Properties 14 | 15 | let storageModel: StorageModel 16 | 17 | // MARK: - Instance Methods 18 | 19 | func createOrUpdate(from fragment: UserFragment, context: StorageContext) -> User { 20 | var user: User! 21 | let predicate = NSPredicate(format: "id == %@", fragment.id) 22 | 23 | if let existingUser = context.manager.first(User.self, sortDescriptors: [], predicate: predicate) { 24 | user = existingUser 25 | } else { 26 | guard let newUser = context.manager.append(User.self) else { 27 | fatalError("User can't be initialized") 28 | } 29 | user = newUser 30 | } 31 | 32 | user.id = fragment.id 33 | user.firstName = fragment.firstName 34 | user.lastName = fragment.lastName 35 | user.avatarURLRaw = fragment.avatarUrl 36 | 37 | return user 38 | } 39 | 40 | func fetch(with id: String?) -> User? { 41 | if let id = id { 42 | let predicate = NSPredicate(format: "id == %@", id) 43 | let user = self.storageModel.viewContext.manager.first(User.self, sortDescriptors: [], predicate: predicate) 44 | return user 45 | } else { 46 | return nil 47 | } 48 | } 49 | 50 | func fetch(with ids: [String?]) -> [User] { 51 | var users: [User] = [] 52 | for id in ids { 53 | if let user = self.fetch(with: id) { 54 | users.append(user) 55 | } 56 | } 57 | return users 58 | } 59 | 60 | func save(context: StorageContext) { 61 | context.save() 62 | } 63 | 64 | func remove() { 65 | self.storageModel.viewContext.manager.clear(User.self, sortDescriptors: []) 66 | } 67 | 68 | func remove(with id: String) { 69 | let predicate = NSPredicate(format: "id == %@", id) 70 | self.storageModel.viewContext.manager.clear(User.self, sortDescriptors: [], predicate: predicate) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Swift-Base/Models/CacheManagers/UserCacheManager/UserCacheManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserCacheManager.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 24/11/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol UserCacheManager { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func createOrUpdate(from fragment: UserFragment, context: StorageContext) -> User 16 | func fetch(with id: String?) -> User? 17 | func fetch(with ids: [String?]) -> [User] 18 | func save(context: StorageContext) 19 | func remove() 20 | func remove(with id: String) 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Models/CurrentUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentUser.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 23/11/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @objc(CurrentUser) 13 | class CurrentUser: User { 14 | } 15 | -------------------------------------------------------------------------------- /Swift-Base/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 23/11/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import Apollo 12 | 13 | class User: NSManagedObject { 14 | 15 | // MARK: - Instance Properties 16 | 17 | var avatarURL: URL? { 18 | if let avatarURLRaw = self.avatarURLRaw { 19 | return URL(string: avatarURLRaw) 20 | } else { 21 | return nil 22 | } 23 | } 24 | 25 | var username: String? { 26 | var fullName = self.firstName ?? "" 27 | 28 | if self.lastName?.isEmpty == false { 29 | if fullName.isEmpty == false { 30 | fullName = "\(fullName) \(self.lastName ?? "")" 31 | } else { 32 | fullName = self.lastName ?? "" 33 | } 34 | } 35 | 36 | return fullName 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/1024.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/120-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/120.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/152.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/167.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/180.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/20.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/29.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/40-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/40-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/40-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/40-2.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/40.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/58-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/58.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/60.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/76.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/80-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/80.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/87.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon-Staging.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120-1.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "40-1.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "58-1.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "40-2.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "80-1.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "152.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "167.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/120-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/40-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/40-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/40-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/40-2.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/58-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/80-1.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fs/ios-base-swift/8c8e791583fee1ee4207896ebe59a84a6820b8c0/Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120-1.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "40-1.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "58-1.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "40-2.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "80-1.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "152.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "167.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Swift-Base/Resources/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Swift-Base/Resources/Localization/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | /*------------Common-----------*/ 4 | "alert_close" = "Close"; 5 | "alert_dismiss" = "Dismiss"; 6 | "alert_ok" = "OK"; 7 | 8 | /*-------------API-----------*/ 9 | "internet_disconnected_title" = "No Internet Connection"; 10 | "internet_disconnected_message" = "Please check your Wi-Fi or mobile network connection and try again."; 11 | "unknown_error_title" = "Something went wrong"; 12 | "unknown_error_message" = "The Swift-Base team has been notified and is working on a fix. Please try again later."; 13 | 14 | /*-------------DEMO-----------*/ 15 | "press_ok" = "Press OK"; 16 | "ok" = "OK"; 17 | -------------------------------------------------------------------------------- /Swift-Base/Resources/Localization/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | /*------------Common-----------*/ 4 | "alert_close" = "Закрыть"; 5 | "alert_dismiss" = "Отменить"; 6 | "alert_ok" = "OK"; 7 | 8 | /*-------------API-----------*/ 9 | "internet_disconnected_title" = "Нет интернет подключения"; 10 | "internet_disconnected_message" = "Пожалуйста, проверьте подключение к Wi-Fi или мобильной сети и повторите попытку."; 11 | "unknown_error_title" = "Что-то пошло не так"; 12 | "unknown_error_message" = "Команда Swift-Base была уведомлена и работает над исправлением. Пожалуйста, попробуйте позже."; 13 | 14 | /*-------------DEMO-----------*/ 15 | "press_ok" = "Нажмите ОК"; 16 | "ok" = "OK"; 17 | -------------------------------------------------------------------------------- /Swift-Base/Services/AuthorizationService/AuthorizationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthorizationService.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | @available(iOS 13.0, *) 13 | protocol AuthorizationService { 14 | 15 | // MARK: - Instance Methods 16 | 17 | func refreshToken() -> AnyPublisher? 18 | func signOut() 19 | } 20 | -------------------------------------------------------------------------------- /Swift-Base/Services/AuthorizationService/DefaultAuthorizationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultAuthorizationService.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20/05/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Apollo 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | final class DefaultAuthorizationService: AuthorizationService { 15 | 16 | // MARK: - Instance Properties 17 | 18 | var apiClient: GraphQLAPIClient = DefaultGraphQLAPIClient.shared 19 | var refreshTokenApiClient = UpdateTokenGraphQLAPIClient.shared 20 | var accessManager: AccessManager = DefaultManagersModule().accessManager 21 | 22 | // MARK: - Instance Methods 23 | 24 | func refreshToken() -> AnyPublisher? { 25 | return nil 26 | } 27 | 28 | func signOut() { 29 | self.accessManager.resetAccess() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Swift-Base/Services/CurrentUserService/CurrentUserService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentUserService.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 12/01/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | @available(iOS 13.0, *) 13 | protocol CurrentUserService { 14 | 15 | // MARK: - Instance Methods 16 | 17 | func fetchCurrentUser() -> AnyPublisher 18 | func storedCurrentUser() -> CurrentUser? 19 | func updateUser(input: UpdateUserInput) -> AnyPublisher 20 | } 21 | -------------------------------------------------------------------------------- /Swift-Base/Services/CurrentUserService/DefaultCurrentUserService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultCurrentUserService.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 12/01/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | import Apollo 12 | 13 | @available(iOS 13.0, *) 14 | final class DefaultCurrentUserService: CurrentUserService { 15 | 16 | // MARK: - Instance Properties 17 | 18 | let apiClient: GraphQLAPIClient 19 | let currentUserCacheManager: CurrentUserCacheManager 20 | let userCacheManager: UserCacheManager 21 | let userDefaultsManager: UserDefaultsManager 22 | let firebaseAnalyticsService: FirebaseAnalyticsService 23 | 24 | // MARK: - 25 | 26 | private var createDeviceSubscriber: AnyCancellable? 27 | 28 | // MARK: - Initializers 29 | 30 | init(apiClient: GraphQLAPIClient, currentUserCacheManager: CurrentUserCacheManager, userCacheManager: UserCacheManager, userDefaultsManager: UserDefaultsManager, firebaseAnalyticsService: FirebaseAnalyticsService) { 31 | self.apiClient = apiClient 32 | self.currentUserCacheManager = currentUserCacheManager 33 | self.userCacheManager = userCacheManager 34 | self.userDefaultsManager = userDefaultsManager 35 | self.firebaseAnalyticsService = firebaseAnalyticsService 36 | } 37 | 38 | // MARK: - Instance Methods 39 | 40 | func fetchCurrentUser() -> AnyPublisher { 41 | let fetchCurrentUserProfile = FetchCurrentUserQuery() 42 | let queue = DispatchQueue(label: "Fetch_CurrentUser_Query", qos: .utility) 43 | return self.apiClient.fetchPublisher(query: fetchCurrentUserProfile, 44 | cachePolicy: CachePolicy.fetchIgnoringCacheData, 45 | queue: queue, 46 | contextIdentifier: nil) 47 | .tryMap { result in 48 | guard let currentUserFragment = result.data?.me?.fragments.currentUserFragment else { 49 | self.firebaseAnalyticsService.logEvent(with: AnalyticKeys.CurrentUser.failureFetchingUser) 50 | throw MyError(title: "Error", message: "Can't fetch user.") 51 | } 52 | let context = Cache.storageModel.viewContext.createPrivateQueueChildContext() 53 | _ = self.currentUserCacheManager.createOrUpdate(from: currentUserFragment, context: context) 54 | self.currentUserCacheManager.save(context: context) 55 | guard let storedUser = self.currentUserCacheManager.currentUser() else { 56 | throw MyError(title: "Error", message: "User was not stored") 57 | } 58 | self.firebaseAnalyticsService.logEvent(with: AnalyticKeys.CurrentUser.successFetchingUser) 59 | #warning("Implement registering device") 60 | // self.registerDevice() 61 | return storedUser 62 | } 63 | .mapError({ error in 64 | return error as? MyError ?? MyError(title: "Error", message: error.localizedDescription) 65 | }) 66 | .receive(on: DispatchQueue.main) 67 | .eraseToAnyPublisher() 68 | } 69 | 70 | func storedCurrentUser() -> CurrentUser? { 71 | self.currentUserCacheManager.currentUser() 72 | } 73 | 74 | func updateUser(input: UpdateUserInput) -> AnyPublisher { 75 | let updateUserMutation = UpdateUserMutation(input: input) 76 | let queue = DispatchQueue(label: "Update_User_Mutation", qos: .utility) 77 | return self.apiClient.performPublisher(mutation: updateUserMutation, queue: queue) 78 | .tryMap({ result in 79 | guard let updateData = result.data?.updateUser else { 80 | self.firebaseAnalyticsService.logEvent(with: AnalyticKeys.CurrentUser.failureUpdatinUser) 81 | throw MyError(title: "Error", message: "Can't update user.") 82 | } 83 | let userFragment = updateData.me.fragments.currentUserFragment 84 | let context = Cache.storageModel.viewContext.createPrivateQueueChildContext() 85 | _ = self.currentUserCacheManager.createOrUpdate(from: userFragment, context: context) 86 | self.currentUserCacheManager.save(context: context) 87 | guard let storedUser = self.currentUserCacheManager.currentUser() else { 88 | throw MyError(title: "Error", message: "User was not stored") 89 | } 90 | self.firebaseAnalyticsService.logEvent(with: AnalyticKeys.CurrentUser.successUpdatingUser) 91 | return storedUser 92 | }) 93 | .mapError({ error in 94 | return error as? MyError ?? MyError(title: "Error", message: error.localizedDescription) 95 | }) 96 | .receive(on: DispatchQueue.main) 97 | .eraseToAnyPublisher() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Swift-Base/Services/FirebaseAnalyticsService/AnalyticKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticKeys.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 12/01/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum AnalyticKeys { 12 | 13 | // MARK: - Default 14 | 15 | enum CurrentUser { 16 | static let failureFetchingUser = "FAILURE_FETCHING_USER" 17 | static let successFetchingUser = "SUCCESS_FETCHING_USER" 18 | static let failureUpdatinUser = "FAILURE_UPDATING_USER" 19 | static let successUpdatingUser = "SUCCESS_UPDATING_USER" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Services/FirebaseAnalyticsService/DefaultFirebaseAnalyticsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultFirebaseAnalyticsService.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 12/01/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #warning("Implement analytics") 12 | struct DefaultFirebaseAnalyticsService: FirebaseAnalyticsService { 13 | 14 | // MARK: - Instance methods 15 | 16 | func logEvent(with name: String) { 17 | } 18 | 19 | func logEvent(with name: String, parameters: [AnyHashable: Any]) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Services/FirebaseAnalyticsService/FirebaseAnalyticsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseAnalyticsService.swift 3 | // Swift-Base 4 | // 5 | // Created by Maksim Kapitonov on 12/01/2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol FirebaseAnalyticsService { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func logEvent(with name: String) 16 | func logEvent(with name: String, parameters: [AnyHashable: Any]) 17 | } 18 | -------------------------------------------------------------------------------- /Swift-Base/Stroyboards/Base.lproj/Main_Storyboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Swift-Base/Stroyboards/Base.lproj/Main_iPad.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Swift-Base/Stroyboards/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Swift-Base/Stroyboards/ru.lproj/Main_Storyboard.strings: -------------------------------------------------------------------------------- 1 | /*Use this to generate new strings files - 2 | ibtool Base.lproj/Main.storyboard --generate-strings-file ~/Desktop/str.strings 3 | */ 4 | 5 | /* Class = "UIButton"; normalTitle = "Tap to show alert"; ObjectID = "Eky-89-Wjw"; */ 6 | "Eky-89-Wjw.normalTitle" = "Tap to show alert"; 7 | 8 | /* Class = "UILabel"; accessibilityLabel = "Hello World!"; ObjectID = "LaI-Mr-U2B"; */ 9 | "LaI-Mr-U2B.accessibilityLabel" = "Hello World!"; 10 | 11 | /* Class = "UILabel"; text = "Hello World!"; ObjectID = "LaI-Mr-U2B"; */ 12 | "LaI-Mr-U2B.text" = "Hello World!"; 13 | -------------------------------------------------------------------------------- /Swift-Base/Stroyboards/ru.lproj/Main_iPad.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Swift-Base/Supporting Files/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | CLIENT_ID 7 | REVERSED_CLIENT_ID 8 | REVERSED_CLIENT_ID 9 | ANDROID_CLIENT_ID 10 | ANDROID_CLIENT_ID 11 | API_KEY 12 | API_KEY 13 | GCM_SENDER_ID 14 | GSM_SENDER_ID 15 | PLIST_VERSION 16 | 1 17 | BUNDLE_ID 18 | com.ios.base.flatstack 19 | PROJECT_ID 20 | PROJECT_ID 21 | STORAGE_BUCKET 22 | STORAGE_BUCKET 23 | IS_ADS_ENABLED 24 | 25 | IS_ANALYTICS_ENABLED 26 | 27 | IS_APPINVITE_ENABLED 28 | 29 | IS_GCM_ENABLED 30 | 31 | IS_SIGNIN_ENABLED 32 | 33 | GOOGLE_APP_ID 34 | 1:644059465480:ios:ae6225d242dbe438 35 | DATABASE_URL 36 | DABASE_URL 37 | 38 | 39 | -------------------------------------------------------------------------------- /Swift-Base/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | $(PRODUCT_NAME) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLName 29 | com.flatstack.Swift-Base 30 | CFBundleURLSchemes 31 | 32 | DeepLink 33 | 34 | 35 | 36 | CFBundleVersion 37 | $(CURRENT_PROJECT_VERSION) 38 | ITSAppUsesNonExemptEncryption 39 | 40 | LSRequiresIPhoneOS 41 | 42 | NSAppTransportSecurity 43 | 44 | NSExceptionDomains 45 | 46 | httpbin.org 47 | 48 | NSIncludesSubdomains 49 | 50 | NSTemporaryExceptionAllowsInsecureHTTPLoads 51 | 52 | NSTemporaryExceptionMinimumTLSVersion 53 | TLSv1.1 54 | 55 | 56 | 57 | UILaunchStoryboardName 58 | LaunchScreen 59 | UIMainStoryboardFile 60 | Main_Storyboard 61 | UIMainStoryboardFile~ipad 62 | Main_iPad 63 | UIRequiredDeviceCapabilities 64 | 65 | armv7 66 | 67 | UIStatusBarStyle 68 | UIStatusBarStyleDefault 69 | UISupportedInterfaceOrientations 70 | 71 | UIInterfaceOrientationPortrait 72 | 73 | UISupportedInterfaceOrientations~ipad 74 | 75 | UIInterfaceOrientationPortrait 76 | UIInterfaceOrientationPortraitUpsideDown 77 | UIInterfaceOrientationLandscapeLeft 78 | UIInterfaceOrientationLandscapeRight 79 | 80 | UIViewControllerBasedStatusBarAppearance 81 | 82 | URL_HOST 83 | ${URL_HOST} 84 | 85 | 86 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Error/MyError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // Swift-Base 4 | // 5 | // Created by Анастасия Леонтьева on 20.05.2021. 6 | // Copyright © 2021 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct MyError: Error { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var title: String? 16 | var message: String 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Event/Event.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Event.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 11.05.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class Event { 12 | 13 | // MARK: - Nested Types 14 | 15 | public typealias Handler = ((T) -> Void) 16 | 17 | // MARK: - Instance Properties 18 | 19 | fileprivate(set) var listeners: [EventListener] = [] 20 | 21 | // MARK: - Instance Methods 22 | 23 | @discardableResult 24 | public func connect(_ receiver: AnyObject, handler: @escaping Handler) -> EventListener { 25 | let listener = EventListener(receiver: receiver, 26 | event: self, 27 | handler: handler) 28 | 29 | self.listeners.append(listener) 30 | 31 | return listener 32 | } 33 | 34 | public func disconnect(_ receiver: AnyObject) { 35 | while let index = self.listeners.firstIndex(where: { $0.receiver === receiver }) { 36 | self.listeners.remove(at: index) 37 | } 38 | } 39 | 40 | public func dispose(connection: EventConnection) { 41 | if let index = self.listeners.firstIndex(where: { $0.connection === connection }) { 42 | self.listeners.remove(at: index) 43 | } 44 | } 45 | 46 | public func emit(data: T) { 47 | for listener in self.listeners { 48 | listener.emit(data: data) 49 | } 50 | } 51 | } 52 | 53 | // MARK: - 54 | 55 | public extension Event where T == Void { 56 | 57 | // MARK: - Instance Methods 58 | 59 | func emit() { 60 | self.emit(data: Void()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Event/EventConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventConnection.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 12.05.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol EventConnection: AnyObject { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var isPaused: Bool { get } 16 | 17 | // MARK: - Instance Methods 18 | 19 | func unpause() 20 | func pause() 21 | 22 | func dispose() 23 | } 24 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Event/EventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventListener.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 12.05.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class EventListener { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public fileprivate(set) weak var receiver: AnyObject? 16 | 17 | public let event: Event 18 | public let handler: Event.Handler 19 | 20 | public let connection: EventConnection 21 | 22 | // MARK: - Initializers 23 | 24 | init(receiver: AnyObject, event: Event, handler: @escaping Event.Handler) { 25 | self.receiver = receiver 26 | 27 | self.event = event 28 | self.handler = handler 29 | 30 | self.connection = EventListenerConnection(event: event) 31 | } 32 | 33 | // MARK: - Instance Methods 34 | 35 | public func emit(data: T) { 36 | if (self.receiver != nil) && (!self.connection.isPaused) { 37 | self.handler(data) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Event/EventListenerConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventListenerConnection.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 12.05.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class EventListenerConnection: EventConnection { 12 | 13 | // MARK: - Instance Properties 14 | 15 | let event: Event 16 | 17 | // MARK: - EventConnection 18 | 19 | fileprivate(set) var isPaused = false 20 | 21 | // MARK: - Initializers 22 | 23 | init(event: Event) { 24 | self.event = event 25 | } 26 | 27 | // MARK: - Instance Methods 28 | 29 | func unpause() { 30 | self.isPaused = false 31 | } 32 | 33 | func pause() { 34 | self.isPaused = true 35 | } 36 | 37 | func dispose() { 38 | self.event.dispose(connection: self) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/ArrayExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Array { 12 | 13 | // MARK: - Intance Methods 14 | 15 | mutating func removeFirst(where predicate: ((Element) -> Bool)) -> Element? { 16 | guard let index = self.firstIndex(where: predicate) else { 17 | return nil 18 | } 19 | 20 | return self.remove(at: index) 21 | } 22 | 23 | mutating func prepend(_ element: Element) { 24 | self.insert(element, at: 0) 25 | } 26 | } 27 | 28 | // MARK: - Array 29 | 30 | public extension Array where Element: AnyObject { 31 | 32 | // MARK: - Instance Methods 33 | 34 | @discardableResult 35 | mutating func remove(object: Element) -> Element? { 36 | guard let index = self.firstIndex(where: { $0 === object }) else { 37 | return nil 38 | } 39 | 40 | return self.remove(at: index) 41 | } 42 | 43 | func contains(object: Element) -> Bool { 44 | return self.contains(where: { $0 === object }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/CGPointExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPointExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | public extension CGPoint { 13 | 14 | // MARK: - Instance Properties 15 | 16 | var adjusted: CGPoint { 17 | return CGPoint(x: Int(floor(self.x)), 18 | y: Int(floor(self.y))) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/CGRectExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRectExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | public extension CGRect { 13 | 14 | // MARK: - Instance Properties 15 | 16 | var top: CGFloat { 17 | return self.origin.y 18 | } 19 | 20 | var bottom: CGFloat { 21 | return self.origin.y + self.size.height 22 | } 23 | 24 | var left: CGFloat { 25 | return self.origin.x 26 | } 27 | 28 | var right: CGFloat { 29 | return self.origin.x + self.size.width 30 | } 31 | 32 | // MARK: - 33 | 34 | var adjusted: CGRect { 35 | return CGRect(x: Int(floor(self.origin.x)), 36 | y: Int(floor(self.origin.y)), 37 | width: Int(ceil(self.size.width)), 38 | height: Int(ceil(self.size.height))) 39 | } 40 | 41 | // MARK: - Initializers 42 | 43 | init(x: CGFloat, y: CGFloat, size: CGSize) { 44 | self.init(x: x, y: y, width: size.width, height: size.height) 45 | } 46 | 47 | init(x: Double, y: Double, size: CGSize) { 48 | self.init(x: x, y: y, width: Double(size.width), height: Double(size.height)) 49 | } 50 | 51 | init(x: Int, y: Int, size: CGSize) { 52 | self.init(x: CGFloat(x), y: CGFloat(y), width: size.width, height: size.height) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/CGSizeExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSizeExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | public extension CGSize { 13 | 14 | // MARK: - Instance Properties 15 | 16 | var adjusted: CGSize { 17 | return CGSize(width: Int(ceil(self.width)), 18 | height: Int(ceil(self.height))) 19 | } 20 | 21 | // MARK: - Initializers 22 | 23 | init(equilateral side: CGFloat) { 24 | self.init(width: side, height: side) 25 | } 26 | 27 | init(equilateral side: Double) { 28 | self.init(width: side, height: side) 29 | } 30 | 31 | init(equilateral side: Int) { 32 | self.init(width: side, height: side) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/CollectionExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 26.05.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Collection { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func split(maxSplits: Int, by predicate: (Element, Element) throws -> Bool) rethrows -> [SubSequence] { 16 | var subSequences: [SubSequence] = [] 17 | 18 | var elementIndex = self.startIndex 19 | 20 | while (elementIndex < self.endIndex) && (subSequences.count < maxSplits) { 21 | let firstElement = self[elementIndex] 22 | 23 | var nextElementIndex = self.index(after: elementIndex) 24 | 25 | while nextElementIndex < self.endIndex { 26 | let nextElement = self[nextElementIndex] 27 | 28 | if !(try predicate(firstElement, nextElement)) { 29 | break 30 | } 31 | 32 | nextElementIndex = self.index(after: nextElementIndex) 33 | } 34 | 35 | subSequences.append(self[elementIndex.. Bool) rethrows -> [SubSequence] { 48 | return try self.split(maxSplits: self.count, by: predicate) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/DataExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 20.05.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Data { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var hexEncoded: String { 16 | return self.reduce("", { result, byte in 17 | return result + String(format: "%02hhx", byte) 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/DateFormatterExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatterExtension.swift 3 | // Tools 4 | // 5 | // Created by Timur Shafigullin on 15/04/2020. 6 | // Copyright © 2020 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension DateFormatter { 12 | 13 | // MARK: - Initializers 14 | 15 | public convenience init(dateFormat: String) { 16 | self.init() 17 | 18 | self.dateFormat = dateFormat 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/FloatingPointExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FloatingPointExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 18.04.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension FloatingPoint { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var degreesToRadians: Self { 16 | return self * .pi / 180 17 | } 18 | 19 | var radiansToDegrees: Self { 20 | return self * 180 / .pi 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/NSLayoutConstraintExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSLayoutConstraintExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension NSLayoutConstraint { 12 | 13 | // MARK: - Instance Properties 14 | 15 | @IBInspectable final var preciseConstant: Int { 16 | get { 17 | return Int(self.constant * UIScreen.main.scale) 18 | } 19 | 20 | set { 21 | self.constant = CGFloat(newValue) / UIScreen.main.scale 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/StringExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension String { 12 | 13 | // MARK: - Type Properties 14 | 15 | static let empty = "" 16 | 17 | // MARK: - Instance Methods 18 | 19 | func localized(tableName: String? = nil, comment: String = "") -> String { 20 | return NSLocalizedString(self, tableName: tableName, bundle: Bundle.main, value: "", comment: comment) 21 | } 22 | 23 | // MARK: - 24 | 25 | func prefix(count: Int) -> String { 26 | return ((self.count > count) ? String(self[.. String { 30 | return ((self.count > index) ? String(self[self.index(self.startIndex, offsetBy: index)...]) : "") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/UIColorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColorExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIColor { 12 | 13 | // MARK: - Initializers 14 | 15 | convenience init(red: CGFloat, green: CGFloat, blue: CGFloat) { 16 | self.init(red: red, green: green, blue: blue, alpha: 1.0) 17 | } 18 | 19 | convenience init(redByte: UInt8, greenByte: UInt8, blueByte: UInt8, alphaByte: UInt8) { 20 | self.init(red: CGFloat(redByte) / 255.0, 21 | green: CGFloat(greenByte) / 255.0, 22 | blue: CGFloat(blueByte) / 255.0, 23 | alpha: CGFloat(alphaByte) / 255.0) 24 | } 25 | 26 | convenience init(redByte: UInt8, greenByte: UInt8, blueByte: UInt8, alpha: CGFloat = 1.0) { 27 | self.init(red: CGFloat(redByte) / 255.0, 28 | green: CGFloat(greenByte) / 255.0, 29 | blue: CGFloat(blueByte) / 255.0, 30 | alpha: alpha) 31 | } 32 | 33 | convenience init(white: CGFloat) { 34 | self.init(white: white, alpha: 1.0) 35 | } 36 | 37 | convenience init(whiteByte: UInt, alphaByte: UInt8) { 38 | self.init(white: CGFloat(whiteByte) / 255.0, alpha: CGFloat(alphaByte) / 255.0) 39 | } 40 | 41 | convenience init(whiteByte: UInt, alpha: CGFloat = 1.0) { 42 | self.init(white: CGFloat(whiteByte) / 255.0, alpha: alpha) 43 | } 44 | 45 | convenience init(withRGBHex hex: UInt32) { 46 | self.init(redByte: UInt8((hex >> 16) & 255), 47 | greenByte: UInt8((hex >> 8) & 255), 48 | blueByte: UInt8(hex & 255)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/UIEdgeInsets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIEdgeInsets.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIEdgeInsets { 12 | 13 | // MARK: - Initializers 14 | 15 | init(equilateral side: CGFloat) { 16 | self.init(top: side, left: side, bottom: side, right: side) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Extensions/UIImageExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIImage { 12 | 13 | // MARK: - Instance Methods 14 | 15 | final func scaled(to size: CGSize) -> UIImage? { 16 | UIGraphicsBeginImageContextWithOptions(size, false, 0.0) 17 | 18 | self.draw(in: CGRect(x: 0.0, 19 | y: 0.0, 20 | size: size)) 21 | 22 | let scaledImage = UIGraphicsGetImageFromCurrentImageContext() 23 | 24 | UIGraphicsEndImageContext() 25 | 26 | return scaledImage 27 | } 28 | 29 | final func scaled(to width: CGFloat) -> UIImage? { 30 | let scaleFactor = width / self.size.width 31 | 32 | let scaledSize: CGSize = CGSize(width: self.size.width * scaleFactor, height: self.size.height * scaleFactor) 33 | 34 | UIGraphicsBeginImageContext(scaledSize) 35 | 36 | self.draw(in: CGRect(x: 0.0, 37 | y: 0.0, 38 | size: scaledSize)) 39 | 40 | let scaledImage = UIGraphicsGetImageFromCurrentImageContext() 41 | 42 | UIGraphicsEndImageContext() 43 | 44 | return scaledImage 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ImageLoader/DefaultImageLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultImageLoader.swift 3 | // Tools 4 | // 5 | // Created by Timur Shafigullin on 03/03/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Kingfisher 11 | 12 | class DefaultImageLoader: ImageLoader { 13 | 14 | // MARK: - Instance Methods 15 | 16 | func loadImage(for url: URL, in imageView: UIImageView, placeholder: UIImage? = nil) { 17 | imageView.kf.setImage(with: url, placeholder: placeholder) 18 | } 19 | 20 | func cancelLoading(in imageView: UIImageView) { 21 | imageView.kf.cancelDownloadTask() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ImageLoader/ImageLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLoader.swift 3 | // Tools 4 | // 5 | // Created by Timur Shafigullin on 19/04/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ImageLoader { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func loadImage(for url: URL, in imageView: UIImageView, placeholder: UIImage?) 16 | 17 | func cancelLoading(in imageView: UIImageView) 18 | } 19 | 20 | // MARK: - 21 | 22 | extension ImageLoader { 23 | 24 | // MARK: - Instance Methods 25 | 26 | func loadImage(for url: URL, in imageView: UIImageView, placeholder: UIImage? = nil) { 27 | return self.loadImage(for: url, in: imageView, placeholder: placeholder) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Log/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log.swift 3 | // Tools 4 | // 5 | // Created by Timur Shafigullin on 02/11/2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum LogEvent: String { 12 | 13 | // MARK: - Enumeration cases 14 | 15 | case e = "[‼️]" // error 16 | case i = "[ℹ️]" // info 17 | case d = "[💬]" // debug 18 | case v = "[🔬]" // verbose 19 | case w = "[⚠️]" // warning 20 | case s = "[🔥]" // severe 21 | } 22 | 23 | // Wrapping Swift.print() within LOGGING flag 24 | 25 | func print(_ object: Any) { 26 | #if LOGGING 27 | Swift.print(object) 28 | #endif 29 | } 30 | 31 | class Log { 32 | 33 | // MARK: - Type Properties 34 | 35 | static var dateFormat = "hh:mm:ss" 36 | 37 | static var dateFormatter: DateFormatter { 38 | let formatter = DateFormatter() 39 | 40 | formatter.dateFormat = dateFormat 41 | formatter.locale = Locale.current 42 | formatter.timeZone = TimeZone.current 43 | 44 | return formatter 45 | } 46 | 47 | // MARK: - Type Methods 48 | 49 | private static var isLoggingEnabled: Bool { 50 | #if LOGGING 51 | return true 52 | #else 53 | return false 54 | #endif 55 | } 56 | 57 | private class func sourceFileName(filePath: String) -> String { 58 | let components = filePath.components(separatedBy: "/") 59 | return components.isEmpty ? "" : components.last! 60 | } 61 | 62 | private class func printLog(_ object: Any?, filename: String, line: Int, column: Int, funcName: String, event: LogEvent) { 63 | if Log.isLoggingEnabled { 64 | var body = "\(Date().string) \(event.rawValue)[\(sourceFileName(filePath: filename))]:\(line) \(column) \(funcName)" 65 | 66 | if let object = object { 67 | body.append(" -> \(object)") 68 | } 69 | 70 | print(body) 71 | } 72 | } 73 | 74 | // MARK: - 75 | 76 | class func e(_ object: Any? = nil, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) { 77 | Log.printLog(object, filename: filename, line: line, column: column, funcName: funcName, event: .e) 78 | } 79 | 80 | class func d(_ object: Any? = nil, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) { 81 | Log.printLog(object, filename: filename, line: line, column: column, funcName: funcName, event: .d) 82 | } 83 | 84 | class func i(_ object: Any? = nil, sender: Any, line: Int = #line, column: Int = #column, funcName: String = #function) { 85 | Log.printLog(object, filename: "\(String(describing: type(of: sender)))", line: line, column: column, funcName: funcName, event: .i) 86 | } 87 | 88 | class func i(_ object: Any? = nil, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) { 89 | Log.printLog(object, filename: filename, line: line, column: column, funcName: funcName, event: .i) 90 | } 91 | 92 | class func v(_ object: Any? = nil, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) { 93 | Log.printLog(object, filename: filename, line: line, column: column, funcName: funcName, event: .v) 94 | } 95 | 96 | class func w(_ object: Any? = nil, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) { 97 | Log.printLog(object, filename: filename, line: line, column: column, funcName: funcName, event: .w) 98 | } 99 | 100 | class func s(_ object: Any? = nil, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) { 101 | Log.printLog(object, filename: filename, line: line, column: column, funcName: funcName, event: .s) 102 | } 103 | } 104 | 105 | // MARK: - Date 106 | 107 | private extension Date { 108 | 109 | // MARK: - Instance Methods 110 | 111 | var string: String { 112 | return Log.dateFormatter.string(from: self) 113 | } 114 | } 115 | 116 | private enum Constants { 117 | 118 | // MARK: - Type Properties 119 | 120 | static let defaultDateFormat = "[HH:mm:ss.SSSS]" 121 | 122 | static let lowLayerLabel = "<* >" 123 | static let mediumLayerLabel = "<** >" 124 | static let highLayerLabel = "<***>" 125 | static let extraLayerLabel = "<--->" 126 | } 127 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Log/LogConsolePrinter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogConsolePrinter.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 15/04/2020. 6 | // Copyright © 2020 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class LogConsolePrinter: LogPrinter { 12 | 13 | // MARK: - Type Properties 14 | 15 | public static let shared = LogConsolePrinter() 16 | 17 | // MARK: - Initializers 18 | 19 | private init() { } 20 | 21 | // MARK: - LogPrinter 22 | 23 | public func print(_ line: String) { 24 | Swift.print(line) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Log/LogFilePrinter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogFilePrinter.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 15/04/2020. 6 | // Copyright © 2020 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class LogFilePrinter: LogPrinter { 12 | 13 | // MARK: - Instance Properties 14 | 15 | private var headerContent: String { 16 | self.fileHeader.isEmpty ? .empty : "\(self.fileHeader)\n" 17 | } 18 | 19 | // MARK: - 20 | 21 | public let encoding: String.Encoding 22 | 23 | public let fileHeader: String 24 | public let fileName: String 25 | public let filePath: String 26 | 27 | public var content: String? { 28 | try? String(contentsOfFile: self.filePath, encoding: self.encoding) 29 | } 30 | 31 | // MARK: - Initializers 32 | 33 | public init?(encoding: String.Encoding = .utf8, fileHeader: String = "", fileName: String) { 34 | let fileManager = FileManager.default 35 | 36 | guard let documentURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { 37 | return nil 38 | } 39 | 40 | self.encoding = encoding 41 | 42 | self.fileHeader = fileHeader 43 | self.fileName = fileName 44 | self.filePath = documentURL.appendingPathComponent(fileName).path 45 | 46 | do { 47 | try fileManager.createDirectory( 48 | at: documentURL, 49 | withIntermediateDirectories: true, 50 | attributes: nil 51 | ) 52 | 53 | var isDirectory: ObjCBool = false 54 | 55 | if fileManager.fileExists(atPath: filePath, isDirectory: &isDirectory), isDirectory.boolValue { 56 | return nil 57 | } 58 | 59 | try clear() 60 | } catch { 61 | return nil 62 | } 63 | } 64 | 65 | // MARK: - LogPrinter 66 | 67 | public func print(_ line: String) { 68 | try? "\(content ?? headerContent)\(line)\n".write(toFile: filePath, atomically: true, encoding: encoding) 69 | } 70 | 71 | // MARK: - Instance Methods 72 | 73 | public func clear() throws { 74 | try headerContent.write(toFile: filePath, atomically: true, encoding: encoding) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Log/LogPrinter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogPrinter.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 15/04/2020. 6 | // Copyright © 2020 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol LogPrinter: AnyObject { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func print(_ line: String) 16 | } 17 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Protocols/DictionaryReceiver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryReceiver.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 13.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol DictionaryReceiver { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func apply(dictionary: [String: Any]) 16 | } 17 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Protocols/KeyboardHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardHandler.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 06.06.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol KeyboardHandler { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func handle(keyboardHeight: CGFloat, view: UIView) 16 | 17 | func subscribeToKeyboardNotifications() 18 | func unsubscribeFromKeyboardNotifications() 19 | } 20 | 21 | // MARK: - 22 | 23 | public extension KeyboardHandler where Self: AnyObject { 24 | 25 | // MARK: - Instance Methods 26 | 27 | func unsubscribeFromKeyboardNotifications() { 28 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) 29 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) 30 | } 31 | } 32 | 33 | // MARK: - 34 | 35 | public extension KeyboardHandler where Self: UIViewController { 36 | 37 | // MARK: - Instance Methods 38 | 39 | fileprivate func onKeyboardWillShow(with notification: NSNotification) { 40 | guard let userInfo = notification.userInfo else { 41 | return 42 | } 43 | 44 | guard let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { 45 | return 46 | } 47 | 48 | self.handle(keyboardHeight: self.view.frame.bottom - self.view.convert(keyboardFrame.origin, from: nil).y, 49 | view: self.view) 50 | } 51 | 52 | fileprivate func onKeyboardWillHide(with notification: NSNotification) { 53 | self.handle(keyboardHeight: 0.0, view: self.view) 54 | } 55 | 56 | // MARK: - 57 | 58 | func subscribeToKeyboardNotifications() { 59 | NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, 60 | object: nil, 61 | queue: nil, 62 | using: { [weak self] notification in 63 | self?.onKeyboardWillShow(with: notification as NSNotification) 64 | }) 65 | 66 | NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, 67 | object: nil, 68 | queue: nil, 69 | using: { [weak self] notification in 70 | self?.onKeyboardWillHide(with: notification as NSNotification) 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Protocols/KeyboardScrollableHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardScrollableHandler.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 06.06.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol KeyboardScrollableHandler: KeyboardHandler { 12 | 13 | // MARK: - Nested Types 14 | 15 | associatedtype ScrollableView: UIScrollView 16 | 17 | // MARK: - Instance Properties 18 | 19 | var scrollableView: ScrollableView { get } 20 | } 21 | 22 | // MARK: - 23 | 24 | public extension KeyboardScrollableHandler { 25 | 26 | // MARK: - Instance Methods 27 | 28 | func handle(keyboardHeight: CGFloat, view: UIView) { 29 | let viewHeight = self.scrollableView.convert(CGPoint(x: 0.0, y: view.bounds.height), from: view).y 30 | 31 | let keyboardHeight = max((keyboardHeight - (viewHeight - self.scrollableView.bounds.height)), 0.0) 32 | 33 | self.scrollableView.contentInset = UIEdgeInsets(top: self.scrollableView.contentInset.top, 34 | left: self.scrollableView.contentInset.left, 35 | bottom: keyboardHeight, 36 | right: self.scrollableView.contentInset.right) 37 | 38 | self.scrollableView.scrollIndicatorInsets = UIEdgeInsets(top: self.scrollableView.scrollIndicatorInsets.top, 39 | left: self.scrollableView.scrollIndicatorInsets.left, 40 | bottom: keyboardHeight, 41 | right: self.scrollableView.scrollIndicatorInsets.right) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/CoreData/CoreDataStorageExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStorageExtension.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 25.08.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | extension NSManagedObject: StorageObject { 13 | 14 | // MARK: - Type Properties 15 | 16 | public static var entityName: String { 17 | return self.entity().name ?? String(describing: self) 18 | } 19 | } 20 | 21 | // MARK: - 22 | 23 | extension NSManagedObjectContext { 24 | 25 | // MARK: - Instance Properties 26 | 27 | var storageContextType: StorageContextType { 28 | switch self.concurrencyType { 29 | case .mainQueueConcurrencyType: 30 | return .mainQueue 31 | 32 | case .privateQueueConcurrencyType, .confinementConcurrencyType: 33 | return .privateQueue 34 | 35 | @unknown default: 36 | fatalError() 37 | } 38 | } 39 | } 40 | 41 | // MARK: - 42 | 43 | extension StorageFetchRequest { 44 | 45 | // MARK: - Instance Methods 46 | 47 | func coreDataFetchRequest() -> NSFetchRequest { 48 | let coreDataFetchRequest = NSFetchRequest(entityName: Object.entityName) 49 | 50 | coreDataFetchRequest.predicate = self.predicate 51 | coreDataFetchRequest.sortDescriptors = self.sortDescriptors 52 | 53 | coreDataFetchRequest.returnsObjectsAsFaults = self.returnsObjectsAsFaults 54 | 55 | coreDataFetchRequest.includesSubentities = self.includesSubentities 56 | coreDataFetchRequest.includesPendingChanges = self.includesPendingChanges 57 | coreDataFetchRequest.includesPropertyValues = self.includesPropertyValues 58 | 59 | coreDataFetchRequest.shouldRefreshRefetchedObjects = self.shouldRefreshRefetchedObjects 60 | 61 | coreDataFetchRequest.fetchOffset = self.fetchOffset 62 | coreDataFetchRequest.fetchLimit = self.fetchLimit 63 | coreDataFetchRequest.fetchBatchSize = self.fetchBatchSize 64 | 65 | coreDataFetchRequest.relationshipKeyPathsForPrefetching = self.relationshipsForPrefetching 66 | 67 | return coreDataFetchRequest 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/CoreData/CoreDataStorageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStorageManager.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 25.02.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | public class CoreDataStorageManager: StorageManager { 13 | 14 | // MARK: - Instance Properties 15 | 16 | public unowned let managedObjectContext: NSManagedObjectContext 17 | 18 | // MARK: - StorageManager 19 | 20 | unowned public let context: StorageContext 21 | 22 | // MARK: - Initializers 23 | 24 | public required init(managedObjectContext: NSManagedObjectContext, context: StorageContext) { 25 | self.managedObjectContext = managedObjectContext 26 | 27 | self.context = context 28 | } 29 | 30 | // MARK: - Instance Methods 31 | 32 | public func first(with fetchRequest: StorageFetchRequest) -> Object? { 33 | var fetchRequest = fetchRequest 34 | 35 | fetchRequest.fetchLimit = 1 36 | fetchRequest.fetchBatchSize = 1 37 | 38 | return self.fetch(with: fetchRequest).first 39 | } 40 | 41 | public func last(with fetchRequest: StorageFetchRequest) -> Object? { 42 | let sortDescriptors = fetchRequest.sortDescriptors.map({ sortDescriptor in 43 | return sortDescriptor.reversedSortDescriptor as! NSSortDescriptor 44 | }) 45 | 46 | var fetchRequest = fetchRequest 47 | 48 | fetchRequest.sortDescriptors = sortDescriptors 49 | fetchRequest.fetchLimit = 1 50 | fetchRequest.fetchBatchSize = 1 51 | 52 | return self.fetch(with: fetchRequest).first 53 | } 54 | 55 | public func fetch(with fetchRequest: StorageFetchRequest) -> [Object] { 56 | guard let objects = (try? self.managedObjectContext.fetch(fetchRequest.coreDataFetchRequest())) as? [Object] else { 57 | return [] 58 | } 59 | 60 | return objects 61 | } 62 | 63 | public func count(with fetchRequest: StorageFetchRequest) -> Int { 64 | guard let count = try? self.managedObjectContext.count(for: fetchRequest.coreDataFetchRequest()) else { 65 | return 0 66 | } 67 | 68 | return count 69 | } 70 | 71 | public func clear(with fetchRequest: StorageFetchRequest) { 72 | guard let objects = try? self.managedObjectContext.fetch(fetchRequest.coreDataFetchRequest()) else { 73 | return 74 | } 75 | 76 | for object in objects { 77 | self.managedObjectContext.delete(object) 78 | } 79 | } 80 | 81 | public func remove(object: Object) { 82 | guard let object = object as? NSManagedObject, !object.isDeleted else { 83 | return 84 | } 85 | 86 | self.managedObjectContext.delete(object) 87 | } 88 | 89 | public func append(_ type: Object.Type) -> Object? { 90 | guard let entity = NSEntityDescription.entity(forEntityName: Object.entityName, in: self.managedObjectContext) else { 91 | return nil 92 | } 93 | 94 | return NSManagedObject(entity: entity, insertInto: self.managedObjectContext) as? Object 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/CoreData/CoreDataStorageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStorage.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 25.02.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | public class CoreDataStorageModel>: StorageModel { 13 | 14 | // MARK: - Instance Propertes 15 | 16 | public fileprivate(set) lazy var persistentContainer: NSPersistentContainer = { [unowned self] in 17 | let container = NSPersistentContainer(name: self.identifier) 18 | 19 | container.loadPersistentStores(completionHandler: { storeDescription, error in 20 | guard error == nil else { 21 | fatalError() 22 | } 23 | }) 24 | container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy 25 | container.viewContext.undoManager = nil 26 | container.viewContext.shouldDeleteInaccessibleFaults = true 27 | 28 | container.viewContext.automaticallyMergesChangesFromParent = true 29 | 30 | return container 31 | }() 32 | 33 | public fileprivate(set) lazy var managedObjectContext: NSManagedObjectContext = { [unowned self] in 34 | return self.persistentContainer.viewContext 35 | }() 36 | 37 | public fileprivate(set) lazy var managedObjectModel: NSManagedObjectModel = { [unowned self] in 38 | return self.persistentContainer.managedObjectModel 39 | }() 40 | 41 | public fileprivate(set) lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { [unowned self] in 42 | return self.persistentContainer.persistentStoreCoordinator 43 | }() 44 | 45 | // MARK: - Storage 46 | 47 | public let identifier: String 48 | 49 | public fileprivate(set) lazy var viewContext: StorageContext = { [unowned self] in 50 | return Context(managedObjectContext: self.managedObjectContext, model: self, parent: nil) 51 | }() 52 | 53 | // MARK: - Initializers 54 | 55 | public required init(identifier: String) { 56 | assert(!identifier.isEmpty) 57 | 58 | self.identifier = identifier 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/StorageContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageContext.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 24.02.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol StorageContext: AnyObject { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var observers: [StorageContextObserver] { get } 16 | 17 | var model: StorageModel! { get } 18 | var parent: StorageContext? { get } 19 | 20 | var manager: StorageManager { get } 21 | 22 | var type: StorageContextType { get } 23 | 24 | // MARK: - Instance Methods 25 | 26 | func addObserver(_ observer: StorageContextObserver) 27 | func removeObserver(_ observer: StorageContextObserver) 28 | 29 | func createMainQueueChildContext() -> StorageContext 30 | func createPrivateQueueChildContext() -> StorageContext 31 | 32 | func performAndWait(block: @escaping () -> Void) 33 | func perform(block: @escaping () -> Void) 34 | 35 | func rollback() 36 | func save() 37 | } 38 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/StorageContextObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageContextObserver.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 10.05.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol StorageContextObserver: AnyObject { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func storageContext(_ storageContext: StorageContext, didRemoveObjects objects: [StorageObject]) 16 | func storageContext(_ storageContext: StorageContext, didAppendObjects objects: [StorageObject]) 17 | func storageContext(_ storageContext: StorageContext, didUpdateObjects objects: [StorageObject]) 18 | func storageContext(_ storageContext: StorageContext, didChangeObjects objects: [StorageObject]) 19 | } 20 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/StorageContextType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageContextType.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 22.09.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum StorageContextType { 12 | 13 | // MARK: - Enumeration Cases 14 | 15 | case mainQueue 16 | case privateQueue 17 | } 18 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/StorageFetchRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageFetchRequest.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 24.02.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct StorageFetchRequest { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public var sortDescriptors: [NSSortDescriptor] 16 | public var predicate: NSPredicate? 17 | 18 | // MARK: - 19 | 20 | public var returnsObjectsAsFaults: Bool = true 21 | 22 | public var includesSubentities: Bool = true 23 | public var includesPendingChanges: Bool = true 24 | public var includesPropertyValues: Bool = true 25 | 26 | public var shouldRefreshRefetchedObjects: Bool = false 27 | 28 | public var fetchOffset: Int = 0 29 | public var fetchLimit: Int = 0 30 | public var fetchBatchSize: Int = 0 31 | 32 | public var relationshipsForPrefetching: [String]? 33 | 34 | // MARK: - Initializers 35 | 36 | public init(sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) { 37 | self.sortDescriptors = sortDescriptors 38 | self.predicate = predicate 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/StorageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageManager.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 24.02.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol StorageManager { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var context: StorageContext { get } 16 | 17 | // MARK: - Instance Methods 18 | 19 | func first(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> Object? 20 | func first(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> Object? 21 | func first(with fetchRequest: StorageFetchRequest) -> Object? 22 | 23 | func last(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> Object? 24 | func last(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> Object? 25 | func last(with fetchRequest: StorageFetchRequest) -> Object? 26 | 27 | func fetch(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> [Object] 28 | func fetch(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> [Object] 29 | func fetch(with fetchRequest: StorageFetchRequest) -> [Object] 30 | 31 | func count(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> Int 32 | func count(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> Int 33 | func count(with fetchRequest: StorageFetchRequest) -> Int 34 | 35 | func clear(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) 36 | func clear(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) 37 | func clear(with fetchRequest: StorageFetchRequest) 38 | 39 | func remove(object: Object) 40 | func append(_ type: Object.Type) -> Object? 41 | } 42 | 43 | // MARK: - 44 | 45 | public extension StorageManager { 46 | 47 | // MARK: - Instance Methods 48 | 49 | func first(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> Object? { 50 | return self.first(with: StorageFetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)) 51 | } 52 | 53 | func first(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> Object? { 54 | return self.first(type, sortDescriptors: sortDescriptors, predicate: nil) 55 | } 56 | 57 | func last(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> Object? { 58 | return self.last(with: StorageFetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)) 59 | } 60 | 61 | func last(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> Object? { 62 | return self.last(type, sortDescriptors: sortDescriptors, predicate: nil) 63 | } 64 | 65 | func fetch(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> [Object] { 66 | return self.fetch(with: StorageFetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)) 67 | } 68 | 69 | func fetch(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> [Object] { 70 | return self.fetch(type, sortDescriptors: sortDescriptors, predicate: nil) 71 | } 72 | 73 | func count(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) -> Int { 74 | return self.count(with: StorageFetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)) 75 | } 76 | 77 | func count(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) -> Int { 78 | return self.count(type, sortDescriptors: sortDescriptors, predicate: nil) 79 | } 80 | 81 | func clear(_ type: Object.Type, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate?) { 82 | self.clear(with: StorageFetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)) 83 | } 84 | 85 | func clear(_ type: Object.Type, sortDescriptors: [NSSortDescriptor]) { 86 | self.clear(type, sortDescriptors: sortDescriptors, predicate: nil) 87 | } 88 | 89 | func append(_ type: Object.Type) -> Object? { 90 | self.append(type) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/StorageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageModel.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 25.08.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol StorageModel: AnyObject { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var identifier: String { get } 16 | 17 | var viewContext: StorageContext { get } 18 | 19 | // MARK: - Initializers 20 | 21 | init(identifier: String) 22 | } 23 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Storage/StorageObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageObject.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 24.02.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol StorageObject: AnyObject { 12 | 13 | // MARK: - Instance Properties 14 | 15 | static var entityName: String { get } 16 | } 17 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ViewControllers/LoggedNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedNavigationController.swift 3 | // RoomDebts 4 | // 5 | // Created by Timur Shafigullin on 20/01/2019. 6 | // Copyright © 2019 Timur Shafigullin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class LoggedNavigationController: UINavigationController { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public fileprivate(set) final var isViewAppeared = false 16 | 17 | // MARK: - Initializers 18 | 19 | deinit { 20 | Log.i("deinit", sender: self) 21 | } 22 | 23 | // MARK: - Instance Methods 24 | 25 | public override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | 28 | Log.i("didReceiveMemoryWarning()", sender: self) 29 | } 30 | 31 | public override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | Log.i("viewDidLoad()", sender: self) 35 | 36 | self.isViewAppeared = false 37 | } 38 | 39 | public override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | 42 | Log.i("viewWillAppear()", sender: self) 43 | 44 | self.isViewAppeared = false 45 | } 46 | 47 | public override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | 50 | Log.i("viewDidAppear()", sender: self) 51 | 52 | self.isViewAppeared = true 53 | } 54 | 55 | public override func viewWillDisappear(_ animated: Bool) { 56 | super.viewWillDisappear(animated) 57 | 58 | Log.i("viewWillDisappear()", sender: self) 59 | } 60 | 61 | public override func viewDidDisappear(_ animated: Bool) { 62 | super.viewDidDisappear(animated) 63 | 64 | Log.i("viewDidDisappear()", sender: self) 65 | 66 | self.isViewAppeared = false 67 | } 68 | 69 | // MARK: - 70 | 71 | public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 72 | super.prepare(for: segue, sender: sender) 73 | 74 | Log.i("prepare(for: \(String(describing: segue.identifier)))", sender: self) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ViewControllers/LoggedPageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedPageViewController.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class LoggedPageViewController: UIPageViewController { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public fileprivate(set) final var isViewAppeared = false 16 | 17 | // MARK: - Initializers 18 | 19 | deinit { 20 | Log.i("deinit", sender: self) 21 | } 22 | 23 | // MARK: - Instance Methods 24 | 25 | public override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | 28 | Log.i("didReceiveMemoryWarning()", sender: self) 29 | } 30 | 31 | public override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | Log.i("viewDidLoad()", sender: self) 35 | 36 | self.isViewAppeared = false 37 | } 38 | 39 | public override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | 42 | Log.i("viewWillAppear()", sender: self) 43 | 44 | self.isViewAppeared = false 45 | } 46 | 47 | public override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | 50 | Log.i("viewDidAppear()", sender: self) 51 | 52 | self.isViewAppeared = true 53 | } 54 | 55 | public override func viewWillDisappear(_ animated: Bool) { 56 | super.viewWillDisappear(animated) 57 | 58 | Log.i("viewWillDisappear()", sender: self) 59 | } 60 | 61 | public override func viewDidDisappear(_ animated: Bool) { 62 | super.viewDidDisappear(animated) 63 | 64 | Log.i("viewDidDisappear()", sender: self) 65 | 66 | self.isViewAppeared = false 67 | } 68 | 69 | // MARK: - 70 | 71 | public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 72 | super.prepare(for: segue, sender: sender) 73 | 74 | Log.i("prepare(for: \(String(describing: segue.identifier)))", sender: self) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ViewControllers/LoggedTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedTabBarController.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class LoggedTabBarController: UITabBarController { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public fileprivate(set) final var isViewAppeared = false 16 | 17 | // MARK: - Initializers 18 | 19 | deinit { 20 | Log.i("deinit", sender: self) 21 | } 22 | 23 | // MARK: - Instance Methods 24 | 25 | public override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | 28 | Log.i("didReceiveMemoryWarning()", sender: self) 29 | } 30 | 31 | public override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | Log.i("viewDidLoad()", sender: self) 35 | 36 | self.isViewAppeared = false 37 | } 38 | 39 | public override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | 42 | Log.i("viewWillAppear()", sender: self) 43 | 44 | self.isViewAppeared = false 45 | } 46 | 47 | public override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | 50 | Log.i("viewDidAppear()", sender: self) 51 | 52 | self.isViewAppeared = true 53 | } 54 | 55 | public override func viewWillDisappear(_ animated: Bool) { 56 | super.viewWillDisappear(animated) 57 | 58 | Log.i("viewWillDisappear()", sender: self) 59 | } 60 | 61 | public override func viewDidDisappear(_ animated: Bool) { 62 | super.viewDidDisappear(animated) 63 | 64 | Log.i("viewDidDisappear()", sender: self) 65 | 66 | self.isViewAppeared = false 67 | } 68 | 69 | // MARK: - 70 | 71 | public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 72 | super.prepare(for: segue, sender: sender) 73 | 74 | Log.i("prepare(for: \(String(describing: segue.identifier)))", sender: self) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ViewControllers/LoggedTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedTableViewController.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class LoggedTableViewController: UITableViewController { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public fileprivate(set) final var isViewAppeared = false 16 | 17 | // MARK: - Initializers 18 | 19 | deinit { 20 | Log.i("deinit", sender: self) 21 | } 22 | 23 | // MARK: - Instance Methods 24 | 25 | public override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | 28 | Log.i("didReceiveMemoryWarning()", sender: self) 29 | } 30 | 31 | public override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | Log.i("viewDidLoad()", sender: self) 35 | 36 | self.isViewAppeared = false 37 | } 38 | 39 | public override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | 42 | Log.i("viewWillAppear()", sender: self) 43 | 44 | self.isViewAppeared = false 45 | } 46 | 47 | public override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | 50 | Log.i("viewDidAppear()", sender: self) 51 | 52 | self.isViewAppeared = true 53 | } 54 | 55 | public override func viewWillDisappear(_ animated: Bool) { 56 | super.viewWillDisappear(animated) 57 | 58 | Log.i("viewWillDisappear()", sender: self) 59 | } 60 | 61 | public override func viewDidDisappear(_ animated: Bool) { 62 | super.viewDidDisappear(animated) 63 | 64 | Log.i("viewDidDisappear()", sender: self) 65 | 66 | self.isViewAppeared = false 67 | } 68 | 69 | // MARK: - 70 | 71 | public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 72 | super.prepare(for: segue, sender: sender) 73 | 74 | Log.i("prepare(for: \(String(describing: segue.identifier)))", sender: self) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ViewControllers/LoggedViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedViewController.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 01.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class LoggedViewController: UIViewController { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public fileprivate(set) final var isViewAppeared = false 16 | 17 | // MARK: - Initializers 18 | 19 | deinit { 20 | Log.i("deinit", sender: self) 21 | } 22 | 23 | // MARK: - Instance Methods 24 | 25 | public override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | 28 | Log.i("didReceiveMemoryWarning()", sender: self) 29 | } 30 | 31 | public override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | Log.i("viewDidLoad()", sender: self) 35 | 36 | self.isViewAppeared = false 37 | } 38 | 39 | public override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | 42 | Log.i("viewWillAppear()", sender: self) 43 | 44 | self.isViewAppeared = false 45 | } 46 | 47 | public override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | 50 | Log.i("viewDidAppear()", sender: self) 51 | 52 | self.isViewAppeared = true 53 | } 54 | 55 | public override func viewWillDisappear(_ animated: Bool) { 56 | super.viewWillDisappear(animated) 57 | 58 | Log.i("viewWillDisappear()", sender: self) 59 | } 60 | 61 | public override func viewDidDisappear(_ animated: Bool) { 62 | super.viewDidDisappear(animated) 63 | 64 | Log.i("viewDidDisappear()", sender: self) 65 | 66 | self.isViewAppeared = false 67 | } 68 | 69 | // MARK: - 70 | 71 | public override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 72 | super.prepare(for: segue, sender: sender) 73 | 74 | Log.i("prepare(for: \(String(describing: segue.identifier)))", sender: self) 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ViewControllers/MessagePresenting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessagePresenting.swift 3 | // YouMakeUp 4 | // 5 | // Created by Timur Shafigullin on 21/01/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol MessagePresenting { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var presenterController: UIViewController { get } 16 | 17 | // MARK: - Instance Methods 18 | 19 | func showMessage(withTitle title: String?, message: String?, okHandler: (() -> Void)?) 20 | } 21 | 22 | // MARK: - 23 | 24 | extension MessagePresenting where Self: UIViewController { 25 | 26 | // MARK: - Instance Properties 27 | 28 | var presenterController: UIViewController { 29 | return self 30 | } 31 | 32 | // MARK: - Instance Methods 33 | 34 | func showMessage(withTitle title: String?, message: String?, okHandler: (() -> Void)?) { 35 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 36 | 37 | alertController.addAction(UIAlertAction(title: "OK".localized(), style: .cancel, handler: { action in 38 | okHandler?() 39 | })) 40 | 41 | self.present(alertController, animated: true) 42 | } 43 | 44 | func showMessage(withTitle title: String?, message: String?) { 45 | self.showMessage(withTitle: title, message: message, okHandler: nil) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Swift-Base/Tools/ViewControllers/TransparentNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransparentNavigationController.swift 3 | // YouMakeUp 4 | // 5 | // Created by Oleg Gorelov on 08/04/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TransparentNavigationController: LoggedNavigationController { 12 | 13 | // MARK: - Instance Methods 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | self.navigationBar.setBackgroundImage(UIImage(), for: .default) 19 | self.navigationBar.shadowImage = UIImage() 20 | self.navigationBar.isTranslucent = true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Views/IconTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconTextField.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 07.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable public class IconTextField: TextField { 12 | 13 | // MARK: - Instance Properties 14 | 15 | fileprivate let iconImageView: UIImageView = UIImageView() 16 | 17 | // MARK: - 18 | 19 | @IBInspectable public var icon: UIImage? { 20 | get { 21 | return self.iconImageView.image 22 | } 23 | 24 | set { 25 | self.iconImageView.image = newValue 26 | 27 | self.layoutLeftView() 28 | } 29 | } 30 | 31 | @IBInspectable public var iconLeftInset: CGFloat = 10.0 { 32 | didSet { 33 | self.layoutLeftView() 34 | } 35 | } 36 | 37 | @IBInspectable public var iconTintColor: UIColor { 38 | get { 39 | return self.iconImageView.tintColor 40 | } 41 | 42 | set { 43 | self.iconImageView.tintColor = newValue 44 | } 45 | } 46 | 47 | // MARK: - Initializers 48 | 49 | public override init(frame: CGRect = .zero) { 50 | super.init(frame: frame) 51 | 52 | self.initialize() 53 | } 54 | 55 | public required init?(coder aDecoder: NSCoder) { 56 | super.init(coder: aDecoder) 57 | 58 | self.initialize() 59 | } 60 | 61 | // MARK: - Instance Methods 62 | 63 | fileprivate func initialize() { 64 | self.iconImageView.contentMode = .right 65 | 66 | self.leftView = self.iconImageView 67 | self.leftViewMode = .always 68 | } 69 | 70 | fileprivate func layoutLeftView() { 71 | if let imageWidth = self.iconImageView.image?.size.width { 72 | self.iconImageView.frame = CGRect(x: 0.0, 73 | y: 0.0, 74 | width: imageWidth + self.iconLeftInset, 75 | height: self.bounds.height) 76 | } else { 77 | self.iconImageView.frame = CGRect(x: 0.0, 78 | y: 0.0, 79 | width: self.iconLeftInset, 80 | height: self.bounds.height) 81 | } 82 | } 83 | 84 | // MARK: - UIView 85 | 86 | public override func layoutSubviews() { 87 | super.layoutSubviews() 88 | 89 | self.layoutLeftView() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Views/RoundImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundImageView.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 22.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class RoundImageView: UIImageView { 12 | 13 | // MARK: - Initializers 14 | 15 | public override init(image: UIImage?, highlightedImage: UIImage?) { 16 | super.init(image: image, highlightedImage: highlightedImage) 17 | 18 | self.layer.masksToBounds = true 19 | } 20 | 21 | public override init(image: UIImage?) { 22 | super.init(image: image) 23 | 24 | self.layer.masksToBounds = true 25 | } 26 | 27 | public override init(frame: CGRect = CGRect.zero) { 28 | super.init(frame: frame) 29 | 30 | self.layer.masksToBounds = true 31 | } 32 | 33 | public required init?(coder aDecoder: NSCoder) { 34 | super.init(coder: aDecoder) 35 | 36 | self.layer.masksToBounds = true 37 | } 38 | 39 | // MARK: - Instance Methods 40 | 41 | public override func layoutSubviews() { 42 | super.layoutSubviews() 43 | 44 | self.layer.cornerRadius = min(self.frame.width, self.frame.height) * 0.5 45 | self.layer.shadowOpacity = 0.2 46 | self.layer.shadowOffset = CGSize(width: 0, height: 2) 47 | self.layer.shadowRadius = 5 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Views/RoundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundView.swift 3 | // Tools 4 | // 5 | // Created by Marat Galeev on 28.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class RoundView: UIView { 12 | 13 | // MARK: - Initializers 14 | 15 | public override init(frame: CGRect = CGRect.zero) { 16 | super.init(frame: frame) 17 | 18 | self.layer.masksToBounds = true 19 | } 20 | 21 | public required init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | 24 | self.layer.masksToBounds = true 25 | } 26 | 27 | // MARK: - Instance Methods 28 | 29 | public override func layoutSubviews() { 30 | super.layoutSubviews() 31 | 32 | self.layer.cornerRadius = min(self.frame.width, self.frame.height) * 0.5 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Views/RoundedImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedImageView.swift 3 | // YouMakeUp 4 | // 5 | // Created by Oleg Gorelov on 09/04/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable public class RoundedImageView: UIImageView { 12 | 13 | // MARK: - Instance Properties 14 | 15 | @IBInspectable public var cornerRadius: CGFloat = 0.0 { 16 | didSet { 17 | self.layer.cornerRadius = self.cornerRadius 18 | self.layer.masksToBounds = (self.cornerRadius > 0.0) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Views/RoundedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedView.swift 3 | // YouMakeUp 4 | // 5 | // Created by Timur Shafigullin on 13/02/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable public class RoundedView: UIView { 12 | 13 | // MARK: - Instance Properties 14 | 15 | @IBInspectable public var cornerRadius: CGFloat = 0.0 { 16 | didSet { 17 | self.layer.cornerRadius = self.cornerRadius 18 | self.layer.masksToBounds = (self.cornerRadius > 0.0) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Views/TextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextField.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 06.01.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable public class TextField: UITextField { 12 | 13 | // MARK: - Instance Properties 14 | 15 | @IBInspectable public var leftInset: CGFloat = 8.0 { 16 | didSet { 17 | self.setNeedsLayout() 18 | } 19 | } 20 | 21 | @IBInspectable public var rightInset: CGFloat = 0.0 { 22 | didSet { 23 | self.setNeedsLayout() 24 | } 25 | } 26 | 27 | @IBInspectable public var topInset: CGFloat = 2.0 { 28 | didSet { 29 | self.setNeedsLayout() 30 | } 31 | } 32 | 33 | @IBInspectable public var bottomInset: CGFloat = 0.0 { 34 | didSet { 35 | self.setNeedsLayout() 36 | } 37 | } 38 | 39 | // MARK: - 40 | 41 | public var isTextEmpty: Bool { 42 | return self.text?.isEmpty ?? true 43 | } 44 | 45 | // MARK: - Instance Methods 46 | 47 | public override func textRect(forBounds bounds: CGRect) -> CGRect { 48 | let leftInset = (self.leftView?.frame.width ?? 0.0) + self.leftInset 49 | let rightInset = (self.rightView?.frame.width ?? 0.0) + self.rightInset 50 | 51 | return CGRect(x: leftInset, 52 | y: self.topInset, 53 | width: bounds.width - leftInset - rightInset, 54 | height: bounds.height - self.topInset - self.bottomInset) 55 | } 56 | 57 | public override func editingRect(forBounds bounds: CGRect) -> CGRect { 58 | return self.textRect(forBounds: bounds) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Swift-Base/Tools/Views/TextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextView.swift 3 | // Tools 4 | // 5 | // Created by Oleg Gorelov on 30/05/2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable public class TextView: UITextView { 12 | 13 | // MARK: - Instance Properties 14 | 15 | @IBInspectable public var bottomOutset: CGFloat { 16 | get { 17 | return self.touchEdgeOutset.bottom 18 | } 19 | 20 | set { 21 | self.touchEdgeOutset.bottom = newValue 22 | } 23 | } 24 | 25 | @IBInspectable public var leftOutset: CGFloat { 26 | get { 27 | return self.touchEdgeOutset.left 28 | } 29 | 30 | set { 31 | self.touchEdgeOutset.left = newValue 32 | } 33 | } 34 | 35 | @IBInspectable public var rightOutset: CGFloat { 36 | get { 37 | return self.touchEdgeOutset.right 38 | } 39 | 40 | set { 41 | self.touchEdgeOutset.right = newValue 42 | } 43 | } 44 | 45 | @IBInspectable public var topOutset: CGFloat { 46 | get { 47 | return self.touchEdgeOutset.top 48 | } 49 | 50 | set { 51 | self.touchEdgeOutset.top = newValue 52 | } 53 | } 54 | 55 | public var touchEdgeOutset: UIEdgeInsets = UIEdgeInsets.zero 56 | 57 | // MARK: - UIControl 58 | 59 | public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 60 | let rect = CGRect(x: self.bounds.origin.x - self.touchEdgeOutset.left, 61 | y: self.bounds.origin.y - self.touchEdgeOutset.top, 62 | width: self.bounds.width + self.touchEdgeOutset.left + self.touchEdgeOutset.right, 63 | height: self.bounds.height + self.touchEdgeOutset.top + self.touchEdgeOutset.bottom) 64 | 65 | return rect.contains(point) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/UIWebActivityIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIWebActivityIndicator.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class UIWebActivityIndicator: WebActivityIndicator { 12 | 13 | // MARK: - Type Properties 14 | 15 | public static let shared = UIWebActivityIndicator() 16 | 17 | // MARK: - Instance Properties 18 | 19 | public fileprivate(set) var activityCount: Int = 0 { 20 | didSet { 21 | if self.activityCount > 0 { 22 | UIApplication.shared.isNetworkActivityIndicatorVisible = true 23 | } else { 24 | UIApplication.shared.isNetworkActivityIndicatorVisible = false 25 | } 26 | } 27 | } 28 | 29 | // MARK: - Initializers 30 | 31 | fileprivate init() { } 32 | 33 | // MARK: - Instance Methods 34 | 35 | public func incrementActivityCount() { 36 | self.activityCount += 1 37 | } 38 | 39 | public func decrementActivityCount() { 40 | if self.activityCount > 0 { 41 | self.activityCount -= 1 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebActivityIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebActivityIndicator.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol WebActivityIndicator: AnyObject { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var activityCount: Int { get } 16 | 17 | // MARK: - Instance Methods 18 | 19 | func incrementActivityCount() 20 | func decrementActivityCount() 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebErrorBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebErrorBase.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol WebErrorBase { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var logDescription: String { get } 16 | } 17 | 18 | // MARK: - URLError 19 | 20 | extension URLError: WebErrorBase { 21 | 22 | // MARK: - Instance Properties 23 | 24 | public var logDescription: String { 25 | return "URLError: \(self.code.rawValue)" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebHandler.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol WebHandler { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var service: WebService { get } 16 | var request: WebRequest { get } 17 | 18 | var isLoading: Bool { get } 19 | 20 | // MARK: - Instance Methods 21 | 22 | func keepAlive() 23 | 24 | func responseData(queue: DispatchQueue?, completion: @escaping ((Data?, WebError?) -> Void)) 25 | func responseString(queue: DispatchQueue?, encoding: String.Encoding?, completion: @escaping ((String?, WebError?) -> Void)) 26 | func responseJSON(queue: DispatchQueue?, options: JSONSerialization.ReadingOptions, completion: @escaping ((Any?, WebError?) -> Void)) 27 | 28 | func abort() 29 | } 30 | 31 | public extension WebHandler { 32 | 33 | // MARK: - Instance Methods 34 | 35 | func keepAlive() { } 36 | 37 | func responseData(completion: @escaping ((Data?, WebError?) -> Void)) { 38 | self.responseData(queue: nil, completion: completion) 39 | } 40 | 41 | func responseString(encoding: String.Encoding?, completion: @escaping ((String?, WebError?) -> Void)) { 42 | self.responseString(queue: nil, encoding: encoding, completion: completion) 43 | } 44 | 45 | func responseString(queue: DispatchQueue? = nil, completion: @escaping ((String?, WebError?) -> Void)) { 46 | self.responseString(queue: queue, encoding: nil, completion: completion) 47 | } 48 | 49 | func responseJSON(options: JSONSerialization.ReadingOptions, completion: @escaping ((Any?, WebError?) -> Void)) { 50 | self.responseJSON(queue: nil, options: options, completion: completion) 51 | } 52 | 53 | func responseJSON(queue: DispatchQueue? = nil, completion: @escaping ((Any?, WebError?) -> Void)) { 54 | self.responseJSON(queue: queue, options: .allowFragments, completion: completion) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebMethod.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum WebMethod { 12 | 13 | // MARK: - Enumeration Cases 14 | 15 | case head 16 | case get 17 | case delete 18 | case patch 19 | case post 20 | case put 21 | 22 | // MARK: - Instance Properties 23 | 24 | public var logDescription: String { 25 | switch self { 26 | case .head: 27 | return "HEAD" 28 | 29 | case .get: 30 | return "GET" 31 | 32 | case .delete: 33 | return "DELETE" 34 | 35 | case .patch: 36 | return "PATCH" 37 | 38 | case .post: 39 | return "POST" 40 | 41 | case .put: 42 | return "PUT" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebMultiPart.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebMultiPart.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 21.03.2018. 6 | // Copyright © 2018 Flastack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias WebMultiPart = [WebMultiPartItem] 12 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebMultiPartDataItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebMultiPartDataItem.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 22.03.2018. 6 | // Copyright © 2018 Flastack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct WebMultiPartDataItem: WebMultiPartItem { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public let data: Data 16 | 17 | public let name: String 18 | public let fileName: String 19 | public let mimeType: String 20 | 21 | // MARK: - WebMultiPartItem 22 | 23 | public var stream: InputStream { 24 | return InputStream(data: self.data) 25 | } 26 | 27 | public var streamLength: UInt64 { 28 | return UInt64(self.data.count) 29 | } 30 | 31 | // MARK: - Initializers 32 | 33 | public init(data: Data, name: String, fileName: String, mimeType: String) { 34 | self.data = data 35 | 36 | self.name = name 37 | self.fileName = fileName 38 | self.mimeType = mimeType 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebMultiPartItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebMultiPartItem.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 22.03.2018. 6 | // Copyright © 2018 Flastack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol WebMultiPartItem { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var name: String { get } 16 | var fileName: String { get } 17 | var mimeType: String { get } 18 | 19 | var stream: InputStream { get } 20 | var streamLength: UInt64 { get } 21 | } 22 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebRequest.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct WebRequest { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public var method: WebMethod 16 | public var path: String 17 | 18 | public var parameters: [String: Any] 19 | public var headers: [String: String] 20 | 21 | public var encoding: WebRequestEncoding 22 | 23 | // MARK: - 24 | 25 | public var logDescription: String { 26 | return "\(self.method.logDescription) *\(self.path)" 27 | } 28 | 29 | // MARK: - Initializers 30 | 31 | public init(method: WebMethod, path: String, parameters: [String: Any] = [:], headers: [String: String] = [:], encoding: WebRequestEncoding = .url) { 32 | self.method = method 33 | self.path = path 34 | 35 | self.parameters = parameters 36 | self.headers = headers 37 | 38 | self.encoding = encoding 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebRequestEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebRequestEncoding.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 13.09.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum WebRequestEncoding { 12 | 13 | // MARK: - Enumeration Cases 14 | 15 | case url 16 | case json 17 | case plist 18 | } 19 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebService.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol WebService { 12 | 13 | // MARK: - Instance Properties 14 | 15 | var serverBaseURL: URL { get } 16 | 17 | var urlRequestAdapter: WebURLRequestAdapter? { get } 18 | var activityIndicator: WebActivityIndicator? { get } 19 | 20 | // MARK: - Instance Methods 21 | 22 | func make(request: WebRequest) -> WebHandler 23 | 24 | func upload(multiPart: WebMultiPart, to path: String, encodingCompletion: @escaping (WebHandler?, WebError?) -> Void) 25 | } 26 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebStatusCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebStatusCode.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct WebStatusCode: WebErrorBase { 12 | 13 | // MARK: - Instance Properties 14 | 15 | public var rawValue: Int 16 | 17 | // MARK: - WebErrorBase 18 | 19 | public var logDescription: String { 20 | return "WebStatusCode: \(self.rawValue)" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Swift-Base/Tools/WebService/WebURLRequestAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebURLRequestAdapter.swift 3 | // Tools 4 | // 5 | // Created by Almaz Ibragimov on 08.03.2018. 6 | // Copyright © 2018 Flatstack. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol WebURLRequestAdapter { 12 | 13 | // MARK: - Instance Methods 14 | 15 | func adapt(urlRequest: URLRequest) throws -> URLRequest 16 | } 17 | -------------------------------------------------------------------------------- /Swift-Base/ViewControllers/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Swift-Base 4 | // 5 | // Created by FS Flatstack on 13/02/2015. 6 | // Copyright © 2015 Flatstack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: LoggedViewController { 12 | 13 | // MARK: - Instance Properties 14 | 15 | @IBOutlet private weak var label: UILabel! 16 | 17 | // MARK: - Instance Methods 18 | 19 | @IBAction private func tapShowAlert(_ sender: AnyObject) { 20 | let alertController = UIAlertController( 21 | title: nil, 22 | message: R.string.localizable.press_ok(), 23 | preferredStyle: .alert 24 | ) 25 | 26 | alertController.addAction(UIAlertAction(title: R.string.localizable.alert_ok(), style: .cancel)) 27 | 28 | self.present(alertController, animated: true) 29 | } 30 | 31 | // MARK: - UIViewController 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Swift-BaseStaging/Swift-BaseStaging.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${BUNDLE_DISPLAY_NAME} 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLName 29 | com.flatstack.Swift-Base 30 | CFBundleURLSchemes 31 | 32 | DeepLink 33 | 34 | 35 | 36 | CFBundleVersion 37 | $(CURRENT_PROJECT_VERSION) 38 | ITSAppUsesNonExemptEncryption 39 | 40 | LSRequiresIPhoneOS 41 | 42 | NSAppTransportSecurity 43 | 44 | NSExceptionDomains 45 | 46 | httpbin.org 47 | 48 | NSIncludesSubdomains 49 | 50 | NSTemporaryExceptionAllowsInsecureHTTPLoads 51 | 52 | NSTemporaryExceptionMinimumTLSVersion 53 | TLSv1.1 54 | 55 | 56 | 57 | UILaunchStoryboardName 58 | LaunchScreen 59 | UIMainStoryboardFile 60 | Main_Storyboard 61 | UIMainStoryboardFile~ipad 62 | Main_iPad 63 | UIRequiredDeviceCapabilities 64 | 65 | armv7 66 | 67 | UIStatusBarStyle 68 | UIStatusBarStyleDefault 69 | UISupportedInterfaceOrientations 70 | 71 | UIInterfaceOrientationPortrait 72 | 73 | UISupportedInterfaceOrientations~ipad 74 | 75 | UIInterfaceOrientationPortrait 76 | UIInterfaceOrientationPortraitUpsideDown 77 | UIInterfaceOrientationLandscapeLeft 78 | UIInterfaceOrientationLandscapeRight 79 | 80 | UIViewControllerBasedStatusBarAppearance 81 | 82 | URL_HOST 83 | ${URL_HOST} 84 | 85 | 86 | -------------------------------------------------------------------------------- /Swift-BaseTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Swift-BaseTests/Swift_BaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swift_BaseTests.swift 3 | // Swift-BaseTests 4 | // 5 | // Created by Timur Shafigullin on 05/03/2019. 6 | // Copyright © 2019 Flatstack. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class Swift_BaseTests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDown() { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | #team_id "XXX" # Developer Portal Team ID 2 | 3 | # you can even provide different app identifiers, Apple IDs and team names per lane: 4 | # More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md 5 | -------------------------------------------------------------------------------- /fastlane/Deliverfile: -------------------------------------------------------------------------------- 1 | ###################### More Options ###################### 2 | # If you want to have even more control, check out the documentation 3 | # https://github.com/fastlane/fastlane/blob/master/deliver/Deliverfile.md 4 | 5 | 6 | ###################### Automatically generated ###################### 7 | # Feel free to remove the following line if you use fastlane (which you should) 8 | 9 | #username "XXX" # your Apple ID user 10 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # Customise this file, documentation can be found here: 2 | # https://github.com/fastlane/fastlane/tree/master/fastlane/docs 3 | # All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md 4 | # can also be listed using the `fastlane actions` command 5 | 6 | # Change the syntax highlighting to Ruby 7 | # All lines starting with a # are ignored when running `fastlane` 8 | 9 | # If you want to automatically update fastlane if a new version is available: 10 | # update_fastlane 11 | 12 | # This is the minimum version number required. 13 | # Update this, if you use features of a newer version 14 | fastlane_version "2.120.0" 15 | 16 | default_platform(:ios) 17 | 18 | before_all do 19 | setup_semaphore 20 | 21 | ENV["SCHEME_PRODUCTION"] = "Swift-Base" 22 | ENV["BUNDLE_ID_PRODUCTION"] = "com.flatstack.ios.Swift-Base" 23 | 24 | ENV["SCHEME_STAGING"] = "Swift-BaseStaging" 25 | ENV["BUNDLE_ID_STAGING"] = "com.flatstack.ios.Swift-Base.staging" 26 | 27 | ENV["CHANGELOG_FILE"] = File.expand_path("../changelog.txt") 28 | end 29 | 30 | platform :ios do 31 | # -------------------------------------- # 32 | # Helpers # 33 | # -------------------------------------- # 34 | 35 | lane :build do 36 | match(type: 'appstore') 37 | cocoapods 38 | gym(scheme: ENV["SCHEME_PRODUCTION"], clean: true, codesigning_identity: "iPhone Distribution") 39 | end 40 | 41 | lane :deploy_staging do 42 | sync_code_signing(type: "appstore") 43 | cocoapods(clean_install: true) 44 | 45 | build_app( 46 | scheme: ENV["SCHEME_STAGING"], 47 | clean: true, 48 | codesigning_identity: "iPhone Distribution" 49 | ) 50 | 51 | upload_to_testflight(username: "apple@flatstack.com") 52 | upload_to_firebase 53 | end 54 | 55 | lane :deploy_production do 56 | sync_code_signing(type: "appstore") 57 | cocoapods(clean_install: true) 58 | 59 | build_app( 60 | scheme: ENV["SCHEME_PRODUCTION"], 61 | clean: true, 62 | codesigning_identity: "iPhone Distribution" 63 | ) 64 | 65 | upload_to_testflight(username: "apple@flatstack.com") 66 | upload_to_firebase 67 | end 68 | 69 | # -------------------------------------- # 70 | # Private # 71 | # -------------------------------------- # 72 | 73 | desc "Get build number" 74 | private_lane :custom_get_number_of_build do |options| 75 | get_info_plist_value(key: "CFBundleVersion") 76 | end 77 | 78 | desc "Get build number. Need params :value as String" 79 | private_lane :custom_set_number_of_build do |options| 80 | set_info_plist_value(key: "CFBundleVersion", value: options[:value]) 81 | end 82 | 83 | # -------------------------------------- # 84 | # Deploy # 85 | # -------------------------------------- # 86 | 87 | desc "Generate a changelog between last merged commit and last commit" 88 | private_lane :create_default_changelog do |options| 89 | delete_changelog() 90 | 91 | last_merged_commit = sh("git log --pretty=format:\"%h\" --merges -1") 92 | last_commit = sh("git log --pretty=format:\"%h\" -1") 93 | 94 | create_changelog( 95 | changelog_from_commit: last_merged_commit, 96 | changelog_to_commit: last_commit 97 | ) 98 | end 99 | 100 | desc "Generate a changelog. Need params :changelog_from_commit, :changelog_to_commit as String" 101 | private_lane :create_changelog do |options| 102 | delete_changelog() 103 | 104 | current_date = Time.now.strftime("%Y/%m/%d %H:%M:%S") 105 | first_message = "This version was uploaded automatically by CI\nCI Build number is " + custom_get_number_of_build().to_s + "\nUploaded: " + current_date + "\nChangelog:\n" 106 | changelog = changelog_from_git_commits( 107 | between: [options[:changelog_from_commit], options[:changelog_to_commit]], 108 | pretty: "- %an, %ar : %s", 109 | merge_commit_filtering: 'exclude_merges' 110 | ) 111 | full_message = first_message + changelog 112 | 113 | # Write to file 114 | out_file = File.new(ENV["CHANGELOG_FILE"], "w+") 115 | out_file.puts(full_message) 116 | out_file.close 117 | end 118 | 119 | desc "Read changelog" 120 | private_lane :read_changelog do |options| 121 | if File.file?(ENV["CHANGELOG_FILE"]) 122 | #read the file 123 | puts File.read(ENV["CHANGELOG_FILE"]) 124 | end 125 | end 126 | 127 | desc "Deploy a scheme to firebase." 128 | private_lane :upload_to_firebase do 129 | firebase_app_distribution( 130 | app: "1:644059465480:ios:1f9f7065c70a913c5ccebd", 131 | firebase_cli_path: "/usr/local/bin/firebase", 132 | ) 133 | end 134 | 135 | # -------------------------------------- # 136 | # Clean # 137 | # -------------------------------------- # 138 | 139 | desc "Delete changelog" 140 | private_lane :delete_changelog do |options| 141 | if File.file?(ENV["CHANGELOG_FILE"]) 142 | File.delete(ENV["CHANGELOG_FILE"]) 143 | end 144 | end 145 | end 146 | 147 | # More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md 148 | # All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md 149 | 150 | # fastlane reports which actions are used 151 | # No personal data is recorded. Learn more at https://github.com/fastlane/enhancer 152 | -------------------------------------------------------------------------------- /fastlane/Matchfile: -------------------------------------------------------------------------------- 1 | git_url("git@github.com:fs/ios-base-swift-signing.git") 2 | 3 | storage_mode("git") 4 | 5 | type("development") # The default type, can be: appstore, adhoc, enterprise or development 6 | 7 | app_identifier(["com.flatstack.ios.Swift-Base.staging", "com.flatstack.ios.Swift-Base"]) 8 | username("apple@flatstack.com") # Your Apple Developer Portal username 9 | team_id("Y49MTY8CU2") -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-semaphore' 6 | gem 'fastlane-plugin-firebase_app_distribution' 7 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios build 20 | ``` 21 | fastlane ios build 22 | ``` 23 | 24 | ### ios deploy_staging 25 | ``` 26 | fastlane ios deploy_staging 27 | ``` 28 | 29 | ### ios deploy_production 30 | ``` 31 | fastlane ios deploy_production 32 | ``` 33 | 34 | 35 | ---- 36 | 37 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 38 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 39 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 40 | -------------------------------------------------------------------------------- /fastlane/report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /rename-project.command: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PATH=$PATH:~/usr/local/bin 4 | 5 | BASE_NAME="Swift-Base" 6 | 7 | #Renaming 8 | transform() { 9 | # Rename all text entry in files which contain string from first argument 10 | git grep -lz "$1" | xargs -0 perl -i'' -pE "s/$1/$2/g" 11 | # Rename all files contains string from first argument 12 | find . -depth -name "*$1*" -exec bash -c 'for f; do base=${f##*/}; mv -- "$f" "${f%/*}/${base//'$1'/'$2'}"; done' _ {} + 13 | } 14 | 15 | DIRECTORY=$(dirname "${0}") 16 | cd "$DIRECTORY" 17 | 18 | #Request new name 19 | echo "Input new project name" 20 | read NEW_NAME 21 | 22 | #Confirmation 23 | echo "New name is $NEW_NAME in $DIRECTORY" 24 | read -p "Are you sure? " -n 1 -r 25 | echo "" 26 | 27 | if [[ ! $REPLY =~ ^[Yy]$ ]] 28 | then 29 | exit 1 30 | fi 31 | 32 | #Self destruction 33 | rm ./rename-project.command 34 | 35 | #Git setup 36 | git init 37 | git add -A 38 | git commit -m "Inital commit" 39 | 40 | #Body 41 | transform "$BASE_NAME" "$NEW_NAME" 42 | 43 | #Git renaming commit 44 | git add -A 45 | git commit -m "Rename project" 46 | 47 | #Success message 48 | echo "Success" 49 | 50 | git status 51 | --------------------------------------------------------------------------------