Compare commits

...

465 Commits

Author SHA1 Message Date
Ivan Smolin 3d57f5ccc4 Merge pull request 'HolderViewSkeletonsConfiguration, CALayer support for DashedBoundsLayer' (#29) from feature/skeletons_holder_configuration into master
Reviewed-on: #29
2024-02-12 10:52:50 +03:00
Ivan Smolin e8780d0238 Update: `ViewSkeletonsConfiguration`. It's possible to enable or disable animation for specific skeletons now.
Added: `HolderViewSkeletonsConfiguration` for skeleton root view configuration
Added: `DashedBoundsLayer` can now be applied to `CALayer`
2024-02-09 16:14:32 +03:00
Ivan Smolin b7862cb891 Merge pull request 'fix: revert `TextSkeletonsConfiguration` line height calculation' (#28) from fix/skeleton_line_height_calculation into master
Reviewed-on: #28
2024-02-01 14:41:53 +03:00
Ivan Smolin e8ca5ad039 fix: revert `TextSkeletonsConfiguration` line height calculation 2024-02-01 14:35:27 +03:00
Ivan Smolin 8fe2bfd6e8 Merge pull request 'feature/update_table_kit_repo' (#27) from feature/update_table_kit_repo into master
Reviewed-on: #27
2024-02-01 10:45:47 +03:00
Ivan Smolin 18e5421e14 feat: Update: use TouchInstinct `TableKit` fork instead of original one
Update: remove default value from `BoolValueDefaultsStorage`
2024-02-01 10:14:21 +03:00
Ivan Smolin 5549854c75 feat: update tablekit repo 2024-01-31 12:18:40 +03:00
Ivan Smolin af7c5bc9fd Merge pull request 'fix: build and warnings' (#26) from fix/build_and_warnings into master
Reviewed-on: #26
2024-01-30 21:27:35 +03:00
Ivan Smolin 6a7af59ffc fix: build and warnings 2024-01-30 21:25:13 +03:00
Ivan Smolin 36b4f84306 Merge pull request 'Added: `xcprivacy` files' (#25) from fix/minor_fixes_and_xcprivacy into master
Reviewed-on: #25
2024-01-30 20:51:04 +03:00
Ivan Smolin 5c7df9373d Added: `xcprivacy` files
Update: Correctly detect app reinstall in `AppInstallLifetimeSingleValueStorage`
Update: use `xHeight` instead of `pointSize` for default skeleton line height calculation
Update: update `linkTextAttributes` in `UITextView` when setting interactive url parts
2024-01-30 20:40:14 +03:00
Ivan Smolin 5c44471dbb Merge pull request 'fix: change UIButton event propogation avoidance method' (#24) from fix/uibutton_event_propogation_fix into master
Reviewed-on: #24
2023-11-24 12:57:00 +03:00
Ivan Smolin 7a7987239d fix: change UIButton event propogation avoidance method 2023-11-23 21:16:36 +03:00
Ivan Smolin b65029056f Merge pull request 'fix: StatefulButton state configuration for iOS 15+' (#23) from fix/stateful_button_state into master
Reviewed-on: #23
2023-11-08 18:20:01 +03:00
Ivan Smolin 815d749bd9 fix: StatefulButton state configuration for iOS 15+ 2023-11-08 18:17:10 +03:00
Ivan Smolin 03059969b2 Merge pull request 'feat: Set reasonable defaults for SkeletonConfiguration' (#22) from feature/skeleton_configuration_defaults into master
Reviewed-on: #22
2023-11-07 16:39:55 +03:00
Ivan Smolin 1a9ed8353b feat: Set reasonable defaults for SkeletonConfiguration 2023-11-07 16:29:10 +03:00
Ivan Smolin 03aa25e529 Merge pull request 'fix: Changed access level from internal to public of title and subtitle view in BaseTitleSubtitleView' (#21) from fix/base_title_subtitle_view_access_level into master
Reviewed-on: #21
2023-11-03 22:24:57 +03:00
Ivan Smolin 48a2b47428 fix: Changed access level from internal to public of title and subtitle view in BaseTitleSubtitleView 2023-11-03 22:21:05 +03:00
Ivan Smolin d06c1c35d4 Merge pull request 'feat: Added: BaseTitleSubtitleView which can be inherited for fine-tuning skeletons and other behavior.' (#20) from feature/base_title_subtitle_view into master
Reviewed-on: #20
2023-11-03 19:38:19 +03:00
Ivan Smolin 9fab98b560 feat: Added: BaseTitleSubtitleView which can be inherited for fine-tuning skeletons and other behavior.
Update: Changed lines number calculation method in TextSkeletonsConfiguration.
2023-11-03 18:56:26 +03:00
Ivan Smolin 94cf900f7c Merge pull request 'feat: Added: maxWidth parameter to BaseViewSkeletonsConfiguration.' (#19) from feature/skeletons_configuration into master
Reviewed-on: #19
2023-11-02 21:55:21 +03:00
Ivan Smolin c1e96eee35 feat: Added: maxWidth parameter to BaseViewSkeletonsConfiguration.
Added: custom SkeletonConfigurations for nested SkeletonPresenters.
Update: Many fixes and improvements to TextSkeletonsConfiguration.
2023-11-02 21:40:01 +03:00
Ivan Smolin ed8a2113c4 Merge pull request 'feat: Skeletonable can now control custom geometry change notification.' (#18) from feature/skeletons_tuning into master
Reviewed-on: #18
2023-11-01 15:55:48 +03:00
Ivan Smolin 8652795ddb feat: Skeletonable can now control custom geometry change notification.
Filter hidden views from skeletonable views by default.
2023-11-01 14:45:57 +03:00
Ivan Smolin 38dc604cbc Merge pull request 'feat: DefaultTitleSubtitleView support for separated configuration of title and subtitle labels layout.' (#17) from feature/layout_improvenments into master
Reviewed-on: #17
2023-10-30 16:36:31 +03:00
Ivan Smolin abe9ad5dc1 feat: DefaultTitleSubtitleView support for separated configuration of title and subtitle labels layout.
BaseListItemView fixed trailing insets when trailing view is hidden.
2023-10-30 15:21:55 +03:00
Ivan Smolin abd18d848c Merge pull request 'fix: layout DSL heuristics' (#16) from feature/layout_dsl_heuristics into master
Reviewed-on: #16
2023-10-23 14:05:16 +03:00
Ivan Smolin 4a076b8865 fix: layout DSL heuristics 2023-10-20 20:40:21 +03:00
Ivan Smolin 767c19d17b Merge pull request 'feat: Custom string attributes to `BaseTextAttributes`' (#15) from feature/uiviewbackground into master
Reviewed-on: #15
2023-10-09 23:52:09 +03:00
Ivan Smolin 90cd941eff docs: update docs 2023-10-09 23:30:37 +03:00
Ivan Smolin 2bf1fc052a feat: Custom string attributes to `BaseTextAttributes`
- Customizeable `UIViewBackground` and `UIViewBorder` for `UIView.Appearance`
- Keychain single value storage for codable models -`CodableSingleValueKeychainStorage`
- Renamed methods `startAnimation` and `stopAnimation` of `SkeletonPresenter`, so it won't conflict with `Animatable` protocol anymore
2023-10-09 23:03:51 +03:00
Ivan Smolin a03fc1f7ee Merge pull request 'build: fix compile issue' (#14) from build/fix_compile_issue into master
Reviewed-on: #14
2023-09-06 12:07:18 +03:00
Ivan Smolin 7945aa3a62 build: fix compile issue 2023-09-06 11:51:16 +03:00
Ivan Smolin 93b0f61b00 Merge pull request 'feat: TIApplication module and other fixes and improvements' (#13) from feature/ti_application into master
Reviewed-on: #13
2023-09-06 11:31:36 +03:00
Ivan Smolin a0c7faa4a3 fix: code review notes 2023-09-05 16:49:05 +03:00
Ivan Smolin 60734996f6 feat: add universal DSL to TISwiftUtils, remove unused code 2023-08-24 12:07:43 +03:00
Ivan Smolin eaea4abd75 feat: TIApplication module and other fixes and improvements 2023-08-23 20:49:23 +03:00
Ivan Smolin b8611321fb Merge pull request 'feature/bottom-sheet' (#11) from feature/bottom-sheet into master
Reviewed-on: #11
2023-07-28 16:18:36 +03:00
Ivan Smolin 13a5925443 fix: review notes 2023-07-28 16:08:35 +03:00
Ivan Smolin c485434f51 build: add makefile for parallel execution of pod repo push action 2023-07-26 22:24:02 +03:00
Ivan Smolin 843a887ec7 fix: move presentation detents settings to modal view controller appearance, force use nan for undefined layout dimension, fix related layout issues 2023-07-26 17:31:28 +03:00
Ivan Smolin 8007532351 fix: keyboard overlapping for footer and content view of BaseModalViewController 2023-07-25 19:09:21 +03:00
Ivan Smolin 0ef1edfacb build: move included pan modal sources to separate dependency 2023-07-25 17:32:21 +03:00
Ivan Smolin 27d5a3a9ca fix: fix code review notes 2023-07-24 20:48:46 +03:00
Ivan Smolin 4e8f60543d Merge branch 'master' into feature/bottom-sheet 2023-07-24 11:23:40 +03:00
Ivan Smolin c2b31a90d6 Merge pull request 'feature/stateful_button_improvements' (#12) from feature/stateful_button_improvements into master
Reviewed-on: #12
2023-07-24 10:00:37 +03:00
Ivan Smolin 1a4c42fa46 fix: StatefulButton appearance configuration 2023-07-17 18:51:41 +03:00
Ivan Smolin 094c0c40d8 build: prevent compile-time failure in application extensions 2023-07-11 16:30:34 +03:00
Ivan Smolin b22fd239e9 fix: text size calculation in cluster icon renderer 2023-07-10 18:38:41 +03:00
Ivan Smolin 65ae079e62 fix: conform TemplateDrawingOperation to OrientationAwareDrawingOperation 2023-07-10 17:25:24 +03:00
Ivan Smolin 7c8a29a7f9 build: update changelog and podspec versions 2023-07-10 16:37:27 +03:00
Ivan Smolin df2faa4cd5 feat: MarkerIconFactory can now return optional image. In this case MapManagers will show the default marker icon 2023-07-10 16:17:29 +03:00
Ivan Smolin 36019f7429 feat: TICoreGraphicsUtils module for drawing operations and other CoreGraphics related functionality 2023-07-10 16:17:29 +03:00
Ivan Smolin 5245d48a8a feat: stateful button improvements 2023-07-10 16:17:28 +03:00
Nikita Semenov 83655d2bac Merge pull request 'feat: migrating storages' (#10) from feature/migrating_storage into master
Reviewed-on: #10
2023-07-09 22:33:51 +03:00
Nikita Semenov 6b7be340f5 fix: added default logger parameter 2023-07-09 22:15:36 +03:00
Nikita Semenov 6084dd5fec fix: move tifoundationlogger to it's own folder 2023-07-09 22:05:58 +03:00
Nikita Semenov 85b206bf18 fix: move tifoundationlogger to it's own folder 2023-07-09 21:51:41 +03:00
Nikita Semenov da527644a7 fix: move tifoundationlogger to it's own folder 2023-07-07 15:39:11 +03:00
Nikita Semenov 45c060403f fix: logic of getting value from storage 2023-07-07 15:17:54 +03:00
Nikita Semenov a79ff67a38 fix: logic of deletion 2023-07-07 14:00:43 +03:00
Nikita Semenov c631053131 fix: code review notes 2023-07-07 09:01:17 +03:00
Nikita Semenov c55b8f73a9 fix: code review notes 2023-07-06 18:42:18 +03:00
Nikita Semenov 25c0d04d11 feat: update tests, migration fixes, factory method for migration storage added 2023-07-06 12:34:48 +03:00
Nikita Semenov b97ea5bc8f docs: update appearance of view controller in documentation 2023-07-03 10:17:47 +03:00
Nikita Semenov 06a83190ab fix: refactoring with new layout configuration api 2023-07-03 08:46:16 +03:00
Nikita Semenov 3f112d2d26 Merge branch 'master' into feature/bottom-sheet
# Conflicts:
#	CHANGELOG.md
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeeplink/TIDeeplink.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITextProcessing/TITextProcessing.podspec
#	TIUIElements/Sources/Views/Helpers/WrappedViewLayout+Helpers.swift
#	TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderImageView.swift
#	TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderView.swift
#	TIUIElements/Sources/Wrappers/Constraints/CenterConstraints.swift
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIWebView/TIWebView.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2023-07-03 07:31:44 +03:00
Nikita Semenov 808d40eca5 feat: added bodal wrapper view controller 2023-07-03 01:47:52 +03:00
Nikita Semenov 46ecd6970f fix: migration api refactoring + test coverage 2023-07-02 18:00:38 +03:00
Nikita Semenov 0776c99e38 Merge branch 'master' into feature/migrating_storage
# Conflicts:
#	CHANGELOG.md
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeeplink/TIDeeplink.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITextProcessing/TITextProcessing.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIWebView/TIWebView.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
#	docs/tikeychainutils/singlevaluestorage.md
2023-06-30 22:03:35 +03:00
Nikita Semenov a06f4952b9 fix: reimagine migration storage api 2023-06-28 09:26:15 +03:00
Ivan Smolin 77559babdb Merge pull request 'feature/stack_appearance_layout' (#9) from feature/stack_appearance_layout into master
Reviewed-on: #9
2023-06-26 19:51:15 +03:00
Ivan Smolin 86413b3bb4 build: fix code review notes 2023-06-26 14:06:56 +03:00
Nikita Semenov 9056b2fe8c feat: migrating storages 2023-06-25 20:14:06 +03:00
Ivan Smolin 0e45bb462d build: update changelog and up version 2023-06-17 01:56:50 +03:00
Ivan Smolin e8b026302e BaseStackView with configurable items appearance
CollectionTableViewCell self-sizing
ViewAppearance.WrappedViewLayout support for all WrappedViewHolders
ViewCallbacks support for all BaseInitializeableViews
2023-06-17 01:42:14 +03:00
Ivan Smolin 818d4dbe8d Merge pull request 'feature/flat_map_async_operation_result_type_codable_storage' (#8) from feature/flat_map_async_operation_result_type_codable_storage into master
Reviewed-on: #8
2023-06-15 13:30:22 +03:00
Nikita Semenov 86fddafcdf docs: added playground for bottom sheet 2023-06-13 10:33:20 +03:00
Nikita Semenov 919423ecda feat: completed bottom sheet api 2023-06-13 09:29:53 +03:00
Nikita Semenov c06bb56964 fix: remove unused file 2023-06-13 05:59:22 +03:00
Nikita Semenov b141dc5a45 feat: pan modal changes 2023-06-12 22:05:03 +03:00
Ivan Smolin 1e3b986c83 fix: iOS 12 crash - failed to demangle superclass of UIClosureObserverOperation from mangled name '\M^? \^Hp\M-}\M^?' 2023-06-09 16:09:17 +03:00
Ivan Smolin 2ea88a94aa build: fix excluded files pattern in TIFoundationUtils 2023-06-09 12:42:55 +03:00
Ivan Smolin 1be28959bc docs: update playground pages for AsyncOperation and SingleValueExpirationStorage 2023-06-09 11:45:55 +03:00
Ivan Smolin bf613b99e8 build: update changelog and podspec version, fix playground compile issues 2023-06-09 10:14:07 +03:00
Ivan Smolin 23c17c9d85 feat: Added flatMap operator for AsyncOperation
CodableKeyValueStorage now returns Swift.Result with typed errors.
SingleValueExpirationStorage for time aware entries (expirable api tokens, etc.)
AsyncOperation variants of process methods in NetworkServices.
2023-06-09 10:00:12 +03:00
Ivan Smolin f50bb09ad8 Merge pull request 'feature/async_single_value_storage_map_ui_settings' (#7) from feature/async_single_value_storage_map_ui_settings into master
Reviewed-on: #7
2023-06-07 20:00:41 +03:00
Ivan Smolin 005d80c531 feat: Added UserLocationFetcher helper that requests authorization and subscribes to user location updates 2023-06-05 14:36:45 +03:00
Ivan Smolin 33cc31b957 docs: update podspecs and changelog 2023-05-31 18:19:43 +03:00
Ivan Smolin 19a319f03c build: fix playground and podspec issues 2023-05-31 17:33:22 +03:00
Ivan Smolin 193a060cff feat: add AsyncSingleValueStorage for TINetworkingCache 2023-05-31 17:33:22 +03:00
Ivan Smolin a5bc2dc8f0 feat: current location marker and other ui settings to supported maps 2023-05-31 17:33:22 +03:00
Ivan Smolin 5a74c342d9 Merge pull request 'feature/single_value_storages' (#6) from feature/single_value_storages into master
Reviewed-on: #6
2023-05-31 17:18:13 +03:00
Ivan Smolin 5bb3092726 build: podspec and misc fixes 2023-05-29 12:27:59 +03:00
Ivan Smolin 83a3e5b491 build: fix swiftlint issues 2023-05-26 16:07:40 +03:00
Ivan Smolin 7b9e8b0885 feat: add RecoverableErrorType typealias to DefaultRecoverableNetworkService 2023-05-26 10:49:17 +03:00
Ivan Smolin 529277d098 feat: use DecodingError instead of untyped error in TINetworking decoding 2023-05-25 18:17:49 +03:00
Ivan Smolin ecfb83bafa feat: add TILogging module and TINetworking error logging 2023-05-25 11:42:56 +03:00
Ivan Smolin c0189dc7ae fix: fingerprints update in DefaultFingerprintsProvider 2023-05-24 16:14:39 +03:00
Ivan Smolin a0401bc9fa build: bump podspec versions 2023-05-24 15:51:28 +03:00
Ivan Smolin 43a12e322f feat: add TIKeychainUtils playground with SingleValueStorage examples 2023-05-24 15:50:59 +03:00
Ivan Smolin 5ca564476a feat: `SingleValueStorage` implementations + `AppInstallLifetimeSingleValueStorage` for automatically removing keychain items on app reinstall.
`DefaultRecoverableJsonNetworkService` supports iOS 12.
2023-05-24 10:57:17 +03:00
Ivan Smolin 0e0a8d733e Merge pull request 'feature/http_status_codes_for_error_responses' (#5) from feature/http_status_codes_for_error_responses into master
Reviewed-on: #5
2023-05-24 10:04:07 +03:00
Ivan Smolin 7fd33b6157 build: update build scripts, disable autocorrection dy default 2023-05-24 09:28:20 +03:00
Ivan Smolin 4f0c9a8ed1 build: update min deployment target for Xcode 14 2023-05-22 13:35:04 +03:00
Ivan Smolin e7517c23f8 build: update podspec version 2023-05-22 11:57:27 +03:00
Ivan Smolin 975435bb90 build: update podspecs 2023-05-19 18:30:53 +03:00
Ivan Smolin fd0365a370 fix: code lint issues in TINetworking 2023-05-19 18:23:19 +03:00
Ivan Smolin 4ea76a8499 feat: add SwiftLint 2023-05-19 17:48:08 +03:00
Ivan Smolin 6358386303 feat: added HTTP status codes to `EndpointErrorResult.apiError` responses 2023-05-19 16:30:37 +03:00
Nikita Semenov dd4c9072a9 Merge pull request 'feat: updated gitmodules urls' (#4) from fix/git_modules into master
Reviewed-on: #4
2023-04-27 13:59:21 +03:00
Nikita Semenov 5aff5f99bb docs: bumbed version + changelog update 2023-04-27 13:50:52 +03:00
Nikita Semenov 4fe395d295 feat: updated gitmodules urls 2023-04-27 13:47:58 +03:00
Vladimir Makarov a925f70c04 Merge pull request 'fix: `Antlr4` dependency added to podspec' (#3) from fix/Antlr4_dependency into master
Reviewed-on: #3
2023-04-21 12:17:46 +03:00
Vladimir Makarov 41d18bf3a6 fix: `Antlr4` dependency added to podspec 2023-04-21 11:57:07 +03:00
Vladimir Makarov fc6a735d94 Merge pull request 'TITextProcessing' (#1) from feature/TITextProcessing into master
Reviewed-on: #1
2023-04-21 11:27:15 +03:00
Vladimir Makarov 77ce6a1c95 feat: `Private methods` MARK changed to `Public methods` 2023-04-21 11:23:15 +03:00
Vladimir Makarov cf54d8c798 feat: Protocols, default implementations, playground and markdown added 2023-04-20 12:31:09 +03:00
Vladimir Makarov 93a8ee68f3 `TITextProcessing` for regex and text formatting added 2023-04-19 14:22:05 +03:00
Vladimir Makarov 5a14c61a9a Merge pull request 'fix: Podspecs' source and homepage urls' (#2) from fix/pods_source_url into master
Reviewed-on: #2
2023-04-19 14:00:21 +03:00
Vladimir Makarov 4a9355fd80 fix: Podspecs' source and homepage urls 2023-04-19 13:20:18 +03:00
Nikita Semenov 45e2b9ff19 Merge branch 'feature/deeplink_api' into 'master'
Feature/deeplink api

See merge request touchinstinct/LeadKit!13
2023-04-14 09:56:53 +00:00
Nikita Semenov 26866427a9 fix: source path to TIDeeplink library 2023-04-14 12:55:37 +03:00
Nikita Semenov 26a7578dc8 refactor: minor code style refactoring 2023-04-14 12:09:12 +03:00
Nikita Semenov 0612370587 docs: docs refactoring + extensions reorganization for handler and mapper 2023-04-13 14:53:57 +03:00
Nikita Semenov fe80863656 fix: remove meta doc file 2023-04-12 13:46:35 +03:00
Nikita Semenov 3dd0c34d27 fix: code review notes 2023-04-12 13:35:04 +03:00
Nikita Semenov 56527b6dba fix: code review notes 2023-04-12 13:02:20 +03:00
Nikita Semenov af044aa591 feat: integrate new base navigation deeplink handler 2023-04-04 13:21:50 +03:00
Nikita Semenov c442ee2623 Merge branch 'deeplink_generics_experimential' into feature/deeplink_api
# Conflicts:
#	TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift
#	TIDeepLink/Sources/DeeplinkHandler/Helpers/UIViewController+DeeplinkHandler.swift
2023-04-04 12:10:21 +03:00
Nikita Semenov 3ccadd07b4 fix: remove tiuielements updates 2023-04-04 12:08:39 +03:00
Nikita Semenov 9a9b57df4b refactor: code refactoring 2023-04-04 12:04:45 +03:00
Ivan Smolin 06b687c47e feat: experiments with deeplink generics 2023-04-03 14:01:12 +03:00
Nikita Semenov a99c29ea73 feat: update findHandler method to findAction 2023-04-01 17:46:13 +03:00
Nikita Semenov 270ac1a4d5 fix: code review notes 2023-03-31 10:19:45 +03:00
Nikita Semenov 22b133648b feat: bumped podspecs versions 2023-03-30 15:19:57 +03:00
Nikita Semenov 2245765b27 Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	CHANGELOG.md
2023-03-30 15:19:27 +03:00
Nikita Semenov 30479ae8b8 Merge branch 'feature/skeletons_status' into 'master'
feat: added callbacks for views when skeletons status changed

See merge request touchinstinct/LeadKit!14
2023-03-30 12:10:12 +00:00
Nikita Semenov 8d253d1458 docs: added documentation for new Skeletonable callbacks API 2023-03-30 15:07:43 +03:00
Nikita Semenov dcc9d23676 docs: small refactoring of documentation 2023-03-30 13:44:30 +03:00
Nikita Semenov d9e4ea5ae8 feat: updated deeplink model + documentation for deeplinks api added 2023-03-30 13:41:06 +03:00
Nikita Semenov 775f95a931 fix: podspecs file for TIUIElements 2023-03-29 15:28:38 +03:00
Nikita Semenov 39bad32c49 fix: changed names of views properties in SkeletonLayer 2023-03-29 11:50:48 +03:00
Nikita Semenov 05a6236425 fix: podfile for TIUIElements 2023-03-29 10:00:22 +03:00
Nikita Semenov 7f45ce0594 Merge branch 'master' into feature/skeletons_status
# Conflicts:
#	CHANGELOG.md
2023-03-29 09:58:28 +03:00
Nikita Semenov c407dabdf5 Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	CHANGELOG.md
2023-03-28 17:42:10 +03:00
Nikita Semenov 29d7a6ca65 Merge branch 'feature/placeholder_api' into 'master'
feat: placeholder api

See merge request touchinstinct/LeadKit!11
2023-03-28 12:22:55 +00:00
Nikita Semenov 2d0819064a Merge branch 'feature/image_placeholder_api' into 'feature/placeholder_api'
feat: placeholder image view

See merge request touchinstinct/LeadKit!12
2023-03-28 12:06:50 +00:00
Nikita Semenov 76da4ab223 feat: added callbacks for views when skeletons status changed 2023-03-28 13:15:07 +03:00
Nikita Semenov 66508d505d feat: bump pods version + changelog updates 2023-03-27 10:36:09 +03:00
Nikita Semenov c8985cde1e Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	LeadKit.podspec
#	Package.swift
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
#	project-scripts/push_to_podspecs.sh
2023-03-27 10:34:04 +03:00
Nikita Semenov 1bc200034c fix: typos 2023-03-20 13:47:55 +03:00
Nikita Semenov cde2420f7d fix: code review notes 2023-03-18 12:51:58 +03:00
Nikita Semenov 026700b4c7 feat: placeholder image view 2023-03-17 16:39:35 +03:00
Nikita Semenov 4ccebe8e8b fix: code review notes 2023-03-17 11:46:44 +03:00
Nikita Semenov f3ed27e83f fix: typo 2023-03-17 11:38:58 +03:00
Nikita Semenov a234943394 fix: code review notes 2023-03-17 11:34:21 +03:00
Nikita Semenov 1374c4df1f feat: update placeholders interactions with opend keyboard 2023-03-17 11:10:07 +03:00
Nikita Semenov 318fd40f0b docs: updated first example of usage PlaceholderFactory 2023-03-16 19:12:46 +03:00
Nikita Semenov 5ebb97de4c fix: applyBaseStyle method signature 2023-03-16 18:09:29 +03:00
Nikita Semenov 507fc8fa05 fix: defaults string names changed by code style 2023-03-16 15:49:55 +03:00
Nikita Semenov 1932262ad5 fix: code review notes 2023-03-16 14:25:42 +03:00
Nikita Semenov 2c23e86852 fix: Constaints usage 2023-03-15 17:24:20 +03:00
Nikita Semenov 164edf9a5d feat: placeholder api 2023-03-15 17:10:10 +03:00
Nikita Semenov 2f71b10dc0 Merge branch 'feature/new_appearance_configurations' into 'master'
feat: new appearance configurations

See merge request touchinstinct/LeadKit!10
2023-03-15 13:55:42 +00:00
Nikita Semenov 9d99d4e4e3 feat: moved corner configuration to UIViewBorder 2023-03-14 18:27:40 +03:00
Nikita Semenov 9cc412208c docs: updated documentation for skeletons 2023-03-14 16:20:18 +03:00
Nikita Semenov a8fc13ff1e fix: CHANGELOG last version number 2023-03-14 15:44:26 +03:00
Nikita Semenov 22fc660e56 fix: code review notes 2023-03-14 15:43:43 +03:00
Nikita Semenov 5d2bea19fb fix: access control for wrappedViewLayout changed to internal 2023-03-14 14:31:37 +03:00
Nikita Semenov e2c9c6c102 feat: new appearance configurations 2023-03-14 14:29:29 +03:00
Vladimir Makarov 332e895659 Merge branch 'feature/maps_placemark_icon_updating' into 'master'
feat: Placemark appearance updating added

See merge request touchinstinct/LeadKit!3
2023-03-13 09:55:44 +00:00
Vladimir Makarov 9dca21afd9 `BaseItemPlacemarkManager` for single placemark managers added 2023-03-13 10:54:37 +01:00
Vladimir Makarov 511c2b9653 Optional for `placemarkPosition` removed 2023-03-13 10:47:35 +01:00
Vladimir Makarov 2b1511657d `BaseClusterPlacemarkManager` inheritance updated, optional casts removed 2023-03-13 10:47:35 +01:00
Vladimir Makarov f91453a065 Libraries versions updated to 1.38.0, CHANGELOG updated 2023-03-13 10:47:35 +01:00
Vladimir Makarov 23b74ec3d5 `CacheKeyProvider` updated 2023-03-13 10:47:35 +01:00
Vladimir Makarov 4c973b393d `BaseClusterPlacemarkManager` added 2023-03-13 10:47:35 +01:00
Vladimir Makarov 0090c83f87 Libraries versions updated to 1.36.2 2023-03-13 10:47:35 +01:00
Vladimir Makarov 144ea7b703 feat: Placemark appearance updating added 2023-03-13 10:47:35 +01:00
Nikita Semenov 278e175f3a Merge branch 'feature/update_TIDeveloperUtils_dependencies' into 'master'
fix: dependencies

See merge request touchinstinct/LeadKit!9
2023-03-13 08:33:52 +00:00
Nikita Semenov 6af9a64135 fix: dependencies 2023-03-13 11:24:12 +03:00
Nikita Semenov efde6153a8 Merge branch 'feature/skeletons_api' into 'master'
Feature/skeletons api

See merge request touchinstinct/LeadKit!1
2023-03-13 07:46:08 +00:00
Nikita Semenov e3ae781f1d fix: typo + small refactoring 2023-03-13 10:42:17 +03:00
Nikita Semenov 23c2cbacea fix: code review notes 2023-03-11 22:07:06 +03:00
Nikita Semenov 40aa2876d1 fix: code review notes 2023-03-09 22:01:45 +03:00
Nikita Semenov 2250b1b4d9 fix: code review notes 2023-03-09 20:51:25 +03:00
Nikita Semenov 19bb08aa66 feat: added new sys of hidding views below the skeletons 2023-03-09 17:49:37 +03:00
Nikita Semenov 597755474c fix: url to source in podspecs 2023-03-09 12:49:56 +03:00
Nikita Semenov cb29a3e9ca fix: code review notes 2023-03-09 12:48:24 +03:00
Nikita Semenov eafb434c88 Merge branch 'feature/skeletons_docs' into 'feature/skeletons_api'
Feature/skeletons docs

See merge request touchinstinct/LeadKit!5
2023-03-07 17:51:06 +00:00
Nikita Semenov cfd5d5f2f8 fix: code review notes 2023-03-07 20:23:58 +03:00
Nikita Semenov 9c8510af14 feat: updated skeletons documentation for new api and code review comment 2023-03-07 18:42:03 +03:00
Nikita Semenov e942d08503 Merge branch 'feature/skeletons_api' into feature/skeletons_docs 2023-03-07 14:42:07 +03:00
Nikita Semenov 2c081d508f Merge branch 'master' into feature/skeletons_api 2023-03-07 14:39:22 +03:00
Nikita Semenov f01644b408 Merge branch 'feature/changed_skeletons_presenting_system' into 'feature/skeletons_api'
feat: removed SkeletonsPresenter and move all logic to extensions of UIView and UIViewController

See merge request touchinstinct/LeadKit!6
2023-03-07 10:08:20 +00:00
Nikita Semenov b533eaaae6 fix: remove useless baseView 2023-03-07 13:00:34 +03:00
Vladimir Makarov 9db353b360 Merge branch 'feature/yandex_min_deployment_target' into 'master'
TIYandexMapUtils min deployment target updated

See merge request touchinstinct/LeadKit!8
2023-03-07 09:55:09 +00:00
Vladimir Makarov e4c84ca511 `TIYandexMapUtils` min deployment target updated 2023-03-07 10:32:35 +01:00
Vladimir Makarov ce2f3ca064 Merge branch 'feature/yandex_maps' into 'master'
YandexMapsMobile version updated, map manager memory leak removed

See merge request touchinstinct/LeadKit!7
2023-03-07 09:13:08 +00:00
Vladimir Makarov cbd38b84e2 Podspec versions updated 2023-03-07 09:44:26 +01:00
Vladimir Makarov c8a4b0bd51 `YandexMapsMobile` version updated, map manager memory leak removed 2023-03-07 09:41:18 +01:00
Nikita Semenov 7a0747843a feat: removed computed @objc properties 2023-03-07 11:38:09 +03:00
Nikita Semenov 343d36cb85 feat: removed SkeletonsPresenter and move all logic to extensions of UIView and UIViewController 2023-03-07 10:11:56 +03:00
Nikita Semenov 29347d77e1 fix: code review notes 2023-03-06 19:27:56 +03:00
Nikita Semenov 64604bdce0 Merge branch 'master' into feature/skeletons_api
# Conflicts:
#	CHANGELOG.md
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIWebView/TIWebView.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2023-03-06 19:27:47 +03:00
Nikita Semenov e27c844f92 Merge branch 'fix/remove_logger_api' into 'master'
fix: removed custom logger wrapper

See merge request touchinstinct/LeadKit!4
2023-03-06 11:58:52 +00:00
Nikita Semenov 01d99cb246 docs: change release version 2023-03-06 14:57:52 +03:00
Nikita Semenov 48f6655efc fix: logic of font size determination 2023-03-06 14:55:36 +03:00
Nikita Semenov bc9ac01463 fix: compilation of the projects 2023-03-06 13:40:12 +03:00
Nikita Semenov dda06ece3c fix: dependencies of TIDeveloperUtils module 2023-03-06 10:59:55 +03:00
Nikita Semenov 5ea58f3746 feat: update TIDeveloperUtils module, removed TILogger module 2023-03-06 10:47:09 +03:00
Nikita Semenov 2061050c78 fix: removed custom logger wrapper 2023-03-05 17:52:00 +03:00
Nikita Semenov e199cd4220 docs: skeletons playground page 2023-03-05 17:42:45 +03:00
Nikita Semenov 6e30957fe5 docs: skeletons api playground page 2023-03-05 17:41:11 +03:00
Nikita Semenov 84e7093903 Merge branch 'fix/repo_urls' into 'master'
Fix/repo urls

See merge request touchinstinct/LeadKit!2
2023-03-03 16:30:51 +00:00
Nikita Semenov 1da2d4d501 fix: bump version back + update push to podspec script 2023-03-03 17:47:25 +03:00
Nikita Semenov 118bca1c9d feat: added default implementation of to protocol 2023-03-03 14:39:32 +03:00
Nikita Semenov fbab4a491b fix: code review notes 2023-03-03 14:35:46 +03:00
Nikita Semenov fd2fa45909 feat: padding configuration for skeleton layer 2023-03-03 14:01:48 +03:00
Nikita Semenov e471bae469 docs: bumps podspec version of pods 2023-03-02 16:54:47 +03:00
Nikita Semenov 698243ee39 fix: updated url to ti repositories 2023-03-02 16:48:17 +03:00
Nikita Semenov 59ef1093c7 fix: change default shape of UIImageViews 2023-03-01 20:40:33 +03:00
Nikita Semenov 7442884856 fix: minor changes 2023-03-01 20:39:04 +03:00
Nikita Semenov 6e506aa385 feat: updated exporting of environment variable 2023-03-01 19:16:33 +03:00
Nikita Semenov 9f5d7387d7 feat: skeletons api 2023-03-01 19:05:52 +03:00
Ivan Smolin e9b32ce326
Merge pull request #345 from TouchInstinct/fix/push_podspecs_fixups
build: push podspecs fixups
2023-03-01 09:54:44 +03:00
Ivan Smolin a9a8ddde9e build: push podspecs fixups 2023-02-28 16:59:57 +03:00
Ivan Smolin 893f6f191d
Merge pull request #344 from TouchInstinct/feature/documentation_generation
feat: Auto documentation generation
2023-02-28 15:04:08 +03:00
Ivan Smolin 56d1ee998d build: remove --use-cache flag due build failure 2023-02-28 14:56:36 +03:00
Ivan Smolin 55fe6b7126 docs: fix review notes 2023-02-28 14:56:36 +03:00
Ivan Smolin 5f7e0bf273 feat: Auto documentation generation for `TIFoundationUtils` playground and compile checks for playground before release
`AsyncOperation` fixed ordering of chain operations execution
2023-02-28 14:56:36 +03:00
Nikita Semenov 3a321a7fbf
Merge pull request #343 from TouchInstinct/feature/swiftui_previews
feat: extensions for SwiftUI previews
2023-02-21 09:04:31 +03:00
Nikita Semenov 275afb655f fix: push to podspec update 2023-02-15 12:46:29 +03:00
Nikita Semenov f3c5002f4e feat: added dashed layer support for UIViewController 2023-02-14 20:12:33 +03:00
Nikita Semenov 3406962d21 docs: updated release version in changelog 2023-02-14 19:54:14 +03:00
Nikita Semenov f1d5b27f3d feat: DashedBoundsLayer for debugging frames of the views visually 2023-02-14 19:53:14 +03:00
Nikita Semenov f4a516bf86 feat: rebase preview extensions to new framework TIDeveloperUtils 2023-02-14 17:52:20 +03:00
Nikita Semenov 46be3ce9de feat: extensions for SwiftUI previews 2023-02-14 16:34:46 +03:00
Nikita Semenov 30c5b72b26
Merge pull request #342 from TouchInstinct/feature/base_views
Feature/base views
2023-02-14 14:34:29 +03:00
Nikita Semenov f67005df71
Merge pull request #339 from TouchInstinct/feature/appearance_customization_model
feat: api for configuration Views' appearance and layout
2023-02-14 11:51:55 +03:00
Nikita Semenov 05fa3dad31
Merge pull request #340 from TouchInstinct/feature/view_containers
feat: container views
2023-02-14 11:39:28 +03:00
Nikita Semenov 0c3b987370 fix: code review notes 2023-02-13 21:19:35 +03:00
Nikita Semenov 7765c01074 fix: code review notes 2023-02-12 18:35:28 +03:00
Nikita Semenov f2c390f71a feat: updated method to hide and show subtitle of DefaultTitleSubtitleView 2023-02-12 18:15:31 +03:00
Nikita Semenov 7e41552521 feat: DefaultTitleSubtileView, BaseListItemView, StatefulButton appearance configuration update 2023-02-11 21:22:03 +03:00
Nikita Semenov bd7d31cf67 fix: code review notes 2023-02-10 17:53:21 +03:00
Nikita Semenov 4e1270205e docs: added change log information 2023-02-10 16:28:53 +03:00
Nikita Semenov c5209dc9f6 feat: container views 2023-02-10 15:27:35 +03:00
Nikita Semenov 3ef71a6892 fix: updated 1.33.0 version in changelog 2023-02-09 23:18:11 +03:00
Nikita Semenov 04ee83b8df fix: code review notes 2023-02-09 21:41:52 +03:00
Nikita Semenov 6ff2c8bf37 fix: access to with(appearance:) method 2023-02-09 17:48:14 +03:00
Nikita Semenov 2d3e12164d feat: api for configuration Views' appearance and layout 2023-02-09 15:17:02 +03:00
Nikita Semenov fffe5bd8d4
Merge pull request #336 from TouchInstinct/feature/web_view
Feature/web view
2023-01-30 13:22:26 +03:00
Nikita Semenov f6e00bb53a fix: code review notes 2023-01-30 11:42:06 +03:00
Nikita Semenov a67c42886d Merge branch 'master' into feature/web_view
# Conflicts:
#	CHANGELOG.md
#	LeadKit.podspec
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2023-01-30 11:37:24 +03:00
Nikita Semenov 407995db35 Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	LeadKit.podspec
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2023-01-22 20:11:32 +03:00
Nikita Semenov 63777fef99 fix: update an approach to handling operation in TIDeeplinkService 2023-01-22 20:09:24 +03:00
Ivan Smolin 79a5cc3665
Merge pull request #335 from TouchInstinct/feature/uitextview_url_interactive
feat: URLInteractiveTextView for terms and conditions hints in login flow
2023-01-11 18:28:01 +03:00
Nikita Semenov 0204aa9b10 feat: added json decoder parameter for js error model init 2023-01-11 14:46:37 +03:00
Nikita Semenov 79a5ed4149 fix: remove redundant constants 2023-01-11 14:35:19 +03:00
Nikita Semenov 3bab367ce2 fix: code review notes 2023-01-11 14:19:21 +03:00
Nikita Semenov caeded9561 fix: searching deeplink handler in navigation stack 2023-01-11 13:25:20 +03:00
Nikita Semenov 3d5aa7a41d fix: remove default implementation of deeplink handler in service object 2023-01-11 12:02:07 +03:00
Nikita Semenov 47217f7a59 fix: paramenters for js error model to correspond to js exception models 2023-01-10 20:00:03 +03:00
Nikita Semenov e4c8118b6b fix: code review notes 2023-01-10 19:53:47 +03:00
Nikita Semenov f885183499 fix: updated push to podspecs script 2023-01-10 19:27:53 +03:00
Nikita Semenov aff54859eb feat: complete deeplink api 2023-01-10 19:26:46 +03:00
Nikita Semenov 3e7307424a fix: change types of parameters for js errors 2023-01-09 15:29:41 +03:00
Nikita Semenov a7e44a3d9a feat: initial deep link settings 2023-01-06 07:20:10 +07:00
Nikita Semenov c75ff4c1d0 fix: code review notes 2022-12-30 17:15:46 +07:00
Nikita Semenov b98678b235 fix: code review notes 2022-12-30 02:24:19 +03:00
Ivan Smolin c7ba0d1f30 fix: add link to each part of attributed text 2022-12-27 14:03:08 +03:00
Nikita Semenov 9d3bbc9c71 fix: code review notes 2022-12-27 00:36:31 +03:00
Nikita Semenov 7add7c46ab fix: bump podspec version 2022-12-26 11:19:47 +03:00
Ivan Smolin 724ed400c4 style: review notes 2022-12-26 10:50:29 +03:00
Ivan Smolin ad5fbc96fb fix: initializEable -> initializAble 2022-12-26 10:50:13 +03:00
Nikita Semenov d12e07484d feat: custom webview api 2022-12-26 10:36:58 +03:00
Ivan Smolin 5017e7e7b7 feat: URLInteractiveTextView for terms and conditions hints in login flow 2022-12-23 11:09:55 +03:00
Ivan Smolin 6e442ce37f
Merge pull request #334 from TouchInstinct/feature/pin_code_token_storage
feat: Base classes for encryption and decryption user token with pin code or biometry + Pin code validators
2022-12-21 15:29:11 +03:00
Ivan Smolin e6864f3911 build: move TIFoundationUitls/DataStorage/* to TIFoundationUitls/DataStorage/Sources/ 2022-12-21 15:24:48 +03:00
Ivan Smolin 18cd001ab9 feat: Base classes for encryption and decryption user token with pin code or biometry
Pin code validators
2022-12-19 16:32:44 +03:00
Ivan Smolin 8c696af6d9
Merge pull request #333 from TouchInstinct/feature/base_text_attributes_fixes
fix: correct detection of the necessity of using attributed string
2022-12-15 19:46:22 +05:00
Ivan Smolin 1fb3ca883b
fix typo 2022-12-15 15:50:12 +05:00
Ivan Smolin 3730736319 fix: correct detection of the necessity of using attributed string 2022-12-15 14:03:08 +05:00
Ivan Smolin 9dcdd1c63c
Merge pull request #331 from TouchInstinct/feature/1.29.0_features
BaseTextAttributes, Operation and other
2022-12-09 13:38:54 +03:00
Ivan Smolin ba929fd344 fix: code review notes 2022-12-07 11:05:16 +03:00
Ivan Smolin 2a89de15e3 feat: - Added: BaseTextAttributes can now measure text size and provides paragraph style configuration API.
- Removed: ViewText. Was fully replaced with BaseTextAttributes
- Fixed: Operation.flattenDependencies used in Operation.add(to:waitUntilFinished:) now works correctly.
- Added: Now it's possible to add dependent operation to start of the queue.
2022-12-03 22:55:08 +03:00
Nikita Semenov f7596a73d8
Merge pull request #330 from TouchInstinct/fix/push_to_podspecs
feat: added new pod to the script
2022-11-18 19:25:51 +03:00
Nikita Semenov 39ddcf498f feat: added new pod to the script 2022-11-18 19:23:54 +03:00
Nikita Semenov 98ee540aac
Merge pull request #327 from TouchInstinct/feature/logging_api
Логирование
2022-11-18 19:04:22 +03:00
Nikita Semenov 4778f2e70d feat: code review notes 2022-11-18 18:53:28 +03:00
Nikita Semenov 1c1fa1290b feat: added new system of registering for shacking motion event 2022-11-18 17:13:22 +03:00
Nikita Semenov d2ed1e837a fix: ambiguous usage of view output protocol 2022-11-17 10:32:04 +03:00
Nikita Semenov 401d9365d0 fix: code review notes 2022-11-17 10:30:11 +03:00
Nikita Semenov 5159ee5a4d fix: code review notes 2022-11-11 09:07:25 +03:00
Nikita Semenov dffb4c6015 fix: code review notes 2022-11-10 17:46:25 +03:00
Nikita Semenov f8b7934204 fix: code review notes + change gcd on async/await 2022-11-09 21:14:58 +03:00
Nikita Semenov 9fa4b7c058
Merge branch 'master' into feature/logging_api 2022-11-08 18:15:19 +03:00
Nikita Semenov e1c9596010 fix: default value for presenting method 2022-11-08 18:11:39 +03:00
Nikita Semenov d0576acd95 fix: chacking for opend logging list view controller 2022-11-08 18:06:32 +03:00
Nikita Semenov 13cf92c9c1 refactor: change podspec version + changelog updated 2022-11-08 17:47:01 +03:00
Nikita Semenov d6069db9df Merge branch 'filters_api' into feature/logging_api 2022-11-08 17:36:31 +03:00
Nikita Semenov 69c2a85718 feat: change logic of registration on shaking motion 2022-11-08 17:34:55 +03:00
Nikita Semenov 4ac0eace66 fix: os log store scope 2022-11-08 15:15:48 +03:00
Nikita Semenov 77abc2c5a5 fix: typo 2022-11-08 15:13:01 +03:00
Nikita Semenov 8ffd2b589b fix: check on shacking motion in toggling window 2022-11-08 15:08:29 +03:00
Vladimir Makarov 6ccb8fcdc3
Merge pull request #329 from TouchInstinct/fix/weak_target_reference
Weak target reference capturing in `RefreshControl` fixed
2022-10-18 20:13:42 +05:00
Vladimir Makarov 8e0043cd48 fix: weak target reference in `RefreshControl` fixed 2022-10-18 16:21:38 +05:00
Nikita Semenov 31ddf69dc0
Merge pull request #328 from TouchInstinct/filters_api
fix: podspec configuration
2022-10-11 10:47:36 +03:00
Nikita Semenov 592036951a fix: update min version 2022-10-11 10:47:21 +03:00
Nikita Semenov 01931a9e62 fix: podspec configuration 2022-10-11 09:36:35 +03:00
Nikita Semenov ea13f317b9
Merge pull request #320 from TouchInstinct/filters_api
Фильтры с тегами
2022-10-11 09:28:50 +03:00
Nikita Semenov 7e62f9b571 Merge branch 'master' into filters_api
# Conflicts:
#	CHANGELOG.md
#	LeadKit.podspec
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2022-10-11 08:56:10 +03:00
Grigory Boyko 7cfd6f8424 feat: update version 2022-10-11 08:52:46 +03:00
Grigory Boyko 4f0a652e09 fix: add escaping 2022-10-11 08:52:14 +03:00
Grigory Boyko da98e5ff35 fix: add optional 2022-10-11 08:52:14 +03:00
Grigory Boyko 83e5caa13a feat: add failure completion in RequestExecutor 2022-10-11 08:52:14 +03:00
Ivan Smolin a53c015b79 fix: Use OperationQueue instead of NSLock in `DefaultTokenInterceptor` 2022-10-11 08:51:41 +03:00
Grigory Boyko 873b2ecbcb feat: change push_to_podspecs.sh 2022-10-11 08:50:39 +03:00
Nikita Semenov 3157cac5d5
Merge pull request #324 from TouchInstinct/feature/range_filters
Range filters
2022-10-11 08:29:11 +03:00
Nikita Semenov f60a443eed fix: file creator prefix removed 2022-10-04 15:32:42 +03:00
Nikita Semenov f230add1ed refactor: naming changes 2022-10-04 12:41:32 +03:00
Nikita Semenov 444d3b159d fix: code review notes 2022-10-04 12:35:46 +03:00
Nikita Semenov f4dc72b61f refactor: typos 2022-10-04 12:08:38 +03:00
Nikita Semenov 54a01db216 feat: open logs on shacking motion + code review notes 2022-10-04 11:02:05 +03:00
Nikita Semenov 72aabd4412 fix: code review notes + small fixes 2022-10-03 12:42:26 +03:00
Nikita Semenov 070d7c199a fix: code review notes 2022-10-03 12:01:32 +03:00
Nikita Semenov 2d9bb4a5d5 refactor: added license and replace some helper objects 2022-10-03 08:03:25 +03:00
Nikita Semenov 3af9cc5b35 refactor: remove blank space 2022-10-02 22:28:13 +03:00
Nikita Semenov f6f4d2f214 fix: added more visibility for logging presenter button 2022-10-02 21:38:40 +03:00
Nikita Semenov e9edf3ab21 feat: added logger views 2022-10-02 21:11:52 +03:00
Nikita Semenov ff720fca0d feat: initial list view version 2022-09-29 15:44:27 +03:00
Nikita Semenov 31582b3bc5 fix: errors 2022-09-27 20:12:55 +03:00
Nikita Semenov 3bb9e74461 fix: package file update 2022-09-27 20:05:28 +03:00
Nikita Semenov 37818d3153 feat: basic logger 2022-09-27 20:00:44 +03:00
Grigory Boyko e2f9b481ab
Merge pull request #326 from TouchInstinct/feature/add_escaping
fix: add escaping
2022-09-26 14:37:55 +03:00
Grigory Boyko fffa5aa6eb feat: update version 2022-09-26 14:33:11 +03:00
Grigory Boyko 826c66f8af fix: add escaping 2022-09-26 12:34:53 +03:00
Grigory Boyko 91097e2f65
Merge pull request #325 from TouchInstinct/fix/add_failure_completion
feat: add failure completion in RequestExecutor
2022-09-23 18:44:35 +03:00
Grigory Boyko da4389aa03 fix: add optional 2022-09-23 18:34:37 +03:00
Grigory Boyko 81792aba72 feat: add failure completion in RequestExecutor 2022-09-23 15:03:54 +03:00
Nikita Semenov a8785b35e8 fix: code review notes 2022-09-21 19:58:24 +03:00
Nikita Semenov ca7bf6326d refactor: naming fixes 2022-09-21 18:05:17 +03:00
Nikita Semenov 116d2154f8 feat: range filters view 2022-09-21 17:50:13 +03:00
Nikita Semenov dde0eba7a8
Merge pull request #322 from TouchInstinct/feature/list_filters
Фильтры в виде списка
2022-09-06 18:48:26 +03:00
Nikita Semenov f3081861a0 fix: review notes 2022-09-06 15:42:12 +03:00
Nikita Semenov 698b79d10f refactor: spaces in switch 2022-09-05 17:19:28 +03:00
Nikita Semenov 21a73a8f7c fix: code review notes 2022-09-05 17:13:02 +03:00
Nikita Semenov 3696269635 fix: picker selection 2022-09-02 12:01:51 +03:00
Nikita Semenov 92f06c83a4 fix: code review notes 2022-09-02 11:51:37 +03:00
Nikita Semenov 2eea25b4af docs: changelog update 2022-08-31 19:09:48 +03:00
Nikita Semenov 090b86563d fix: multiselection table setup 2022-08-31 19:01:10 +03:00
Nikita Semenov c25ff8c431 fix: picker system 2022-08-31 18:53:40 +03:00
Nikita Semenov 77f6208ff5 fix: logic of picking cells + refactoring 2022-08-31 17:52:56 +03:00
Nikita Semenov eaa787e00f fix: image view size determination 2022-08-30 22:04:44 +03:00
Nikita Semenov 8cbf5b96e6 fix: container table view cell subviews 2022-08-30 22:01:06 +03:00
Nikita Semenov fa130aecdb fix: style in init of table view 2022-08-30 21:58:24 +03:00
Nikita Semenov 635a9bb9b0 fix: image of selected view 2022-08-30 21:51:16 +03:00
Nikita Semenov d16fc21d7e fix: table view delegate methods 2022-08-30 21:45:36 +03:00
Nikita Semenov 4880dcad27 fix: initialisation of table view logic 2022-08-30 21:42:51 +03:00
Nikita Semenov 0bc8574a32 fix: update table director with diffable data source 2022-08-30 21:34:29 +03:00
Nikita Semenov ec1fe892ad fix: merging filters_api 2022-08-30 14:13:11 +03:00
Nikita Semenov d9cdfdec0f Merge branch 'filters_api' into feature/list_filters
# Conflicts:
#	TIEcommerce/Sources/Filters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionCell/Models/FilterCellViewModelProtocol.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionCell/ViewModels/DefaultFilterCellViewModel.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionCell/Views/DefaultFilterCollectionCell.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionView/Helpers/Array+FilterPropertyValueRepresenter.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionView/Models/DefaultFilterPropertyValue.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionView/Protocols/FilterPropertyValueRepresenter.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionView/ViewModels/DefaultFilterViewModel.swift
#	TIEcommerce/Sources/Filters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift
#	TIEcommerce/Sources/Filters/Helpers/UICollectionViewLayout+DefaultLayout.swift
#	TIEcommerce/Sources/Filters/Models/BaseFilterCellAppearance.swift
#	TIEcommerce/Sources/Filters/Models/DefaultFilterModel.swift
#	TIEcommerce/Sources/Filters/Models/DefaultFilterPropertyValue.swift
#	TIEcommerce/Sources/Filters/Models/FilterCellViewModelProtocol.swift
#	TIEcommerce/Sources/Filters/Models/FiltersLayoutConfiguration.swift
#	TIEcommerce/Sources/Filters/Protocols/FilterCellAppearanceProtocol.swift
#	TIEcommerce/Sources/Filters/Protocols/FilterPropertyValueRepresenter.swift
#	TIEcommerce/Sources/Filters/Protocols/FilterRepresenter.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Helpers/UICollectionViewLayout+DefaultLayout.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Models/BaseFilterCellAppearance.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Models/DefaultFilterModel.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Models/DefaultFilterPropertyValue.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Models/FiltersLayoutConfiguration.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterCellAppearanceProtocol.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterCellViewModelProtocol.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterPropertyValueRepresenter.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterRepresenter.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Protocols/FiltersViewModelProtocol.swift
#	TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFilterCellViewModel.swift
#	TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFiltersViewModel.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Views/BaseFiltersCollectionView.swift
#	TIEcommerce/Sources/Filters/TagsFilters/Views/DefaultFilterCollectionCell.swift
#	TIEcommerce/Sources/Filters/ViewModels/DefaultFilterCellViewModel.swift
#	TIEcommerce/Sources/Filters/Views/BaseFiltersCollectionView.swift
#	TIEcommerce/Sources/Filters/Views/DefaultFilterCollectionCell.swift
#	TIEcommerce/TIEcommerce.podspec
#	TISwiftUtils/Sources/Extensions/Array/Array+SafeSubscript.swift
2022-08-30 13:59:09 +03:00
Ivan Smolin 6abac15fc7
Merge pull request #323 from TouchInstinct/fix/token_interactor
fix: Use OperationQueue instead of NSLock in `DefaultTokenInterceptor`
2022-08-29 10:40:41 +03:00
Ivan Smolin 0171f9d64f fix: Use OperationQueue instead of NSLock in `DefaultTokenInterceptor` 2022-08-26 22:08:52 +03:00
Nikita Semenov fc6ec80a29 fix: code review notes 2022-08-11 20:01:52 +03:00
Nikita Semenov 9eaf42aa4e fix: code review notes 2022-08-11 14:13:21 +03:00
Nikita Semenov 0abda665bf fix: code review notes 2022-08-11 02:25:58 +03:00
Nikita Semenov 95651b2b58 refactor: remove comments 2022-08-11 00:47:54 +03:00
Nikita Semenov 60d1fd2044 fix: public typealias 2022-08-11 00:45:23 +03:00
Nikita Semenov a253639073 fix: additional appearance configuration 2022-08-11 00:43:15 +03:00
Nikita Semenov 901cdc571f fix: renames + additional configuration 2022-08-11 00:41:18 +03:00
Nikita Semenov 08fdb69d68 fix: code review notes 2022-08-11 00:31:36 +03:00
Nikita Semenov ab9fafeb19 fix: review notes 2022-08-10 16:35:55 +03:00
Nikita Semenov 50d3d5a36b fix: selection and deselection of cells 2022-08-10 15:54:30 +03:00
Nikita Semenov 53a0ced7f6 fix: review notes 2022-08-10 14:26:24 +03:00
Nikita Semenov 3cd6f0a9d0 fix: refactor + applying snapshot 2022-08-09 18:22:38 +03:00
Nikita Semenov 048b5c1f83 fix: applying snapshot 2022-08-09 18:05:51 +03:00
Nikita Semenov 95fc92dfca fix: fix exclusion of cells 2022-08-09 18:02:30 +03:00
Nikita Semenov 73abd949eb fix: remove class property identifier 2022-08-09 17:01:10 +03:00
Nikita Semenov e28d71caf7 fix: cell registration 2022-08-09 16:55:25 +03:00
Nikita Semenov 5eb0183d92 fix: code review notes 2022-08-09 16:41:52 +03:00
Nikita Semenov 373b0db19e fix: cell configuration 2022-08-05 14:50:39 +03:00
Nikita Semenov 95a0045582 fix: review notes 2022-08-05 14:29:18 +03:00
Grigory Boyko 2c8fc0a8a5
Merge pull request #321 from TouchInstinct/feature/ecommerce
feat: change push_to_podspecs.sh
2022-08-05 10:29:06 +07:00
Nikita Semenov 9888bb22d5 fix: base table view constraints 2022-08-04 22:57:55 +03:00
Nikita Semenov 6ef2c83990 fix: access to view model initializer + renames 2022-08-04 22:49:47 +03:00
Nikita Semenov 4c525b8dad feat: list of filters 2022-08-04 22:45:06 +03:00
Nikita Semenov ae52ec03c6 fix: edge insets init + refactoring 2022-08-04 21:34:14 +03:00
Nikita Semenov 7bef631668 fix: usage of generics 2022-08-04 18:49:23 +03:00
Nikita Semenov 2748d5e3b0 fix: type casting 2022-08-04 18:45:38 +03:00
Nikita Semenov 47280c7cd7 fix: review notes 2022-08-04 18:20:08 +03:00
Nikita Semenov c583b8a98a fix: logic of selecting filters 2022-08-04 17:23:58 +03:00
Nikita Semenov 7f6883c7b7 fix: view model configuration 2022-08-04 17:01:19 +03:00
Nikita Semenov ef1eb2d8ac fix: collection view life cycle 2022-08-04 16:53:33 +03:00
Nikita Semenov 0534d41bae fix: update collection view with diffable data source 2022-08-04 16:44:19 +03:00
Nikita Semenov 7c479ff428 fix: default inset of the cells 2022-08-03 21:49:17 +03:00
Nikita Semenov b6321d3b19 fix: collection view life cycle 2022-08-03 21:47:05 +03:00
Nikita Semenov 82d52f4a40 fix: double view model assignment 2022-08-03 21:40:43 +03:00
Nikita Semenov af1c9da523 fix: creating of wrapped view 2022-08-03 21:38:57 +03:00
Nikita Semenov cead71df3d fix: update podscpecs versions 2022-08-03 21:34:07 +03:00
Nikita Semenov 18b48957a4 Merge branch 'master' into filters_api
# Conflicts:
#	CHANGELOG.md
2022-08-03 21:32:23 +03:00
Nikita Semenov edec8bc94f fix: refactoring and review changes 2022-08-03 21:27:30 +03:00
Nikita Semenov 276fbc8c10 fix: excluding selected cells 2022-08-03 20:55:22 +03:00
Nikita Semenov 62aecb0941 fix: layout + collection cell updates 2022-08-03 20:28:15 +03:00
Nikita Semenov c6d17e96ab fix: remove excessive level of an abstraction 2022-08-03 19:57:33 +03:00
Grigory Boyko 8466a4a4db feat: change push_to_podspecs.sh 2022-08-03 23:17:01 +07:00
boykogri 30cf6856b1
Merge pull request #319 from TouchInstinct/feature/ecommerce
Feature/ecommerce
2022-08-03 22:15:22 +07:00
Grigory Boyko b7b95f0ef4 feat: change version 2022-08-03 22:14:30 +07:00
Grigory Boyko 617ebbfde1 fix: Comments from pull request 2022-08-03 16:33:48 +07:00
Grigory Boyko 651892d182 fix: edit changelog 2022-08-02 22:16:37 +07:00
Grigory Boyko 28126dae4b feat: edit changelog and podspec summary 2022-08-02 22:14:38 +07:00
Grigory Boyko b57f205812 feat: add TINetworking dependency 2022-08-02 22:08:44 +07:00
Grigory Boyko 50d59857a7 fix: change podspec to 1.27.0 2022-08-02 21:41:45 +07:00
Grigory Boyko 8cfd3b466c fix: Remove optional for cart 2022-08-02 17:08:03 +07:00
Grigory Boyko 1db3bdb944 feat: Update podspec to 1.27, small models changes 2022-08-02 16:21:00 +07:00
Grigory Boyko d5479f745c feat: Add promocodes and bonuses logic 2022-08-02 12:18:09 +07:00
Grigory Boyko eb66a4e8ca fix: Comments from pull request 2022-08-02 11:38:35 +07:00
Nikita Semenov 76f6635cbc fix: view holder protocol udpate 2022-08-01 19:42:28 +03:00
Nikita Semenov de3aad7147 fix: bug with excluding selected cells 2022-08-01 17:42:56 +03:00
Grigory Boyko 343c40888d fix: add actual changes 2022-08-01 21:11:39 +07:00
Grigory Boyko 179a368fe5 fix: Delete useless protocols, add cartRequestExecutor 2022-08-01 21:09:49 +07:00
Nikita Semenov 50f6670710 fix: default cell corner radius 2022-08-01 16:49:23 +03:00
Nikita Semenov 104116fd36 fix: change default cell appearance 2022-08-01 16:41:45 +03:00
Grigory Boyko 48ac99c7d8 feat: Add receipt, cart service 2022-08-01 19:29:35 +07:00
Nikita Semenov fa29f08d2d fix: bugs with access control 2022-08-01 12:29:42 +03:00
Nikita Semenov 9e96b83c1e fix: refactor conformance to collection holder protocol 2022-08-01 12:25:29 +03:00
Grigory Boyko c92b9bf563 feat: Add CartEditable block 2022-08-01 15:17:21 +07:00
Nikita Semenov c85ab9113b fix: access of filters collection view + change log update 2022-08-01 09:48:57 +03:00
Nikita Semenov 761e4b98bf fix: update podspec version 2022-08-01 09:42:22 +03:00
Grigory Boyko ea1392f279 fix: package.swift file 2022-08-01 08:59:54 +07:00
Nikita Semenov 01c4ef6f44 fix: code refactoring + add additional protocols 2022-07-31 21:01:09 +03:00
Nikita Semenov b52d57a5e7 feat: initial filters api 2022-07-31 13:53:28 +03:00
Grigory Boyko cddae04f2e feat: Add initial files for Cart module 2022-07-29 21:08:11 +07:00
Alexey Gurin 826471c825
Merge pull request #318 from TouchInstinct/feature/pass_url_to_request_error
Pass url to request error
2022-07-28 18:34:54 +04:00
Alexey Gurin 6810fac839 feat: Up dependencies version to 1.25.0 2022-07-28 17:36:07 +04:00
Alexey Gurin a6b63c237b feat: Add changelog for 1.25.0 2022-07-28 17:33:18 +04:00
Nikita Semenov 6eefc607e5
Merge pull request #316 from TouchInstinct/fix/spm_dependencies
fix: dependencies of core libraries
2022-07-28 13:49:42 +03:00
Alexey Gurin db19e24bc4 feat: Update RequestError cases and url passing to them 2022-07-28 14:28:05 +04:00
Alexey Gurin af73b2964f feat: Add url passing to response validation methods 2022-07-28 14:25:32 +04:00
Nikita Semenov b2c6f7b852 fix: dependencies of core libraries 2022-07-28 12:43:54 +03:00
Nikita Semenov 0803060787
Merge pull request #315 from TouchInstinct/feature/alerts_api
alerts api
2022-07-28 11:55:33 +03:00
Nikita Semenov 17a70d613b docs: code review notes 2022-07-27 17:37:34 +03:00
Nikita Semenov a917723dcb docs: code review notes 2022-07-27 16:08:13 +03:00
Nikita Semenov 174d472f1b revert: deleted alert modifier 2022-07-27 14:40:38 +03:00
Nikita Semenov 9a427adab7 fix: code review notes + added custom modifier for alerts 2022-07-27 13:58:44 +03:00
Nikita Semenov cfbf53faf8 fix: code review notes 2022-07-26 15:33:50 +03:00
Nikita Semenov bf46b602a3 fix: code review notes 2022-07-26 15:20:33 +03:00
Nikita Semenov a88a85fe75 fix: move alerts into TIUIKitCore 2022-07-26 15:09:42 +03:00
Nikita Semenov 34c7407ecb docs: SUI alerts 2022-07-26 13:59:25 +03:00
Nikita Semenov ffa66048b0 fix: removed TISwiftUIElements lib and moved components to TISwiftUICore 2022-07-26 13:53:25 +03:00
Nikita Semenov 8138291153 feat: SUI alerts added to different library 2022-07-26 13:40:56 +03:00
Nikita Semenov 7b79d6b250 Merge branch 'master' into feature/alerts_api
# Conflicts:
#	CHANGELOG.md
#	LeadKit.podspec
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2022-07-26 13:02:35 +03:00
Nikita Semenov 693843bb70 fix: code review notes 2022-07-25 19:01:55 +03:00
Nikita Semenov 24d1384e0f
doc: implement documentation for alerts api 2022-07-25 16:36:32 +03:00
Nikita Semenov b96478c248 fix: dialogue type alert actions' styles 2022-07-25 15:53:38 +03:00
Nikita Semenov 37b7224264 feat: added default localization provider for actions + documentation added 2022-07-25 15:34:04 +03:00
Nikita Semenov c223a026f2 fix: custom alerts configuration for sui + version control changes 2022-07-25 14:08:01 +03:00
Ivan Smolin a89c620d91
Merge pull request #314 from TouchInstinct/feature/TIUIKitCore_updates
feat: WrappedViewHolder, ReconfigurableView, UITextView BaseTextAttributes, ReusableUIViewPresenter
2022-07-25 11:48:19 +03:00
Nikita Semenov 000e88f98a fix: small access control fixes 2022-07-24 15:34:09 +03:00
Nikita Semenov 52751dc801 feat: added initial alerts factory implementation 2022-07-24 15:25:28 +03:00
Ivan Smolin 88da2ab508 feat: UITextView now support configuration with BaseTextAttributes
ReconfigurableView & ChangeableViewModel for non-destructing state update
WrappedViewHolder protocol with table/collection view cell implementations
2022-07-18 20:19:02 +03:00
Ivan Smolin 7e319dcb03
Merge pull request #313 from TouchInstinct/fix/DefaultTokenInterceptor_adapt_check
feat: Asynchronous request preprocessing
2022-07-18 13:35:58 +03:00
629 changed files with 42560 additions and 1461 deletions

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "build-scripts"]
path = build-scripts
url = https://github.com/TouchInstinct/BuildScripts.git
url = https://git.svc.touchin.ru/TouchInstinct/BuildScripts.git

View File

@ -1,5 +1,278 @@
# Changelog
### 1.56.0
- **Update**: `ViewSkeletonsConfiguration`. It's possible to enable or disable animation for specific skeletons now.
- **Added**: `HolderViewSkeletonsConfiguration` for skeleton root view configuration
- **Added**: `DashedBoundsLayer` can now be applied to `CALayer`
### 1.55.1
- **Update**: revert `TextSkeletonsConfiguration` line height calculation
### 1.55.0
- **Update**: use TouchInstinct `TableKit` fork instead of original one
- **Update**: remove default value from `BoolValueDefaultsStorage`
### 1.54.6
- **Added**: `xcprivacy` files
- **Update**: Correctly detect app reinstall in `AppInstallLifetimeSingleValueStorage`
- **Update**: use `xHeight` instead of `pointSize` for default skeleton line height calculation
- **Update**: update `linkTextAttributes` in `UITextView` when setting interactive url parts
### 1.54.5
- **Update**: Сhange `StatefulButton` event propogation avoidance method.
### 1.54.4
- **Update**: Fix `StatefulButton` state configuration for iOS 15+.
### 1.54.3
- **Update**: Set reasonable defaults for `SkeletonConfiguration`.
### 1.54.2
- **Update**: Changed access level from internal to public of title and subtitle view in `BaseTitleSubtitleView`.
### 1.54.1
- **Added**: `BaseTitleSubtitleView` which can be inherited for fine-tuning skeletons and other behavior.
- **Update**: Changed lines number calculation method in `TextSkeletonsConfiguration`.
### 1.54.0
- **Added**: `maxWidth` parameter to `BaseViewSkeletonsConfiguration`.
- **Added**: custom `SkeletonConfigurations` for nested `SkeletonPresenters`.
- **Update**: Many fixes and improvenments to `TextSkeletonsConfiguration`.
### 1.53.3
- **Update**: `Skeletonable` can now control custom geometry change notification.
- **Update**: Filter hidden views from skeletonable views by default.
### 1.53.2
- **Update**: `DefaultTitleSubtitleView` support for separated configuration of title and subtitle labels layout.
- **Update**: `BaseListItemView` fixed trailing insets when trailing view is hidden.
### 1.53.1
- **Update**: Insets layout heuristics for `WrappedViewHodler` implementations
### 1.53.0
- **Added**: Custom string attributes to `BaseTextAttributes`
- **Added**: Customizeable `UIViewBackground` and `UIViewBorder` for `UIView.Appearance`
- **Added**: Keychain single value storage for codable models -`CodableSingleValueKeychainStorage`
- **Update**: Renamed methods `startAnimation` and `stopAnimation` of `SkeletonPresenter`, so it won't conflict with `Animatable` protocol anymore
### 1.52.0
- **Added**: `TIApplication` module with core dependencies of main application and its extension targets
- **Added**: `DefaultHomogeneousItemsCollectionView` default collection view implementation with configurable identical-type cells
- **Update**: Changed implementation of `AppInstallLifetimeSingleValueStorage`. Now it uses `SingleValueStorage<Bool>` to be able to migrate stored UserDefaults values
- **Added**: `UserLocationFetcher.OnLocationFetchFailureCallback` and `ItemDistanceTo` in `TIMapUtils`
- **Added**: Tap handler closure to `DefaultConfigurableStatefulButton.ViewModel`
- **Added**: Universal DSL
### 1.51.0
- **Added**: `BaseModalViewController` implementing `PanModalPresentable` with additional functionality
- **Added**: `BaseModalWrapperViewController` for wrapping `UIViewController`s with `BaseModalViewController` functionality
### 1.50.0
- **Updated**: Fix activity indicator positioning for `StatefulButton` on iOS 15+ and disabled state touch handling
- **Added**: iOS 15+ activity indicator placement support in `StatefulButton`
- **Added**: `TICoreGraphicsUtils` module for drawing operations and other CoreGraphics related functionality
- **Update**: `MarkerIconFactory` can now return optional `UIImage`. In this case MapManagers will show the default marker icon.
### 1.49.0
- **Added**: `BaseMigratingSingleValueKeychainStorage` and `BaseMigratingSingleValueDefaultsStorage` implementations for migrating keys from one storage to another.
### 1.48.0
- **Added**: `BaseStackView` with configurable items appearance
- **Fixed**: `CollectionTableViewCell` self-sizing
- **Added**: `ViewAppearance.WrappedViewLayout` support for all `WrappedViewHolders`
- **Added**: `ViewCallbacks` support for all `BaseInitializeableViews`
### 1.47.0
- **Added**: `flatMap` operator for `AsyncOperation`
- **Update**: `CodableKeyValueStorage` now returns `Swift.Result` with typed errors.
- **Added**: `SingleValueExpirationStorage` for time aware entries (expirable api tokens, etc.)
- **Added**: `AsyncOperation` variants of process methods in NetworkServices.
### 1.46.0
- **Added**: `AsyncSingleValueStorage` and `SingleValueStorageAsyncWrapper<SingleValueStorage>` for async access to SingleValue storages wtih swift concurrency support
- **Added**: `BaseMapUISettings` used to configure map view of different providers + user location icon rendering for yandex maps
- **Added**: `UserLocationFetcher` helper that requests authorization and subscribes to user location updates
- **Update**: add `DEVELOPMENT_INSTALL` support for all podspecs and fix playground compilation issues
### 1.45.0
- **Added**: `SingleValueStorage` implementations + `AppInstallLifetimeSingleValueStorage` for automatically removing keychain items on app reinstall.
- **Added**: `TILogging` with error logging types
- **Update**: `DefaultRecoverableJsonNetworkService` supports iOS 12.
- **Update**: `DefaultFingerprintsProvider` now uses `SingleValueStorage`
### 1.44.0
- **Added**: HTTP status codes to `EndpointErrorResult.apiError` responses
- **Added**: SwiftLint pre-build SPM step to TINetworking module
### 1.43.1
- **Fixed**: build scripts submodule url
### 1.43.0
- **Added**: `TITextProcessing` for regex and text formatting added
### 1.42.1
- **Fixed**: Podspecs source and homepage urls
### 1.42.0
- **Added**: TIDeeplink to support deeplink API
### 1.41.0
- **Update**: added callbacks for views while skeletons change status to presented or hidden
### 1.40.0
- **Added**: `PlaceholderFactory` for creating `DefaultPlaceholderView` views
- **Added**: `DefaultPlaceholderImageView`
### 1.39.0
- **Added**: UIButton Appearance model
- **Added**: `SpacedWrappedViewLayout` for spacing configurations
- **Update**: UIView appearance model with border configurations
### 1.38.0
- **Added**: Placemarks states for icon updating
- **Added**: Selecting / deselecting markers through cluster manager
### 1.37.0
- **Added**: API for converting view hierarchy to skeletons
### 1.36.1
- **Update**: `YandexMapsMobile` version updated
- **Fix**: Map manager memory leak removed
### 1.36.0
- **Removed**: `TILogger`module
- **Updated**: moved `LoggingPresenter` to `TIDeveloperUtils` module.
### 1.35.1
- **Added**: Auto documentation generation for `TIFoundationUtils` playground and compile checks for playground before release
- **Updated**: `AsyncOperation` fixed ordering of chain operations execution
### 1.35.0
- **Added**: `TIDeveloperUtils` framework, that contains different utils for development
- **Added**: `UIView` and `UIViewController` extensions for showing SwiftUI previews
- **Added**: `DashedBoundsLayer` for debugging views' frames visually
### 1.34.0
- **Added**: `BaseListItemView` for displaying three views horizontally
- **Added**: `DefaultTitleSubtitleView` for displaying one or two labels vertically
- **Update**: `StatefulButton` now can be configured with `ViewAppearance` model for each state
### 1.33.0
- **Added**: `ViewAppearance` and `ViewLayout` models for setting up Views' appearance and layout
- **Added**: `TableKit.Row` extension for configuration inner View's appearance and layout
- **Added**: `WrappableView` with typealiases for creating wrapped in the container views
- **Added**: `CollectionTableViewCell` and `ContainerView`
- **Update**: Separator appearance configureation for table views
### 1.32.0
- **Added**: `BaseInitializableWebView` with navigation and error handling api.
### 1.31.0
- **Added**: `URLInteractiveTextView` for terms and conditions hints in login flow
### 1.30.0
- **Added**: Base classes for encryption and decryption user token with pin code or biometry
- **Added**: Pin code validators
### 1.29.1
- **Updated**: `BaseTextAttributes` correct detection of the necessity of using attributed string
### 1.29.0
- **Added**: `BaseTextAttributes`can now measure text size and provides paragraph style configuration API.
- **Removed**: `ViewText`. Was fully replaced with `BaseTextAttributes`
- **Fixed**: `Operation.flattenDependencies` used in `Operation.add(to:waitUntilFinished:)` now works correctly.
- **Added**: Now it's possible to add dependent operation to start of the queue.
### 1.28.0
- **Add**: `LoggingPresenter`to present list of logs with ability of sharing it
- **Add**: `TILogger` wrapper object to log events.
### 1.27.1
- **Fix**: Weak target reference in `RefreshControl`
### 1.27.0
- **Add**: Tag like filter collection view
- **Add**: List like filter table view
- **Add**: Range like filter view
### 1.26.3
- **Update**: Add @escaping in `RequestExecutor.ExecutionClosure`
### 1.26.2
- **Update**: Add failureCompletion in `RequestExecutor`
### 1.26.1
- **Fix**: Use OperationQueue instead of NSLock in `DefaultTokenInterceptor`
- **Update**: AsyncOperation refactoring
### 1.26.0
- **Add**: `TIEcommerce` module with Cart, products, promocodes, bonuses and other related actions.
### 1.25.0
- **Update**: `RequestError` cases now contain additional url assotiated value
- **Update**: Network requests error catching now throws `RequestError` with url
### 1.24.0
- **Add**: `AlertFactory` for presenting alerts in SwiftUI and UIKit.
### 1.23.0
- **Update**: `UITextView` now support configuration with `BaseTextAttributes`
- **Add**: `ReconfigurableView` & `ChangeableViewModel` for non-destructing state update
- **Add**: `WrappedViewHolder` protocol with table/collection view cell implementations
- **Add**: `UIViewPresenter` and `ReusableUIViewPresenter` protocols with default implementation for proper handling view/cells reuse
### 1.22.0
- **Update**: Asynchronous request preprocessing

View File

@ -89,9 +89,10 @@ GEM
PLATFORMS
x86_64-darwin-20
x86_64-darwin-21
DEPENDENCIES
cocoapods (~> 1.11)
BUNDLED WITH
2.3.10
2.3.26

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = "LeadKit"
s.version = "1.22.0"
s.version = "1.35.0"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://github.com/TouchInstinct/LeadKit"
s.homepage = "https://git.svc.touchin.ru/TouchInstinct/LeadKit"
s.license = "Apache License, Version 2.0"
s.author = "Touch Instinct"
s.source = { :git => "https://github.com/TouchInstinct/LeadKit.git", :tag => s.version }
s.source = { :git => "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", :tag => s.version }
s.platform = :ios, '10.0'
s.swift_versions = ['5.1']

107
Makefile Normal file
View File

@ -0,0 +1,107 @@
export SRCROOT := $(shell pwd)
push_to_podspecs: TISwiftUtils.target TIFoundationUtils.target TICoreGraphicsUtils.target TIKeychainUtils.target TIUIKitCore.target TIUIElements.target TIWebView.target TIBottomSheet.target TISwiftUICore.target TITableKitUtils.target TIDeeplink.target TIDeveloperUtils.target TILogging.target TINetworking.target TIMoyaNetworking.target TINetworkingCache.target TIMapUtils.target TIAppleMapUtils.target TIGoogleMapUtils.target TIPagination.target TIAuth.target TIEcommerce.target TITextProcessing.target TIApplication.target
$(call clean)
TISwiftUtils.target:
MODULE_NAME="TISwiftUtils" ./project-scripts/push_to_podspecs.sh
touch TISwiftUtils.target
TIFoundationUtils.target: TISwiftUtils.target TILogging.target
MODULE_NAME="TIFoundationUtils" ./project-scripts/push_to_podspecs.sh
touch TIFoundationUtils.target
TICoreGraphicsUtils.target:
MODULE_NAME="TICoreGraphicsUtils" ./project-scripts/push_to_podspecs.sh
touch TICoreGraphicsUtils.target
TIKeychainUtils.target: TIFoundationUtils.target
MODULE_NAME="TIKeychainUtils" ./project-scripts/push_to_podspecs.sh
touch TIKeychainUtils.target
TIUIKitCore.target: TISwiftUtils.target
MODULE_NAME="TIUIKitCore" ./project-scripts/push_to_podspecs.sh
touch TIUIKitCore.target
TIUIElements.target: TIUIKitCore.target TILogging.target
MODULE_NAME="TIUIElements" ./project-scripts/push_to_podspecs.sh
touch TIUIElements.target
TIWebView.target: TIUIKitCore.target
MODULE_NAME="TIWebView" ./project-scripts/push_to_podspecs.sh
touch TIWebView.target
TIBottomSheet.target: TIUIElements.target
MODULE_NAME="TIBottomSheet" ./project-scripts/push_to_podspecs.sh
touch TIBottomSheet.target
TISwiftUICore.target: TIUIKitCore.target
MODULE_NAME="TISwiftUICore" ./project-scripts/push_to_podspecs.sh
touch TISwiftUICore.target
TITableKitUtils.target: TIUIElements.target
MODULE_NAME="TITableKitUtils" ./project-scripts/push_to_podspecs.sh
touch TITableKitUtils.target
TIDeeplink.target: TIFoundationUtils.target
MODULE_NAME="TIDeeplink" ./project-scripts/push_to_podspecs.sh
touch TIDeeplink.target
TIDeveloperUtils.target: TIUIElements.target
MODULE_NAME="TIDeveloperUtils" ./project-scripts/push_to_podspecs.sh
touch TIDeveloperUtils.target
TINetworking.target: TIFoundationUtils.target
MODULE_NAME="TINetworking" ./project-scripts/push_to_podspecs.sh
touch TINetworking.target
TILogging.target:
MODULE_NAME="TILogging" ./project-scripts/push_to_podspecs.sh
touch TILogging.target
TIMoyaNetworking.target: TINetworking.target
MODULE_NAME="TIMoyaNetworking" ./project-scripts/push_to_podspecs.sh
touch TIMoyaNetworking.target
TINetworkingCache.target: TINetworking.target
MODULE_NAME="TINetworkingCache" ./project-scripts/push_to_podspecs.sh
touch TINetworkingCache.target
TIMapUtils.target: TILogging TICoreGraphicsUtils.target
MODULE_NAME="TIMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIMapUtils.target
TIAppleMapUtils.target: TIMapUtils.target
MODULE_NAME="TIAppleMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIAppleMapUtils.target
TIGoogleMapUtils.target: TIMapUtils.target
MODULE_NAME="TIGoogleMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIGoogleMapUtils.target
TIYandexMapUtils.target: TIMapUtils.target
MODULE_NAME="TIYandexMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIYandexMapUtils.target
TIPagination.target: TISwiftUtils.target
MODULE_NAME="TIPagination" ./project-scripts/push_to_podspecs.sh
touch TIPagination.target
TIAuth.target: TIUIKitCore.target TIKeychainUtils.target
MODULE_NAME="TIAuth" ./project-scripts/push_to_podspecs.sh
touch TIAuth.target
TIEcommerce.target: TINetworking.target TIUIElements.target
MODULE_NAME="TIEcommerce" ./project-scripts/push_to_podspecs.sh
touch TIEcommerce.target
TITextProcessing.target:
MODULE_NAME="TITextProcessing" ./project-scripts/push_to_podspecs.sh
touch TITextProcessing.target
TIApplication.target: TIFoundationUtils.target TILogging.target
MODULE_NAME="TIApplication" ./project-scripts/push_to_podspecs.sh
touch TIApplication.target
clean:
rm *.target

View File

@ -1,79 +1,95 @@
{
"object": {
"pins": [
{
"package": "Alamofire",
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
"state": {
"branch": null,
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae",
"version": "5.4.3"
}
},
{
"package": "Cache",
"repositoryURL": "https://github.com/hyperoslo/Cache.git",
"state": {
"branch": null,
"revision": "c7f4d633049c3bd649a353bad36f6c17e9df085f",
"version": "6.0.0"
}
},
{
"package": "Cursors",
"repositoryURL": "https://github.com/petropavel13/Cursors",
"state": {
"branch": null,
"revision": "a1561869135e72832eff3b1e729075c56c2eebf6",
"version": "0.5.1"
}
},
{
"package": "KeychainAccess",
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state": {
"branch": null,
"revision": "84e546727d66f1adc5439debad16270d0fdd04e7",
"version": "4.2.2"
}
},
{
"package": "Moya",
"repositoryURL": "https://github.com/Moya/Moya.git",
"state": {
"branch": null,
"revision": "9b906860e3c3c09032879465c471e6375829593f",
"version": "15.0.0"
}
},
{
"package": "ReactiveSwift",
"repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state": {
"branch": null,
"revision": "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version": "6.7.0"
}
},
{
"package": "RxSwift",
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
"state": {
"branch": null,
"revision": "b4307ba0b6425c0ba4178e138799946c3da594f8",
"version": "6.5.0"
}
},
{
"package": "TableKit",
"repositoryURL": "https://github.com/maxsokolov/TableKit.git",
"state": {
"branch": null,
"revision": "8bf4840d9d0475a92352f02f368f88b74eced447",
"version": "2.11.0"
}
"pins" : [
{
"identity" : "alamofire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Alamofire/Alamofire.git",
"state" : {
"revision" : "bc268c28fb170f494de9e9927c371b8342979ece",
"version" : "5.7.1"
}
]
},
"version": 1
},
{
"identity" : "antlr4",
"kind" : "remoteSourceControl",
"location" : "https://github.com/antlr/antlr4",
"state" : {
"revision" : "44d87bc1d130c88aa452894aa5f7e2f710f68253",
"version" : "4.10.1"
}
},
{
"identity" : "cache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"revision" : "c7f4d633049c3bd649a353bad36f6c17e9df085f",
"version" : "6.0.0"
}
},
{
"identity" : "cursors",
"kind" : "remoteSourceControl",
"location" : "https://github.com/petropavel13/Cursors",
"state" : {
"revision" : "52f27b82cb1cbbc2b5fd09514c48b9c75e3b0300",
"version" : "0.6.0"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state" : {
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
"version" : "4.2.2"
}
},
{
"identity" : "moya",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Moya/Moya.git",
"state" : {
"revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26",
"version" : "15.0.3"
}
},
{
"identity" : "panmodal",
"kind" : "remoteSourceControl",
"location" : "https://git.svc.touchin.ru/TouchInstinct/PanModal",
"state" : {
"revision" : "ced7c1703f90746df0224b6e0d33c146d9ae4284",
"version" : "1.3.1"
}
},
{
"identity" : "reactiveswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state" : {
"revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version" : "6.7.0"
}
},
{
"identity" : "rxswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveX/RxSwift.git",
"state" : {
"revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4",
"version" : "6.6.0"
}
},
{
"identity" : "tablekit",
"kind" : "remoteSourceControl",
"location" : "https://git.svc.touchin.ru/TouchInstinct/TableKit.git",
"state" : {
"revision" : "fec9537745799fab55df7477cb3ec2b4ea5c254d",
"version" : "2.12.0"
}
}
],
"version" : 2
}

View File

@ -1,25 +1,39 @@
// swift-tools-version:5.1
// swift-tools-version:5.7
#if canImport(PackageDescription)
import PackageDescription
let package = Package(
name: "LeadKit",
platforms: [
.iOS(.v11)
.iOS(.v12)
],
products: [
// MARK: - Application
.library(name: "TIApplication", targets: ["TIApplication"]),
// MARK: - UIKit
.library(name: "TIUIKitCore", targets: ["TIUIKitCore"]),
.library(name: "TIUIElements", targets: ["TIUIElements"]),
.library(name: "TIWebView", targets: ["TIWebView"]),
.library(name: "TIBottomSheet", targets: ["TIBottomSheet"]),
// MARK: - SwiftUI
.library(name: "TISwiftUICore", targets: ["TISwiftUICore"]),
// MARK: - Utils
.library(name: "TISwiftUtils", targets: ["TISwiftUtils"]),
.library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]),
.library(name: "TICoreGraphicsUtils", targets: ["TICoreGraphicsUtils"]),
.library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]),
.library(name: "TITableKitUtils", targets: ["TITableKitUtils"]),
.library(name: "TIDeeplink", targets: ["TIDeeplink"]),
.library(name: "TIDeveloperUtils", targets: ["TIDeveloperUtils"]),
// MARK: - Networking
@ -31,60 +45,154 @@ let package = Package(
.library(name: "TIMapUtils", targets: ["TIMapUtils"]),
.library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]),
// MARK: - Elements
.library(name: "OTPSwiftView", targets: ["OTPSwiftView"]),
.library(name: "TITransitions", targets: ["TITransitions"]),
.library(name: "TIPagination", targets: ["TIPagination"]),
.library(name: "TIAuth", targets: ["TIAuth"]),
.library(name: "TIEcommerce", targets: ["TIEcommerce"]),
.library(name: "TITextProcessing", targets: ["TITextProcessing"])
],
dependencies: [
.package(url: "https://github.com/maxsokolov/TableKit.git", .upToNextMajor(from: "2.11.0")),
.package(url: "https://git.svc.touchin.ru/TouchInstinct/TableKit.git", .upToNextMinor(from: "2.12.0")),
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.2.2")),
.package(url: "https://github.com/petropavel13/Cursors", .upToNextMajor(from: "0.5.1")),
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.4.0")),
.package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0")),
.package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0"))
.package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0")),
.package(url: "https://github.com/antlr/antlr4", .upToNextMinor(from: "4.10.1")),
.package(url: "https://git.svc.touchin.ru/TouchInstinct/PanModal", .upToNextMinor(from: "1.3.0"))
],
targets: [
// MARK: - Application architecture
.target(name: "TIApplication",
dependencies: ["TILogging", "TIFoundationUtils", "KeychainAccess"],
path: "TIApplication/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - UIKit
.target(name: "TIUIKitCore", path: "TIUIKitCore/Sources"),
.target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"),
.target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"),
.target(name: "TIUIElements",
dependencies: ["TIUIKitCore", "TILogging"],
path: "TIUIElements/Sources",
exclude: ["../TIUIElements.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"),
.target(name: "TIBottomSheet",
dependencies: ["PanModal", "TIUIElements", "TIUIKitCore", "TISwiftUtils"],
path: "TIBottomSheet/Sources",
exclude: ["../TIBottomSheet.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - SwiftUI
.target(name: "TISwiftUICore", path: "TISwiftUICore/Sources"),
.target(name: "TISwiftUICore",
dependencies: ["TIUIKitCore", "TISwiftUtils"],
path: "TISwiftUICore/Sources"),
// MARK: - Utils
.target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"),
.target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils"),
.target(name: "TIKeychainUtils", dependencies: ["TIFoundationUtils", "KeychainAccess"], path: "TIKeychainUtils/Sources"),
.target(name: "TISwiftUtils",
path: "TISwiftUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIFoundationUtils",
dependencies: ["TISwiftUtils", "TILogging"],
path: "TIFoundationUtils",
exclude: ["TIFoundationUtils.app"],
resources: [
.copy("PrivacyInfo.xcprivacy"),
],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TICoreGraphicsUtils",
dependencies: [],
path: "TICoreGraphicsUtils/Sources",
exclude: ["../TICoreGraphicsUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIKeychainUtils",
dependencies: ["TIFoundationUtils", "KeychainAccess"],
path: "TIKeychainUtils/Sources",
exclude: ["../TIKeychainUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"),
.target(name: "TIDeeplink", dependencies: ["TIFoundationUtils"], path: "TIDeeplink", exclude: ["TIDeeplink.app"]),
.target(name: "TIDeveloperUtils", dependencies: ["TISwiftUtils", "TIUIKitCore", "TIUIElements"], path: "TIDeveloperUtils/Sources"),
.target(name: "TILogging", path: "TILogging/Sources", plugins: ["TISwiftLintPlugin"]),
// MARK: - Networking
.target(name: "TINetworking", dependencies: ["TIFoundationUtils", "Alamofire"], path: "TINetworking/Sources"),
.target(name: "TIMoyaNetworking", dependencies: ["TINetworking", "TIFoundationUtils", "Moya"], path: "TIMoyaNetworking"),
.target(name: "TINetworkingCache", dependencies: ["TIFoundationUtils", "TINetworking", "Cache"], path: "TINetworkingCache/Sources"),
.target(name: "TINetworking",
dependencies: ["TIFoundationUtils", "Alamofire", "TILogging"],
path: "TINetworking/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIMoyaNetworking",
dependencies: ["TINetworking", "Moya"],
path: "TIMoyaNetworking/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TINetworkingCache",
dependencies: ["TINetworking", "Cache"],
path: "TINetworkingCache/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - Maps
.target(name: "TIMapUtils", dependencies: [], path: "TIMapUtils/Sources"),
.target(name: "TIAppleMapUtils", dependencies: ["TIMapUtils"], path: "TIAppleMapUtils/Sources"),
.target(name: "TIMapUtils",
dependencies: ["TILogging", "TICoreGraphicsUtils"],
path: "TIMapUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIAppleMapUtils",
dependencies: ["TIMapUtils"],
path: "TIAppleMapUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - Elements
.target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"),
.target(name: "TITransitions", path: "TITransitions/Sources"),
.target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"),
.target(name: "TIAuth", dependencies: ["TIFoundationUtils"], path: "TIAuth/Sources"),
.target(name: "TIAuth", dependencies: ["TIUIKitCore", "TIKeychainUtils"], path: "TIAuth/Sources"),
.target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"),
.target(name: "TITextProcessing",
dependencies: [.product(name: "Antlr4", package: "antlr4")],
path: "TITextProcessing/Sources",
exclude: ["../TITextProcessing.app"]),
.binaryTarget(name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "89651e1c87fb62faf076ef785a5b1af7f43570b2b74c6773526e0d5114e0578e"),
.plugin(name: "TISwiftLintPlugin",
capability: .buildTool(),
dependencies: ["SwiftLintBinary"]),
// MARK: - Tests
.testTarget(
name: "TITimerTests",
dependencies: ["TIFoundationUtils"],
path: "Tests/TITimerTests"),
.testTarget(
name: "TITextProcessingTests",
dependencies: ["TITextProcessing"],
path: "Tests/TITextProcessingTests"),
.testTarget(
name: "TIFoundationUtilsTests",
dependencies: ["TIFoundationUtils", "TISwiftUtils", "TILogging"],
path: "Tests/TIFoundationUtilsTests")
]
)
#endif

View File

@ -0,0 +1,57 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import PackagePlugin
import Foundation
@main
struct SwiftLintPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
let swiftlintScriptPath = context.package.directory.appending(["build-scripts", "xcode", "build_phases", "swiftlint.sh"])
let swiftlintExecutablePath = try context.tool(named: "swiftlint").path
let srcRoot = context.package.directory.string
let targetDir = target.directory.string
let relativeTargetDir = targetDir.replacingOccurrences(of: srcRoot, with: "")
let clearRelativeTargetDir = relativeTargetDir[relativeTargetDir.index(after: relativeTargetDir.startIndex)...] // trim leading /
return [
.prebuildCommand(displayName: "SwiftLint linting \(target.name)...",
executable: swiftlintScriptPath,
arguments: [
swiftlintExecutablePath,
context.package.directory.appending(subpath: "swiftlint_base.yml")
],
environment: [
"SCRIPT_DIR": swiftlintScriptPath.removingLastComponent().string,
"SRCROOT": srcRoot,
"SCRIPT_INPUT_FILE_COUNT": "1",
"SCRIPT_INPUT_FILE_0": clearRelativeTargetDir,
// "FORCE_LINT": "1", // Lint all files in target (not only modified)
// "AUTOCORRECT": "1"
],
outputFilesDirectory: context.package.directory)
]
}
}

110
README.md
View File

@ -1,23 +1,105 @@
# LeadKit
LeadKit is the iOS framework with a bunch of tools for rapid app development.
## Additional
This repository contains the following additional frameworks:
This repository contains the following frameworks:
- [TISwiftUtils](TISwiftUtils) - a bunch of useful helpers for Swift development.
- [TIFoundationUtils](TIFoundationUtils) - set of helpers for Foundation framework classes.
- [TIUIKitCore](TIUIKitCore) - core ui elements and protocols from LeadKit.
- [TITransitions](TITransitions) - set of custom transitions to present controller.
- [TISwiftUICore](TISwiftUICore) Core UI elements: protocols, views and helpers.
- [TIUIElements](TIUIElements) - bunch of of useful protocols and views.
- [OTPSwiftView](OTPSwiftView) - a fully customizable OTP view.
- [TISwiftUtils](TISwiftUtils) - a bunch of useful helpers for development.
- [TITableKitUtils](TITableKitUtils) - Set of helpers for TableKit classes.
- [TIFoundationUtils](TIFoundationUtils) - Set of helpers for Foundation framework classes.
- [TIKeychainUtils](TIKeychainUtils) - Set of helpers for Keychain classes.
- [TITableKitUtils](TITableKitUtils) - set of helpers for TableKit classes.
- [TIKeychainUtils](TIKeychainUtils) - set of helpers for Keychain classes.
- [TIPagination](TIPagination) - realisation of paginating items from a data source.
- [TINetworking](TINetworking) - Swagger-frendly networking layer helpers.
- [TIMoyaNetworking](TIMoyaNetworking) - Moya + Swagger network service.
- [TIAppleMapUtils](TIAppleMapUtils) - set of helpers for map objects clustering and interacting using Apple MapKit.
- [TIGoogleMapUtils](TIGoogleMapUtils) - set of helpers for map objects clustering and interacting using Google Maps SDK.
- [TIYandexMapUtils](TIYandexMapUtils) - set of helpers for map objects clustering and interacting using Yandex Maps SDK.
- [TIAuth](TIAuth) - login, registration, confirmation and other related actions
Useful docs:
## Playgrounds
### Create new Playground
```sh
$ cd TIModuleName
$ touch PlaygroundPodfile
$ echo "ENV[\"DEVELOPMENT_INSTALL\"] = \"true\"
target 'TIModuleName' do
platform :ios, IOS_VERSION_NUMBER
use_frameworks!
pod 'TIDependencyModuleName', :path => '../../../../TIDependencyModuleName/TIDependencyModuleName.podspec'
pod 'TIModuleName', :path => '../../../../TIModuleName/TIModuleName.podspec'
end" > PlaygroundPodfile
$ nef playground --name TIModuleName --cocoapods --custom-podfile PlaygroundPodfile
```
See example of `PlaygroundPodfile` in `TIFoundationUtils`
### Rename/add pages to Playground
For every new feature in module create new Playground page with documentation in comments. See [nef markdown documentation](https://github.com/bow-swift/nef#-generating-a-markdown-project).
### Create symlink to nef playground
```sh
$ cd TIModuleName
$ ln -s TIModuleName.app/Contents/MacOS/TIModuleName.playground TIModuleName.playground
```
### Add nef files to TIModuleName.app/.gitignore
```
# gitignore nef files
**/build/
**/nef/
LICENSE
```
### Exclude .app bundles from package sources
#### SPM
```swift
.target(name: "TIModuleName", dependencies: ..., path: ..., exclude: ["TIModuleName.app"]),
```
#### Podspec
```ruby
sources = 'your_sources_expression'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
```
## Docs:
- [TIFoundationUtils](docs/tifoundationutils)
* [AsyncOperation](docs/tifoundationutils/asyncoperation.md)
- [TICoreGraphicsUtils](docs/ticoregraphicsutils)
* [DrawingOperations](docs/ticoregraphicsutils/drawingoperations.md)
- [TIKeychainUtils](docs/tikeychainutils)
* [SingleValueStorage](docs/tikeychainutils/singlevaluestorage.md)
- [TIUIElements](docs/tiuielements)
* [Skeletons](docs/tiuielements/skeletons.md)
* [Placeholders](docs/tiuielements/placeholder.md)
- [TITextProcessing](docs/titextprocessing)
* [TITextProcessing](docs/titextprocessing/titextprocessing.md)
- [TIDeeplink](docs/tideeplink/deeplinks.md)
- [TIBottomSheet](docs/tibottomsheet/tibottomsheet.md)
- [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle.
- [Snippets](docs/snippets.md) - useful commands and scripts for development.
@ -28,7 +110,7 @@ Useful docs:
./setup
```
- If legacy [Source](https://github.com/TouchInstinct/LeadKit/tree/master/Sources) folder needed, [build dependencies for LeadKit.xcodeproj](https://github.com/TouchInstinct/LeadKit/blob/master/docs/snippets.md#build-dependencies-for-LeadKit.xcodeproj).
- If legacy [Source](https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/master/Sources) folder needed, [build dependencies for LeadKit.xcodeproj](https://git.svc.touchin.ru/TouchInstinct/LeadKit/blob/master/docs/snippets.md#build-dependencies-for-LeadKit.xcodeproj).
- Make sure the commit message codestyle is followed. More about [Semantic Commit Messages](docs/semantic-commit-messages.md).
@ -38,16 +120,20 @@ Useful docs:
```swift
dependencies: [
.package(url: "https://github.com/TouchInstinct/LeadKit.git", from: "x.y.z"),
.package(url: "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", from: "x.y.z"),
],
```
### Cocoapods
```ruby
source 'https://github.com/TouchInstinct/Podspecs.git'
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
pod 'TISwiftUtils', 'x.y.z'
pod 'TIFoundationUtils', 'x.y.z'
# ...
```
## Legacy
Code located in root `Sources` folder and `LeadKit.podspec` should be treated as legacy and shouldn't be used in newly created projects. Please use TI* modules via SPM or CocoaPods.

View File

@ -31,8 +31,8 @@ import Alamofire
/// - mapping: Errors that occurs during mapping json into model.
public enum RequestError: Error {
case noConnection
case network(error: Error, response: Data?)
case invalidResponse(error: AFError, response: Data?)
case mapping(error: Error, response: Data)
case noConnection(url: String?)
case network(error: Error, response: Data?, url: String?)
case invalidResponse(error: AFError, response: Data?, url: String?)
case mapping(error: Error, response: Data, url: String?)
}

View File

@ -80,9 +80,9 @@ public extension ObservableType where Element == DataRequest {
///
/// - Parameter statusCodes: set of status codes to validate
/// - Returns: Observable on self
func validate(statusCodes: Set<Int>) -> Observable<Element> {
func validate(statusCodes: Set<Int>, url: String? = nil) -> Observable<Element> {
map { $0.validate(statusCode: statusCodes) }
.catchAsRequestError()
.catchAsRequestError(url: url)
}
}
@ -93,7 +93,9 @@ private extension ObservableType where Element == ServerResponse {
do {
return try transform($0)
} catch {
throw RequestError.mapping(error: error, response: $0.1)
throw RequestError.mapping(error: error,
response: $0.1,
url: $0.0.url?.absoluteString)
}
}
}
@ -103,10 +105,14 @@ private extension ObservableType where Element == ServerResponse {
do {
return try transform((response, result))
.catch {
throw RequestError.mapping(error: $0, response: result)
throw RequestError.mapping(error: $0,
response: result,
url: response.url?.absoluteString)
}
} catch {
throw RequestError.mapping(error: error, response: result)
throw RequestError.mapping(error: error,
response: result,
url: response.url?.absoluteString)
}
}
}
@ -114,7 +120,8 @@ private extension ObservableType where Element == ServerResponse {
private extension ObservableType {
func catchAsRequestError(with request: DataRequest? = nil) -> Observable<Element> {
func catchAsRequestError(with request: DataRequest? = nil,
url: String? = nil) -> Observable<Element> {
self.catch { error in
let resultError: RequestError
let response = request?.data
@ -126,10 +133,10 @@ private extension ObservableType {
case let urlError as URLError:
switch urlError.code {
case .notConnectedToInternet:
resultError = .noConnection
resultError = .noConnection(url: url)
default:
resultError = .network(error: urlError, response: response)
resultError = .network(error: urlError, response: response, url: url)
}
case let afError as AFError:
@ -137,21 +144,21 @@ private extension ObservableType {
case let .sessionTaskFailed(error):
switch error {
case let urlError as URLError where urlError.code == .notConnectedToInternet:
resultError = .noConnection
resultError = .noConnection(url: url)
default:
resultError = .network(error: error, response: response)
resultError = .network(error: error, response: response, url: url)
}
case .responseSerializationFailed, .responseValidationFailed:
resultError = .invalidResponse(error: afError, response: response)
resultError = .invalidResponse(error: afError, response: response, url: url)
default:
resultError = .network(error: afError, response: response)
resultError = .network(error: afError, response: response, url: url)
}
default:
resultError = .network(error: error, response: response)
resultError = .network(error: error, response: response, url: url)
}
throw resultError

View File

@ -117,7 +117,8 @@ public extension Reactive where Base: SessionManager {
}
return requestObservable
.validate(statusCodes: self.base.acceptableStatusCodes.union(additionalValidStatusCodes))
.validate(statusCodes: self.base.acceptableStatusCodes.union(additionalValidStatusCodes),
url: url.absoluteString)
}
}
@ -191,7 +192,8 @@ public extension Reactive where Base: SessionManager {
return self.upload(data, urlRequest: urlRequest)
.map { $0 as DataRequest }
.validate(statusCodes: self.base.acceptableStatusCodes.union(additionalValidStatusCodes))
.validate(statusCodes: self.base.acceptableStatusCodes.union(additionalValidStatusCodes),
url: try? requestParameters.url.asURL().absoluteString)
.flatMap {
$0.rx.apiResponse(mappingQueue: self.base.mappingQueue, decoder: decoder)
}

View File

@ -34,7 +34,7 @@ public extension Error {
/// - Returns: optional target object
/// - Throws: an error during decoding
func handleMappingError<T: Decodable>(with decoder: JSONDecoder = JSONDecoder()) throws -> T? {
guard let self = requestError, case .mapping(_, let response) = self else {
guard let self = requestError, case .mapping(_, let response, _) = self else {
return nil
}

View File

@ -24,7 +24,11 @@ import TIMapUtils
import MapKit
import UIKit
open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, [ApplePlacemarkManager<Model>], MKMapRect>, MKMapViewDelegate {
open class AppleClusterPlacemarkManager<Model>: BaseClusterPlacemarkManager<MKAnnotationView,
ApplePlacemarkManager<Model>,
MKMapRect>,
MKMapViewDelegate {
public weak var mapViewDelegate: MKMapViewDelegate?
private let mapDelegateSelectors = NSObject.instanceMethodSelectors(of: MKMapViewDelegate.self)
@ -36,7 +40,8 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
self.mapViewDelegate = mapViewDelegate
super.init(dataModel: placemarkManagers,
super.init(placemarkPosition: .from(coordinates: placemarkManagers.map(\.placemarkPosition)),
dataModel: placemarkManagers,
iconFactory: iconFactory?.asAnyMarkerIconFactory { $0.map { $0.dataModel } },
tapHandler: tapHandler)
}
@ -55,10 +60,10 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
override open func configure(placemark: MKAnnotationView) {
guard let clusterAnnotation = placemark.annotation as? MKClusterAnnotation,
let placemarkManagers = clusterAnnotation.memberAnnotations as? [ApplePlacemarkManager<Model>] else {
return
}
return
}
placemark.image = iconFactory?.markerIcon(for: placemarkManagers)
placemark.image = iconFactory?.markerIcon(for: placemarkManagers, state: .default)
}
// MARK: - MKMapViewDelegate
@ -79,6 +84,7 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
configure(placemark: defaultAnnotationView)
return defaultAnnotationView
case let placemarkManager as ApplePlacemarkManager<Model>:
let defaultAnnotationView = placemarkManager.iconFactory != nil
? MKAnnotationView(annotation: annotation,
@ -89,6 +95,7 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
placemarkManager.configure(placemark: defaultAnnotationView)
return defaultAnnotationView
default:
return nil
}
@ -107,8 +114,29 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
}
_ = tapHandler?(placemarkManagers, .from(coordinates: placemarkManagers.map { $0.coordinate }))
case let placemarkManager as ApplePlacemarkManager<Model>:
_ = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate)
let isTapHandled = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate) ?? false
if isTapHandled {
placemarkManager.state = .selected
}
default:
return
}
}
open func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
guard !(mapViewDelegate?.responds(to: #selector(mapView(_:didDeselect:))) ?? false) else {
mapViewDelegate?.mapView?(mapView, didDeselect: view)
return
}
switch view.annotation {
case let placemarkManager as ApplePlacemarkManager<Model>:
placemarkManager.state = .default
default:
return
}

View File

@ -24,9 +24,10 @@ import TIMapUtils
import MapKit
open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
ApplePlacemarkManager<DataModel>,
AppleClusterPlacemarkManager<DataModel>,
MKCameraUpdate> {
ApplePlacemarkManager<DataModel>,
AppleClusterPlacemarkManager<DataModel>,
MKCameraUpdate,
AppleMapUISettings> {
public typealias ClusteringIdentifier = String
@ -44,7 +45,8 @@ open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
return nil
}
return ApplePlacemarkManager(dataModel: $0,
return ApplePlacemarkManager(map: map,
dataModel: $0,
position: position,
clusteringIdentifier: clusteringIdentifierGetter($0),
iconFactory: iconFactory?.asAnyMarkerIconFactory(),
@ -65,27 +67,18 @@ open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
}
public convenience init(map: MKMapView,
positionGetter: @escaping PositionGetter,
clusteringIdentifierGetter: @escaping (DataModel) -> ClusteringIdentifier,
iconFactory: DefaultMarkerIconFactory<DataModel>? = nil,
clusterIconFactory: DefaultClusterMarkerIconFactory<DataModel>? = nil,
mapViewDelegate: MKMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) {
self.init(map: map,
positionGetter: positionGetter,
clusteringIdentifierGetter: clusteringIdentifierGetter,
iconFactory: nil as DefaultMarkerIconFactory<DataModel>?,
clusterIconFactory: nil as DefaultClusterMarkerIconFactory<DataModel>?,
mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler)
}
public convenience init(map: MKMapView,
mapViewDelegate: MKMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) where DataModel: MapLocatable & Clusterable, DataModel.ClusterIdentifier == ClusteringIdentifier, DataModel.Position == CLLocationCoordinate2D {
selectPlacemarkHandler: @escaping SelectPlacemarkHandler)
where DataModel: MapLocatable, DataModel.Position == CLLocationCoordinate2D,
DataModel: Clusterable, DataModel.ClusterIdentifier == ClusteringIdentifier {
self.init(map: map,
positionGetter: { $0.position },
clusteringIdentifierGetter: { $0.clusterIdentifier },
iconFactory: iconFactory,
clusterIconFactory: clusterIconFactory,
mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler)
}

View File

@ -0,0 +1,58 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIMapUtils
import MapKit
open class AppleMapUISettings: BaseMapUISettings<MKMapView> {
open class Defaults: BaseMapUISettings<MKMapView>.Defaults {
public static var showCompassButton: Bool {
false
}
}
public var showCompassButton = false
public init(showUserLocation: Bool = Defaults.showUserLocation,
isZoomEnabled: Bool = Defaults.isZoomEnabled,
isTiltEnabled: Bool = Defaults.isTiltEnabled,
isRotationEnabled: Bool = Defaults.isRotationEnabled,
showCompassButton: Bool = Defaults.showCompassButton) {
self.showCompassButton = showCompassButton
super.init(showUserLocation: showUserLocation,
isZoomEnabled: isZoomEnabled,
isTiltEnabled: isTiltEnabled,
isRotationEnabled: isRotationEnabled)
}
override open func apply(to mapView: MKMapView) {
super.apply(to: mapView)
mapView.showsUserLocation = showUserLocation
mapView.isZoomEnabled = isZoomEnabled
mapView.isPitchEnabled = isTiltEnabled
mapView.isRotateEnabled = isRotationEnabled
mapView.showsCompass = showCompassButton
}
}

View File

@ -23,23 +23,58 @@
import TIMapUtils
import MapKit
open class ApplePlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, Model, CLLocationCoordinate2D>, MKAnnotation {
open class ApplePlacemarkManager<Model>: BaseItemPlacemarkManager<MKAnnotationView, Model, CLLocationCoordinate2D>,
MKAnnotation {
// MARK: - MKAnnotation
/// A map where all placemarks are placed
public let map: MKMapView
public let coordinate: CLLocationCoordinate2D
/// Identifier required for correct cluster placement
public var clusteringIdentifier: String?
/// Point (coordinates) itself of the current placemark manager
public var coordinate: CLLocationCoordinate2D {
placemarkPosition
}
/// The current state of a manager's placemark
override public var state: MarkerState {
didSet {
guard let placemark = placemark else {
return
}
/*
Although the icon is being updated, it is necessary to manually deselect
the annotation of the current placemark if it is currently selected.
*/
if let annotation = placemark.annotation {
switch state {
case .default:
map.deselectAnnotation(annotation, animated: true)
case .selected:
map.selectAnnotation(annotation, animated: true)
}
}
placemark.image = iconFactory?.markerIcon(for: dataModel, state: state)
}
}
public init(dataModel: Model,
public init(map: MKMapView,
dataModel: Model,
position: CLLocationCoordinate2D,
clusteringIdentifier: String?,
iconFactory: AnyMarkerIconFactory<DataModel>?,
tapHandler: TapHandlerClosure?) {
self.coordinate = position
self.map = map
self.clusteringIdentifier = clusteringIdentifier
super.init(dataModel: dataModel,
super.init(placemarkPosition: position,
dataModel: dataModel,
iconFactory: iconFactory,
tapHandler: tapHandler)
}
@ -47,7 +82,10 @@ open class ApplePlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView,
// MARK: - PlacemarkManager
override open func configure(placemark: MKAnnotationView) {
placemark.image = iconFactory?.markerIcon(for: dataModel)
placemark.clusteringIdentifier = clusteringIdentifier
super.configure(placemark: placemark)
// Setting required values of the current manager and placemark respectively
self.placemark?.clusteringIdentifier = clusteringIdentifier
self.state = .default
}
}

View File

@ -1,16 +1,23 @@
Pod::Spec.new do |s|
s.name = 'TIAppleMapUtils'
s.version = '1.22.0'
s.version = '1.56.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '11.0'
s.swift_versions = ['5.3']
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
s.source_files = s.name + '/Sources/**/*'
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIMapUtils', s.version.to_s
end

View File

@ -0,0 +1,54 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public struct BundleIdentifier {
public let appPrefix: String
public let appIdentifier: String
public let fullIdentifier: String
public let defaultAppGroupIdenfier: String
static let bundleIdentifierSeparator = "."
public init(appPrefix: String, appIdentifier: String) {
self.appPrefix = appPrefix
self.appIdentifier = appIdentifier
self.fullIdentifier = appPrefix + Self.bundleIdentifierSeparator + appIdentifier
self.defaultAppGroupIdenfier = "group." + fullIdentifier
}
public init?(bundle: Bundle = .main) {
guard let fullIdenfifier = bundle.bundleIdentifier,
var components = bundle.bundleIdentifier?
.components(separatedBy: Self.bundleIdentifierSeparator),
let lastComponent = components.popLast() else {
return nil
}
self.fullIdentifier = fullIdenfifier
self.appIdentifier = lastComponent
self.appPrefix = components.joined(separator: Self.bundleIdentifierSeparator)
self.defaultAppGroupIdenfier = "group." + fullIdentifier
}
}

View File

@ -0,0 +1,116 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import TIFoundationUtils
import KeychainAccess
import TILogging
import UIKit
public struct CoreDependencies {
public var dateFormattersResusePool = DateFormattersReusePool()
public var iso8601DateFormattersReusePool = ISO8601DateFormattersReusePool()
public var jsonCodingConfigurator: JsonCodingConfigurator
public var jsonKeyValueDecoder: JSONKeyValueDecoder
public var jsonKeyValueEncoder: JSONKeyValueEncoder
public var device: UIDevice = .current
public var bundle: Bundle = .main
public var fileManager: FileManager = .default
public var logger: DefaultOSLogErrorLogger
public var keychain: Keychain
public var defaults: UserDefaults
public var appGroupDefaults: UserDefaults?
public var appGroupKeychain: Keychain?
public var appGroupCacheDirectory: URL?
public var networkCallbackQueue: DispatchQueue
public init(bundleIdentifier: BundleIdentifier,
customAppGroupIdentifier: String? = nil) {
jsonCodingConfigurator = JsonCodingConfigurator(dateFormattersReusePool: dateFormattersResusePool,
iso8601DateFormattersReusePool: iso8601DateFormattersReusePool)
jsonKeyValueDecoder = JSONKeyValueDecoder(jsonDecoder: jsonCodingConfigurator.jsonDecoder)
jsonKeyValueEncoder = JSONKeyValueEncoder(jsonEncoder: jsonCodingConfigurator.jsonEncoder)
logger = DefaultOSLogErrorLogger(subsystem: bundleIdentifier.fullIdentifier, category: "general")
keychain = Keychain(service: bundleIdentifier.fullIdentifier).accessibility(.whenUnlockedThisDeviceOnly)
defaults = .standard
let appGroupIdentifier = customAppGroupIdentifier ?? bundleIdentifier.defaultAppGroupIdenfier
if let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) {
var appGroupCacheURL: URL
if #available(iOS 16.0, *) {
appGroupCacheURL = containerURL.appending(path: "Caches", directoryHint: .isDirectory)
} else {
appGroupCacheURL = containerURL.appendingPathComponent("Caches", isDirectory: true)
}
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
do {
try fileManager.createDirectory(at: appGroupCacheURL,
withIntermediateDirectories: true)
try appGroupCacheURL.setResourceValues(resourceValues)
appGroupCacheDirectory = appGroupCacheURL
} catch {
logger.log(error: error, file: #file, line: #line)
}
appGroupDefaults = UserDefaults(suiteName: appGroupIdentifier)
appGroupKeychain = Keychain(service: bundleIdentifier.appPrefix,
accessGroup: appGroupIdentifier)
.accessibility(.whenUnlockedThisDeviceOnly)
} else {
appGroupCacheDirectory = nil
appGroupDefaults = nil
appGroupKeychain = nil
}
networkCallbackQueue = DispatchQueue(label: bundleIdentifier.fullIdentifier + ".network-callback-queue",
attributes: .concurrent)
}
public init?(bundle: Bundle = .main,
customAppGroupIdentifier: String? = nil) {
guard let bundleIdentifier = BundleIdentifier(bundle: bundle) else {
return nil
}
self.init(bundleIdentifier: bundleIdentifier,
customAppGroupIdentifier: customAppGroupIdentifier)
}
}

View File

@ -0,0 +1,26 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol TargetDependencies {
static func assemble() -> Self
static func assembleForPreview() -> Self
}

View File

@ -0,0 +1,25 @@
Pod::Spec.new do |s|
s.name = 'TIApplication'
s.version = '1.56.0'
s.summary = 'Application architecture.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIFoundationUtils', s.version.to_s
s.dependency 'TILogging', s.version.to_s
s.dependency 'KeychainAccess', "~> 4.2"
end

View File

@ -0,0 +1,35 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import LocalAuthentication
public protocol BiometryService {
var isBiometryAuthAvailable: Bool { get }
var biometryType: LABiometryType { get }
}
extension LAContext: BiometryService {
public var isBiometryAuthAvailable: Bool {
canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
}

View File

@ -20,7 +20,8 @@
// THE SOFTWARE.
//
public protocol FingerprintsSettingsStorage {
/// Should be true by default (on app first run)
var shouldResetFingerprints: Bool { get set }
import Foundation
public protocol BiometrySettingsStorage {
var isBiometryAuthEnabled: Bool { get set }
}

View File

@ -0,0 +1,48 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
open class DefaultBiometrySettingsStorage: BiometrySettingsStorage {
public enum StorageKeys {
static var isBiometryAuthEnabledStorageKey: String {
"isBiometryAuthEnabled"
}
}
public var defaultsStorage: UserDefaults
// MARK: - BiometrySettingsService
public var isBiometryAuthEnabled: Bool {
get {
defaultsStorage.bool(forKey: StorageKeys.isBiometryAuthEnabledStorageKey)
}
set {
defaultsStorage.set(newValue, forKey: StorageKeys.isBiometryAuthEnabledStorageKey)
}
}
public init(defaultsStorage: UserDefaults = .standard) {
self.defaultsStorage = defaultsStorage
}
}

View File

@ -21,18 +21,15 @@
//
import Foundation
import TIUIKitCore
@MainActor
protocol CodeConfirmPresenter {
protocol CodeConfirmPresenter: LifecyclePresenter {
// MARK: - User actions handling
func inputChanged(newInput: String?)
func refreshCode()
// MARK: - View lifecycle handling
func viewDidPresented()
// MARK: - Autofill
func autofill(code: String, with codeId: String?)

View File

@ -20,8 +20,6 @@
// THE SOFTWARE.
//
import Foundation
@MainActor
public protocol CodeConfirmStateStorage: AnyObject {
var currentUserInput: String? { get set }

View File

@ -77,6 +77,8 @@ open class DefaultCodeConfirmPresenter<ConfirmResponse: CodeConfirmResponse,
private let codeRefreshTimer = TITimer(mode: .activeAndBackground)
private let codeLifetimeTimer = TITimer(mode: .activeAndBackground)
private var executingTask: Cancellable?
public var output: Output
public var requests: Requests
public weak var stateStorage: CodeConfirmStateStorage?
@ -190,7 +192,7 @@ open class DefaultCodeConfirmPresenter<ConfirmResponse: CodeConfirmResponse,
if let code = newInput, code.count >= config.codeLength {
stateStorage?.isExecutingRequest = true
Task {
executingTask = Task {
await confirm(code: code)
}
}
@ -200,7 +202,7 @@ open class DefaultCodeConfirmPresenter<ConfirmResponse: CodeConfirmResponse,
stateStorage?.canRequestNewCode = false
stateStorage?.canRefreshCodeAfter = nil
Task {
executingTask = Task {
await refreshCode()
}
}
@ -213,6 +215,10 @@ open class DefaultCodeConfirmPresenter<ConfirmResponse: CodeConfirmResponse,
for: currentCodeResponse)
}
open func viewWillDestroy() {
executingTask?.cancel()
}
// MARK: - Autofill
open func autofill(code: String, with codeId: String? = nil) {

View File

@ -0,0 +1,93 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import CommonCrypto
open class AESCipher: Cipher {
public struct CryptError: Error {
public let ccCryptorStatus: Int32
public let key: Data
public let iv: Data
}
public var iv: Data
public var key: Data
public init(iv: Data, key: Data) {
self.iv = iv
self.key = key
}
// MARK: - Cipher
public func encrypt(data: Data) -> Result<Data, CipherError> {
crypt(data: data, operation: CCOperation(kCCEncrypt))
}
public func decrypt(data: Data) -> Result<Data, CipherError> {
crypt(data: data, operation: CCOperation(kCCDecrypt))
}
private func crypt(data: Data, operation: CCOperation) -> Result<Data, CipherError> {
let cryptDataLength = data.count + kCCBlockSizeAES128
var cryptData = Data(count: cryptDataLength)
var bytesLength = Int.zero
let status = cryptData.withUnsafeMutableBytes { cryptBytes in
data.withUnsafeBytes { dataBytes in
iv.withUnsafeBytes { ivBytes in
key.withUnsafeBytes { keyBytes in
CCCrypt(operation,
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
keyBytes.baseAddress,
key.count,
ivBytes.baseAddress,
dataBytes.baseAddress,
data.count,
cryptBytes.baseAddress,
cryptDataLength,
&bytesLength)
}
}
}
}
guard status == kCCSuccess else {
let error = CryptError(ccCryptorStatus: status,
key: key,
iv: iv)
if operation == kCCEncrypt {
return .failure(.failedToEncrypt(data: data, error: error))
} else {
return .failure(.failedToDecrypt(encryptedData: data, error: error))
}
}
cryptData.removeSubrange(bytesLength..<cryptData.count)
return .success(cryptData)
}
}

View File

@ -0,0 +1,28 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public protocol Cipher {
func encrypt(data: Data) -> Result<Data, CipherError>
func decrypt(data: Data) -> Result<Data, CipherError>
}

View File

@ -0,0 +1,28 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public enum CipherError: Error {
case failedToEncrypt(data: Data, error: Error)
case failedToDecrypt(encryptedData: Data, error: Error)
}

View File

@ -0,0 +1,78 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import CommonCrypto
open class DefaultPBKDF2PasswordDerivator: PasswordDerivator {
public struct CryptError: Error {
public let derivationStatus: Int32
public let password: String
public let salt: Data
func asCipherError() -> CipherError {
var mutablePassword = password
return .failedToEncrypt(data: mutablePassword.withUTF8 { Data($0) },
error: self)
}
}
public func derive(password: String, salt: Data) -> Result<Data, CipherError> {
var failureResult: Result<Data, CipherError>?
let derivedKeyBytes = Array<UInt8>(unsafeUninitializedCapacity: CryptoConstants.keyLength) { derivedKeyBuffer, initializedCount in
guard let derivedKeyStartAddress = derivedKeyBuffer.baseAddress else {
failureResult = .failure(CryptError(derivationStatus: CCStatus(kCCMemoryFailure),
password: password,
salt: salt)
.asCipherError())
initializedCount = .zero
return
}
let deriviationStatus = salt.withContiguousStorageIfAvailable { saltBytes in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password,
password.count,
saltBytes.baseAddress,
salt.count,
CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512),
UInt32(CryptoConstants.pbkdf2NumberOfIterations),
derivedKeyStartAddress,
CryptoConstants.keyLength)
} ?? CCStatus(kCCParamError)
guard deriviationStatus == kCCSuccess else {
initializedCount = .zero
failureResult = .failure(CryptError(derivationStatus: deriviationStatus,
password: password,
salt: salt)
.asCipherError())
return
}
}
return failureResult ?? .success(Data(derivedKeyBytes))
}
}

View File

@ -0,0 +1,41 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
open class DefaultSaltPreprocessor: SaltPreprocessor {
public typealias DeviceIdProviderClosure = () -> String?
private let deviceIdProvider: DeviceIdProviderClosure
public init(deviceIdProvider: @escaping DeviceIdProviderClosure) {
self.deviceIdProvider = deviceIdProvider
}
// MARK: - SaltPreprocessor
public func preprocess(salt: Data) -> Data {
deviceIdProvider().map {
salt + Data($0.utf8)
} ?? salt
}
}

View File

@ -0,0 +1,98 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import Security
open class DefaultTokenCipher: TokenCipher {
public var saltPreprocessor: SaltPreprocessor
public var passwordDerivator: PasswordDerivator
public init(saltPreprocessor: SaltPreprocessor, passwordDerivator: PasswordDerivator) {
self.saltPreprocessor = saltPreprocessor
self.passwordDerivator = passwordDerivator
}
open func createCipher(iv: Data, key: Data) -> Cipher {
AESCipher(iv: iv, key: key)
}
open func generateIV() -> Data {
generateRandomData(count: CryptoConstants.ivLength)
}
open func generateSalt() -> Data {
generateRandomData(count: CryptoConstants.saltLength)
}
open func generateRandomData(count: Int) -> Data {
let randomBytes = Array<UInt8>(unsafeUninitializedCapacity: count) { buffer, initializedCount in
guard let startAddress = buffer.baseAddress,
SecRandomCopyBytes(kSecRandomDefault,
count,
startAddress) == errSecSuccess else {
initializedCount = .zero
return
}
initializedCount = count
}
return Data(randomBytes)
}
// MARK: - TokenCipher
open func derive(password: String, using salt: Data) -> Result<Data, CipherError> {
passwordDerivator.derive(password: password,
salt: saltPreprocessor.preprocess(salt: salt))
}
open func encrypt(token: Data, using password: String) -> Result<StringEncryptionResult, CipherError> {
let iv = generateIV()
let salt = generateSalt()
return derive(password: password, using: salt)
.flatMap {
createCipher(iv: iv, key: $0)
.encrypt(data: token)
.map {
StringEncryptionResult(salt: salt, iv: iv, value: $0)
}
}
}
open func decrypt(token: StringEncryptionResult, using key: Data) -> Result<Data, CipherError> {
createCipher(iv: token.iv, key: key)
.decrypt(data: token.value)
}
open func decrypt(token: StringEncryptionResult, using password: String) -> Result<Data, CipherError> {
passwordDerivator.derive(password: password,
salt: saltPreprocessor.preprocess(salt: token.salt))
.flatMap {
createCipher(iv: token.iv,
key: $0)
.decrypt(data: token.value)
}
}
}

View File

@ -0,0 +1,27 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public protocol PasswordDerivator {
func derive(password: String, salt: Data) -> Result<Data, CipherError>
}

View File

@ -20,6 +20,8 @@
// THE SOFTWARE.
//
public protocol FingerprintsSecureStorage {
var knownPins: [String: Set<String>] { get set }
import Foundation
public protocol SaltPreprocessor {
func preprocess(salt: Data) -> Data
}

View File

@ -0,0 +1,31 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public protocol TokenCipher {
func derive(password: String, using salt: Data) -> Result<Data, CipherError>
func encrypt(token: Data, using password: String) -> Result<StringEncryptionResult, CipherError>
func decrypt(token: StringEncryptionResult, using key: Data) -> Result<Data, CipherError>
func decrypt(token: StringEncryptionResult, using password: String) -> Result<Data, CipherError>
}

View File

@ -0,0 +1,35 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public struct EqualDigitsValidationRule: ValidationRule {
private let minEqualDigits: UInt
public init(minEqualDigits: UInt) {
self.minEqualDigits = minEqualDigits
}
// MARK: - ValidationRule
public func validate(input: String) -> Bool {
!input.containsSequenceOfEqualCharacters(minEqualCharacters: minEqualDigits)
}
}

View File

@ -0,0 +1,39 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public struct OrderedDigitsValidationRule: ValidationRule {
private let ascendingSequence: Bool
private let minLength: UInt
public init(ascendingSequence: Bool, minLength: UInt) {
self.ascendingSequence = ascendingSequence
self.minLength = minLength
}
// MARK: - ValidationRule
public func validate(input: String) -> Bool {
ascendingSequence
? !input.containsAscendingSequence(minLength: minLength)
: !input.containsDescendingSequence(minLength: minLength)
}
}

View File

@ -0,0 +1,91 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
private extension Substring.SubSequence {
func recursivePairCheck(requiredMatches: UInt,
sequenceRequiredMatches: UInt,
checkClosure: (Character, Character) -> Bool) -> Bool {
guard sequenceRequiredMatches > 0 else {
return true
}
guard !isEmpty else {
return false
}
let tail = dropFirst()
guard let current = first, let next = tail.first else {
return false
}
let matched = checkClosure(current, next)
let reducedMatches = sequenceRequiredMatches - (matched ? 1 : 0)
let currentSequenceMatch = matched
&& tail.recursivePairCheck(requiredMatches: requiredMatches,
sequenceRequiredMatches: reducedMatches,
checkClosure: checkClosure)
return currentSequenceMatch || tail.recursivePairCheck(requiredMatches: requiredMatches,
sequenceRequiredMatches: requiredMatches,
checkClosure: checkClosure)
}
func recursivePairCheck(requiredMatches: UInt, checkClosure: (Character, Character) -> Bool) -> Bool {
recursivePairCheck(requiredMatches: requiredMatches,
sequenceRequiredMatches: requiredMatches,
checkClosure: checkClosure)
}
func containsOrderedSequence(minLength: UInt, orderingClosure: ((Int, Int) -> Bool)) -> Bool {
recursivePairCheck(requiredMatches: minLength - 1) {
guard let current = $0.intValue, let next = $1.intValue else {
return false
}
return orderingClosure(current, next)
}
}
}
private extension Character {
var intValue: Int? {
return Int(String(self))
}
}
extension String {
func containsSequenceOfEqualCharacters(minEqualCharacters: UInt) -> Bool {
Substring(self).recursivePairCheck(requiredMatches: minEqualCharacters - 1) { $0 == $1 }
}
func containsAscendingSequence(minLength: UInt) -> Bool {
Substring(self).containsOrderedSequence(minLength: minLength) { $0 + 1 == $1 }
}
func containsDescendingSequence(minLength: UInt) -> Bool {
Substring(self).containsOrderedSequence(minLength: minLength) { $0 - 1 == $1 }
}
}

View File

@ -0,0 +1,25 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol ValidationRule {
func validate(input: String) -> Bool
}

View File

@ -0,0 +1,39 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
open class DefaultInputValidator<Violation: Hashable>: InputValidator {
public var rules: [Violation: ValidationRule]
public init(rules: [Violation: ValidationRule]) {
self.rules = rules
}
convenience init(violations: [Violation], rulesCreator: (Violation) -> ValidationRule) {
self.init(rules: .init(uniqueKeysWithValues: violations.map { ($0, rulesCreator($0)) }) )
}
// MARK: - InputValidator
open func validate(input: String) -> Set<Violation> {
Set(rules.filter { !$0.value.validate(input: input) }.keys)
}
}

View File

@ -0,0 +1,35 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public enum DefaultViolation: Hashable {
case orderedDigits(ascending: Bool, minLength: UInt)
case equalDigits(minEqualDigits: UInt)
public var defaultValidationRule: ValidationRule {
switch self {
case let .orderedDigits(ascending, minLength):
return OrderedDigitsValidationRule(ascendingSequence: ascending, minLength: minLength)
case let .equalDigits(minEqualDigits):
return EqualDigitsValidationRule(minEqualDigits: minEqualDigits)
}
}
}

View File

@ -0,0 +1,29 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol InputValidator {
associatedtype Violation: Hashable
var rules: [Violation: ValidationRule] { get set }
func validate(input: String) -> Set<Violation>
}

View File

@ -0,0 +1,41 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
enum CryptoConstants {
static var saltLength: Int {
32
}
static var ivLength: Int {
16
}
static var keyLength: Int {
32
}
static var pbkdf2NumberOfIterations: Int {
8192
}
}

View File

@ -0,0 +1,72 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import KeychainAccess
import Foundation
import TIFoundationUtils
import LocalAuthentication
open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data> {
open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults {
public static var encryptedTokenKeyStorageKey: StorageKey<Data> {
.init(rawValue: keychainServiceIdentifier + ".encryptedTokenKey")
}
public static var reusableLAContext: LAContext {
let context = LAContext()
context.touchIDAuthenticationAllowableReuseDuration = LATouchIDAuthenticationMaximumAllowableReuseDuration
return context
}
}
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
localAuthContext: LAContext = Defaults.reusableLAContext,
encryptedTokenKeyStorageKey: StorageKey<Data> = Defaults.encryptedTokenKeyStorageKey,
appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
let getValueClosure: GetValueClosure = { keychain, storageKey in
do {
guard let value = try keychain.getData(storageKey.rawValue) else {
return .failure(.valueNotFound)
}
return .success(value)
} catch {
return .failure(.unableToExtractData(underlyingError: error))
}
}
let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
do {
return .success(try keychain.set(value, key: storageKey.rawValue))
} catch {
return .failure(.unableToWriteData(underlyingError: error))
}
}
super.init(keychain: keychain.authenticationContext(localAuthContext),
storageKey: encryptedTokenKeyStorageKey,
getValueClosure: getValueClosure,
storeValueClosure: storeValueClosure,
appFirstRunCheckStorage: appFirstRunCheckStorage)
}
}

View File

@ -0,0 +1,68 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import TIFoundationUtils
import KeychainAccess
open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEncryptionResult> {
open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults {
public static var encryptedTokenStorageKey: StorageKey<StringEncryptionResult> {
.init(rawValue: keychainServiceIdentifier + ".encryptedToken")
}
}
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
encryptedTokenStorageKey: StorageKey<StringEncryptionResult> = Defaults.encryptedTokenStorageKey,
appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
let getValueClosure: GetValueClosure = { keychain, storageKey in
do {
guard let value = try keychain.getData(storageKey.rawValue) else {
return .failure(.valueNotFound)
}
do {
return .success(try StringEncryptionResult(storableData: value))
} catch {
return .failure(.unableToDecode(underlyingError: error))
}
} catch {
return .failure(.unableToExtractData(underlyingError: error))
}
}
let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
do {
return .success(try keychain.set(value.asStorableData(), key: storageKey.rawValue))
} catch {
return .failure(.unableToWriteData(underlyingError: error))
}
}
super.init(keychain: keychain,
storageKey: encryptedTokenStorageKey,
getValueClosure: getValueClosure,
storeValueClosure: storeValueClosure,
appFirstRunCheckStorage: appFirstRunCheckStorage)
}
}

View File

@ -0,0 +1,38 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIFoundationUtils
import Foundation
open class DefaultResetAuthSettingsStorage: DefaultAppFirstRunCheckStorage {
public enum Defaults {
public static var shouldResetAuthDataKey: StorageKey<Bool> {
.init(rawValue: "shouldResetAuthData")
}
}
public override init(defaults: UserDefaults = .standard,
storageKey: StorageKey<Bool> = Defaults.shouldResetAuthDataKey) {
super.init(defaults: defaults, storageKey: storageKey)
}
}

View File

@ -0,0 +1,53 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIFoundationUtils
import KeychainAccess
import Foundation
import TIKeychainUtils
open class SingleValueAuthKeychainStorage<ValueType>:
AppInstallLifetimeSingleValueStorage<BaseSingleValueKeychainStorage<ValueType>, BoolValueDefaultsStorage> {
public typealias GetValueClosure = BaseSingleValueKeychainStorage<ValueType>.GetValueClosure
public typealias StoreValueClosure = BaseSingleValueKeychainStorage<ValueType>.StoreValueClosure
open class Defaults {
public static var keychainServiceIdentifier: String {
Bundle.main.bundleIdentifier ?? "ru.touchin.TIAuth"
}
}
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
storageKey: StorageKey<ValueType>,
getValueClosure: @escaping GetValueClosure,
storeValueClosure: @escaping StoreValueClosure,
appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
let keychainStorage = BaseSingleValueKeychainStorage(keychain: keychain,
storageKey: storageKey,
getValueClosure: getValueClosure,
storeValueClosure: storeValueClosure)
super.init(storage: keychainStorage, appFirstRunCheckStorage: appFirstRunCheckStorage)
}
}

View File

@ -0,0 +1,67 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public struct StringEncryptionResult {
public struct DataRangeMismatch: Error {
public let dataLength: Int
public let valueRangeLowerBound: Int
}
public let salt: Data
public let iv: Data
public let value: Data
private static var saltRange: Range<Int> {
.zero..<CryptoConstants.saltLength
}
private static var ivRange: Range<Int> {
saltRange.endIndex..<CryptoConstants.saltLength + CryptoConstants.ivLength
}
private static var valueRange: PartialRangeFrom<Int> {
ivRange.endIndex...
}
public init(salt: Data, iv: Data, value: Data) {
self.salt = salt
self.iv = iv
self.value = value
}
public init(storableData: Data) throws {
guard Self.valueRange.contains(storableData.endIndex) else {
throw DataRangeMismatch(dataLength: storableData.count,
valueRangeLowerBound: Self.valueRange.lowerBound)
}
self.init(salt: storableData[Self.saltRange],
iv: storableData[Self.ivRange],
value: storableData[Self.valueRange])
}
public func asStorableData() -> Data {
salt + iv + value
}
}

View File

@ -1,16 +1,25 @@
Pod::Spec.new do |s|
s.name = 'TIAuth'
s.version = '1.22.0'
s.version = '1.56.0'
s.summary = 'Login, registration, confirmation and other related actions'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '13.0'
s.swift_versions = ['5.3']
s.swift_versions = ['5.7']
s.source_files = s.name + '/Sources/**/*'
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIFoundationUtils', s.version.to_s
s.dependency 'TIKeychainUtils', s.version.to_s
s.dependency 'TIUIKitCore', s.version.to_s
end

View File

@ -0,0 +1,14 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
target 'TIBottomSheet' do
platform :ios, 11
use_frameworks!
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
end

View File

@ -0,0 +1,62 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TIUIKitCore
import UIKit
extension BaseModalViewController {
open class BaseAppearance: UIView.BaseAppearance<UIView.DefaultWrappedLayout> {
public var presentationDetents: [ModalViewPresentationDetent]
public var dragViewState: DragView.State
public var headerViewState: ModalHeaderView.State
public var footerViewState: ModalFooterView<FooterContentView>.State
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
background: UIViewBackground = UIViewColorBackground(color: .clear),
border: UIViewBorder = BaseUIViewBorder(),
shadow: UIViewShadow? = nil,
presentationDetents: [ModalViewPresentationDetent] = [.maxHeight],
dragViewState: DragView.State = .hidden,
headerViewState: ModalHeaderView.State = .hidden,
footerViewState: ModalFooterView<FooterContentView>.State = .hidden) {
self.presentationDetents = presentationDetents
self.dragViewState = dragViewState
self.headerViewState = headerViewState
self.footerViewState = footerViewState
super.init(layout: layout,
background: background,
border: border,
shadow: shadow)
}
}
public final class DefaultAppearance: BaseAppearance, ViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
}

View File

@ -0,0 +1,473 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TISwiftUtils
import TIUIElements
import TIUIKitCore
import UIKit
import PanModal
open class BaseModalViewController<ContentView: UIView,
FooterContentView: UIView>: BaseInitializableViewController, PanModalPresentable {
// MARK: - Public Properties
public let dragView = DragView()
public let headerView = ModalHeaderView()
private(set) public lazy var contentView = createContentView()
private(set) public lazy var footerView = createFooterView()
public private(set) lazy var dragViewBottomToHeaderViewTopConstraint: NSLayoutConstraint = {
dragView.bottomAnchor.constraint(equalTo: headerView.topAnchor)
}()
public private(set) lazy var dragViewBottomToContentViewTopConstraint: NSLayoutConstraint = {
dragView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
}()
public private(set) lazy var dragViewConstraints: SubviewConstraints = {
let trailingConstraint = dragView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: dragView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
trailingConstraint: trailingConstraint,
topConstraint: dragView.topAnchor.constraint(equalTo: view.topAnchor),
bottomConstraint: dragViewBottomToHeaderViewTopConstraint)
let centerXConstraint = dragView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = dragView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: dragView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: dragView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var headerViewToSuperviewTopConstraint: NSLayoutConstraint = {
headerView.topAnchor.constraint(equalTo: view.topAnchor)
}()
public private(set) lazy var headerBottomToContentTopConstraint: NSLayoutConstraint = {
headerView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
}()
public private(set) lazy var headerViewConstraints: SubviewConstraints = {
let leadingConstraint = headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: dragViewBottomToHeaderViewTopConstraint,
bottomConstraint: headerBottomToContentTopConstraint)
let centerXConstraint = headerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = headerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: headerView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: headerView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var contentViewTopToSuperviewConstraint: NSLayoutConstraint = {
contentView.topAnchor.constraint(equalTo: view.topAnchor)
}()
public private(set) lazy var contentViewBottomToSuperviewConstraint: NSLayoutConstraint = {
contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
}()
public private(set) lazy var contentViewConstraints: SubviewConstraints = {
let leadingConstraint = contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: headerBottomToContentTopConstraint,
bottomConstraint: contentViewBottomToSuperviewConstraint)
let centerXConstraint = contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: contentView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: contentView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var footerViewConstraints: SubviewConstraints = {
let leadingConstraint = footerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = footerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: footerView.topAnchor.constraint(equalTo: contentView.bottomAnchor),
bottomConstraint: footerView.bottomAnchor.constraint(equalTo: view.bottomAnchor))
let centerXConstraint = footerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = footerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: footerView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: footerView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
// MARK: - Modal View Controller Configuration
public var viewControllerAppearance: BaseAppearance = .init(background: UIViewColorBackground(color: .white))
open var panScrollable: UIScrollView? {
contentView as? UIScrollView
}
public var panScrollableInsets: UIEdgeInsets = .zero {
didSet {
panScrollable?.contentInset = panScrollableInsets
}
}
public var dimmedView = DimmedView()
public var showDragIndicator = true
open var headerViewHeight: CGFloat {
let dragViewHeight = getHeight(of: dragView)
let headerViewHeight = getHeight(of: headerView)
let dragViewVerticalInsets = getDragViewVerticalInsets()
let headerViewVerticalInsets = getHeaderViewVerticalInsets()
return dragViewHeight + headerViewHeight + dragViewVerticalInsets + headerViewVerticalInsets
}
open var longFormHeight: PanModalHeight {
let detents = getSortedDetents()
return detents.max()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
open var mediumFormHeight: PanModalHeight {
let detents = getSortedDetents()
if detents.count > 1 {
return detents[1].panModalHeight(headerHeight: headerViewHeight)
}
return detents.first?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
open var shortFormHeight: PanModalHeight {
let detents = getSortedDetents()
return detents.min()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
private var keyboardDidShownObserver: NSObjectProtocol?
private var keyboardDidHiddenObserver: NSObjectProtocol?
// MARK: - Life Cycle
deinit {
let notificationCenter = NotificationCenter.default
if let keyboardDidShownObserver {
notificationCenter.removeObserver(keyboardDidShownObserver)
}
if let keyboardDidHiddenObserver {
notificationCenter.removeObserver(keyboardDidHiddenObserver)
}
}
// MARK: - BaseInitializableViewController
open override func addViews() {
super.addViews()
view.addSubviews(dragView, headerView, contentView, footerView)
}
open override func configureLayout() {
super.configureLayout()
for view in [dragView, headerView, contentView, footerView] {
view.translatesAutoresizingMaskIntoConstraints = false
}
configureDragViewLayout()
configureHeaderViewLayout()
configureContentViewLayout()
configureFooterViewLayout()
}
open override func bindViews() {
super.bindViews()
keyboardDidShownObserver = NotificationCenter.default
.addObserver(forName: UIResponder.keyboardDidShowNotification,
object: nil,
queue: .main) { [weak self] notification in
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: false)
}
keyboardDidHiddenObserver = NotificationCenter.default
.addObserver(forName: UIResponder.keyboardDidHideNotification,
object: nil,
queue: .main) { [weak self] notification in
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: true)
}
}
open override func configureAppearance() {
super.configureAppearance()
view.configureUIView(appearance: viewControllerAppearance)
configureDragViewAppearance()
configureHeaderViewAppearance()
configureFooterViewAppearance()
}
// MARK: - Open Methods
open func createContentView() -> ContentView {
ContentView()
}
open func createFooterView() -> ModalFooterView<FooterContentView> {
ModalFooterView<FooterContentView>()
}
open func configureLayoutForKeyboard(_ notification: Notification, isKeyboardHidden: Bool) {
guard let keyboardHeight = getKeyboardHeight(notification) else {
return
}
if case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState {
let bottomInset = footerViewAppearance.layout.insets.add(\.bottom,
to: \.bottom,
of: .vertical(bottom: keyboardHeight))
footerViewConstraints.edgeConstraints.bottomConstraint.constant = bottomInset
} else if let panScrollable {
var insets = panScrollableInsets
if isKeyboardHidden {
panScrollable.contentInset = insets
} else {
insets.bottom += keyboardHeight
panScrollable.contentInset = insets
}
}
}
// MARK: - Private Methods
private func configureDragViewLayout() {
guard case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState else {
return
}
let bottomConstraint: NSLayoutConstraint
let bottomConstant: CGFloat
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
bottomConstraint = dragViewBottomToHeaderViewTopConstraint
bottomConstant = dragViewAppearance.layout.insets.add(\.bottom,
to: \.top,
of: headerViewAppearance.layout.insets)
} else {
dragViewBottomToHeaderViewTopConstraint.isActive = false
bottomConstraint = dragViewBottomToContentViewTopConstraint
bottomConstant = dragViewAppearance.layout.insets.bottom
}
dragViewConstraints.edgeConstraints.bottomConstraint = bottomConstraint
dragViewConstraints.update(from: dragViewAppearance.layout)
bottomConstraint.setActiveConstantOrDeactivate(constant: bottomConstant)
}
private func configureHeaderViewLayout() {
guard case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState else {
return
}
let topConstraint: NSLayoutConstraint
let topConstant: CGFloat
if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
topConstraint = dragViewBottomToHeaderViewTopConstraint
topConstant = dragViewAppearance.layout.insets.add(\.bottom,
to: \.top,
of: headerViewAppearance.layout.insets)
} else {
dragViewBottomToHeaderViewTopConstraint.isActive = false
topConstraint = headerViewToSuperviewTopConstraint
let topInset = headerViewAppearance.layout.insets.top
topConstant = topInset.isFinite ? topInset : .zero
}
headerViewConstraints.edgeConstraints.topConstraint = topConstraint
headerViewConstraints.update(from: headerViewAppearance.layout)
topConstraint.setActiveConstantOrDeactivate(constant: topConstant)
}
private func configureContentViewLayout() {
let topConstraint: NSLayoutConstraint
let topConstant: CGFloat
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
contentViewTopToSuperviewConstraint.isActive = false
topConstraint = headerBottomToContentTopConstraint
topConstant = headerViewAppearance.layout.insets.bottom
} else if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
contentViewTopToSuperviewConstraint.isActive = false
headerBottomToContentTopConstraint.isActive = false
topConstraint = dragViewBottomToContentViewTopConstraint
topConstant = dragViewAppearance.layout.insets.bottom
} else {
headerBottomToContentTopConstraint.isActive = false
dragViewBottomToContentViewTopConstraint.isActive = false
topConstraint = contentViewTopToSuperviewConstraint
topConstant = .zero
}
contentViewConstraints.edgeConstraints.topConstraint = topConstraint
let layout = UIView.DefaultWrappedLayout(insets: .horizontal(.zero)
.vertical(top: topConstant)
.replacingNan(with: .zero))
contentViewConstraints.update(from: layout)
}
private func configureFooterViewLayout() {
guard case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState else {
return
}
contentViewConstraints.edgeConstraints.bottomConstraint.isActive = false
contentViewConstraints.edgeConstraints.bottomConstraint = footerViewConstraints.edgeConstraints.topConstraint
footerViewConstraints.update(from: footerViewAppearance.layout)
}
private func configureDragViewAppearance() {
switch viewControllerAppearance.dragViewState {
case .hidden:
dragView.isHidden = true
case let .presented(appearance):
dragView.configure(appearance: appearance)
}
}
private func configureHeaderViewAppearance() {
switch viewControllerAppearance.headerViewState {
case .hidden:
headerView.isHidden = true
case let .presented(appearance):
headerView.configure(appearance: appearance)
}
}
private func configureFooterViewAppearance() {
switch viewControllerAppearance.footerViewState {
case .hidden:
footerView.isHidden = true
case let .presented(appearance):
footerView.configureBaseWrappedViewHolder(appearance: appearance)
}
}
private func getSortedDetents() -> [ModalViewPresentationDetent] {
viewControllerAppearance.presentationDetents.uniqued().sorted()
}
private func getHeight(of view: UIView) -> CGFloat {
guard !view.isHidden else {
return .zero
}
return getFittingSize(forView: view).height
}
private func getDragViewVerticalInsets() -> CGFloat {
guard case let .presented(appearance) = viewControllerAppearance.dragViewState else {
return .zero
}
return appearance.layout.insets.vertical(onNan: .zero)
}
private func getHeaderViewVerticalInsets() -> CGFloat {
guard case let .presented(appearance) = viewControllerAppearance.headerViewState else {
return .zero
}
return appearance.layout.insets.vertical(onNan: .zero)
}
private func getFittingSize(forView view: UIView) -> CGSize {
let targetSize = CGSize(width: UIScreen.main.bounds.width,
height: UIView.layoutFittingCompressedSize.height)
return view.systemLayoutSizeFitting(targetSize)
}
private func getKeyboardHeight(_ notification: Notification) -> CGFloat? {
guard let userInfo = notification.userInfo else {
return nil
}
return (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height
}
}

View File

@ -0,0 +1,54 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
open class DefaultModalWrapperViewController<ContentController: UIViewController>: BaseModalViewController<UIView, UIView> {
private(set) public var contentViewController: ContentController
public init(contentViewController: ContentController) {
self.contentViewController = contentViewController
super.init(nibName: nil, bundle: nil)
addChild(contentViewController)
contentViewController.didMove(toParent: self)
}
@available(*, unavailable)
required public init?(coder: NSCoder) {
contentViewController = ContentController()
super.init(coder: coder)
}
open override func createContentView() -> UIView {
contentViewController.view
}
}
public extension UIViewController {
func wrappedInBottomSheetController() -> DefaultModalWrapperViewController<UIViewController> {
DefaultModalWrapperViewController(contentViewController: self)
}
}

View File

@ -0,0 +1,34 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIKitCore
extension UIViewShadow {
public static var defaultModalViewShadow: Self {
.init {
Color(.black)
Opacity(0.5)
Offset(0, -5)
Radius(10)
}
}
}

View File

@ -0,0 +1,67 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import PanModal
public struct ModalViewPresentationDetent: Hashable {
// MARK: - Default Values
public static var headerOnly: Self {
Self(height: -.greatestFiniteMagnitude)
}
public static func height(_ height: CGFloat) -> Self {
Self(height: height)
}
public static var maxHeight: Self {
Self(height: .greatestFiniteMagnitude)
}
// MARK: - Public Properties
public var height: CGFloat
// MARK: - Internal Methods
func panModalHeight(headerHeight: CGFloat = .zero) -> PanModalHeight {
if self == .headerOnly {
return .contentHeight(headerHeight)
}
if self == .maxHeight {
return .maxHeight
}
return .contentHeight(height)
}
}
// MARK: - Comparable
extension ModalViewPresentationDetent: Comparable {
public static func < (lhs: ModalViewPresentationDetent, rhs: ModalViewPresentationDetent) -> Bool {
lhs.height < rhs.height
}
}

View File

@ -0,0 +1,70 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TIUIKitCore
import UIKit
public final class DragView: BaseInitializableView, AppearanceConfigurable {
// MARK: - Nested Types
private enum Constants {
static var dragViewTopInset: CGFloat {
8
}
static var dragViewBorder: UIViewBorder {
UIViewRoundedBorder(cornerRadius: dragViewSize.height / 2)
}
static var dragViewBackground: UIViewColorBackground {
UIViewColorBackground(color: .lightGray)
}
static var dragViewSize: CGSize {
CGSize(width: 52, height: 7)
}
}
public enum State {
case hidden
case presented(Appearance)
}
public final class Appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .vertical(top: Constants.dragViewTopInset),
size: Constants.dragViewSize,
centerOffset: .centerHorizontal()),
background: Constants.dragViewBackground,
border: Constants.dragViewBorder)
}
}
// MARK: - AppearanceConfigurable
public func configure(appearance: Appearance) {
configureUIView(appearance: appearance)
}
}

View File

@ -0,0 +1,45 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TIUIKitCore
import UIKit
open class ModalFooterView<ContentView: UIView>: ContainerView<ContentView> {
// MARK: - Nested Types
public enum State {
case hidden
case presented(Appearance)
}
}
public extension ModalFooterView {
final class Appearance: BaseWrappedViewHolderAppearance<DefaultWrappedAppearance, DefaultWrappedLayout>,
WrappedViewHolderAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(bottom: .zero),
size: .fixedHeight(44)))
}
}
}

View File

@ -0,0 +1,235 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TIUIKitCore
import UIKit
open class ModalHeaderView: BaseInitializableView, AppearanceConfigurable {
// MARK: - Nested Types
public enum State {
case hidden
case presented(Appearance)
}
public enum ContentViewState {
case leadingButton(BaseButtonStyle)
case trailingButton(BaseButtonStyle)
case buttons(leading: BaseButtonStyle, trailing: BaseButtonStyle)
case custom(view: UIView, appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>)
}
// MARK: - Public properties
public let leadingButton = StatefulButton()
public let trailingButton = StatefulButton()
public private(set) lazy var leftTrailingToRightLeadingConstraint: NSLayoutConstraint = {
leadingButton.trailingAnchor.constraint(equalTo: trailingButton.leadingAnchor)
}()
public private(set) lazy var leftTrailingToSuperviewTrailing: NSLayoutConstraint = {
leadingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
}()
public private(set) lazy var leadingButtonConstraints: SubviewConstraints = {
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingButton.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingConstraint: leftTrailingToRightLeadingConstraint,
topConstraint: leadingButton.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: leadingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = leadingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = leadingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: leadingButton.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: leadingButton.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var rightLeadingToSuperviewLeadingConstraint: NSLayoutConstraint = {
trailingButton.leadingAnchor.constraint(equalTo: leadingAnchor)
}()
public private(set) lazy var trailingButtonConstraints: SubviewConstraints = {
let trailingConstraint = trailingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leftTrailingToRightLeadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: trailingButton.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: trailingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = trailingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = trailingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: trailingButton.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: trailingButton.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public var customViewConstraints: SubviewConstraints?
// MARK: - BaseInitializableView
open override func addViews() {
super.addViews()
addSubviews(leadingButton, trailingButton)
}
open override func configureLayout() {
super.configureLayout()
for view in [leadingButton, trailingButton] {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
// MARK: - AppearanceConfigurable
open func configure(appearance: Appearance) {
configureUIView(appearance: appearance)
configureContentView(state: appearance.contentViewState)
}
open func configureContentView(state: ContentViewState) {
var leadingButtonStyle: BaseButtonStyle?
var trailingButtonStyle: BaseButtonStyle?
switch state {
case let .leadingButton(style):
leadingButtonStyle = style
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToSuperviewTrailing
case let .trailingButton(style):
trailingButtonStyle = style
trailingButtonConstraints.edgeConstraints.leadingConstraint = rightLeadingToSuperviewLeadingConstraint
case let .buttons(leading, trailing):
leadingButtonStyle = leading
trailingButtonStyle = trailing
case let .custom(view, appearance):
configureCustomView(view, withLayout: appearance.layout)
view.configureUIView(appearance: appearance)
}
configure(buttonStyle: leadingButtonStyle, forButton: leadingButton, constraints: leadingButtonConstraints)
configure(buttonStyle: trailingButtonStyle, forButton: trailingButton, constraints: trailingButtonConstraints)
if let leadingButtonStyle, let trailingButtonStyle {
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToRightLeadingConstraint
trailingButtonConstraints.edgeConstraints.leadingConstraint = leftTrailingToRightLeadingConstraint
let spacing = leadingButtonStyle.appearance.layout.insets.add(\.right,
to: \.left,
of: trailingButtonStyle.appearance.layout.insets)
leftTrailingToRightLeadingConstraint.setActiveConstantOrDeactivate(constant: spacing)
}
}
// MARK: - Private methods
private func configureCustomView(_ view: UIView, withLayout layout: WrappedViewLayout) {
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
let edgeConstraints = EdgeConstraints(leadingConstraint: view.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingConstraint: view.trailingAnchor.constraint(equalTo: trailingAnchor),
topConstraint: view.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: view.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerConstraints = CenterConstraints(centerXConstraint: view.centerXAnchor.constraint(equalTo: centerXAnchor),
centerYConstraint: view.centerYAnchor.constraint(equalTo: centerYAnchor))
let sizeConstraints = SizeConstraints(widthConstraint: view.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: view.heightAnchor.constraint(equalToConstant: .zero))
let customViewConstraints = SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
self.customViewConstraints = customViewConstraints
customViewConstraints.update(from: layout)
}
private func configure(buttonStyle: BaseButtonStyle?,
forButton button: StatefulButton,
constraints: SubviewConstraints?) {
guard let buttonStyle else {
button.isHidden = true
return
}
button.isHidden = false
constraints?.update(from: buttonStyle.appearance.layout)
button.apply(style: buttonStyle)
}
}
// MARK: - Appearance
public extension ModalHeaderView {
final class Appearance: BaseWrappedAppearance<DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(top: .zero),
size: .fixedHeight(44)))
}
public var contentViewState: ContentViewState
public init(layout: DefaultWrappedLayout = .defaultLayout,
background: UIViewBackground = UIViewColorBackground(color: .clear),
border: UIViewBorder = BaseUIViewBorder(),
shadow: UIViewShadow? = nil,
contentViewState: ContentViewState = .leadingButton(.init())) {
self.contentViewState = contentViewState
super.init(layout: layout,
background: background,
border: border,
shadow: shadow)
}
}
}

View File

@ -0,0 +1,50 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import PanModal
open class PassthroughDimmedView: DimmedView {
public weak var hitTestHandlerView: UIView?
public var isTransparent = false
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let hitTestHandlerView else {
return super.hitTest(point, with: event)
}
return hitTestHandlerView.hitTest(point, with: event)
}
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
hitTestHandlerView == nil || super.point(inside: point, with: event)
}
open override func onChange(dimState: DimmedView.DimState) {
super.onChange(dimState: dimState)
if isTransparent {
alpha = .zero
}
}
}

View File

@ -0,0 +1,3 @@
**/build/
**/nef/
LICENSE

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>launcher</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.fortysevendeg.nef</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
## gitignore nef files
**/build/
**/nef/
LICENSE
## User data
**/xcuserdata/
podfile.lock
**.DS_Store
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
**Pods**
## Carthage
**Carthage**
## SPM
.build
.swiftpm
swiftpm

View File

@ -0,0 +1,14 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
target 'TIBottomSheet' do
platform :ios, 12
use_frameworks!
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
end

View File

@ -0,0 +1,110 @@
/*:
# TIBottomSheet
TIBottomSheet содержить базовую реализацию модального котроллера и немного видоизмененную библиотеку PanModal.
## Базовый контроллер
Для создания модального котроллера можно унаследоваться от `BaseModalViewController`. Данный клас принимает два generic типа: тип основного контента, тип контента футера.
*/
import TIBottomSheet
import UIKit
class EmptyViewController: BaseModalViewController<UIView, UIView> { }
/*:
## Обертка вокруг существующего контроллера
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `DefaultModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
*/
import TIUIKitCore
final class OldMassiveViewController: BaseInitializableViewController {
// some implementation
}
typealias ModalOldMassiveViewController = DefaultModalWrapperViewController<OldMassiveViewController>
class PresentingViewController: BaseInitializableViewController {
// some implementation
@objc private func onButtonTapped() {
presentPanModal(ModalOldMassiveViewController(contentViewController: OldMassiveViewController()))
}
}
/*:
## Контент модального контроллера
Модальный котроллер может содержать следующие элементы: `DragView`, `HeaderView`, `FooterView`. Каждый из них является опциональным и без дополнительных настроек не будет показываться.
DragView - небольшая view, за которую пользователь "держит" модальный контроллер
HeaderView - контейнер, содержащий в себе кнопки назад/закрыть или какие-то другие элементы управления
FooterView - view, располагающаяся внизу контроллера, поверх всего контента (модальный контроллер уже настроен так, чтобы при скролле в самый низ, футер не перекрывал последнюю ячейку)
Для настройки каждого у котроллера есть свойство `viewControllerAppearance`. Через него будет настраиваться весь контроллер. Однако стоит заметить, что котроллер не будет настраивать передаваимую вью, содержащую основной контент. Стандартно котроллер будет пытаться расположить контент так, чтобы он заполнил все пространство.
Вот пример настройки внешнего вида так, чтобы был видет dragView и headerView с левой кнопкой:
*/
import TIUIElements
let customViewController = BaseModalViewController<UIView, UIView>()
customViewController.viewControllerAppearance = BaseModalViewController.DefaultAppearance.make {
$0.dragViewState = .presented(.defaultAppearance)
$0.headerViewState = .presented(.make {
$0.layout.size = .fixedHeight(52)
$0.backgroundColor = .white
$0.contentViewState = .leadingButton(.init(titles: [.normal: "Close"],
appearance: .init(stateAppearances: [
.normal: .init(background: UIViewColorBackground(color: .blue))
])))
})
}
/*:
## "Якори" контроллера
Раньше для настройки высоты контроллера необходимо было пользоваться свойствами `longFormHeight`, `shortFormHeight`. В базовом контроллере можно лишь передать список точек на которых контроллер должен будет задержаться:
*/
let detentsViewController = BaseModalViewController<UIView, UIView>()
detentsViewController.viewControllerAppearance.presentationDetents = [.headerOnly, .height(300), .maxHeight]
/*:
- headerOnly будет сам пытаться вычеслить высоту хедера и dragView, показывая только их
- height(_) будет показывать контроллер на переданной высоте
- maxHeight - вся высота экрана (до safeArea)
В данный массив не рекомендуется передавать больше 3 значений, т.к. модальное окно все равно сможет занять только 3 положения на экране.
## DimmedView и PassthroughDimmedView
Для контроля `DimmedView` (затемняющей view) есть отдельное свойство `dimmedView`. Эти классы позволяют настраивать поведение при тапе в затемнённую область и кастомизировать затемнение под ваши нужды.
*/
let shadowViewController = BaseModalViewController<UIView, UIView>()
let dimmedView = PassthroughDimmedView()
dimmedView.hitTestHandlerView = shadowViewController.view
dimmedView.configureUIView(appearance: UIView.DefaultAppearance(shadow: UIViewShadow(radius: 8,
color: .black,
opacity: 0.3)))
shadowViewController.dimmedView = dimmedView
/*:
## Контроль закрытия
`PanModalPresentable` не умеет в настройку закрытия контроллера, делая это самостоятельно через `dismiss(animated:completion:)`. Теперь можно настроить закрытие самостоятельно через свойства: `onTapToDismiss` и `onDragToDismiss`.
# Взаимодействие с PanModal
Если нет необходимости или возможности использовать `BaseModalViewController`, вы все так же можете пользоваться протоколом `PanModalRepresentable`. Вот список изменений протокола:
- Открытие/закрытие модального окна теперь можно настроить с помощью свойств `onTapToDismiss` и `onDragToDismiss`
- Можно настроить промежуточное состояние модального окна с `mediumFormHeight`
- `DimmedView` открыт для наследования и может создаваться в `dimmedView` у
> Для `BaseModalViewController` все свойства из `PanModalPresentable` все также работают, т.е. вы можете их переопределять, добавлять и изменять по необходимости.
*/

View File

@ -0,0 +1,30 @@
import UIKit
public protocol NefPlaygroundLiveViewable {}
extension UIView: NefPlaygroundLiveViewable {}
extension UIViewController: NefPlaygroundLiveViewable {}
#if NOT_IN_PLAYGROUND
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
public static func needsIndefiniteExecution(_ state: Bool) {}
}
}
#else
import PlaygroundSupport
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
}
public static func needsIndefiniteExecution(_ state: Bool) {
PlaygroundPage.current.needsIndefiniteExecution = state
}
}
}
#endif

View File

@ -0,0 +1,47 @@
import Foundation
import XCTest
public extension Nef {
static func run<T: XCTestCase>(testCase class: T.Type) {
startTestObserver()
T.defaultTestSuite.run()
}
static private func startTestObserver() {
_ = testObserverInstalled
}
static private var testObserverInstalled = { () -> NefTestFailObserver in
let testObserver = NefTestFailObserver()
XCTestObservationCenter.shared.addTestObserver(testObserver)
return testObserver
}()
}
// MARK: enrich the output for XCTest
fileprivate class NefTestFailObserver: NSObject, XCTestObservation {
private var numberOfFailedTests = 0
func testSuiteWillStart(_ testSuite: XCTestSuite) {
numberOfFailedTests = 0
}
func testSuiteDidFinish(_ testSuite: XCTestSuite) {
if numberOfFailedTests > 0 {
print("💢 Test Suite '\(testSuite.name)' finished with \(numberOfFailedTests) failed \(numberOfFailedTests > 1 ? "tests" : "test").")
} else {
print("🔅 Test Suite '\(testSuite.name)' finished successfully.")
}
}
func testCase(_ testCase: XCTestCase,
didFailWithDescription description: String,
inFile filePath: String?,
atLine lineNumber: Int) {
numberOfFailedTests += 1
print("Test Fail '\(testCase.name)':\(UInt(lineNumber)): \(description.description)")
}
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>

View File

@ -0,0 +1,396 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.debug.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.debug.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.release.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
11F06D2789C6CF40767861CF /* Frameworks */ = {
isa = PBXGroup;
children = (
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
1F7782E3A7AD7291B7C09F56 /* Pods */ = {
isa = PBXGroup;
children = (
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */,
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TIBottomSheet */,
8B39A26C21D40F8700DE2643 /* Products */,
1F7782E3A7AD7291B7C09F56 /* Pods */,
11F06D2789C6CF40767861CF /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TIBottomSheet */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TIBottomSheet;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TIBottomSheet */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */;
buildPhases = (
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TIBottomSheet;
productName = TIBottomSheet2;
productReference = 8BACBE8322576CAD00266845 /* TIBottomSheet.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8B39A26321D40F8700DE2643 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "47 Degrees";
TargetAttributes = {
8BACBE8222576CAD00266845 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TIBottomSheet */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-TIBottomSheet-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8BACBE7F22576CAD00266845 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
8B39A27721D40F8800DE2643 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8B39A27821D40F8800DE2643 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8BACBE8822576CAD00266845 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIBottomSheet_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
8BACBE8922576CAD00266845 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIBottomSheet_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TIBottomSheet.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef location = "group:TIBottomSheet.playground"></FileRef>
<FileRef
location = "group:TIBottomSheet.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019. The nef authors.</string>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
#!/bin/bash
workspace="TIBottomSheet.xcworkspace"
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
open "`pwd`/$workspacePath/$workspace"

View File

@ -0,0 +1 @@
TIBottomSheet.app/Contents/MacOS/TIBottomSheet.playground

View File

@ -0,0 +1,29 @@
Pod::Spec.new do |s|
s.name = 'TIBottomSheet'
s.version = '1.56.0'
s.summary = 'Base models for creating bottom sheet view controllers'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'castlele' => 'nikita.semenov@touchin.ru',
'petropavel13' => 'ivan.smolin@touchin.ru'}
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIUIElements', s.version.to_s
s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'TISwiftUtils', s.version.to_s
s.dependency 'PanModal', '~> 1.3.0'
end

View File

@ -0,0 +1,8 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TICoreGraphicsUtils' do
platform :ios, 11.0
use_frameworks!
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
end

View File

@ -22,11 +22,9 @@
import CoreGraphics.CGGeometry
public typealias CGContextSize = (width: Int, height: Int)
public extension CGSize {
var ceiledContextSize: CGContextSize {
(width: Int(ceil(width)), height: Int(ceil(height)))
var ceiledContextSize: CGSize {
CGSize(width: ceil(width), height: ceil(height))
}
}

View File

@ -23,19 +23,19 @@
import UIKit
public struct BorderDrawingOperation: DrawingOperation {
public var frameableContentSize: CGSize
public var frameableContentRect: CGRect
public var border: CGFloat
public var color: CGColor
public var radius: CGFloat
public var exteriorBorder: Bool
public init(frameableContentSize: CGSize,
public init(frameableContentRect: CGRect,
border: CGFloat,
color: CGColor,
radius: CGFloat,
exteriorBorder: Bool) {
self.frameableContentSize = frameableContentSize
self.frameableContentRect = frameableContentRect
self.border = border
self.color = color
self.radius = radius
@ -47,10 +47,10 @@ public struct BorderDrawingOperation: DrawingOperation {
public func affectedArea(in context: CGContext?) -> CGRect {
let margin = exteriorBorder ? border : 0
let width = frameableContentSize.width + margin * 2
let height = frameableContentSize.height + margin * 2
let width = frameableContentRect.width + margin * 2
let height = frameableContentRect.height + margin * 2
return CGRect(origin: .zero,
return CGRect(origin: frameableContentRect.origin,
size: CGSize(width: width, height: height))
}
@ -59,7 +59,8 @@ public struct BorderDrawingOperation: DrawingOperation {
let ctxSize = drawArea.size.ceiledContextSize
let ctxRect = CGRect(origin: .zero, size: CGSize(width: ctxSize.width, height: ctxSize.height))
let ctxRect = CGRect(origin: frameableContentRect.origin,
size: CGSize(width: ctxSize.width, height: ctxSize.height))
let widthDiff = CGFloat(ctxSize.width) - drawArea.width // difference between context width and real width
let heightDiff = CGFloat(ctxSize.height) - drawArea.height // difference between context height and real height

View File

@ -45,6 +45,6 @@ public struct CALayerDrawingOperation: DrawingOperation {
context.concatenate(offsetTransform)
layer.render(in: context)
offsetTransform.concatenating(offsetTransform.inverted())
context.concatenate(offsetTransform.inverted())
}
}

View File

@ -55,8 +55,10 @@ public struct SolidFillDrawingOperation: DrawingOperation {
switch shape {
case let .rect(rect):
return rect
case let .ellipse(rect):
return rect
case let .path(path):
return path.boundingBox
}
@ -68,8 +70,10 @@ public struct SolidFillDrawingOperation: DrawingOperation {
switch shape {
case let .rect(rect):
context.fill(rect)
case let .ellipse(rect):
context.fillEllipse(in: rect)
case let .path(path):
context.addPath(path)
context.fillPath()

View File

@ -0,0 +1,57 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public struct TemplateDrawingOperation: OrientationAwareDrawingOperation {
public var image: CGImage
public var imageSize: CGSize
public var color: CGColor
public var flipHorizontallyDuringDrawing: Bool
public init(image: CGImage,
imageSize: CGSize,
color: CGColor,
flipHorizontallyDuringDrawing: Bool = true) {
self.image = image
self.imageSize = imageSize
self.color = color
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
}
public func affectedArea(in context: CGContext?) -> CGRect {
CGRect(origin: .zero, size: imageSize)
}
public func apply(in context: CGContext) {
let imageRect = CGRect(origin: .zero, size: imageSize)
apply(in: context) {
$0.setFillColor(color)
$0.clip(to: imageRect, mask: image)
$0.fill(imageRect)
$0.setBlendMode(.multiply)
}
}
}

View File

@ -26,48 +26,45 @@ import CoreText
public struct TextDrawingOperation: OrientationAwareDrawingOperation {
public var text: String
public var font: CTFont
public var textColor: CGColor
public var textAttributes: [NSAttributedString.Key: Any]
public var flipHorizontallyDuringDrawing: Bool
public var desiredOffset: CGPoint
private var line: CTLine {
let textAttributes: [NSAttributedString.Key : Any] = [
.font: font,
.foregroundColor: textColor
]
public var attributedString: NSAttributedString {
NSAttributedString(string: text, attributes: textAttributes)
}
let attributedString = NSAttributedString(string: text, attributes: textAttributes)
public var line: CTLine {
CTLineCreateWithAttributedString(attributedString)
}
return CTLineCreateWithAttributedString(attributedString)
public var desiredContextSize: CGSize {
let imageBounds = CTLineGetImageBounds(line, nil)
return CGSize(width: max(imageBounds.width, imageBounds.maxX) + desiredOffset.x,
height: max(imageBounds.height, imageBounds.maxY) + desiredOffset.y)
}
public init(text: String,
font: CTFont,
textColor: CGColor,
textAttributes: [NSAttributedString.Key: Any],
flipHorizontallyDuringDrawing: Bool = true,
desiredOffset: CGPoint = .zero) {
self.text = text
self.font = font
self.textColor = textColor
self.textAttributes = textAttributes
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
self.desiredOffset = desiredOffset
}
// MARK: - DrawingOperation
public func affectedArea(in context: CGContext? = nil) -> CGRect {
CGRect(origin: desiredOffset, size: CTLineGetImageBounds(line, context).size)
CTLineGetImageBounds(line, context).offset(by: desiredOffset)
}
public func apply(in context: CGContext) {
apply(in: context) {
let originForDrawing = offsetForDrawing(CTLineGetImageBounds(line, context).origin)
let desiredOffsetForDrawing = offsetForDrawing(desiredOffset)
let textPosition = CGPoint(x: desiredOffsetForDrawing.x - originForDrawing.x,
y: desiredOffsetForDrawing.y - originForDrawing.y)
$0.textPosition = textPosition
$0.textPosition = offsetForDrawing(affectedArea(in: context).origin)
CTLineDraw(line, $0)
}
}

View File

@ -0,0 +1,5 @@
# gitignore nef files
**/build/
**/nef/
LICENSE
end

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>launcher</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.fortysevendeg.nef</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
## gitignore nef files
**/build/
**/nef/
LICENSE
## User data
**/xcuserdata/
podfile.lock
**.DS_Store
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
**Pods**
## Carthage
**Carthage**
## SPM
.build
.swiftpm
swiftpm

View File

@ -0,0 +1,8 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TICoreGraphicsUtils' do
platform :ios, 12.0
use_frameworks!
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
end

View File

@ -0,0 +1,128 @@
/*:
# `DrawingOperation` - протокол для инкапсулирования низкоуровневых вызовов отрисовки CoreGraphicss.
Позволяет:
- сгруппировать низкоуровневые операции CoreGraphics в более высокоуровневые
- использовать композицию из высокоуровневых операций
- получить размер изображения необходимый для создания контекста
## Базовые операции
"Из коробки" доступны самые часто испольуемые операции.
*/
/*:
### `SolidFillDrawingOperation`
Операция для заполнения области рисования выбранным цветом и выбранной формой.
Например, можно нарисовать прямоугольник или круг на существующей области рисования
или просто создать изображение с однотонный фоном.
*/
import TICoreGraphicsUtils
import UIKit
let solidFillSize = CGSize(width: 200, height: 200)
let renderer = UIGraphicsImageRenderer(size: solidFillSize)
let solidFillDrawingOperation = SolidFillDrawingOperation(color: UIColor.purple.cgColor,
rect: CGRect(origin: .zero,
size: solidFillSize))
let solidFillImage = renderer.image {
solidFillDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `BorderDrawingOperation`
Операция создаёт рамку определённой формы и размера.
*/
let borderDrawingOperation = BorderDrawingOperation(frameableContentRect: CGRect(origin: .zero,
size: solidFillSize),
border: 2,
color: UIColor.red.cgColor,
radius: 4,
exteriorBorder: false)
let borderImage = renderer.image {
borderDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `CALayerDrawingOperation`
Операция отрисовывает содержимое CALayer в изображение. Обычно используется для снапшота UIView.
*/
let button = UIButton(type: .custom)
button.setTitle("This is button", for: .normal)
button.setBackgroundImage(borderImage, for: .normal)
button.sizeToFit()
let layerDrawingOperation = CALayerDrawingOperation(layer: button.layer, offset: .zero)
let layerRenderer = UIGraphicsImageRenderer(size: button.bounds.size)
let buttonSnapshotImage = layerRenderer.image {
layerDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `TextDrawingOperation`
Операция отрисовывает текст с заданными атрибутами
*/
let textDrawingOperaton = TextDrawingOperation(text: "This is string",
textAttributes: [
.font: UIFont.boldSystemFont(ofSize: 15),
.foregroundColor: UIColor.white
])
let textRenderer = UIGraphicsImageRenderer(size: textDrawingOperaton.desiredContextSize)
let textImage = textRenderer.image {
textDrawingOperaton.apply(in: $0.cgContext)
}
/*:
### `TemplateDrawingOperation`
Операция заменяет все цвета кроме прозрачного на переданный цвет.
Используется для приданиям иконкам определённого цвета (аналог tintColor).
*/
if let cgImage = textImage.cgImage {
let templateDrawingOperation = TemplateDrawingOperation(image: cgImage,
imageSize: textImage.size,
color: UIColor.red.cgColor)
let tintedImage = textRenderer.image {
templateDrawingOperation.apply(in: $0.cgContext)
}
}
/*:
### `TransformDrawingOperation`
Операция производит изменение размера изображения учитывая пропорции
и другие настройки (по аналогии с contentMode у UIImage).
*/
if let cgImage = textImage.cgImage {
let newSize = CGSize(width: textImage.size.width / 1.5, height: textImage.size.height)
let resizeOperation = TransformDrawingOperation(image: cgImage,
imageSize: textImage.size,
maxNewSize: newSize,
resizeMode: .scaleAspectFill)
let resizeRenderer = UIGraphicsImageRenderer(size: newSize)
let resizedImage = resizeRenderer.image {
resizeOperation.apply(in: $0.cgContext)
}
}

View File

@ -0,0 +1,30 @@
import UIKit
public protocol NefPlaygroundLiveViewable {}
extension UIView: NefPlaygroundLiveViewable {}
extension UIViewController: NefPlaygroundLiveViewable {}
#if NOT_IN_PLAYGROUND
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
public static func needsIndefiniteExecution(_ state: Bool) {}
}
}
#else
import PlaygroundSupport
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
}
public static func needsIndefiniteExecution(_ state: Bool) {
PlaygroundPage.current.needsIndefiniteExecution = state
}
}
}
#endif

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>

View File

@ -0,0 +1,396 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
A319933CF180B210C469FFB1 /* Pods_TICoreGraphicsUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TICoreGraphicsUtils.debug.xcconfig"; path = "Target Support Files/Pods-TICoreGraphicsUtils/Pods-TICoreGraphicsUtils.debug.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TICoreGraphicsUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TICoreGraphicsUtils.release.xcconfig"; path = "Target Support Files/Pods-TICoreGraphicsUtils/Pods-TICoreGraphicsUtils.release.xcconfig"; sourceTree = "<group>"; };
AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TICoreGraphicsUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A319933CF180B210C469FFB1 /* Pods_TICoreGraphicsUtils.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2238D5BA030C14EBEF939AC4 /* Pods */ = {
isa = PBXGroup;
children = (
82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */,
9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TICoreGraphicsUtils */,
8B39A26C21D40F8700DE2643 /* Products */,
2238D5BA030C14EBEF939AC4 /* Pods */,
B51B4BA44249652EDC4FBF61 /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TICoreGraphicsUtils */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TICoreGraphicsUtils;
sourceTree = "<group>";
};
B51B4BA44249652EDC4FBF61 /* Frameworks */ = {
isa = PBXGroup;
children = (
AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TICoreGraphicsUtils */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TICoreGraphicsUtils" */;
buildPhases = (
3A6585E20D9DE6C1873231DC /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TICoreGraphicsUtils;
productName = TICoreGraphicsUtils2;
productReference = 8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8B39A26321D40F8700DE2643 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "47 Degrees";
TargetAttributes = {
8BACBE8222576CAD00266845 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TICoreGraphicsUtils" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TICoreGraphicsUtils */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3A6585E20D9DE6C1873231DC /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-TICoreGraphicsUtils-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8BACBE7F22576CAD00266845 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
8B39A27721D40F8800DE2643 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8B39A27821D40F8800DE2643 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8BACBE8822576CAD00266845 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TICoreGraphicsUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TICoreGraphicsUtils/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TICoreGraphicsUtils;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
8BACBE8922576CAD00266845 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TICoreGraphicsUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TICoreGraphicsUtils/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TICoreGraphicsUtils;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TICoreGraphicsUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TICoreGraphicsUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TICoreGraphicsUtils.xcodeproj">
</FileRef>
</Workspace>

Some files were not shown because too many files have changed in this diff Show More