├── .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 | 
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