├── .ruby-version ├── .swift-version ├── .xcode-version ├── Mintfile ├── docs ├── favicon.ico ├── index │ ├── data.mdb │ ├── navigator.index │ └── availability.index ├── metadata.json ├── img │ ├── deprecated-icon.015b4f17.svg │ ├── added-icon.d6f7e47d.svg │ └── modified-icon.f496e73d.svg ├── js │ ├── highlight-js-shell.dd7f411f.js │ ├── highlight-js-json.471128d2.js │ ├── highlight-js-diff.62d66733.js │ └── highlight-js-http.163e45b6.js └── theme-settings.json ├── Example ├── .swiftlint.yml ├── Tools │ ├── ChatDeeplink (roomID 1, chatID 1).url │ ├── ChatDeeplink (roomID 1, chatID 2).url │ ├── ChatDeeplink (roomID 2, chatID 2).url │ ├── ChatDeeplink (roomID 1, chatID 1).apns │ ├── ChatDeeplink (roomID 1, chatID 2).apns │ └── ChatDeeplink (roomID 2, chatID 2).apns ├── NivelirExample │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── Colors │ │ │ ├── Contents.json │ │ │ ├── Icon.colorset │ │ │ │ └── Contents.json │ │ │ ├── Title.colorset │ │ │ │ └── Contents.json │ │ │ ├── Unimportant.colorset │ │ │ │ └── Contents.json │ │ │ ├── Background.colorset │ │ │ │ └── Contents.json │ │ │ └── Important.colorset │ │ │ │ └── Contents.json │ │ │ ├── Images │ │ │ ├── Contents.json │ │ │ ├── User.imageset │ │ │ │ ├── User.png │ │ │ │ └── Contents.json │ │ │ ├── Browser.imageset │ │ │ │ ├── Browser.pdf │ │ │ │ └── Contents.json │ │ │ ├── Turtlerock.imageset │ │ │ │ ├── turtlerock@2x.jpg │ │ │ │ └── Contents.json │ │ │ ├── Share.imageset │ │ │ │ └── Contents.json │ │ │ ├── MoreTab.imageset │ │ │ │ └── Contents.json │ │ │ ├── Chat.imageset │ │ │ │ └── Contents.json │ │ │ ├── Room.imageset │ │ │ │ └── Contents.json │ │ │ ├── Close.imageset │ │ │ │ └── Contents.json │ │ │ ├── Chevron.imageset │ │ │ │ └── Contents.json │ │ │ ├── RoomsTab.imageset │ │ │ │ └── Contents.json │ │ │ ├── ProfileTab.imageset │ │ │ │ └── Contents.json │ │ │ └── Unauthorized.imageset │ │ │ │ └── Contents.json │ │ │ ├── Brand Assets.brandassets │ │ │ ├── App Icon.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Middle.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── App Icon - App Store.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Middle.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ │ ├── Top Shelf Image Wide.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Helpers │ │ ├── DI │ │ │ ├── Storage │ │ │ │ ├── ServiceStorage.swift │ │ │ │ └── ServiceSharedStorage.swift │ │ │ ├── Scope │ │ │ │ ├── ServiceScope.swift │ │ │ │ └── ServiceSharedScope.swift │ │ │ ├── ServiceKey.swift │ │ │ └── ServiceContainer.swift │ │ ├── Reusable │ │ │ ├── Reusable.swift │ │ │ └── UITableView+Reusable.swift │ │ ├── Colors.swift │ │ └── Images.swift │ ├── Services │ │ ├── Authorization │ │ │ ├── AuthorizationError.swift │ │ │ ├── AuthorizationService.swift │ │ │ └── DefaultAuthorizationService.swift │ │ └── Profile │ │ │ ├── ProfileError.swift │ │ │ └── ProfileService.swift │ ├── Screens │ │ ├── MoreExampleList │ │ │ ├── MoreExampleListModel.swift │ │ │ └── MoreExampleListScreen.swift │ │ ├── Authorization │ │ │ ├── AuthorizationObserver.swift │ │ │ └── AuthorizationScreen.swift │ │ ├── Landmark │ │ │ ├── LandmarkScreen.swift │ │ │ ├── LandmarkMapView.swift │ │ │ ├── LandmarkCircleImage.swift │ │ │ └── LandmarkView.swift │ │ ├── WhatsNewMore │ │ │ └── WhatsNewMoreScreen.swift │ │ ├── WhatsNew │ │ │ └── WhatsNewScreen.swift │ │ ├── RoomList │ │ │ └── RoomListScreen.swift │ │ ├── Chat │ │ │ ├── ChatScreen.swift │ │ │ └── ChatViewController.swift │ │ ├── ChatList │ │ │ └── ChatListScreen.swift │ │ └── Profile │ │ │ └── ProfileScreen.swift │ ├── Routing │ │ ├── Authorization │ │ │ ├── ScreenAuthorizeActionServices.swift │ │ │ ├── ScreenAuthorizeActionScreens.swift │ │ │ └── ScreenAuthorizeActionObserver.swift │ │ ├── Deeplinks │ │ │ └── ChatDeeplinkPayload.swift │ │ ├── Extensions │ │ │ └── HUD+Extensions.swift │ │ └── Sharing │ │ │ └── SharingNivelirLinkItem.swift │ ├── AppDelegate.swift │ └── Dependencies │ │ └── Services.swift ├── NivelirExample.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── NivelirExample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Podfile └── Podfile.lock ├── Sources ├── Tools │ ├── Nullable.swift │ ├── Extensions │ │ ├── UIKit │ │ │ ├── UIScreen+Extensions.swift │ │ │ ├── UIApplication+Extensions.swift │ │ │ ├── UIEdgeInsets+Extensions.swift │ │ │ ├── CGSize+Extensions.swift │ │ │ ├── UIView+Extensions.swift │ │ │ ├── UIWindow+Extensions.swift │ │ │ ├── CACornerMask+Extensions.swift │ │ │ ├── UINavigationController+Extensions.swift │ │ │ ├── UIScrollView+Extenions.swift │ │ │ └── UITabBarController+Extensions.swift │ │ ├── Character+Extensions.swift │ │ ├── Dictionary+Extensions.swift │ │ ├── Collection+Extensions.swift │ │ ├── Optional+Extensions.swift │ │ ├── Result+Extensions.swift │ │ └── String+Extensions.swift │ ├── URLQueryDecoder │ │ ├── Options │ │ │ ├── URLQueryDecodingOptions.swift │ │ │ ├── URLQueryKeyDecodingStrategy.swift │ │ │ ├── URLQueryDataDecodingStrategy.swift │ │ │ ├── URLQueryNonConformingFloatDecodingStrategy.swift │ │ │ └── URLQueryDateDecodingStrategy.swift │ │ └── URLQueryComponent.swift │ ├── DictionaryDecoder │ │ └── Options │ │ │ ├── DictionaryDecodingOptions.swift │ │ │ ├── DictionaryKeyDecodingStrategy.swift │ │ │ ├── DictionaryDataDecodingStrategy.swift │ │ │ ├── DictionaryNonConformingFloatDecodingStrategy.swift │ │ │ └── DictionaryDateDecodingStrategy.swift │ ├── AnyCodingKey.swift │ ├── ObjectAssociation.swift │ └── NotificationObserver.swift ├── Screen │ ├── Container │ │ ├── Storage │ │ │ ├── ScreenContainerStorage.swift │ │ │ ├── ScreenContainerSharedStorage.swift │ │ │ └── ScreenContainerWeakStorage.swift │ │ ├── Extensions │ │ │ ├── UIWindow+ScreenVisibleContainer.swift │ │ │ ├── UINavigationController+ScreenIterableContainer.swift │ │ │ ├── UIViewController+ScreenVisibleContainer.swift │ │ │ └── UITabBarController+ScreenIterableContainer.swift │ │ ├── ScreenIterableContainer.swift │ │ ├── ScreenVisibleContainer.swift │ │ └── ScreenContainer.swift │ ├── Payload │ │ ├── Extensions │ │ │ ├── UIViewController+ScreenPayloadContainer.swift │ │ │ └── ScreenPayloadedContainer+NSObject.swift │ │ ├── ScreenPayload.swift │ │ └── ScreenPayloadedContainer.swift │ ├── Errors │ │ ├── ScreenError.swift │ │ ├── ScreenCanceledError.swift │ │ ├── ScreenContainerNotFoundError.swift │ │ ├── ScreenContainerNotSupportedError.swift │ │ ├── ScreenContainerAlreadyPresentingError.swift │ │ └── ScreenContainerTypeMismatchError.swift │ ├── Actions │ │ ├── Generic │ │ │ ├── Refresh │ │ │ │ ├── ScreenRefreshableContainer.swift │ │ │ │ └── ScreenRefreshAction.swift │ │ │ ├── ScreenFailAction.swift │ │ │ └── ScreenGetAction.swift │ │ ├── Stack │ │ │ └── SetStack │ │ │ │ ├── Modifiers │ │ │ │ ├── ScreenStackModifier.swift │ │ │ │ └── ScreenStackClearModifier.swift │ │ │ │ └── Animations │ │ │ │ ├── ScreenStackCustomAnimation.swift │ │ │ │ └── ScreenStackTransitionAnimation.swift │ │ ├── ScreenActionStorage.swift │ │ ├── Window │ │ │ ├── SetRoot │ │ │ │ └── Animations │ │ │ │ │ ├── ScreenRootCustomAnimation.swift │ │ │ │ │ ├── ScreenRootAnimation.swift │ │ │ │ │ └── ScreenRootTransitionAnimation.swift │ │ │ ├── ScreenMakeKeyAction.swift │ │ │ └── ScreenMakeKeyAndVisibleAction.swift │ │ ├── Tabs │ │ │ └── SelectTab │ │ │ │ └── Animations │ │ │ │ ├── ScreenTabCustomAnimation.swift │ │ │ │ ├── ScreenTabAnimation.swift │ │ │ │ └── ScreenTabTransitionAnimation.swift │ │ └── Any │ │ │ ├── AnyScreenActionBaseBox.swift │ │ │ └── AnyScreenActionBox.swift │ ├── Observation │ │ ├── Storage │ │ │ ├── ScreenObserverStorage.swift │ │ │ ├── ScreenObserverSharedStorage.swift │ │ │ └── ScreenObserverWeakStorage.swift │ │ ├── ScreenObserver.swift │ │ └── ScreenObserverToken.swift │ ├── Route │ │ ├── Aliases │ │ │ ├── ScreenWindowRoute.swift │ │ │ ├── ScreenModalRoute.swift │ │ │ ├── ScreenTabsRoute.swift │ │ │ └── ScreenStackRoute.swift │ │ └── ScreenRouteConvertible.swift │ ├── Navigator │ │ ├── WindowProvider │ │ │ ├── ScreenWindowProvider.swift │ │ │ ├── ScreenKeyWindowProvider.swift │ │ │ └── ScreenCustomWindowProvider.swift │ │ ├── Logger │ │ │ ├── ScreenLogger.swift │ │ │ └── DefaultScreenLogger.swift │ │ └── Iterator │ │ │ ├── ScreenIterationResult.swift │ │ │ └── ScreenIterationPredicate.swift │ ├── Decorators │ │ └── Modal │ │ │ ├── ModalStyle │ │ │ └── ScreenModalStyle.swift │ │ │ └── ScreenTabBarItemDecorator.swift │ ├── Any │ │ ├── AnyScreenBaseBox.swift │ │ └── AnyScreenBox.swift │ └── ScreenKey.swift ├── Addons │ ├── Sharing │ │ ├── Activity │ │ │ ├── SharingActivityCategory.swift │ │ │ ├── SharingVisualActivity.swift │ │ │ ├── SharingSilentActivity.swift │ │ │ ├── SharingActivityType.swift │ │ │ ├── SharingCustomActivity.swift │ │ │ └── SharingActivity.swift │ │ └── Item │ │ │ └── SharingItem.swift │ ├── DocumentPreview │ │ └── Extensions │ │ │ └── UIDocumentInteractionController+ScreenPayloadContainer.swift │ ├── HUD │ │ ├── Progress │ │ │ ├── Footer │ │ │ │ ├── ProgressFooter.swift │ │ │ │ └── Empty │ │ │ │ │ ├── ProgressEmptyFooter.swift │ │ │ │ │ └── ProgressEmptyFooterView.swift │ │ │ ├── Header │ │ │ │ ├── ProgressHeader.swift │ │ │ │ └── Empty │ │ │ │ │ ├── ProgressEmptyHeader.swift │ │ │ │ │ └── ProgressEmptyHeaderView.swift │ │ │ ├── Indicator │ │ │ │ ├── ProgressIndicator.swift │ │ │ │ ├── Failure │ │ │ │ │ └── ProgressFailureIndicator.swift │ │ │ │ ├── Spinner │ │ │ │ │ └── ProgressSpinnerIndicator.swift │ │ │ │ ├── Success │ │ │ │ │ └── ProgressSuccessIndicator.swift │ │ │ │ ├── Activity │ │ │ │ │ └── ProgressActivityIndicator.swift │ │ │ │ └── Image │ │ │ │ │ └── ProgressImageIndicator.swift │ │ │ ├── Animation │ │ │ │ ├── ProgressCustomAnimation.swift │ │ │ │ └── ProgressAnimation.swift │ │ │ └── Content │ │ │ │ ├── AnyProgressContent.swift │ │ │ │ └── ProgressContentView.swift │ │ └── ScreenHideHUDAction.swift │ ├── BottomSheet │ │ ├── Detention │ │ │ ├── BottomSheetDetentionDelegate.swift │ │ │ └── Detent │ │ │ │ ├── BottomSheetDetentContext.swift │ │ │ │ └── BottomSheetDetentKey.swift │ │ ├── BottomSheetBorder.swift │ │ ├── Interaction │ │ │ ├── BottomSheetInteractionState.swift │ │ │ └── Interactions │ │ │ │ ├── BottomSheetInteraction.swift │ │ │ │ └── BottomSheetDismissedInteraction.swift │ │ ├── Presentation │ │ │ └── BottomSheetPresentationState.swift │ │ ├── Animations │ │ │ └── BottomSheetAnimationOptions.swift │ │ ├── BottomSheetGrabber.swift │ │ ├── Extensions │ │ │ └── UIViewController+BottomSheet.swift │ │ ├── BottomSheetRubberBandEffect.swift │ │ ├── Dimming │ │ │ └── BottomSheetDimming.swift │ │ ├── Action │ │ │ └── InvalidBottomSheetContainerError.swift │ │ ├── BottomSheetShadow.swift │ │ ├── BottomSheetCard.swift │ │ └── Decorators │ │ │ └── ScreenBottomSheetDecorator.swift │ ├── MediaPicker │ │ ├── MediaPickerImageExportPreset.swift │ │ ├── Errors │ │ │ ├── UnavailableMediaPickerSourceError.swift │ │ │ ├── UnavailableMediaPickerTypesError.swift │ │ │ └── MediaPickerSourceAccessDeniedError.swift │ │ ├── MediaPickerManager.swift │ │ ├── MediaPickerProxy.swift │ │ └── MediaPickerCameraSettings.swift │ ├── Safari │ │ └── InvalidSafariURLError.swift │ ├── URL │ │ ├── StoreApp │ │ │ └── InvalidStoreAppIDError.swift │ │ ├── Call │ │ │ └── InvalidCallParametersError.swift │ │ ├── Mail │ │ │ └── InvalidMailParametersError.swift │ │ ├── Settings │ │ │ ├── InvalidOpenSettingsURLError.swift │ │ │ └── ScreenOpenSettingsAction.swift │ │ └── FailedToOpenURLError.swift │ ├── StoreProduct │ │ ├── InvalidStoreProductIDError.swift │ │ └── StoreProductManager.swift │ └── Alert │ │ ├── AlertTextField.swift │ │ └── AlertTextFieldsManager.swift ├── Deeplink │ ├── DeeplinkResult.swift │ ├── Errors │ │ ├── DeeplinkError.swift │ │ ├── DeeplinkDecodingError.swift │ │ ├── DeeplinkInvalidContextError.swift │ │ ├── DeeplinkInvalidScreensError.swift │ │ └── DeeplinkWarningsError.swift │ ├── DeeplinkType.swift │ ├── DeeplinkStorage.swift │ ├── Extensions │ │ ├── UNNotificationResponse+Extensions.swift │ │ └── UIApplicationShortcutItem+Extensions.swift │ ├── URL │ │ └── Errors │ │ │ └── URLDeeplinkInvalidComponentsError.swift │ ├── AnyDeeplink.swift │ ├── Notification │ │ └── Errors │ │ │ └── NotificationDeeplinkInvalidUserInfoError.swift │ └── DeeplinkInterceptor.swift └── Info.plist ├── Tests ├── .swiftlint.yml └── Info.plist ├── Gemfile ├── Nivelir.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Scripts ├── swiftlint.sh ├── Helpers │ ├── script-paths.sh │ └── script-run.sh ├── Bootstrap │ ├── congratulations.sh │ ├── welcome.sh │ ├── mintfile.sh │ ├── gemfile.sh │ ├── spm.sh │ ├── bundler.sh │ ├── macos.sh │ ├── swift.sh │ ├── swiftenv.sh │ ├── ruby.sh │ ├── mint.sh │ └── xcode.sh ├── bootstrap.sh └── generate-docs.sh ├── Dangerfile ├── Package.swift ├── Nivelir.podspec ├── .gitignore └── LICENSE /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.0 2 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 6.0 2 | -------------------------------------------------------------------------------- /.xcode-version: -------------------------------------------------------------------------------- 1 | 16.0 2 | -------------------------------------------------------------------------------- /Mintfile: -------------------------------------------------------------------------------- 1 | realm/SwiftLint@0.52.2 2 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/Nivelir/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index/data.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/Nivelir/HEAD/docs/index/data.mdb -------------------------------------------------------------------------------- /Example/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - explicit_acl 3 | - explicit_top_level_acl -------------------------------------------------------------------------------- /Example/Tools/ChatDeeplink (roomID 1, chatID 1).url: -------------------------------------------------------------------------------- 1 | nivelir://chat?room_id=1&chat_id=1 2 | -------------------------------------------------------------------------------- /Example/Tools/ChatDeeplink (roomID 1, chatID 2).url: -------------------------------------------------------------------------------- 1 | nivelir://chat?room_id=1&chat_id=2 2 | -------------------------------------------------------------------------------- /Example/Tools/ChatDeeplink (roomID 2, chatID 2).url: -------------------------------------------------------------------------------- 1 | nivelir://chat?room_id=2&chat_id=2 2 | -------------------------------------------------------------------------------- /docs/index/navigator.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/Nivelir/HEAD/docs/index/navigator.index -------------------------------------------------------------------------------- /docs/index/availability.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/Nivelir/HEAD/docs/index/availability.index -------------------------------------------------------------------------------- /docs/metadata.json: -------------------------------------------------------------------------------- 1 | {"bundleDisplayName":"Nivelir","bundleIdentifier":"ru.hh.Nivelir","schemaVersion":{"major":0,"minor":1,"patch":0}} -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/DI/Storage/ServiceStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol ServiceStorage { 4 | 5 | var service: Any? { get } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Services/Authorization/AuthorizationError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum AuthorizationError: Error { 4 | 5 | case unavailable 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Services/Profile/ProfileError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum ProfileError: Error { 4 | 5 | case unauthorized 6 | case unavailable 7 | } 8 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/DI/Scope/ServiceScope.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol ServiceScope { 4 | 5 | func storage(for service: Any) -> ServiceStorage 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/MoreExampleList/MoreExampleListModel.swift: -------------------------------------------------------------------------------- 1 | struct MoreExampleListModel { 2 | let title: String 3 | let didSelectHandler: () -> Void 4 | } 5 | -------------------------------------------------------------------------------- /Sources/Tools/Nullable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal protocol Nullable { 4 | 5 | static var none: Self { get } 6 | } 7 | 8 | extension Optional: Nullable { } 9 | -------------------------------------------------------------------------------- /Sources/Screen/Container/Storage/ScreenContainerStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal protocol ScreenContainerStorage { 4 | 5 | var value: ScreenContainer? { get } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Addons/Sharing/Activity/SharingActivityCategory.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | public typealias SharingActivityCategory = UIActivity.Category 5 | #endif 6 | -------------------------------------------------------------------------------- /Sources/Screen/Payload/Extensions/UIViewController+ScreenPayloadContainer.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIViewController: ScreenPayloadedContainer { } 5 | #endif 6 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/User.imageset/User.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/Nivelir/HEAD/Example/NivelirExample/Resources/Assets.xcassets/Images/User.imageset/User.png -------------------------------------------------------------------------------- /Sources/Deeplink/DeeplinkResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal enum DeeplinkResult { 4 | 5 | case success(DeeplinkStorage) 6 | case failure(Error) 7 | case warning(Error) 8 | } 9 | -------------------------------------------------------------------------------- /Sources/Screen/Errors/ScreenError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A protocol representing an error that occurs during navigation. 4 | public protocol ScreenError: Error, CustomStringConvertible { } 5 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Browser.imageset/Browser.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/Nivelir/HEAD/Example/NivelirExample/Resources/Assets.xcassets/Images/Browser.imageset/Browser.pdf -------------------------------------------------------------------------------- /Example/NivelirExample/Routing/Authorization/ScreenAuthorizeActionServices.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol ScreenAuthorizeActionServices { 4 | 5 | func authorizationService() -> AuthorizationService 6 | } 7 | -------------------------------------------------------------------------------- /Tests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - explicit_acl 3 | - explicit_top_level_acl 4 | - file_length 5 | - function_body_length 6 | - implicitly_unwrapped_optional 7 | - nesting 8 | - type_body_length 9 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Turtlerock.imageset/turtlerock@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/Nivelir/HEAD/Example/NivelirExample/Resources/Assets.xcassets/Images/Turtlerock.imageset/turtlerock@2x.jpg -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UIScreen+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIScreen { 5 | 6 | internal var pixelSize: CGFloat { 7 | 1.0 / scale 8 | } 9 | } 10 | #endif 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'xcode-install' 4 | 5 | gem 'cocoapods' 6 | gem 'xcpretty' 7 | gem 'xcpretty-json-formatter' 8 | 9 | gem "danger" 10 | gem 'danger-xcode_summary' 11 | gem 'danger-swiftlint' 12 | -------------------------------------------------------------------------------- /Nivelir.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/Addons/DocumentPreview/Extensions/UIDocumentInteractionController+ScreenPayloadContainer.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | extension UIDocumentInteractionController: ScreenPayloadedContainer { } 5 | #endif 6 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Generic/Refresh/ScreenRefreshableContainer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @MainActor 4 | public protocol ScreenRefreshableContainer: ScreenContainer { 5 | 6 | func refresh(completion: @escaping () -> Void) 7 | } 8 | -------------------------------------------------------------------------------- /Example/NivelirExample/Routing/Authorization/ScreenAuthorizeActionScreens.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Nivelir 3 | 4 | @MainActor 5 | protocol ScreenAuthorizeActionScreens { 6 | 7 | func showAuthorizationRoute() -> ScreenWindowRoute 8 | } 9 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Authorization/AuthorizationObserver.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Nivelir 3 | 4 | @MainActor 5 | public protocol AuthorizationObserver: ScreenObserver { 6 | 7 | func authorizationFinished(isAuthorized: Bool) 8 | } 9 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/DI/Storage/ServiceSharedStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ServiceSharedStorage: ServiceStorage { 4 | 5 | let service: Any? 6 | 7 | init(service: Any) { 8 | self.service = service 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Screen/Observation/Storage/ScreenObserverStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @MainActor 4 | internal protocol ScreenObserverStorage: AnyObject { 5 | 6 | var predicate: ScreenObserverPredicate { get } 7 | var value: ScreenObserver? { get } 8 | } 9 | -------------------------------------------------------------------------------- /Scripts/swiftlint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [[ "${SKIP_SWIFTLINT}" == "YES" ]]; then 4 | exit 0 5 | fi 6 | 7 | readonly helpers_path="$( cd "$( dirname "$0" )" && pwd )/Helpers" 8 | 9 | source "${helpers_path}/script-run.sh" 10 | run swiftlint --quiet || true 11 | -------------------------------------------------------------------------------- /Scripts/Helpers/script-paths.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "${helpers_path}" ]; then 4 | readonly helpers_path="$( cd "$( dirname "$0" )" && pwd )" 5 | fi 6 | 7 | readonly tools_path="$( cd "${helpers_path}/../" && pwd )" 8 | readonly root_path="$( cd "${tools_path}/../" && pwd )" 9 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/Reusable/Reusable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol Reusable { 4 | static var reuseIdentifier: String { get } 5 | } 6 | 7 | extension Reusable { 8 | static var reuseIdentifier: String { 9 | "\(Self.self)" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Landmark/LandmarkScreen.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Nivelir 3 | 4 | struct LandmarkScreen: Screen { 5 | 6 | func build(navigator: ScreenNavigator) -> UIViewController { 7 | UIHostingController(rootView: LandmarkView()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Share.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Share.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Footer/ProgressFooter.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | /// A type that contains the data to be displayed on the footer of progress. 5 | /// 6 | /// - SeeAlso: ``ProgressContent`` 7 | public protocol ProgressFooter: ProgressContent { } 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Header/ProgressHeader.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | /// A type that contains the data to be displayed on the header of progress. 5 | /// 6 | /// - SeeAlso: ``ProgressContent`` 7 | public protocol ProgressHeader: ProgressContent { } 8 | #endif 9 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Browser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Browser.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/NivelirExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Indicator/ProgressIndicator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | /// A type that contains the data to be displayed on the indicator of progress. 5 | /// 6 | /// - SeeAlso: ``ProgressContent`` 7 | public protocol ProgressIndicator: ProgressContent { } 8 | #endif 9 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/congratulations.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 4 | 5 | source "${script_path}/common.sh" 6 | 7 | echo "" 8 | echo "${congratulations_style}Congratulations!${default_style} Setting up the development environment successfully completed 🥳" 9 | echo "" 10 | -------------------------------------------------------------------------------- /Sources/Screen/Route/Aliases/ScreenWindowRoute.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Alias for the root route whose container type is `UIWindow`. 5 | /// 6 | /// - SeeAlso: `ScreenRoute` 7 | /// - SeeAlso: `ScreenRootRoute` 8 | public typealias ScreenWindowRoute = ScreenRootRoute 9 | #endif 10 | -------------------------------------------------------------------------------- /Example/NivelirExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/NivelirExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/NivelirExample/Routing/Deeplinks/ChatDeeplinkPayload.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ChatDeeplinkPayload: Decodable { 4 | 5 | enum CodingKeys: String, CodingKey { 6 | case roomID = "room_id" 7 | case chatID = "chat_id" 8 | } 9 | 10 | let roomID: Int 11 | let chatID: Int 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/Character+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Character { 4 | 5 | internal static let leftSquareBracket = Self("[") 6 | internal static let rightSquareBracket = Self("]") 7 | internal static let ampersand = Self("&") 8 | internal static let equals = Self("=") 9 | } 10 | -------------------------------------------------------------------------------- /Nivelir.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/welcome.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 4 | 5 | source "${script_path}/common.sh" 6 | 7 | echo "${default_style}" 8 | echo "This script will set up your development environment." 9 | echo "This might take a few minutes. Please don't interrupt the script." 10 | echo "" -------------------------------------------------------------------------------- /Sources/Screen/Route/Aliases/ScreenModalRoute.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Alias for the root route whose container type is `UIViewController`. 5 | /// 6 | /// - SeeAlso: `ScreenRoute` 7 | /// - SeeAlso: `ScreenRootRoute` 8 | public typealias ScreenModalRoute = ScreenRootRoute 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Dictionary { 4 | 5 | internal func updatingValue(_ value: Value, forKey key: Key) -> Self { 6 | var dictionary = self 7 | 8 | dictionary.updateValue(value, forKey: key) 9 | 10 | return dictionary 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Screen/Route/Aliases/ScreenTabsRoute.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Alias for the root route whose container type is `UITabBarController`. 5 | /// 6 | /// - SeeAlso: `ScreenRoute` 7 | /// - SeeAlso: `ScreenRootRoute` 8 | public typealias ScreenTabsRoute = ScreenRootRoute 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/Collection+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Collection { 4 | 5 | internal var nonEmpty: Self? { 6 | isEmpty ? nil : self 7 | } 8 | 9 | internal subscript(safe index: Index) -> Iterator.Element? { 10 | indices.contains(index) ? self[index] : nil 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/Optional+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Optional { 4 | 5 | internal var isNil: Bool { 6 | self == nil 7 | } 8 | } 9 | 10 | extension Optional where Wrapped: Collection { 11 | 12 | internal var isEmptyOrNil: Bool { 13 | self?.isEmpty ?? true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/DI/Scope/ServiceSharedScope.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ServiceSharedScope: ServiceScope { 4 | 5 | static let `default` = Self() 6 | 7 | private init() { } 8 | 9 | func storage(for service: Any) -> ServiceStorage { 10 | ServiceSharedStorage(service: service) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/Tools/ChatDeeplink (roomID 1, chatID 1).apns: -------------------------------------------------------------------------------- 1 | { 2 | "Simulator Target Bundle": "ru.hh.Nivelir-Example", 3 | "aps": { 4 | "alert": { 5 | "body": "User sent a new message in Room #1 – Chat 1", 6 | "title": "New message" 7 | } 8 | }, 9 | "room_id": 1, 10 | "chat_id": 1 11 | } 12 | -------------------------------------------------------------------------------- /Example/Tools/ChatDeeplink (roomID 1, chatID 2).apns: -------------------------------------------------------------------------------- 1 | { 2 | "Simulator Target Bundle": "ru.hh.Nivelir-Example", 3 | "aps": { 4 | "alert": { 5 | "body": "User sent a new message in Room #1 – Chat 2", 6 | "title": "New message" 7 | } 8 | }, 9 | "room_id": 1, 10 | "chat_id": 2 11 | } 12 | -------------------------------------------------------------------------------- /Example/Tools/ChatDeeplink (roomID 2, chatID 2).apns: -------------------------------------------------------------------------------- 1 | { 2 | "Simulator Target Bundle": "ru.hh.Nivelir-Example", 3 | "aps": { 4 | "alert": { 5 | "body": "User sent a new message in Room #2 – Chat 2", 6 | "title": "New message" 7 | } 8 | }, 9 | "room_id": 2, 10 | "chat_id": 2 11 | } 12 | -------------------------------------------------------------------------------- /Example/NivelirExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Screen/Route/Aliases/ScreenStackRoute.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Alias for the root route whose container type is `UINavigationController`. 5 | /// 6 | /// - SeeAlso: `ScreenRoute` 7 | /// - SeeAlso: `ScreenRootRoute` 8 | public typealias ScreenStackRoute = ScreenRootRoute 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/Addons/Sharing/Activity/SharingVisualActivity.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | public protocol SharingVisualActivity: SharingCustomActivity { 5 | 6 | func prepare( 7 | for items: [SharingItem], 8 | completion: @escaping (_ completed: Bool) -> Void 9 | ) -> AnyModalScreen 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | inhibit_all_warnings! 2 | 3 | target 'NivelirExample' do 4 | platform :ios, '15.0' 5 | use_frameworks! 6 | 7 | pod 'Nivelir', :path => ".." 8 | pod 'SnapKit' 9 | end 10 | 11 | target 'NivelirExample-tvOS' do 12 | platform :tvos, '15.0' 13 | use_frameworks! 14 | 15 | pod 'Nivelir', :path => ".." 16 | pod 'SnapKit' 17 | end 18 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Detention/BottomSheetDetentionDelegate.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | @MainActor 5 | internal protocol BottomSheetDetentionDelegate: AnyObject { 6 | 7 | func bottomSheetCanEndEditing() -> Bool 8 | func bottomSheetDidChangeSelectedDetentKey(to detentKey: BottomSheetDetentKey?) 9 | } 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Stack/SetStack/Modifiers/ScreenStackModifier.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | public protocol ScreenStackModifier: CustomStringConvertible { 6 | 7 | func perform( 8 | stack: [UIViewController], 9 | navigator: ScreenNavigator 10 | ) throws -> [UIViewController] 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/Result+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Result { 4 | 5 | internal func ignoringValue() -> Result { 6 | map { _ in Void() } 7 | } 8 | } 9 | 10 | extension Result where Success == Void { 11 | 12 | internal static var success: Self { 13 | .success(Void()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/User.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "User.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/WhatsNewMore/WhatsNewMoreScreen.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct WhatsNewMoreScreen: Screen { 5 | 6 | func build(navigator: ScreenNavigator) -> UIViewController { 7 | WhatsNewMoreViewController( 8 | screenKey: key, 9 | screenNavigator: navigator 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/MoreTab.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MoreTab.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Detention/Detent/BottomSheetDetentContext.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | public protocol BottomSheetDetentContext { 6 | 7 | var presentedViewController: UIViewController { get } 8 | var containerTraitCollection: UITraitCollection { get } 9 | var maximumDetentValue: CGFloat { get } 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Sources/Deeplink/Errors/DeeplinkError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A protocol representing an error that occurs when processing deeplinks. 4 | public protocol DeeplinkError: Error, CustomStringConvertible { 5 | 6 | var isWarning: Bool { get } 7 | } 8 | 9 | extension DeeplinkError { 10 | 11 | public var isWarning: Bool { 12 | false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/Colors.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | enum Colors { 4 | 5 | static let background = UIColor(named: "Background")! 6 | static let unimportant = UIColor(named: "Unimportant")! 7 | static let important = UIColor(named: "Important")! 8 | static let title = UIColor(named: "Title")! 9 | static let icon = UIColor(named: "Title")! 10 | } 11 | -------------------------------------------------------------------------------- /Example/NivelirExample/Services/Authorization/AuthorizationService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol AuthorizationService: Sendable { 4 | 5 | var isAuthorized: Bool { get } 6 | 7 | @MainActor 8 | func login( 9 | phoneNumber: String, 10 | completion: @escaping (_ result: Result) -> Void 11 | ) 12 | 13 | func logout() 14 | } 15 | -------------------------------------------------------------------------------- /Example/NivelirExample/Services/Profile/ProfileService.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol ProfileService { 4 | 5 | @MainActor 6 | func uploadPhoto( 7 | image: UIImage, 8 | progress: @MainActor @Sendable @escaping (_ ratio: CGFloat) -> Void, 9 | completion: @MainActor @Sendable @escaping (_ result: Result) -> Void 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Deeplink/DeeplinkType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Different types of deep links. 4 | @frozen 5 | public enum DeeplinkType { 6 | 7 | /// A ``Deeplink`` handled from a URL. 8 | case url 9 | 10 | /// A ``Deeplink`` handled from a Notification. 11 | case notification 12 | 13 | /// A ``Deeplink`` handled from a Shortcut. 14 | case shortcut 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Footer/Empty/ProgressEmptyFooter.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | /// Empty footer of progress. 5 | public struct ProgressEmptyFooter: ProgressFooter { 6 | 7 | public typealias View = ProgressEmptyFooterView 8 | 9 | /// Default instance 10 | public static let `default` = Self() 11 | 12 | private init() { } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Header/Empty/ProgressEmptyHeader.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | /// Empty header of progress. 5 | public struct ProgressEmptyHeader: ProgressHeader { 6 | 7 | public typealias View = ProgressEmptyHeaderView 8 | 9 | /// Default instance 10 | public static let `default` = Self() 11 | 12 | private init() { } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Sources/Screen/Navigator/WindowProvider/ScreenWindowProvider.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// A type that provides an instance of `UIWindow` for navigating and searching containers. 5 | @MainActor 6 | public protocol ScreenWindowProvider { 7 | 8 | /// The `UIWindow` for navigating and searching for containers. 9 | var window: UIWindow? { get } 10 | } 11 | #endif 12 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/ScreenActionStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class ScreenActionStorage { 4 | 5 | public private(set) var state: [State] = [] 6 | 7 | internal init() { } 8 | 9 | public func storeState(_ state: State) { 10 | self.state.append(state) 11 | } 12 | 13 | public func clear() { 14 | state.removeAll() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Screen/Container/Extensions/UIWindow+ScreenVisibleContainer.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIWindow: ScreenVisibleContainer { 5 | 6 | /// A Boolean value indicating whether the container is visible. 7 | /// 8 | /// Returns `true` if the window is not hidden. 9 | public var isVisible: Bool { 10 | !isHidden 11 | } 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Addons/Sharing/Activity/SharingSilentActivity.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | public protocol SharingSilentActivity: SharingCustomActivity, Sendable { 5 | 6 | @MainActor 7 | func perform( 8 | for items: [SharingItem], 9 | navigator: ScreenNavigator, 10 | completion: @escaping (_ completed: Bool) -> Void 11 | ) 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Window/SetRoot/Animations/ScreenRootCustomAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | public protocol ScreenRootCustomAnimation { 6 | 7 | func animate( 8 | container: UIWindow, 9 | from root: UIViewController?, 10 | to newRoot: UIViewController, 11 | completion: @escaping () -> Void 12 | ) 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UIApplication+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIApplication { 5 | 6 | internal var firstKeyWindow: UIWindow? { 7 | connectedScenes 8 | .lazy 9 | .compactMap { $0 as? UIWindowScene } 10 | .flatMap { $0.windows } 11 | .first { $0.isKeyWindow } 12 | } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Chat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Chat.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Room.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Room.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Close.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/WhatsNew/WhatsNewScreen.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct WhatsNewScreen: Screen { 5 | 6 | let screens: Screens 7 | 8 | func build(navigator: ScreenNavigator) -> UIViewController { 9 | WhatsNewViewController( 10 | screens: screens, 11 | screenKey: key, 12 | screenNavigator: navigator 13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Screen/Container/Storage/ScreenContainerSharedStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct ScreenContainerSharedStorage: ScreenContainerStorage { 4 | 5 | private let container: ScreenContainer 6 | 7 | internal var value: ScreenContainer? { 8 | container 9 | } 10 | 11 | internal init(_ container: ScreenContainer) { 12 | self.container = container 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Screen/Observation/ScreenObserver.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Protocol-Mark for Observers. 4 | /// 5 | /// The protocol is inherited by other protocols that the observers will implement. 6 | /// 7 | /// ```swift 8 | /// protocol EmployerReviewObserver: ScreenObserver { 9 | /// func employerFeedbackRead(employerReviewID: Int) 10 | /// } 11 | /// ``` 12 | public protocol ScreenObserver: AnyObject { } 13 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Chevron.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Chevron.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/RoomsTab.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "RoomsTab.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Tabs/SelectTab/Animations/ScreenTabCustomAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | public protocol ScreenTabCustomAnimation { 6 | 7 | func animate( 8 | container: UITabBarController, 9 | from selectedTab: UIViewController, 10 | to newSelectedTab: UIViewController, 11 | completion: @escaping () -> Void 12 | ) 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/ProfileTab.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ProfileTab.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/MoreExampleList/MoreExampleListScreen.swift: -------------------------------------------------------------------------------- 1 | import Nivelir 2 | 3 | struct MoreExampleListScreen: Screen { 4 | 5 | let screens: Screens 6 | 7 | func build(navigator: ScreenNavigator) -> UIViewController { 8 | MoreExampleListViewController( 9 | screens: screens, 10 | screenKey: key, 11 | screenNavigator: navigator 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Screen/Container/Extensions/UINavigationController+ScreenIterableContainer.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UINavigationController: ScreenIterableContainer { 5 | 6 | /// Returns nested containers from the navigation stack. 7 | /// 8 | /// - SeeAlso: `viewControllers` 9 | public var nestedContainers: [ScreenContainer] { 10 | viewControllers 11 | } 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Unauthorized.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Unauthorized.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Screen/Navigator/Logger/ScreenLogger.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Screen navigation logger. 4 | public protocol ScreenLogger { 5 | 6 | /// Logs a message. 7 | /// 8 | /// - Parameter info: Info message to be logged. 9 | func info(_ info: @autoclosure () -> String) 10 | 11 | /// Logs an error. 12 | /// 13 | /// - Parameter error: Error to be logged. 14 | func error(_ error: @autoclosure () -> Error) 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UIEdgeInsets+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIEdgeInsets { 5 | 6 | internal var horizontal: CGFloat { 7 | left + right 8 | } 9 | 10 | internal var vertical: CGFloat { 11 | top + bottom 12 | } 13 | 14 | internal init(equilateral side: CGFloat) { 15 | self.init(top: side, left: side, bottom: side, right: side) 16 | } 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Sources/Tools/URLQueryDecoder/Options/URLQueryDecodingOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct URLQueryDecodingOptions { 4 | 5 | internal let dateDecodingStrategy: URLQueryDateDecodingStrategy 6 | internal let dataDecodingStrategy: URLQueryDataDecodingStrategy 7 | internal let nonConformingFloatDecodingStrategy: URLQueryNonConformingFloatDecodingStrategy 8 | internal let keyDecodingStrategy: URLQueryKeyDecodingStrategy 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/CGSize+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension CGSize { 5 | 6 | internal init(equilateral side: CGFloat) { 7 | self.init(width: side, height: side) 8 | } 9 | 10 | internal func outset(by insets: UIEdgeInsets) -> Self { 11 | Self( 12 | width: width + insets.horizontal, 13 | height: height + insets.vertical 14 | ) 15 | } 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/mintfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 4 | 5 | source "${script_path}/common.sh" 6 | source "${helpers_path}/script-run.sh" 7 | 8 | echo "Installing ${swift_style}Swift tools${default_style} specified in Mintfile..." 9 | 10 | if [[ "$(uname -m)" == "arm64" ]]; then 11 | eval "$(/opt/homebrew/bin/brew shellenv)" 12 | fi 13 | 14 | assert_failure '(cd "${root_path}" && mint bootstrap)' 15 | 16 | echo "" 17 | -------------------------------------------------------------------------------- /Sources/Tools/DictionaryDecoder/Options/DictionaryDecodingOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct DictionaryDecodingOptions { 4 | 5 | internal let dateDecodingStrategy: DictionaryDateDecodingStrategy 6 | internal let dataDecodingStrategy: DictionaryDataDecodingStrategy 7 | internal let nonConformingFloatDecodingStrategy: DictionaryNonConformingFloatDecodingStrategy 8 | internal let keyDecodingStrategy: DictionaryKeyDecodingStrategy 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Deeplink/DeeplinkStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal final class DeeplinkStorage { 4 | 5 | internal let value: AnyDeeplink 6 | internal let type: DeeplinkType 7 | internal let scope: DeeplinkScope 8 | 9 | internal init( 10 | value: AnyDeeplink, 11 | type: DeeplinkType, 12 | scope: DeeplinkScope 13 | ) { 14 | self.value = value 15 | self.type = type 16 | self.scope = scope 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Screen/Container/Storage/ScreenContainerWeakStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct ScreenContainerWeakStorage: ScreenContainerStorage { 4 | 5 | internal typealias Container = AnyObject & ScreenContainer 6 | 7 | private weak var container: Container? 8 | 9 | internal var value: ScreenContainer? { 10 | container 11 | } 12 | 13 | internal init(_ container: Container) { 14 | self.container = container 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/BottomSheetBorder.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct BottomSheetBorder: Equatable, Sendable { 5 | 6 | public static let `default` = Self() 7 | 8 | public let width: CGFloat 9 | public let color: UIColor? 10 | 11 | public init( 12 | width: CGFloat = .zero, 13 | color: UIColor = .black 14 | ) { 15 | self.width = width 16 | self.color = color 17 | } 18 | } 19 | #endif 20 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Images/Turtlerock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "turtlerock@2x.jpg", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Screen/Navigator/WindowProvider/ScreenKeyWindowProvider.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// An implementation of `ScreenWindowProvider` providing the first found key `UIWindow`. 5 | public struct ScreenKeyWindowProvider: ScreenWindowProvider { 6 | 7 | /// The `UIWindow` for navigating and searching for containers. 8 | public var window: UIWindow? { 9 | UIApplication.shared.firstKeyWindow 10 | } 11 | 12 | public init() { } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UIView+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIView { 5 | 6 | internal var firstResponder: UIView? { 7 | if isFirstResponder { 8 | return self 9 | } 10 | 11 | for subview in subviews { 12 | if let firstResponder = subview.firstResponder { 13 | return firstResponder 14 | } 15 | } 16 | 17 | return nil 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nivelir (1.9.10) 3 | - SnapKit (5.6.0) 4 | 5 | DEPENDENCIES: 6 | - Nivelir (from `..`) 7 | - SnapKit 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - SnapKit 12 | 13 | EXTERNAL SOURCES: 14 | Nivelir: 15 | :path: ".." 16 | 17 | SPEC CHECKSUMS: 18 | Nivelir: 1f8365f236cdfc17b8300b841792f1fd11112ee1 19 | SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 20 | 21 | PODFILE CHECKSUM: da8b281ef18accce1d0505caaeb1d708354daf4e 22 | 23 | COCOAPODS: 1.15.2 24 | -------------------------------------------------------------------------------- /Sources/Addons/MediaPicker/MediaPickerImageExportPreset.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | /// A type that specifies how to export images to the client application. 5 | @frozen 6 | public enum MediaPickerImageExportPreset: Sendable { 7 | 8 | @available(iOS 11, *) 9 | /// A preset for passing image data as-is to the client. 10 | case current 11 | 12 | /// A preset for converting HEIF formatted images to JPEG. 13 | case compatible 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /Sources/Tools/URLQueryDecoder/Options/URLQueryKeyDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The values that determine how to decode a type’s coding keys from URL query keys. 4 | @frozen 5 | public enum URLQueryKeyDecodingStrategy { 6 | 7 | /// A key decoding strategy that doesn’t change key names during decoding. 8 | case useDefaultKeys 9 | 10 | /// A key decoding strategy defined by the closure you supply. 11 | case custom((_ codingPath: [CodingKey]) -> CodingKey) 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Deeplink/Errors/DeeplinkDecodingError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct DeeplinkDecodingError: DeeplinkError { 4 | 5 | internal let description: String 6 | 7 | internal var isWarning: Bool { 8 | true 9 | } 10 | 11 | internal init( 12 | underlyingError: Error, 13 | trigger: Any 14 | ) { 15 | description = """ 16 | Failed to decode data for \(trigger) with error: 17 | \(underlyingError) 18 | """ 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Example/NivelirExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | func application( 7 | _ application: UIApplication, 8 | configurationForConnecting connectingSceneSession: UISceneSession, 9 | options: UIScene.ConnectionOptions 10 | ) -> UISceneConfiguration { 11 | UISceneConfiguration( 12 | name: "Main", 13 | sessionRole: connectingSceneSession.role 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Tools/DictionaryDecoder/Options/DictionaryKeyDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The values that determine how to decode a type’s coding keys from Dictionary keys. 4 | @frozen 5 | public enum DictionaryKeyDecodingStrategy { 6 | 7 | /// A key decoding strategy that doesn’t change key names during decoding. 8 | case useDefaultKeys 9 | 10 | /// A key decoding strategy defined by the closure you supply. 11 | case custom(@Sendable (_ codingPath: [CodingKey]) -> CodingKey) 12 | } 13 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/RoomList/RoomListScreen.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct RoomListScreen: Screen { 5 | 6 | let services: Services 7 | let screens: Screens 8 | 9 | func build(navigator: ScreenNavigator) -> UIViewController { 10 | RoomListViewController( 11 | authorizationService: services.authorizationService(), 12 | screens: screens, 13 | screenKey: key, 14 | screenNavigator: navigator 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Interaction/BottomSheetInteractionState.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | internal enum BottomSheetInteractionState { 5 | 6 | case starting 7 | case updating 8 | case finished 9 | case cancelled 10 | 11 | internal var isActive: Bool { 12 | switch self { 13 | case .starting, .updating: 14 | return true 15 | 16 | case .finished, .cancelled: 17 | return false 18 | } 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/Addons/Sharing/Activity/SharingActivityType.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | public typealias SharingActivityType = UIActivity.ActivityType 5 | 6 | extension SharingActivityType { 7 | 8 | public static func fromPropertyName(_ name: String = #function) -> Self { 9 | Bundle.main.bundleIdentifier.map { bundleIdentifier in 10 | Self(rawValue: "\(bundleIdentifier).activity.\(name)") 11 | } ?? Self(rawValue: "activity.\(name)") 12 | } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Chat/ChatScreen.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct ChatScreen: Screen { 5 | 6 | let roomID: Int 7 | let chatID: Int 8 | 9 | var traits: Set { 10 | [roomID, chatID] 11 | } 12 | 13 | func build(navigator: ScreenNavigator) -> UIViewController { 14 | ChatViewController( 15 | roomID: roomID, 16 | chatID: chatID, 17 | screenKey: key, 18 | screenNavigator: navigator 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Presentation/BottomSheetPresentationState.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | internal enum BottomSheetPresentationState { 5 | 6 | case presenting 7 | case presented 8 | case dismissing 9 | case dismissed 10 | 11 | internal var canAnimateChanges: Bool { 12 | switch self { 13 | case .presenting, .presented, .dismissing: 14 | return true 15 | 16 | case .dismissed: 17 | return false 18 | } 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/Screen/Navigator/Iterator/ScreenIterationResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A value that represents either a continuation or a stop iteration, 4 | /// including the associated container value in each case. 5 | @frozen 6 | public enum ScreenIterationResult { 7 | 8 | /// Continue iterating if possible, starting from `suitableContainer`. 9 | case shouldContinue(suitableContainer: ScreenContainer?) 10 | 11 | /// Stop iterating on `suitableContainer`. 12 | case shouldStop(suitableContainer: ScreenContainer) 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Screen/Navigator/WindowProvider/ScreenCustomWindowProvider.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// An implementation of `ScreenWindowProvider` that provides a `UIWindow` explicitly passed through the initializer. 5 | public struct ScreenCustomWindowProvider: ScreenWindowProvider { 6 | 7 | /// The `UIWindow` for navigating and searching for containers. 8 | public private(set) weak var window: UIWindow? 9 | 10 | public init(window: UIWindow) { 11 | self.window = window 12 | } 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/ChatList/ChatListScreen.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct ChatListScreen: Screen { 5 | 6 | let roomID: Int 7 | let screens: Screens 8 | 9 | var traits: Set { 10 | [roomID] 11 | } 12 | 13 | func build(navigator: ScreenNavigator) -> UIViewController { 14 | ChatListViewController( 15 | roomID: roomID, 16 | screens: screens, 17 | screenKey: key, 18 | screenNavigator: navigator 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Screen/Errors/ScreenCanceledError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Action canceled. 4 | /// 5 | /// This error occurs whenever an action is canceled. 6 | public struct ScreenCanceledError: ScreenError { 7 | 8 | public let description: String 9 | 10 | /// Creates an error. 11 | /// 12 | /// - Parameters: 13 | /// - trigger: The action that caused the error. 14 | public init(for trigger: Any) { 15 | description = """ 16 | Action canceled: 17 | \(trigger) 18 | """ 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | def report_xcode_summary(platform:) 2 | path = "xcodebuild-#{platform.downcase}.xcresult" 3 | 4 | xcode_summary.ignores_warnings = false 5 | xcode_summary.inline_mode = true 6 | 7 | xcode_summary.report(path) 8 | end 9 | 10 | warn('This pull request is marked as Work in Progress. DO NOT MERGE!') if github.pr_title.include? "[WIP]" 11 | 12 | swiftlint.lint_all_files = true 13 | swiftlint.lint_files(fail_on_error: true, inline_mode: true) 14 | 15 | report_xcode_summary(platform: "iOS") 16 | report_xcode_summary(platform: "tvOS") 17 | -------------------------------------------------------------------------------- /Sources/Tools/URLQueryDecoder/Options/URLQueryDataDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The strategies for decoding raw data. 4 | @frozen 5 | public enum URLQueryDataDecodingStrategy { 6 | 7 | /// The strategy that encodes data using the encoding specified by the data instance itself. 8 | case deferredToData 9 | 10 | /// The strategy that decodes data using Base 64 decoding. 11 | case base64 12 | 13 | /// The strategy that decodes data using a user-defined function. 14 | case custom((_ decoder: Decoder) throws -> Data) 15 | } 16 | -------------------------------------------------------------------------------- /Example/NivelirExample/Routing/Authorization/ScreenAuthorizeActionObserver.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class ScreenAuthorizeActionObserver: AuthorizationObserver { 4 | 5 | private var authorizationFinishedHandler: ((_ isAuthorized: Bool) -> Void) 6 | 7 | init(authorizationFinishedHandler: @escaping (_ isAuthorized: Bool) -> Void) { 8 | self.authorizationFinishedHandler = authorizationFinishedHandler 9 | } 10 | 11 | func authorizationFinished(isAuthorized: Bool) { 12 | authorizationFinishedHandler(isAuthorized) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Profile/ProfileScreen.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct ProfileScreen: Screen { 5 | 6 | let services: Services 7 | let screens: Screens 8 | 9 | func build(navigator: ScreenNavigator) -> UIViewController { 10 | ProfileViewController( 11 | authorizationService: services.authorizationService(), 12 | profileService: services.profileService(), 13 | screens: screens, 14 | screenKey: key, 15 | screenNavigator: navigator 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Screen/Observation/Storage/ScreenObserverSharedStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal final class ScreenObserverSharedStorage: ScreenObserverStorage { 4 | 5 | private let observer: ScreenObserver 6 | 7 | internal let predicate: ScreenObserverPredicate 8 | 9 | internal var value: ScreenObserver? { 10 | observer 11 | } 12 | 13 | internal init( 14 | observer: ScreenObserver, 15 | predicate: ScreenObserverPredicate 16 | ) { 17 | self.observer = observer 18 | self.predicate = predicate 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Tools/DictionaryDecoder/Options/DictionaryDataDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The strategies for decoding raw data. 4 | @frozen 5 | public enum DictionaryDataDecodingStrategy { 6 | 7 | /// The strategy that encodes data using the encoding specified by the data instance itself. 8 | case deferredToData 9 | 10 | /// The strategy that decodes data using Base 64 decoding. 11 | case base64 12 | 13 | /// The strategy that decodes data using a user-defined function. 14 | case custom(@Sendable (_ decoder: Decoder) throws -> Data) 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Addons/Safari/InvalidSafariURLError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | public struct InvalidSafariURLError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Safari does not support the url scheme of: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func invalidSafariURL(for trigger: Any) -> Self { 19 | .failure(InvalidSafariURLError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /docs/img/deprecated-icon.015b4f17.svg: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /Sources/Addons/URL/StoreApp/InvalidStoreAppIDError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | import Foundation 3 | 4 | public struct InvalidStoreAppIDError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Invalid store app ID for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func invalidStoreAppID(for trigger: Any) -> Self { 19 | .failure(InvalidStoreAppIDError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Screen/Payload/Extensions/ScreenPayloadedContainer+NSObject.swift: -------------------------------------------------------------------------------- 1 | #if canImport(ObjectiveC) 2 | import ObjectiveC 3 | 4 | private let screenPayloadAssociation = ObjectAssociation() 5 | 6 | extension ScreenPayloadedContainer where Self: NSObject { 7 | 8 | public var screenPayload: ScreenPayload { 9 | if let payload = screenPayloadAssociation[self] { 10 | return payload 11 | } 12 | 13 | let payload = ScreenPayload() 14 | 15 | screenPayloadAssociation[self] = payload 16 | 17 | return payload 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/Addons/URL/Call/InvalidCallParametersError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | public struct InvalidCallParametersError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Invalid call parameters for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func invalidCallParameters(for trigger: Any) -> Self { 19 | .failure(InvalidCallParametersError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Landmark/LandmarkMapView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MapKit 3 | 4 | struct LandmarkMapView: View { 5 | 6 | @State private var region = MKCoordinateRegion( 7 | center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868), 8 | span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2) 9 | ) 10 | 11 | var body: some View { 12 | Map(coordinateRegion: $region) 13 | } 14 | } 15 | 16 | struct MapView_Previews: PreviewProvider { 17 | static var previews: some View { 18 | LandmarkMapView() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Addons/StoreProduct/InvalidStoreProductIDError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | public struct InvalidStoreProductIDError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Invalid store product ID for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func invalidStoreProductID(for trigger: Any) -> Self { 19 | .failure(InvalidStoreProductIDError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | 5 | internal static let urlPercentEscapedSpace = "%20" 6 | internal static let urlPlusReplacedSpace = "+" 7 | internal static let urlPathSeparator = "/" 8 | 9 | internal static let newLine = "\n" 10 | 11 | internal func indented(spaces: Int) -> String { 12 | let spaces = String(repeating: " ", count: spaces) 13 | 14 | return components(separatedBy: .newlines) 15 | .joined(separator: "\n\(spaces)") 16 | .prepending(contentsOf: spaces) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Authorization/AuthorizationScreen.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct AuthorizationScreen: Screen { 5 | 6 | let services: Services 7 | 8 | func build( 9 | navigator: ScreenNavigator, 10 | observation: ScreenObservation 11 | ) -> UIViewController { 12 | AuthorizationViewController( 13 | authorizationService: services.authorizationService(), 14 | screenObservation: observation, 15 | screenKey: key, 16 | screenNavigator: navigator 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Footer/Empty/ProgressEmptyFooterView.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Empty view for the progress footer. 5 | public final class ProgressEmptyFooterView: UIView, ProgressContentView { 6 | 7 | public let content: ProgressEmptyFooter 8 | 9 | public init(content: ProgressEmptyFooter) { 10 | self.content = content 11 | 12 | super.init(frame: .zero) 13 | } 14 | 15 | @available(*, unavailable) 16 | public required init?(coder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Header/Empty/ProgressEmptyHeaderView.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Empty view for the progress header. 5 | public final class ProgressEmptyHeaderView: UIView, ProgressContentView { 6 | 7 | public var content: ProgressEmptyHeader 8 | 9 | public init(content: ProgressEmptyHeader) { 10 | self.content = content 11 | 12 | super.init(frame: .zero) 13 | } 14 | 15 | @available(*, unavailable) 16 | public required init?(coder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/Addons/URL/Mail/InvalidMailParametersError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | import Foundation 3 | 4 | public struct InvalidMailParametersError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Invalid mail parameters for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func invalidMailParameters(for trigger: Any) -> Self { 19 | .failure(InvalidMailParametersError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Landmark/LandmarkCircleImage.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LandmarkCircleImage: View { 4 | var body: some View { 5 | Image("Turtlerock") 6 | .accessibilityLabel("Turtle Rock") 7 | .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) 8 | .overlay { 9 | Circle().stroke(.white, lineWidth: 4) 10 | } 11 | .shadow(radius: 7) 12 | } 13 | } 14 | 15 | struct CircleImage_Previews: PreviewProvider { 16 | static var previews: some View { 17 | LandmarkCircleImage() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Animations/BottomSheetAnimationOptions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct BottomSheetAnimationOptions: Equatable, Sendable { 5 | 6 | public static let transition = Self() 7 | public static let changes = Self(duration: 0.3) 8 | 9 | public let duration: TimeInterval 10 | public let curve: UIView.AnimationCurve 11 | 12 | public init( 13 | duration: TimeInterval = 0.4, 14 | curve: UIView.AnimationCurve = .easeInOut 15 | ) { 16 | self.duration = duration 17 | self.curve = curve 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/Addons/URL/Settings/InvalidOpenSettingsURLError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | import Foundation 3 | 4 | public struct InvalidOpenSettingsURLError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Invalid settings URL for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func invalidOpenSettingsURL(for trigger: Any) -> Self { 19 | .failure(InvalidOpenSettingsURLError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Addons/StoreProduct/StoreProductManager.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && canImport(StoreKit) && os(iOS) 2 | import UIKit 3 | import StoreKit 4 | 5 | internal final class StoreProductManager: 6 | NSObject, 7 | SKStoreProductViewControllerDelegate { 8 | 9 | private let storeProduct: StoreProduct 10 | 11 | internal init(storeProduct: StoreProduct) { 12 | self.storeProduct = storeProduct 13 | } 14 | 15 | internal func productViewControllerDidFinish( 16 | _ viewController: SKStoreProductViewController 17 | ) { 18 | storeProduct.didFinish?() 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Screen/Container/Extensions/UIViewController+ScreenVisibleContainer.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIViewController: ScreenVisibleContainer { 5 | 6 | /// A Boolean value indicating whether the container is visible. 7 | /// 8 | /// Returns `true` if controller's view is loaded and is not hidden. 9 | public var isVisible: Bool { 10 | viewIfLoaded.map { $0.window != nil && !$0.isHidden } ?? false 11 | } 12 | 13 | @available(iOS 13.0, tvOS 13.0, *) 14 | public var windowScene: UIWindowScene? { 15 | window?.windowScene 16 | } 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Scripts/Helpers/script-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export MINT_PATH="$HOME/.mint" 4 | export MINT_LINK_PATH="$MINT_PATH/bin" 5 | 6 | if [[ -f "/opt/homebrew/bin/brew" ]]; then 7 | eval "$(/opt/homebrew/bin/brew shellenv)" 8 | fi 9 | 10 | run() { 11 | if [ -z "${root_path}" ]; then 12 | if [ -z "${helpers_path}" ]; then 13 | readonly helpers_path="$( cd "$( dirname "$0" )" && pwd )" 14 | fi 15 | 16 | source "${helpers_path}/script-paths.sh" 17 | fi 18 | 19 | if which mint >/dev/null; then 20 | (cd "${root_path}" && mint run "$@") 21 | else 22 | echo "warning: Mint does not exist" 23 | fi 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/BottomSheetGrabber.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct BottomSheetGrabber: Equatable, Sendable { 5 | 6 | public static let `default` = Self() 7 | 8 | public let size: CGSize 9 | public let color: UIColor 10 | public let inset: CGFloat 11 | 12 | public init( 13 | size: CGSize = CGSize(width: 36.0, height: 5.0), 14 | color: UIColor = UIColor.darkGray.withAlphaComponent(0.4), 15 | inset: CGFloat = 5.0 16 | ) { 17 | self.size = size 18 | self.color = color 19 | self.inset = inset 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Extensions/UIViewController+BottomSheet.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIViewController { 5 | 6 | public var bottomSheet: BottomSheetController? { 7 | let viewController = presenting?.presented ?? self 8 | 9 | guard viewController.modalPresentationStyle == .custom else { 10 | return nil 11 | } 12 | 13 | return viewController.transitioningDelegate as? BottomSheetController 14 | } 15 | 16 | public var isPresentedAsBottomSheet: Bool { 17 | (bottomSheet != nil) && (presenting != nil) 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/Deeplink/Extensions/UNNotificationResponse+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UserNotifications) && os(iOS) 2 | import Foundation 3 | import UserNotifications 4 | 5 | extension UNNotificationResponse { 6 | 7 | internal var logDescription: String? { 8 | let userInfo = notification.request.content.userInfo 9 | 10 | let jsonData = try? JSONSerialization.data( 11 | withJSONObject: userInfo, 12 | options: .prettyPrinted 13 | ) 14 | 15 | return jsonData.flatMap { jsonData in 16 | String(data: jsonData, encoding: .utf8) 17 | } 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/DI/ServiceKey.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ServiceKey: Hashable { 4 | 5 | let type: Any.Type 6 | let name: String 7 | let traits: [AnyHashable] 8 | 9 | func hash(into hasher: inout Hasher) { 10 | ObjectIdentifier(type).hash(into: &hasher) 11 | name.hash(into: &hasher) 12 | traits.hash(into: &hasher) 13 | } 14 | } 15 | 16 | extension ServiceKey: Equatable { 17 | 18 | static func == (lhs: Self, rhs: Self) -> Bool { 19 | (lhs.type == rhs.type) 20 | && (lhs.name == rhs.name) 21 | && (lhs.traits == rhs.traits) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Addons/MediaPicker/Errors/UnavailableMediaPickerSourceError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | public struct UnavailableMediaPickerSourceError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Media source is not available for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func unavailableMediaPickerSource(for trigger: Any) -> Self { 19 | .failure(UnavailableMediaPickerSourceError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Addons/MediaPicker/Errors/UnavailableMediaPickerTypesError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | public struct UnavailableMediaPickerTypesError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | Media types are not available for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func unavailableMediaPickerTypes(for trigger: Any) -> Self { 19 | .failure(UnavailableMediaPickerTypesError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Screen/Observation/Storage/ScreenObserverWeakStorage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal final class ScreenObserverWeakStorage: ScreenObserverStorage { 4 | 5 | internal typealias Observer = ScreenObserver & AnyObject 6 | 7 | private weak var observer: Observer? 8 | 9 | internal let predicate: ScreenObserverPredicate 10 | 11 | internal var value: ScreenObserver? { 12 | observer 13 | } 14 | 15 | internal init( 16 | observer: Observer, 17 | predicate: ScreenObserverPredicate 18 | ) { 19 | self.observer = observer 20 | self.predicate = predicate 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Addons/Sharing/Activity/SharingCustomActivity.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | public protocol SharingCustomActivity { 5 | 6 | static var category: SharingActivityCategory { get } 7 | 8 | var type: SharingActivityType? { get } 9 | var title: String { get } 10 | var image: UIImage { get } 11 | 12 | func isApplicable(for items: [SharingItem]) -> Bool 13 | } 14 | 15 | extension SharingCustomActivity { 16 | 17 | public static var category: SharingActivityCategory { 18 | .action 19 | } 20 | 21 | public var type: SharingActivityType? { 22 | nil 23 | } 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/Addons/URL/FailedToOpenURLError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | import Foundation 3 | 4 | public struct FailedToOpenURLError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init( 9 | url: URL, 10 | for trigger: Any 11 | ) { 12 | description = """ 13 | Failed to open URL ("\(url)") for: 14 | \(trigger) 15 | """ 16 | } 17 | } 18 | 19 | extension Result where Failure == Error { 20 | 21 | internal static func failedToOpenURL(_ url: URL, for trigger: Any) -> Self { 22 | .failure(FailedToOpenURLError(url: url, for: trigger)) 23 | } 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/Addons/MediaPicker/Errors/MediaPickerSourceAccessDeniedError.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | public struct MediaPickerSourceAccessDeniedError: ScreenError { 5 | 6 | public let description: String 7 | 8 | public init(for trigger: Any) { 9 | description = """ 10 | User does not allow the app to access the media source for: 11 | \(trigger) 12 | """ 13 | } 14 | } 15 | 16 | extension Result where Failure == Error { 17 | 18 | internal static func mediaPickerSourceAccessDenied(for trigger: Any) -> Self { 19 | .failure(MediaPickerSourceAccessDeniedError(for: trigger)) 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Nivelir", 6 | platforms: [ 7 | .iOS(.v13), 8 | .tvOS(.v13) 9 | ], 10 | products: [ 11 | .library( 12 | name: "Nivelir", 13 | targets: ["Nivelir"] 14 | ) 15 | ], 16 | targets: [ 17 | .target( 18 | name: "Nivelir", 19 | path: "Sources" 20 | ), 21 | .testTarget( 22 | name: "NivelirTests", 23 | dependencies: ["Nivelir"], 24 | path: "Tests" 25 | ) 26 | ], 27 | swiftLanguageVersions: [.v5] 28 | ) 29 | -------------------------------------------------------------------------------- /Sources/Screen/Payload/ScreenPayload.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Payload associated with the screen container. 4 | /// 5 | /// This is a helper class that is used to store navigation data 6 | /// that should be in memory until the screen container itself is released. 7 | /// 8 | /// - SeeAlso: `ScreenPayloadedContainer` 9 | public final class ScreenPayload { 10 | 11 | private var storage: [Any] = [] 12 | 13 | /// Creates an empty payload. 14 | public init() { } 15 | 16 | /// Stores navigation data. 17 | /// 18 | /// - Parameter data: Navigation data. 19 | public func store(_ data: Any) { 20 | storage.append(data) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/BottomSheetRubberBandEffect.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct BottomSheetRubberBandEffect: Sendable { 5 | 6 | public let handler: @Sendable (_ delta: CGFloat) -> CGFloat 7 | 8 | public init(handler: @escaping @Sendable (_ delta: CGFloat) -> CGFloat) { 9 | self.handler = handler 10 | } 11 | 12 | public func callAsFunction(value: CGFloat, limit: CGFloat) -> CGFloat { 13 | handler(abs(limit - value)) 14 | } 15 | } 16 | 17 | extension BottomSheetRubberBandEffect { 18 | 19 | public static let `default` = Self { delta in 20 | 2.0 * delta.squareRoot() 21 | } 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /Sources/Deeplink/URL/Errors/URLDeeplinkInvalidComponentsError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Failed to extract components from deeplink URL. 4 | public struct URLDeeplinkInvalidComponentsError: DeeplinkError { 5 | 6 | public let description: String 7 | 8 | /// Creates an error. 9 | /// 10 | /// - Parameters: 11 | /// - url: A URL that caused the error. 12 | /// - trigger: The deeplink that caused the error. 13 | public init( 14 | url: URL, 15 | for trigger: Any 16 | ) { 17 | description = """ 18 | Failed to extract components from URL "\(url)" for: 19 | \(trigger) 20 | """ 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Detention/Detent/BottomSheetDetentKey.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import Foundation 3 | 4 | public struct BottomSheetDetentKey: Hashable, RawRepresentable, Sendable { 5 | 6 | public let rawValue: String 7 | 8 | public init(rawValue: String) { 9 | self.rawValue = rawValue 10 | } 11 | } 12 | 13 | extension BottomSheetDetentKey { 14 | 15 | public static var content: Self { 16 | Self(rawValue: #function) 17 | } 18 | 19 | public static var large: Self { 20 | Self(rawValue: #function) 21 | } 22 | 23 | public static var medium: Self { 24 | Self(rawValue: #function) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/Tools/URLQueryDecoder/Options/URLQueryNonConformingFloatDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The strategies for encoding nonconforming floating-point numbers, 4 | /// also known as IEEE 754 exceptional values. 5 | @frozen 6 | public enum URLQueryNonConformingFloatDecodingStrategy { 7 | 8 | /// The strategy that throws an error upon decoding an exceptional floating-point value. 9 | case `throw` 10 | 11 | /// The strategy that decodes exceptional floating-point values from a specified string representation. 12 | case convertFromString( 13 | positiveInfinity: String, 14 | negativeInfinity: String, 15 | nan: String 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/Images.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | enum Images { 4 | 5 | static let close = UIImage(named: "Close")! 6 | static let roomsTab = UIImage(named: "RoomsTab")! 7 | static let room = UIImage(named: "RoomsTab")! 8 | static let profileTab = UIImage(named: "ProfileTab")! 9 | static let chat = UIImage(named: "Chat")! 10 | static let user = UIImage(named: "User")! 11 | static let unauthorized = UIImage(named: "Unauthorized")! 12 | static let chevron = UIImage(named: "Chevron")! 13 | static let moreTab = UIImage(named: "MoreTab")! 14 | static let share = UIImage(named: "Share")! 15 | static let browser = UIImage(named: "Browser")! 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Screen/Decorators/Modal/ModalStyle/ScreenModalStyle.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Modal screen presentation style. 5 | /// Changes the presentation animation when the screen is shown modally. 6 | @frozen 7 | public enum ScreenModalStyle { 8 | 9 | /// Default animation using `UIModalPresentationStyle` and `UIModalTransitionStyle`. 10 | case `default`( 11 | presentation: UIModalPresentationStyle? = nil, 12 | transition: UIModalTransitionStyle? = nil 13 | ) 14 | 15 | /// Custom animation using `UIViewControllerTransitioningDelegate` implementation. 16 | case custom(delegate: UIViewControllerTransitioningDelegate) 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Sources/Tools/DictionaryDecoder/Options/DictionaryNonConformingFloatDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The strategies for encoding nonconforming floating-point numbers, 4 | /// also known as IEEE 754 exceptional values. 5 | @frozen 6 | public enum DictionaryNonConformingFloatDecodingStrategy { 7 | 8 | /// The strategy that throws an error upon decoding an exceptional floating-point value. 9 | case `throw` 10 | 11 | /// The strategy that decodes exceptional floating-point values from a specified string representation. 12 | case convertFromString( 13 | positiveInfinity: String, 14 | negativeInfinity: String, 15 | nan: String 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Deeplink/Extensions/UIApplicationShortcutItem+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | extension UIApplicationShortcutItem { 5 | 6 | internal var logDescription: String? { 7 | let description: [String: Any?] = [ 8 | "type": type, 9 | "title": localizedTitle, 10 | "userInfo": userInfo 11 | ] 12 | 13 | let jsonData = try? JSONSerialization.data( 14 | withJSONObject: description, 15 | options: .prettyPrinted 16 | ) 17 | 18 | return jsonData.flatMap { jsonData in 19 | String(data: jsonData, encoding: .utf8) 20 | } 21 | } 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/gemfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly arguments=$@ 4 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 5 | 6 | source "${script_path}/common.sh" 7 | 8 | if [[ "$(uname -m)" == "arm64" ]]; then 9 | eval "$(/opt/homebrew/bin/brew shellenv)" 10 | fi 11 | 12 | eval "$(rbenv init -)" 13 | 14 | if [[ " ${arguments[*]} " == *" ${update_flag} "* ]]; then 15 | echo "Updating ${ruby_style}Ruby gems${default_style} specified in Gemfile..." 16 | assert_failure '(cd "${root_path}" && bundle update)' 17 | else 18 | echo "Installing ${ruby_style}Ruby gems${default_style} specified in Gemfile..." 19 | assert_failure '(cd "${root_path}" && bundle install)' 20 | fi 21 | 22 | echo "" 23 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Generic/ScreenFailAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct ScreenFailAction: ScreenAction { 4 | 5 | public typealias Output = Void 6 | 7 | public let error: Error 8 | 9 | public init(error: Error) { 10 | self.error = error 11 | } 12 | 13 | public func perform( 14 | container: Container, 15 | navigator: ScreenNavigator, 16 | completion: @escaping Completion 17 | ) { 18 | completion(.failure(error)) 19 | } 20 | } 21 | 22 | extension ScreenThenable { 23 | 24 | public func fail(with error: Error) -> Self { 25 | then(ScreenFailAction(error: error)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/img/added-icon.d6f7e47d.svg: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /Sources/Tools/AnyCodingKey.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct AnyCodingKey: CodingKey { 4 | 5 | internal static let `super` = Self("super") 6 | 7 | internal let stringValue: String 8 | internal let intValue: Int? 9 | 10 | internal init(_ stringValue: String) { 11 | self.stringValue = stringValue 12 | self.intValue = nil 13 | } 14 | 15 | internal init(_ intValue: Int) { 16 | self.stringValue = "\(intValue)" 17 | self.intValue = intValue 18 | } 19 | 20 | internal init?(stringValue: String) { 21 | self.init(stringValue) 22 | } 23 | 24 | internal init?(intValue: Int) { 25 | self.init(intValue) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/js/highlight-js-shell.dd7f411f.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This source file is part of the Swift.org open source project 3 | * 4 | * Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | * Licensed under Apache License v2.0 with Runtime Library Exception 6 | * 7 | * See https://swift.org/LICENSE.txt for license information 8 | * See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["highlight-js-shell"],{b65b:function(s,n){function e(s){return{name:"Shell Session",aliases:["console","shellsession"],contains:[{className:"meta",begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/,subLanguage:"bash"}}]}}s.exports=e}}]); -------------------------------------------------------------------------------- /Sources/Screen/Any/AnyScreenBaseBox.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class AnyScreenBaseBox: Screen { 4 | 5 | internal var name: String { 6 | fatalError("\(#function) has not been implemented") 7 | } 8 | 9 | internal var traits: Set { 10 | fatalError("\(#function) has not been implemented") 11 | } 12 | 13 | nonisolated internal var description: String { 14 | fatalError("\(#function) has not been implemented") 15 | } 16 | 17 | // swiftlint:disable:next unavailable_function 18 | internal func build(navigator: ScreenNavigator) -> Container { 19 | fatalError("\(#function) has not been implemented") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/spm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly arguments=$@ 4 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 5 | 6 | source "${script_path}/common.sh" 7 | 8 | if [[ "$(uname -m)" == "arm64" ]]; then 9 | eval "$(/opt/homebrew/bin/brew shellenv)" 10 | fi 11 | 12 | eval "$(swiftenv init -)" 13 | 14 | if [[ " ${arguments[*]} " == *" ${update_flag} "* ]]; then 15 | echo "Updating ${spm_style}Swift packages${default_style} specified in Package.swift..." 16 | assert_failure '(cd "${root_path}" && swift package update)' 17 | else 18 | echo "Resolving ${spm_style}Swift packages${default_style} specified in Package.swift..." 19 | assert_failure '(cd "${root_path}" && swift package resolve)' 20 | fi 21 | 22 | echo "" 23 | -------------------------------------------------------------------------------- /Sources/Deeplink/AnyDeeplink.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Erased type of ``Deeplink`` protocol. 4 | /// 5 | /// - SeeAlso: ``Deeplink`` 6 | @MainActor 7 | public protocol AnyDeeplink { 8 | 9 | /// The default implementation casts `screens` to the ``Deeplink/Screens`` type 10 | /// and performs ``Deeplink/navigate(screens:navigator:handler:)`` navigation. 11 | /// - Parameters: 12 | /// - screens: Screen Factory. 13 | /// - navigator: Navigator for performing navigation actions. 14 | /// - handler: Handler for processing a new ``Deeplink``. 15 | func navigateIfPossible( 16 | screens: Any?, 17 | navigator: ScreenNavigator, 18 | handler: DeeplinkHandler 19 | ) throws 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Dimming/BottomSheetDimming.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct BottomSheetDimming: Equatable, Sendable { 5 | 6 | public static let `default` = Self() 7 | 8 | public let color: UIColor 9 | public let blurStyle: UIBlurEffect.Style? 10 | public let largestUndimmedDetentKey: BottomSheetDetentKey? 11 | 12 | public init( 13 | color: UIColor = UIColor.black.withAlphaComponent(0.6), 14 | blurStyle: UIBlurEffect.Style? = nil, 15 | largestUndimmedDetentKey: BottomSheetDetentKey? = nil 16 | ) { 17 | self.color = color 18 | self.blurStyle = blurStyle 19 | self.largestUndimmedDetentKey = largestUndimmedDetentKey 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Stack/SetStack/Animations/ScreenStackCustomAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public protocol ScreenStackCustomAnimation { 5 | 6 | func isEqual(to other: ScreenStackCustomAnimation) -> Bool 7 | 8 | @MainActor 9 | func animate( 10 | container: UINavigationController, 11 | stack: [UIViewController], 12 | completion: @escaping () -> Void 13 | ) 14 | } 15 | 16 | extension ScreenStackCustomAnimation where Self: Equatable { 17 | 18 | public func isEqual(to other: ScreenStackCustomAnimation) -> Bool { 19 | guard let other = other as? Self else { 20 | return false 21 | } 22 | 23 | return self == other 24 | } 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Window/SetRoot/Animations/ScreenRootAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | @frozen 6 | public enum ScreenRootAnimation { 7 | 8 | case custom(ScreenRootCustomAnimation) 9 | 10 | public func animate( 11 | container: UIWindow, 12 | from root: UIViewController?, 13 | to newRoot: UIViewController, 14 | completion: @escaping () -> Void 15 | ) { 16 | switch self { 17 | case let .custom(animation): 18 | animation.animate( 19 | container: container, 20 | from: root, 21 | to: newRoot, 22 | completion: completion 23 | ) 24 | } 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 6 | readonly bootstrap_path="${script_path}/Bootstrap" 7 | 8 | "${bootstrap_path}/welcome.sh" 9 | "${bootstrap_path}/macos.sh" 10 | 11 | "${bootstrap_path}/homebrew.sh" --update --verify 12 | "${bootstrap_path}/rbenv.sh" --update --verify 13 | "${bootstrap_path}/ruby.sh" 14 | 15 | "${bootstrap_path}/bundler.sh" --update 16 | "${bootstrap_path}/gemfile.sh" 17 | 18 | "${bootstrap_path}/xcode.sh" 19 | 20 | "${bootstrap_path}/swiftenv.sh" --update 21 | "${bootstrap_path}/swift.sh" --update 22 | 23 | "${bootstrap_path}/spm.sh" 24 | 25 | "${bootstrap_path}/mint.sh" --update 26 | "${bootstrap_path}/mintfile.sh" 27 | 28 | "${bootstrap_path}/congratulations.sh" 29 | -------------------------------------------------------------------------------- /Example/NivelirExample/Routing/Extensions/HUD+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | extension HUD { 5 | 6 | static var spinner: Self { 7 | spinner( 8 | ProgressSpinnerIndicator(color: Colors.important), 9 | message: ProgressMessageFooter(text: "Please wait...", color: Colors.title) 10 | ) 11 | } 12 | 13 | static var success: Self { 14 | success(ProgressSuccessIndicator(color: Colors.important)) 15 | } 16 | 17 | static var failure: Self { 18 | failure(ProgressFailureIndicator(color: Colors.important)) 19 | } 20 | 21 | static func percentage(ratio: CGFloat) -> Self { 22 | percentage(ProgressPercentageIndicator(ratio: ratio, color: Colors.important)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Screen/Container/ScreenIterableContainer.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// A screen container that can be iterated over. 5 | /// 6 | /// This type of container is implemented by containers that can have nested containers. 7 | /// For example, `UINavigationController` has nested containers that are stored in a stack. 8 | /// The `UITabBarController` has nested containers that are stored in the tab. 9 | /// 10 | /// This protocol is used to find the specific container in nested containers. 11 | /// 12 | /// - SeeAlso: `ScreenContainer` 13 | @MainActor 14 | public protocol ScreenIterableContainer: ScreenContainer { 15 | 16 | /// Returns the nested containers of a container. 17 | var nestedContainers: [ScreenContainer] { get } 18 | } 19 | #endif 20 | -------------------------------------------------------------------------------- /Sources/Screen/Container/Extensions/UITabBarController+ScreenIterableContainer.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UITabBarController: ScreenIterableContainer { 5 | 6 | /// Returns nested containers in tabs, with the `selectedViewController` container being the last. 7 | /// 8 | /// - SeeAlso: `viewControllers` 9 | /// - SeeAlso: `selectedViewController` 10 | public var nestedContainers: [ScreenContainer] { 11 | let tabContainers = viewControllers ?? [] 12 | 13 | return selectedViewController.map { selectedContainer in 14 | tabContainers 15 | .removingAll { $0 === selectedContainer } 16 | .appending(selectedContainer) 17 | } ?? tabContainers 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/Deeplink/Errors/DeeplinkInvalidContextError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Context` instance is not supported by the deeplink type. 4 | public struct DeeplinkInvalidContextError: DeeplinkError { 5 | 6 | public let description: String 7 | 8 | /// Creates an error. 9 | /// 10 | /// - Parameters: 11 | /// - context: Context instance. 12 | /// - type: Expected context type. 13 | /// - trigger: The deeplink that caused the error. 14 | public init( 15 | context: Any?, 16 | type: Any.Type, 17 | for trigger: Any 18 | ) { 19 | description = """ 20 | The type of the context \(context ?? "nil") does not match the expected type \(type) for: 21 | \(trigger) 22 | """ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Deeplink/Errors/DeeplinkInvalidScreensError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The `Screens` instance is not supported by the deeplink type. 4 | public struct DeeplinkInvalidScreensError: DeeplinkError { 5 | 6 | public let description: String 7 | 8 | /// Creates an error. 9 | /// 10 | /// - Parameters: 11 | /// - screens: Screens instance. 12 | /// - type: Expected screens type. 13 | /// - trigger: The deeplink that caused the error. 14 | public init( 15 | screens: Any?, 16 | type: Any.Type, 17 | for trigger: Any 18 | ) { 19 | description = """ 20 | The type of the screens \(screens ?? "nil") does not match the expected type \(type) for: 21 | \(trigger) 22 | """ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Tabs/SelectTab/Animations/ScreenTabAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | @frozen 6 | public enum ScreenTabAnimation { 7 | 8 | case custom(ScreenTabCustomAnimation) 9 | 10 | public func animate( 11 | container: UITabBarController, 12 | from selectedTab: UIViewController, 13 | to newSelectedTab: UIViewController, 14 | completion: @escaping () -> Void 15 | ) { 16 | switch self { 17 | case let .custom(animation): 18 | animation.animate( 19 | container: container, 20 | from: selectedTab, 21 | to: newSelectedTab, 22 | completion: completion 23 | ) 24 | } 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/bundler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly arguments=$@ 4 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 5 | 6 | source "${script_path}/common.sh" 7 | 8 | echo "Checking ${bundler_style}Bundler${default_style} installation:" 9 | 10 | if [[ "$(uname -m)" == "arm64" ]]; then 11 | eval "$(/opt/homebrew/bin/brew shellenv)" 12 | fi 13 | 14 | eval "$(rbenv init -)" 15 | 16 | if rbenv which bundler &> /dev/null; then 17 | if [[ " ${arguments[*]} " == *" ${update_flag} "* ]]; then 18 | echo " Bundler already installed. Updating..." 19 | assert_failure 'gem update bundler' 20 | else 21 | echo " Bundler already installed." 22 | fi 23 | else 24 | echo " Bundler not found. Installing..." 25 | assert_failure 'gem install bundler' 26 | fi 27 | 28 | echo "" 29 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 4 | 5 | source "${script_path}/common.sh" 6 | 7 | plain_version() { 8 | echo "$@" | awk -F. '{ printf("%d%03d%03d%03d", $1,$2,$3,$4); }' 9 | } 10 | 11 | echo "Checking ${macos_style}macOS${default_style} version:" 12 | 13 | readonly macos_required_version='11.3.0' 14 | readonly macos_version=$(/usr/bin/sw_vers -productVersion 2>&1) 15 | 16 | if [ "$(plain_version ${macos_version})" -lt "$(plain_version ${macos_required_version})" ]; then 17 | echo " ${error_style}Your macOS version (${macos_version}) is older then required version (${macos_required_version}). Exiting...${default_style}" 18 | exit 1 19 | else 20 | echo " Your macOS version: ${macos_version}" 21 | fi 22 | 23 | echo "" 24 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Stack/SetStack/Modifiers/ScreenStackClearModifier.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct ScreenStackClearModifier: ScreenStackModifier { 5 | 6 | public let description: String 7 | 8 | public init() { 9 | description = "Clear" 10 | } 11 | 12 | public func perform( 13 | stack: [UIViewController], 14 | navigator: ScreenNavigator 15 | ) -> [UIViewController] { 16 | [] 17 | } 18 | } 19 | 20 | extension ScreenThenable where Current: UINavigationController { 21 | 22 | public func clear(animation: ScreenStackAnimation? = .default) -> Self { 23 | setStack( 24 | modifier: ScreenStackClearModifier(), 25 | animation: animation 26 | ) 27 | } 28 | } 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/Screen/Observation/ScreenObserverToken.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Token associated with the subscription lifecycle for the observer. 4 | /// 5 | /// Once the token is removed from memory (deinit is called), 6 | /// the subscription will be canceled and new notifications will not be received by the observer. 7 | /// The subscription can also be canceled manually using the ``cancel()`` method. 8 | public final class ScreenObserverToken { 9 | 10 | private let cancellation: () -> Void 11 | 12 | internal init(cancellation: @escaping () -> Void) { 13 | self.cancellation = cancellation 14 | } 15 | 16 | deinit { 17 | cancellation() 18 | } 19 | 20 | /// Unsubscribe the observer. 21 | public func cancel() { 22 | cancellation() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Screen/Route/ScreenRouteConvertible.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type with a root route. 4 | /// 5 | /// Can be used to erase the `ScreenRoute` type. 6 | /// 7 | /// - SeeAlso: `ScreenRoute` 8 | /// - SeeAlso: `ScreenRootRoute` 9 | @MainActor 10 | public protocol ScreenRouteConvertible { 11 | 12 | /// Returns the root route with the actions of the current instance. 13 | /// 14 | /// - Returns: An instance containing all the actions. 15 | func route() -> ScreenRootRoute 16 | } 17 | 18 | extension ScreenRoute: ScreenRouteConvertible { 19 | 20 | public func route() -> ScreenRootRoute { 21 | ScreenRootRoute(actions: actions as? [AnyScreenAction] ?? []) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Animation/ProgressCustomAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// A type describing the animation of updating progress view of``HUD``. 5 | /// 6 | /// See ``ProgressDefaultAnimation`` for example. 7 | @MainActor 8 | public protocol ProgressCustomAnimation { 9 | 10 | /// Animates updates of parts of the progress view. 11 | /// - Parameters: 12 | /// - view: View of the `header`, `indicator` or `footer` part. 13 | /// - previousView: Previous view of the `header`, `indicator` or `footer` part. 14 | /// - containerView: Container view of the `header`, `indicator` or `footer` part. 15 | func animateView( 16 | _ view: UIView, 17 | previousView: UIView, 18 | containerView: UIView 19 | ) 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Nivelir.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "Nivelir" 3 | spec.version = "1.9.10" 4 | spec.summary = "A Swift DSL for navigation in iOS and tvOS apps with a simplified, chainable, and compile time safe syntax." 5 | 6 | spec.homepage = "https://github.com/hhru/Nivelir" 7 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 8 | spec.author = { "Almaz Ibragimov" => "almazrafi@gmail.com" } 9 | spec.source = { :git => "https://github.com/hhru/Nivelir.git", :tag => "#{spec.version}" } 10 | 11 | spec.swift_version = '6.0' 12 | spec.requires_arc = true 13 | spec.source_files = 'Sources/**/*.swift' 14 | 15 | spec.ios.frameworks = 'Foundation' 16 | spec.ios.deployment_target = "13.0" 17 | 18 | spec.tvos.frameworks = 'Foundation' 19 | spec.tvos.deployment_target = "13.0" 20 | end 21 | -------------------------------------------------------------------------------- /Sources/Screen/Container/ScreenVisibleContainer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if canImport(UIKit) 4 | import UIKit 5 | #endif 6 | 7 | /// A screen container that can be visible. 8 | /// 9 | /// This is a simple protocol that is used to determine the visibility of a container. 10 | /// The `UIWindow` and `UIViewController` classes and all their subclasses already implement this protocol 11 | /// 12 | /// - SeeAlso: `ScreenContainer` 13 | @MainActor 14 | public protocol ScreenVisibleContainer: ScreenContainer { 15 | 16 | /// A Boolean value indicating whether the container is visible. 17 | var isVisible: Bool { get } 18 | 19 | #if canImport(UIKit) 20 | /// The scene containing the container. 21 | @available(iOS 13.0, tvOS 13.0, *) 22 | var windowScene: UIWindowScene? { get } 23 | #endif 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Tools/ObjectAssociation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal final class ObjectAssociation: Sendable { 4 | 5 | private let policy: objc_AssociationPolicy 6 | 7 | internal init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { 8 | self.policy = policy 9 | } 10 | 11 | internal subscript(object: AnyObject) -> T? { 12 | get { 13 | objc_getAssociatedObject( 14 | object, 15 | Unmanaged.passRetained(self).toOpaque() 16 | ) as? T 17 | } 18 | 19 | set { 20 | objc_setAssociatedObject( 21 | object, 22 | Unmanaged.passRetained(self).toOpaque(), 23 | newValue, 24 | policy 25 | ) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Content/AnyProgressContent.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Erased protocol type ``ProgressContent``. 5 | /// 6 | /// - SeeAlso: ``ProgressContent`` 7 | @MainActor 8 | public protocol AnyProgressContent { 9 | 10 | /// A console log representation of `self`. 11 | var logDescription: String? { get } 12 | 13 | /// Updates or creates a new instance of the content view using this content 14 | /// if the `contentView` type matches the ``ProgressContent/View`` type. 15 | /// - Parameter contentView: The current view associated with the content. 16 | func updateContentViewIfPossible(_ contentView: UIView?) -> UIView 17 | } 18 | 19 | extension AnyProgressContent { 20 | 21 | public var logDescription: String? { 22 | nil 23 | } 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/Screen/Container/ScreenContainer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A screen container on which the navigation is performed. 4 | /// 5 | /// Any navigation is performed on containers, for UIKit they are divided into several types: 6 | /// - Window container : instances of `UIWindow` 7 | /// - Tabs container: instances of `UITabBarController` 8 | /// - Stack container :  instances of `UINavigationController` 9 | /// - Modal container :  instances of `UIViewController` 10 | /// 11 | /// Since `UITabBarController` and `UINavigationController` are subclasses of `UIViewController`, 12 | /// they are also modal containers. 13 | /// 14 | /// This protocol is already implemented by the `UIWindow`, `UIViewController` classes and all their subclasses. 15 | /// 16 | /// - SeeAlso: `Screen` 17 | public protocol ScreenContainer { } 18 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/Screen/Navigator/Logger/DefaultScreenLogger.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Default Logger implementation. 4 | public final class DefaultScreenLogger: ScreenLogger { 5 | 6 | public let isInfoEnabled: Bool 7 | public let isErrorsEnabled: Bool 8 | 9 | public init( 10 | isInfoEnabled: Bool = true, 11 | isErrorsEnabled: Bool = true 12 | ) { 13 | self.isInfoEnabled = isInfoEnabled 14 | self.isErrorsEnabled = isErrorsEnabled 15 | } 16 | 17 | public func info(_ info: @autoclosure () -> String) { 18 | if isInfoEnabled { 19 | print("Info: \(info())") 20 | } 21 | } 22 | 23 | public func error(_ error: @autoclosure () -> Error) { 24 | if isErrorsEnabled { 25 | print("Error: \(error())") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Action/InvalidBottomSheetContainerError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct InvalidBottomSheetContainerError: ScreenError { 4 | 5 | public let description: String 6 | 7 | public init(container: ScreenContainer, for trigger: Any) { 8 | description = """ 9 | The container \(container) is not presented as a bottom sheet for: 10 | \(trigger) 11 | """ 12 | } 13 | } 14 | 15 | extension Result where Failure == Error { 16 | 17 | internal static func invalidBottomSheetContainer( 18 | _ container: ScreenContainer, 19 | for trigger: Any 20 | ) -> Self { 21 | .failure( 22 | InvalidBottomSheetContainerError( 23 | container: container, 24 | for: trigger 25 | ) 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/js/highlight-js-json.471128d2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This source file is part of the Swift.org open source project 3 | * 4 | * Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | * Licensed under Apache License v2.0 with Runtime Library Exception 6 | * 7 | * See https://swift.org/LICENSE.txt for license information 8 | * See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["highlight-js-json"],{"5ad2":function(n,e){function a(n){const e={className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},a={match:/[{}[\],:]/,className:"punctuation",relevance:0},s={beginKeywords:["true","false","null"].join(" ")};return{name:"JSON",contains:[e,a,n.QUOTE_STRING_MODE,s,n.C_NUMBER_MODE,n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],illegal:"\\S"}}n.exports=a}}]); -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/BottomSheetShadow.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct BottomSheetShadow: Equatable, Sendable { 5 | 6 | public static let `default` = Self() 7 | 8 | public let offset: CGSize 9 | public let radius: CGFloat 10 | public let color: UIColor? 11 | public let opacity: Float 12 | public let shouldRasterize: Bool 13 | 14 | public init( 15 | offset: CGSize = CGSize(width: .zero, height: -3), 16 | radius: CGFloat = 3.0, 17 | color: UIColor? = .black, 18 | opacity: Float = .zero, 19 | shouldRasterize: Bool = false 20 | ) { 21 | self.offset = offset 22 | self.radius = radius 23 | self.color = color 24 | self.opacity = opacity 25 | self.shouldRasterize = shouldRasterize 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Sources/Deeplink/Errors/DeeplinkWarningsError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal struct DeeplinkWarningsError: DeeplinkError { 4 | 5 | internal var description: String { 6 | let warnings = warnings 7 | .map { "\($0)" } 8 | .joined(separator: .newLine) 9 | .indented(spaces: 2) 10 | 11 | return """ 12 | Failed to handle deeplink of \(deeplinkType) type with warnings: 13 | \(warnings) 14 | """ 15 | } 16 | 17 | internal var isWarning: Bool { 18 | true 19 | } 20 | 21 | internal let deeplinkType: Any.Type 22 | internal let warnings: [Error] 23 | 24 | internal init( 25 | deeplinkType: Any.Type, 26 | warnings: [Error] 27 | ) { 28 | self.deeplinkType = deeplinkType 29 | self.warnings = warnings 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Generic/Refresh/ScreenRefreshAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct ScreenRefreshAction: ScreenAction { 4 | 5 | public typealias Output = Void 6 | 7 | public init() { } 8 | 9 | public func perform( 10 | container: Container, 11 | navigator: ScreenNavigator, 12 | completion: @escaping Completion 13 | ) { 14 | guard let refreshableContainer = container as? ScreenRefreshableContainer else { 15 | return completion(.containerTypeMismatch(container, type: ScreenRefreshableContainer.self, for: self)) 16 | } 17 | 18 | refreshableContainer.refresh { completion(.success) } 19 | } 20 | } 21 | 22 | extension ScreenThenable { 23 | 24 | public func refresh() -> Self { 25 | then(ScreenRefreshAction()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/Deeplink/Notification/Errors/NotificationDeeplinkInvalidUserInfoError.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UserNotifications) && os(iOS) 2 | import Foundation 3 | import UserNotifications 4 | 5 | /// Failed to extract userInfo from response to the notification. 6 | public struct NotificationDeeplinkInvalidUserInfoError: DeeplinkError { 7 | 8 | public let description: String 9 | 10 | /// Creates an error. 11 | /// 12 | /// - Parameters: 13 | /// - response: Response to the notification that caused the error. 14 | /// - trigger: The deeplink that caused the error. 15 | public init( 16 | response: UNNotificationResponse, 17 | for trigger: Any 18 | ) { 19 | description = """ 20 | Failed to extract userInfo from response to the notification for: 21 | \(trigger) 22 | """ 23 | } 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Brand Assets.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "App Icon - App Store.imagestack", 5 | "idiom" : "tv", 6 | "role" : "primary-app-icon", 7 | "size" : "1280x768" 8 | }, 9 | { 10 | "filename" : "App Icon.imagestack", 11 | "idiom" : "tv", 12 | "role" : "primary-app-icon", 13 | "size" : "400x240" 14 | }, 15 | { 16 | "filename" : "Top Shelf Image Wide.imageset", 17 | "idiom" : "tv", 18 | "role" : "top-shelf-image-wide", 19 | "size" : "2320x720" 20 | }, 21 | { 22 | "filename" : "Top Shelf Image.imageset", 23 | "idiom" : "tv", 24 | "role" : "top-shelf-image", 25 | "size" : "1920x720" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Scripts/generate-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly helpers_path="$( cd "$( dirname "$0" )" && pwd )/Helpers" 4 | readonly target_name="Nivelir iOS" 5 | readonly derived_data_path="DerivedData" 6 | readonly docs_path="docs" 7 | readonly docarchive_file_path="${derived_data_path}/Build/Products/Debug-iphonesimulator/Nivelir.doccarchive" 8 | readonly url_base_path="Nivelir" 9 | 10 | source "${helpers_path}/script-paths.sh" 11 | 12 | cd $root_path 13 | 14 | xcodebuild docbuild \ 15 | -scheme "${target_name}" \ 16 | -derivedDataPath "${derived_data_path}" \ 17 | -destination 'platform=iOS Simulator,name=iPhone 13' 18 | 19 | rm -rf "${docs_path}" 20 | 21 | $(xcrun --find docc) process-archive \ 22 | transform-for-static-hosting "${docarchive_file_path}" \ 23 | --output-path "${docs_path}" \ 24 | --hosting-base-path "${url_base_path}" 25 | 26 | rm -rf "${derived_data_path}" 27 | -------------------------------------------------------------------------------- /Sources/Addons/MediaPicker/MediaPickerManager.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | internal final class MediaPickerManager: 5 | NSObject, 6 | UINavigationControllerDelegate, 7 | UIImagePickerControllerDelegate { 8 | 9 | nonisolated private let proxy: MediaPickerProxy 10 | 11 | internal init(mediaPicker: MediaPicker) { 12 | self.proxy = MediaPickerProxy(mediaPicker: mediaPicker) 13 | } 14 | 15 | internal override func responds(to aSelector: Selector?) -> Bool { 16 | super.responds(to: aSelector) || proxy.responds(to: aSelector) == true 17 | } 18 | 19 | internal override func forwardingTarget(for aSelector: Selector?) -> Any? { 20 | if proxy.responds(to: aSelector) == true { 21 | return proxy 22 | } 23 | 24 | return super.forwardingTarget(for: aSelector) 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UIWindow+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIWindow { 5 | 6 | /// The root container for the window. 7 | /// 8 | /// The root container provides the content view of the window. 9 | /// Assigning a container to this property (either programmatically or using Interface Builder) 10 | /// installs the container’s view as the content view of the window. 11 | /// The new content view is configured to track the window size, changing as the window size changes. 12 | /// If the window has an existing view hierarchy, the old views are removed before the new ones are installed. 13 | /// 14 | /// The default value of this property is `nil`. 15 | /// 16 | /// - SeeAlso: `rootViewController` 17 | public var root: UIViewController? { 18 | rootViewController 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS X files 2 | .DS_Store 3 | .DS_Store? 4 | .Trashes 5 | .Spotlight-V100 6 | *.swp 7 | 8 | ## Xcode build files 9 | DerivedData/ 10 | build/ 11 | 12 | ## Xcode private settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | # Swift Packages Manager 40 | Packages 41 | .build 42 | .swiftpm 43 | 44 | # CocoaPods 45 | Pods/ 46 | 47 | # Carthage 48 | Carthage/Build 49 | 50 | # fastlane 51 | fastlane/report.xml 52 | fastlane/Preview.html 53 | fastlane/screenshots/**/*.png 54 | fastlane/test_output 55 | -------------------------------------------------------------------------------- /Sources/Addons/Alert/AlertTextField.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Types of text fields added to the alert. 5 | @frozen 6 | public enum AlertTextField: Sendable { 7 | 8 | /// A text field, with text and placeholder customization. 9 | case standard( 10 | text: String, 11 | placeholder: String? = nil 12 | ) 13 | 14 | /// A text field, customizable via block for configuring the text field prior to displaying the alert. 15 | /// This block has no return value and takes a single parameter corresponding to the text field object. 16 | /// Use that parameter to change the text field properties. 17 | case custom(configuration: @Sendable (UITextField) -> Void) 18 | } 19 | 20 | extension AlertTextField { 21 | 22 | /// A text field, with empty text and a placeholder. 23 | public static let standard = Self.standard(text: "") 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/Screen/Payload/ScreenPayloadedContainer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A screen container that can store navigation data. 4 | /// 5 | /// This protocol is used in screen decorators, which can generate data 6 | /// that needs to be stored in memory until the container itself is released. 7 | /// 8 | /// This protocol is already implemented by the `UIViewController` class and all its subclasses. 9 | /// The default implementation for `NSObject` subclasses uses the `objc_setAssociatedObject` method 10 | /// to associate the payload with the container. 11 | /// 12 | /// - SeeAlso: `ScreenContainer` 13 | @MainActor 14 | public protocol ScreenPayloadedContainer: ScreenContainer { 15 | 16 | /// Screen payload. 17 | /// 18 | /// This object stores the data generated by navigation. 19 | /// 20 | /// - SeeAlso: `ScreenPayload` 21 | var screenPayload: ScreenPayload { get } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Tools/URLQueryDecoder/URLQueryComponent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal indirect enum URLQueryComponent { 4 | 5 | case string(String) 6 | case array([Int: Self]) 7 | case dictionary([String: Self]) 8 | 9 | internal var string: String? { 10 | switch self { 11 | case let .string(string): 12 | return string 13 | 14 | default: 15 | return nil 16 | } 17 | } 18 | 19 | internal var array: [Int: Self]? { 20 | switch self { 21 | case let .array(array): 22 | return array 23 | 24 | default: 25 | return nil 26 | } 27 | } 28 | 29 | internal var dictionary: [String: Self]? { 30 | switch self { 31 | case let .dictionary(dictionary): 32 | return dictionary 33 | 34 | default: 35 | return nil 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/swift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 4 | 5 | source "${script_path}/common.sh" 6 | 7 | echo "Checking ${swift_style}Swift${default_style} version:" 8 | 9 | if [[ "$(uname -m)" == "arm64" ]]; then 10 | eval "$(/opt/homebrew/bin/brew shellenv)" 11 | fi 12 | 13 | eval "$(swiftenv init -)" 14 | 15 | readonly swift_required_version=$(cat "${root_path}"/.swift-version) 16 | readonly swift_versions=($(swiftenv versions 2>&1)) 17 | 18 | if [[ " ${swift_versions[@]} " =~ " ${swift_required_version} " ]]; then 19 | echo " Required Swift version ($swift_required_version) already installed." 20 | else 21 | echo " Required Swift version ($swift_required_version) not found. Installing..." 22 | assert_failure '(cd "${root_path}" && swiftenv install $swift_required_version)' 23 | assert_warning '(cd "${root_path}" && swiftenv rehash)' 24 | fi 25 | 26 | echo "" 27 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/BottomSheetCard.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct BottomSheetCard: Equatable, Sendable { 5 | 6 | public static let `default` = Self() 7 | 8 | public let backgroundColor: UIColor? 9 | public let cornerRadius: CGFloat 10 | public let border: BottomSheetBorder? 11 | public let shadow: BottomSheetShadow? 12 | public let contentInsets: UIEdgeInsets 13 | 14 | public init( 15 | backgroundColor: UIColor? = nil, 16 | cornerRadius: CGFloat = 16.0, 17 | border: BottomSheetBorder? = nil, 18 | shadow: BottomSheetShadow? = nil, 19 | contentInsets: UIEdgeInsets = .zero 20 | ) { 21 | self.backgroundColor = backgroundColor 22 | self.cornerRadius = cornerRadius 23 | self.border = border 24 | self.shadow = shadow 25 | self.contentInsets = contentInsets 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/swiftenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly arguments=$@ 4 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 5 | 6 | source "${script_path}/common.sh" 7 | 8 | readonly shell_init_line='if which swiftenv > /dev/null; then eval "$(swiftenv init -)"; fi' 9 | 10 | setup_shell() { 11 | local shell_profile_path=$1 12 | 13 | if [[ ! -f "${shell_profile_path}" ]]; then 14 | > "${shell_profile_path}" 15 | fi 16 | 17 | if [[ $(grep -L "${shell_init_line}" "${shell_profile_path}") ]]; then 18 | echo "${shell_init_line}" >> "${shell_profile_path}" 19 | fi 20 | } 21 | 22 | echo "Checking ${swiftenv_style}swiftenv${default_style} installation:" 23 | 24 | brew_install_if_needed kylef/formulae/swiftenv "$arguments" 25 | setup_shell "${HOME}/.zshrc" 26 | 27 | if [[ -f "${HOME}/.bash_profile" ]]; then 28 | setup_shell "${HOME}/.bash_profile" 29 | fi 30 | 31 | eval "$(swiftenv init -)" 32 | 33 | echo "" 34 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Colors/Icon.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.200", 9 | "green" : "0.200", 10 | "red" : "0.200" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Colors/Title.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.200", 9 | "green" : "0.200", 10 | "red" : "0.200" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Colors/Unimportant.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.557", 9 | "green" : "0.557", 10 | "red" : "0.557" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.643", 27 | "green" : "0.643", 28 | "red" : "0.643" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Content/ProgressContentView.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// The requirements for a content view that you create using a content. 5 | /// 6 | /// This protocol provides a blueprint for a content view object that renders the content that you define. 7 | /// The content view’s content encapsulates all of the supported properties 8 | /// and behaviors for content view customization. 9 | public protocol ProgressContentView: UIView { 10 | 11 | /// The content type associated with the view. 12 | /// The content must be associated with this view type, creating a one-to-one relationship. 13 | associatedtype Content: ProgressContent 14 | 15 | /// The current content of the view. 16 | var content: Content { get } 17 | 18 | /// Creating a view with content. 19 | /// - Parameter content: Content for the initial setup of the view. 20 | init(content: Content) 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.314", 9 | "green" : "0.196", 10 | "red" : "0.843" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.314", 27 | "green" : "0.196", 28 | "red" : "0.843" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Colors/Background.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.129", 27 | "green" : "0.129", 28 | "red" : "0.129" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/NivelirExample/Resources/Assets.xcassets/Colors/Important.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.314", 9 | "green" : "0.196", 10 | "red" : "0.843" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.314", 27 | "green" : "0.196", 28 | "red" : "0.843" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/NivelirExample/Services/Authorization/DefaultAuthorizationService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class DefaultAuthorizationService: AuthorizationService { 4 | 5 | var isAuthorized: Bool { 6 | get { UserDefaults.standard.bool(forKey: #function) } 7 | set { UserDefaults.standard.set(newValue, forKey: #function) } 8 | } 9 | 10 | func login( 11 | phoneNumber: String, 12 | completion: @escaping (_ result: Result) -> Void 13 | ) { 14 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 15 | let random = Int.random(in: 1...10) 16 | 17 | guard random < 8 else { 18 | return completion(.failure(AuthorizationError.unavailable)) 19 | } 20 | 21 | self.isAuthorized = true 22 | 23 | completion(.success(Void())) 24 | } 25 | } 26 | 27 | func logout() { 28 | self.isAuthorized = false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/CACornerMask+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension CACornerMask { 5 | 6 | internal static var allCorners: CACornerMask { 7 | [ 8 | .layerMinXMinYCorner, 9 | .layerMinXMaxYCorner, 10 | .layerMaxXMinYCorner, 11 | .layerMaxXMaxYCorner 12 | ] 13 | } 14 | 15 | internal var rectCorners: UIRectCorner { 16 | let cornerMaskMap: KeyValuePairs = [ 17 | .layerMinXMinYCorner: .topLeft, 18 | .layerMinXMaxYCorner: .bottomLeft, 19 | .layerMaxXMinYCorner: .topRight, 20 | .layerMaxXMaxYCorner: .bottomRight 21 | ] 22 | 23 | return cornerMaskMap 24 | .lazy 25 | .filter { contains($0.key) } 26 | .reduce(into: UIRectCorner()) { result, corner in 27 | result.insert(corner.value) 28 | } 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /docs/js/highlight-js-diff.62d66733.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This source file is part of the Swift.org open source project 3 | * 4 | * Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | * Licensed under Apache License v2.0 with Runtime Library Exception 6 | * 7 | * See https://swift.org/LICENSE.txt for license information 8 | * See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["highlight-js-diff"],{"48b8":function(e,n){function a(e){const n=e.regex;return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,match:n.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/)},{className:"comment",variants:[{begin:n.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/),end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/,end:/$/}]}}e.exports=a}}]); -------------------------------------------------------------------------------- /Sources/Addons/MediaPicker/MediaPickerProxy.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | internal final class MediaPickerProxy: NSObject, Sendable { 5 | 6 | private let mediaPicker: MediaPicker 7 | 8 | internal init(mediaPicker: MediaPicker) { 9 | self.mediaPicker = mediaPicker 10 | } 11 | 12 | @MainActor 13 | @objc internal func imagePickerController( 14 | _ picker: UIImagePickerController, 15 | didFinishPickingMediaWithInfo rawInfo: [String: Any] 16 | ) { 17 | var info: [UIImagePickerController.InfoKey: Any] = [:] 18 | 19 | for (key, value) in rawInfo { 20 | info[UIImagePickerController.InfoKey(rawValue: key)] = value 21 | } 22 | 23 | mediaPicker.didFinish(MediaPickerResult(info: info)) 24 | } 25 | 26 | @MainActor 27 | @objc internal func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 28 | mediaPicker.didFinish(nil) 29 | } 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/ruby.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 4 | 5 | source "${script_path}/common.sh" 6 | 7 | echo "Checking ${ruby_style}Ruby${default_style} version:" 8 | 9 | if [[ "$(uname -m)" == "arm64" ]]; then 10 | eval "$(/opt/homebrew/bin/brew shellenv)" 11 | fi 12 | 13 | eval "$(rbenv init -)" 14 | 15 | readonly ruby_required_version=$(cat "${root_path}"/.ruby-version) 16 | readonly ruby_versions=($(rbenv versions 2>&1)) 17 | readonly ruby_install_flags="-Wno-error=implicit-function-declaration" 18 | 19 | if [[ " ${ruby_versions[@]} " =~ " ${ruby_required_version} " ]]; then 20 | echo " Required Ruby version ($ruby_required_version) already installed." 21 | else 22 | echo " Required Ruby version ($ruby_required_version) not found. Installing..." 23 | assert_failure '(cd "${root_path}" && RUBY_CFLAGS="${ruby_install_flags}" rbenv install $ruby_required_version)' 24 | assert_warning '(cd "${root_path}" && rbenv rehash)' 25 | fi 26 | 27 | echo "" 28 | -------------------------------------------------------------------------------- /Example/NivelirExample/Routing/Sharing/SharingNivelirLinkItem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Nivelir 3 | 4 | struct SharingNivelirLinkItem: SharingCustomItem { 5 | 6 | let url = URL(string: "https://github.com/hhru/Nivelir")! 7 | 8 | var placeholder: Any { 9 | url 10 | } 11 | 12 | func value(for activityType: SharingActivityType?) -> Any? { 13 | guard let activityType else { 14 | return url 15 | } 16 | 17 | switch activityType { 18 | case .copyToPasteboard, .openInBrowser: 19 | return url 20 | 21 | default: 22 | return """ 23 | Nivelir: \(url) 24 | 25 | Send from iOS device. 26 | """ 27 | } 28 | } 29 | 30 | func subject(for activityType: SharingActivityType?) -> String? { 31 | "Nivelir: \(url)" 32 | } 33 | } 34 | 35 | extension SharingItem { 36 | 37 | static var nivelirLink: Self { 38 | .custom(SharingNivelirLinkItem()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Addons/Alert/AlertTextFieldsManager.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | internal final class AlertTextFieldsManager: NSObject { 6 | 7 | private let textFields: [UITextField?] 8 | private let textsChanged: (_ texts: [String]) -> Void 9 | 10 | internal init( 11 | textFields: [UITextField?], 12 | textsChanged: @escaping (_ texts: [String]) -> Void 13 | ) { 14 | self.textFields = textFields 15 | self.textsChanged = textsChanged 16 | 17 | super.init() 18 | 19 | textFields.forEach { textField in 20 | textField?.addTarget( 21 | self, 22 | action: #selector(handleTextFieldEditingChanged), 23 | for: .editingChanged 24 | ) 25 | } 26 | 27 | handleTextFieldEditingChanged() 28 | } 29 | 30 | @objc private func handleTextFieldEditingChanged() { 31 | textsChanged(textFields.map { $0?.text ?? "" }) 32 | } 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/Screen/Errors/ScreenContainerNotFoundError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// No container found. 4 | /// 5 | /// This error occurs whenever an action fails to find the container. 6 | public struct ScreenContainerNotFoundError: ScreenError { 7 | 8 | public let description: String 9 | 10 | /// Creates an error. 11 | /// 12 | /// - Parameters: 13 | /// - type: The type of container that could not be found. 14 | /// - trigger: The action that caused the error. 15 | public init(type: Any.Type, for trigger: Any) { 16 | description = """ 17 | No container of \(type) type found for: 18 | \(trigger) 19 | """ 20 | } 21 | } 22 | 23 | extension Result where Failure == Error { 24 | 25 | internal static func containerNotFound(type: Any.Type, for trigger: Any) -> Self { 26 | .failure( 27 | ScreenContainerNotFoundError( 28 | type: type, 29 | for: trigger 30 | ) 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UINavigationController+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UINavigationController { 5 | 6 | /// The root container of the stack. 7 | /// 8 | /// - SeeAlso: `viewControllers` 9 | public var stackRoot: UIViewController? { 10 | viewControllers.first 11 | } 12 | 13 | /// The container at the top of the stack. 14 | /// 15 | /// - SeeAlso: `topViewController` 16 | public var stackTop: UIViewController? { 17 | topViewController 18 | } 19 | 20 | /// The container associated with the currently visible view in the navigation interface. 21 | /// 22 | /// The currently visible container can belong either to the container at the top of the stack 23 | /// or to a container that was presented modally on top of the stack container itself. 24 | /// 25 | /// - SeeAlso: `visibleViewController` 26 | public var stackVisible: UIViewController? { 27 | visibleViewController 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /Sources/Tools/NotificationObserver.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class NotificationObserver { 4 | 5 | private var token: NSObjectProtocol? 6 | 7 | internal let center: NotificationCenter 8 | internal let name: Notification.Name 9 | internal let queue: OperationQueue? 10 | internal let handler: (@Sendable (_ notification: Notification) -> Void)? 11 | 12 | internal init( 13 | center: NotificationCenter = .default, 14 | name: Notification.Name, 15 | queue: OperationQueue? = nil, 16 | handler: (@Sendable (_ notification: Notification) -> Void)? 17 | ) { 18 | self.center = center 19 | self.name = name 20 | self.queue = queue 21 | self.handler = handler 22 | 23 | self.token = center.addObserver(forName: name, object: nil, queue: queue) { notification in 24 | handler?(notification) 25 | } 26 | } 27 | 28 | deinit { 29 | if let token { 30 | center.removeObserver(token) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Screen/Any/AnyScreenBox.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal final class AnyScreenBox< 4 | Wrapped: Screen, 5 | Container: ScreenContainer 6 | >: AnyScreenBaseBox { 7 | 8 | internal typealias Builder = ( 9 | _ screen: Wrapped, 10 | _ navigator: ScreenNavigator 11 | ) -> Container 12 | 13 | private let wrapped: Wrapped 14 | private let builder: Builder 15 | 16 | internal override var name: String { 17 | wrapped.name 18 | } 19 | 20 | internal override var traits: Set { 21 | wrapped.traits 22 | } 23 | 24 | private let _description: String 25 | internal override var description: String { 26 | _description 27 | } 28 | 29 | internal init(_ wrapped: Wrapped, builder: @escaping Builder) { 30 | self.wrapped = wrapped 31 | self.builder = builder 32 | _description = wrapped.description 33 | } 34 | 35 | internal override func build(navigator: ScreenNavigator) -> Container { 36 | builder(wrapped, navigator) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Tools/URLQueryDecoder/Options/URLQueryDateDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The strategies available for formatting dates when decoding them from URL. 4 | @frozen 5 | public enum URLQueryDateDecodingStrategy { 6 | 7 | /// The strategy that uses formatting from the Date structure. 8 | case deferredToDate 9 | 10 | /// The strategy that decodes dates in terms of seconds since midnight UTC on January 1st, 1970. 11 | case secondsSince1970 12 | 13 | /// The strategy that decodes dates in terms of milliseconds since midnight UTC on January 1st, 1970. 14 | case millisecondsSince1970 15 | 16 | /// The strategy that formats dates according to the ISO 8601 standard. 17 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) 18 | case iso8601 19 | 20 | /// The strategy that defers formatting settings to a supplied date formatter. 21 | case formatted(DateFormatter) 22 | 23 | /// The strategy that formats custom dates by calling a user-defined function. 24 | case custom((_ decoder: Decoder) throws -> Date) 25 | } 26 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/Reusable/UITableView+Reusable.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UITableView { 4 | 5 | func registerReusableHeaderFooterView(of type: T.Type) { 6 | register(type, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier) 7 | } 8 | 9 | func dequeueReusableHeaderFooterView(of type: T.Type) -> T { 10 | dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as! T 11 | } 12 | 13 | func registerReusableCell(of type: T.Type) { 14 | register(type, forCellReuseIdentifier: T.reuseIdentifier) 15 | } 16 | 17 | func dequeueReusableCell(of type: T.Type) -> T { 18 | dequeueReusableCell(withIdentifier: T.reuseIdentifier) as! T 19 | } 20 | 21 | func dequeueReusableCell(of type: T.Type, for indexPath: IndexPath) -> T { 22 | dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/mint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly arguments=$@ 4 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 5 | 6 | source "${script_path}/common.sh" 7 | 8 | readonly shell_mint_path_line="export MINT_PATH=\"\$HOME/.mint\"" 9 | readonly shell_mint_link_path_line="export MINT_LINK_PATH=\"\$MINT_PATH/bin\"" 10 | 11 | setup_shell() { 12 | local shell_profile_path=$1 13 | 14 | if [[ ! -f "${shell_profile_path}" ]]; then 15 | > "${shell_profile_path}" 16 | fi 17 | 18 | if [[ $(grep -L "${shell_mint_path_line}" "${shell_profile_path}") ]]; then 19 | echo "${shell_mint_path_line}" >> "${shell_profile_path}" 20 | fi 21 | 22 | if [[ $(grep -L "${shell_mint_link_path_line}" "${shell_profile_path}") ]]; then 23 | echo "${shell_mint_link_path_line}" >> "${shell_profile_path}" 24 | fi 25 | } 26 | 27 | echo "Checking ${mint_style}Mint${default_style} installation:" 28 | 29 | brew_install_if_needed mint "$arguments" 30 | setup_shell "${HOME}/.zshrc" 31 | 32 | if [[ -f "${HOME}/.bash_profile" ]]; then 33 | setup_shell "${HOME}/.bash_profile" 34 | fi 35 | 36 | echo "" 37 | -------------------------------------------------------------------------------- /Sources/Screen/Navigator/Iterator/ScreenIterationPredicate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A closure box that takes a container as its argument and returns a `ScreenIterationResult` value, 4 | /// indicating whether to continue or stop iterating. 5 | public struct ScreenIterationPredicate { 6 | 7 | private let box: (_ container: ScreenContainer) -> ScreenIterationResult 8 | 9 | /// - Parameter box: A closure that takes a container as its argument and returns a `ScreenIterationResult` value, 10 | /// indicating whether to continue or stop iterating. 11 | public init(_ box: @escaping (_ container: ScreenContainer) -> ScreenIterationResult) { 12 | self.box = box 13 | } 14 | 15 | /// Returns a ScreenIterationResult value that indicates whether to continue or stop iterating. 16 | /// - Parameter container: The container against which to evaluate the predicate. 17 | /// - Returns: `ScreenIterationResult` with the iteration result. 18 | public func checkContainer(_ container: ScreenContainer) -> ScreenIterationResult { 19 | box(container) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Tools/DictionaryDecoder/Options/DictionaryDateDecodingStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The strategies available for formatting dates when decoding them from Dictionary. 4 | @frozen 5 | public enum DictionaryDateDecodingStrategy { 6 | 7 | /// The strategy that uses formatting from the Date structure. 8 | case deferredToDate 9 | 10 | /// The strategy that decodes dates in terms of seconds since midnight UTC on January 1st, 1970. 11 | case secondsSince1970 12 | 13 | /// The strategy that decodes dates in terms of milliseconds since midnight UTC on January 1st, 1970. 14 | case millisecondsSince1970 15 | 16 | /// The strategy that formats dates according to the ISO 8601 standard. 17 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) 18 | case iso8601 19 | 20 | /// The strategy that defers formatting settings to a supplied date formatter. 21 | case formatted(DateFormatter) 22 | 23 | /// The strategy that formats custom dates by calling a user-defined function. 24 | case custom(@Sendable (_ decoder: Decoder) throws -> Date) 25 | } 26 | -------------------------------------------------------------------------------- /Example/NivelirExample/Dependencies/Services.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | struct Services { 5 | 6 | private let container = ServiceContainer() 7 | 8 | let window: UIWindow 9 | 10 | @MainActor 11 | func screenNavigator() -> ScreenNavigator { 12 | container.shared { 13 | ScreenNavigator(window: window) 14 | } 15 | } 16 | 17 | @MainActor 18 | func deeplinkManager() -> DeeplinkManager { 19 | container.shared { 20 | DeeplinkManager( 21 | deeplinkTypes: [ 22 | ChatDeeplink.self 23 | ], 24 | navigator: screenNavigator() 25 | ) 26 | } 27 | } 28 | 29 | func authorizationService() -> AuthorizationService { 30 | container.shared { 31 | DefaultAuthorizationService() 32 | } 33 | } 34 | 35 | @MainActor 36 | func profileService() -> ProfileService { 37 | DefaultProfileService(authorizationService: authorizationService()) 38 | } 39 | } 40 | 41 | extension Services: ScreenAuthorizeActionServices { } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 HeadHunter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/Addons/Sharing/Item/SharingItem.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | @frozen 5 | public enum SharingItem: CustomStringConvertible, @unchecked Sendable { 6 | 7 | case regular(Any) 8 | case custom(SharingCustomItem) 9 | 10 | public var description: String { 11 | switch self { 12 | case let .regular(value): 13 | return "\(value)" 14 | 15 | case let .custom(value): 16 | return "\(value)" 17 | } 18 | } 19 | 20 | internal var activityItem: Any { 21 | switch self { 22 | case let .regular(value): 23 | return value 24 | 25 | case let .custom(value): 26 | return SharingItemManager(item: value) 27 | } 28 | } 29 | 30 | internal init(activityItem: Any) { 31 | switch activityItem { 32 | case let item as SharingCustomItem: 33 | self = .custom(item) 34 | 35 | case let manager as SharingItemManager: 36 | self = .custom(manager.item) 37 | 38 | default: 39 | self = .regular(activityItem) 40 | } 41 | } 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /docs/img/modified-icon.f496e73d.svg: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /Sources/Screen/Errors/ScreenContainerNotSupportedError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The container type is not supported. 4 | /// 5 | /// This error occurs whenever an action handles an unsupported container type. 6 | public struct ScreenContainerNotSupportedError: ScreenError { 7 | 8 | public let description: String 9 | 10 | /// Creates an error. 11 | /// 12 | /// - Parameters: 13 | /// - container: Container that does not match the expected type. 14 | /// - trigger: The action that caused the error. 15 | public init(container: ScreenContainer, for trigger: Any) { 16 | description = """ 17 | The type of the container \(container) is not supported for: 18 | \(trigger) 19 | """ 20 | } 21 | } 22 | 23 | extension Result where Failure == Error { 24 | 25 | internal static func containerNotSupported( 26 | _ container: ScreenContainer, 27 | for trigger: Any 28 | ) -> Self { 29 | .failure( 30 | ScreenContainerNotSupportedError( 31 | container: container, 32 | for: trigger 33 | ) 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Any/AnyScreenActionBaseBox.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class AnyScreenActionBaseBox: 4 | ScreenAction, 5 | CustomStringConvertible { 6 | 7 | internal typealias Output = Output 8 | 9 | nonisolated internal var description: String { 10 | fatalError("\(#function) has not been implemented") 11 | } 12 | 13 | // swiftlint:disable:next unavailable_function 14 | internal func cast(to type: Action.Type) -> Action? { 15 | fatalError("\(#function) has not been implemented") 16 | } 17 | 18 | // swiftlint:disable:next unavailable_function 19 | internal func combine( 20 | with other: Action 21 | ) -> AnyScreenAction? { 22 | fatalError("\(#function) has not been implemented") 23 | } 24 | 25 | // swiftlint:disable:next unavailable_function 26 | internal func perform( 27 | container: Container, 28 | navigator: ScreenNavigator, 29 | completion: @escaping Completion 30 | ) { 31 | fatalError("\(#function) has not been implemented") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Addons/URL/Settings/ScreenOpenSettingsAction.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Action to open the system settings of the application. 5 | public struct ScreenOpenSettingsAction: ScreenAction { 6 | 7 | public typealias Output = Void 8 | 9 | public init() { } 10 | 11 | public func perform( 12 | container: Container, 13 | navigator: ScreenNavigator, 14 | completion: @escaping Completion 15 | ) { 16 | guard let url = URL(string: UIApplication.openSettingsURLString) else { 17 | return completion(.invalidOpenSettingsURL(for: self)) 18 | } 19 | 20 | ScreenOpenURLAction(url: url).perform( 21 | container: container, 22 | navigator: navigator, 23 | completion: completion 24 | ) 25 | } 26 | } 27 | 28 | extension ScreenThenable { 29 | 30 | /// The system launches the Settings app and displays the app’s custom settings, if it has any. 31 | /// - Returns: An instance containing the new action. 32 | public func openAppSettings() -> Self { 33 | then(ScreenOpenSettingsAction()) 34 | } 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/Screen/ScreenKey.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// А screen key that can be used to search for a container in the container hierarchy. 4 | /// 5 | /// Usually you don't need to create an instance of this type. 6 | /// It can be obtained from the `key` property of the `Screen` protocol. 7 | /// 8 | /// - SeeAlso: `Screen`. 9 | /// - SeeAlso: `ScreenKeyedContainer`. 10 | public struct ScreenKey: Hashable, CustomStringConvertible { 11 | 12 | /// Screen name. 13 | public let name: String 14 | 15 | /// Screen traits that are used to distinguish screens with the same name. 16 | public let traits: Set 17 | 18 | public let description: String 19 | 20 | /// Creates a screen key. 21 | /// 22 | /// - Parameters: 23 | /// - name: Screen name. 24 | /// - traits: Screen traits that are used to distinguish screens with the same name. 25 | public init(name: String, traits: Set = []) { 26 | self.name = name 27 | self.traits = traits 28 | let traits = self.traits.map { $0.base } 29 | description = traits.isEmpty 30 | ? name 31 | : "\(name)(\(traits))" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Interaction/Interactions/BottomSheetInteraction.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | @MainActor 5 | internal protocol BottomSheetInteraction { 6 | 7 | var gestureInitialValue: CGFloat { get } 8 | var currentDetentDelta: CGFloat { get } 9 | 10 | var canRecognizeGestures: Bool { get } 11 | 12 | func start( 13 | presentationController: BottomSheetPresentationController, 14 | gestureRecognizer: UIPanGestureRecognizer, 15 | simultaneousScrollView: UIScrollView? 16 | ) 17 | 18 | func update( 19 | presentationController: BottomSheetPresentationController, 20 | gestureRecognizer: UIPanGestureRecognizer, 21 | simultaneousScrollView: UIScrollView? 22 | ) 23 | 24 | func finish( 25 | presentationController: BottomSheetPresentationController, 26 | gestureRecognizer: UIPanGestureRecognizer, 27 | simultaneousScrollView: UIScrollView? 28 | ) 29 | 30 | func cancel( 31 | presentationController: BottomSheetPresentationController, 32 | gestureRecognizer: UIPanGestureRecognizer, 33 | simultaneousScrollView: UIScrollView? 34 | ) 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Window/ScreenMakeKeyAction.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Makes the receiver the key window. 5 | public struct ScreenMakeKeyAction: ScreenAction { 6 | 7 | public typealias Output = Void 8 | 9 | public init() { } 10 | 11 | public func perform( 12 | container: Container, 13 | navigator: ScreenNavigator, 14 | completion: @escaping Completion 15 | ) { 16 | container.makeKey() 17 | 18 | completion(.success) 19 | } 20 | } 21 | 22 | extension ScreenThenable where Current: UIWindow { 23 | 24 | /// Makes the receiver the key window. 25 | /// 26 | /// Usage examples 27 | /// ============== 28 | /// 29 | /// - Makes the window container of the current container the key window: 30 | /// 31 | /// ``` swift 32 | /// navigator.navigate(from: container) { route in 33 | /// route 34 | /// .window 35 | /// .makeKey() 36 | /// } 37 | /// ``` 38 | /// 39 | /// - Returns: An instance containing the new action. 40 | public func makeKey() -> Self { 41 | then(ScreenMakeKeyAction()) 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Landmark/LandmarkView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct LandmarkView: View { 4 | var body: some View { 5 | VStack { 6 | LandmarkMapView() 7 | .ignoresSafeArea(edges: .top) 8 | .frame(height: 300) 9 | 10 | LandmarkCircleImage() 11 | .offset(y: -130) 12 | .padding(.bottom, -130) 13 | 14 | VStack(alignment: .leading) { 15 | Text("Turtle Rock") 16 | .font(.title) 17 | 18 | HStack { 19 | Text("Joshua Tree National Park") 20 | Spacer() 21 | Text("California") 22 | } 23 | .font(.subheadline) 24 | .foregroundColor(.secondary) 25 | 26 | Divider() 27 | 28 | Text("About Turtle Rock").font(.title2) 29 | 30 | Text("Descriptive text goes here.") 31 | } 32 | .padding() 33 | 34 | Spacer() 35 | } 36 | } 37 | } 38 | 39 | struct ContentView_Previews: PreviewProvider { 40 | static var previews: some View { 41 | LandmarkView() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/ScreenHideHUDAction.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Action to hide the currently displayed ``HUD``. 5 | public struct ScreenHideHUDAction: ScreenAction { 6 | 7 | public typealias Output = Void 8 | 9 | public init() { } 10 | 11 | public func perform( 12 | container: Container, 13 | navigator: ScreenNavigator, 14 | completion: @escaping Completion 15 | ) { 16 | navigator.logInfo("Dismissing HUD presented by \(type(of: container))") 17 | 18 | guard let window = navigator.window else { 19 | return completion(.containerNotFound(type: UIWindow.self, for: self)) 20 | } 21 | 22 | HUD.hide(in: window) { 23 | completion(.success) 24 | } 25 | } 26 | } 27 | 28 | extension ScreenThenable { 29 | 30 | /// Finds the top-most HUD subview in window of navigator that hasn’t finished and hides it. 31 | /// The counterpart to this method is ``ScreenThenable/showHUD(_:animation:duration:)``. 32 | /// - Returns: An instance containing the new action. 33 | public func hideHUD() -> Self { 34 | then(ScreenHideHUDAction()) 35 | } 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Generic/ScreenGetAction.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct ScreenGetAction: ScreenAction { 4 | 5 | public typealias Output = Void 6 | 7 | public typealias Body = ( 8 | _ container: Container, 9 | _ completion: @escaping Completion 10 | ) -> Void 11 | 12 | public let body: Body 13 | 14 | public init(body: @escaping Body) { 15 | self.body = body 16 | } 17 | 18 | public init(body: @escaping (_ container: Container) -> Void) { 19 | self.init { container, completion in 20 | body(container) 21 | completion(.success) 22 | } 23 | } 24 | 25 | public func perform( 26 | container: Container, 27 | navigator: ScreenNavigator, 28 | completion: @escaping Completion 29 | ) { 30 | body(container, completion) 31 | } 32 | } 33 | 34 | extension ScreenThenable { 35 | 36 | public func get(body: @escaping ScreenGetAction.Body) -> Self { 37 | then(ScreenGetAction(body: body)) 38 | } 39 | 40 | public func get(body: @escaping (_ container: Current) -> Void) -> Self { 41 | then(ScreenGetAction(body: body)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Screen/Errors/ScreenContainerAlreadyPresentingError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The container is already presenting another container. 4 | /// 5 | /// This error occurs whenever an action fails to present a modal container on a container 6 | /// which is already presenting another container. 7 | public struct ScreenContainerAlreadyPresentingError: ScreenError { 8 | 9 | public let description: String 10 | 11 | /// Creates an error. 12 | /// 13 | /// - Parameters: 14 | /// - container: Container that is already presenting another screen. 15 | /// - trigger: The action that caused the error. 16 | public init(container: ScreenContainer, for trigger: Any) { 17 | description = """ 18 | The container \(container) is already presenting another container for: 19 | \(trigger) 20 | """ 21 | } 22 | } 23 | 24 | extension Result where Failure == Error { 25 | 26 | internal static func containerAlreadyPresenting( 27 | _ container: ScreenContainer, 28 | for trigger: Any 29 | ) -> Self { 30 | .failure( 31 | ScreenContainerAlreadyPresentingError( 32 | container: container, 33 | for: trigger 34 | ) 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/js/highlight-js-http.163e45b6.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This source file is part of the Swift.org open source project 3 | * 4 | * Copyright (c) 2021 Apple Inc. and the Swift project authors 5 | * Licensed under Apache License v2.0 with Runtime Library Exception 6 | * 7 | * See https://swift.org/LICENSE.txt for license information 8 | * See https://swift.org/CONTRIBUTORS.txt for Swift project authors 9 | */ 10 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["highlight-js-http"],{c01d:function(e,n){function a(e){const n=e.regex,a="HTTP/(2|1\\.[01])",s=/[A-Za-z][A-Za-z0-9-]*/,t={className:"attribute",begin:n.concat("^",s,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},i=[t,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+a+" \\d{3})",end:/$/,contains:[{className:"meta",begin:a},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:i}},{begin:"(?=^[A-Z]+ (.*?) "+a+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:a},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:i}},e.inherit(t,{relevance:0})]}}e.exports=a}}]); -------------------------------------------------------------------------------- /Sources/Addons/Sharing/Activity/SharingActivity.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | public struct SharingActivity { 5 | 6 | private typealias Body = (_ navigator: ScreenNavigator) -> UIActivity 7 | 8 | private let body: Body 9 | 10 | private init(body: @escaping Body) { 11 | self.body = body 12 | } 13 | 14 | internal func activity(navigator: ScreenNavigator) -> UIActivity { 15 | body(navigator) 16 | } 17 | } 18 | 19 | extension SharingActivity { 20 | 21 | public static func regular(_ value: UIActivity) -> Self { 22 | Self { _ in 23 | value 24 | } 25 | } 26 | 27 | @MainActor 28 | public static func custom(_ value: Value) -> Self { 29 | Self { navigator in 30 | SharingActivityManager( 31 | navigator: navigator, 32 | activity: value 33 | ) 34 | } 35 | } 36 | 37 | @MainActor 38 | public static func custom(_ value: Value) -> Self { 39 | Self { navigator in 40 | SharingActivityManager( 41 | navigator: navigator, 42 | activity: value 43 | ) 44 | } 45 | } 46 | } 47 | #endif 48 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Indicator/Failure/ProgressFailureIndicator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Progress indicator displaying an error with an animated cross. 5 | /// 6 | /// Use this object to customize the display of the cross in the view. 7 | public struct ProgressFailureIndicator: ProgressIndicator { 8 | 9 | public typealias View = ProgressFailureIndicatorView 10 | 11 | /// The default configuration. 12 | public static let `default` = Self() 13 | 14 | /// The stroke color for the shape path. 15 | public let color: UIColor 16 | 17 | /// Insets relative to the superview. 18 | public let insets: UIEdgeInsets 19 | 20 | public var logDescription: String? { 21 | ".failure" 22 | } 23 | 24 | /// Creates new indicator content. 25 | /// - Parameters: 26 | /// - color: The color used to stroke the shape’s path. 27 | /// - insets: Insets relative to the superview. 28 | public init( 29 | color: UIColor = .lightGray, 30 | insets: UIEdgeInsets = UIEdgeInsets( 31 | top: 24.0, 32 | left: 24.0, 33 | bottom: 24.0, 34 | right: 24.0 35 | ) 36 | ) { 37 | self.color = color 38 | self.insets = insets 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Indicator/Spinner/ProgressSpinnerIndicator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Progress indicator showing the spinner. 5 | /// 6 | /// Use this object to customize the display of the spinner in the view. 7 | public struct ProgressSpinnerIndicator: ProgressIndicator { 8 | 9 | public typealias View = ProgressSpinnerIndicatorView 10 | 11 | /// The default configuration. 12 | public static let `default` = Self() 13 | 14 | /// The color used to stroke the shape’s path. 15 | public let color: UIColor 16 | 17 | /// Additional indents to expand the container. 18 | public let insets: UIEdgeInsets 19 | 20 | public var logDescription: String? { 21 | ".spinner" 22 | } 23 | 24 | /// Creates new indicator content. 25 | /// - Parameters: 26 | /// - color: The color used to stroke the shape’s path. 27 | /// - insets: Additional indents to expand the container. 28 | public init( 29 | color: UIColor = .lightGray, 30 | insets: UIEdgeInsets = UIEdgeInsets( 31 | top: 20.0, 32 | left: 20.0, 33 | bottom: 20.0, 34 | right: 20.0 35 | ) 36 | ) { 37 | self.color = color 38 | self.insets = insets 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Indicator/Success/ProgressSuccessIndicator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// A progress indicator displaying an animated checkmark. 5 | /// 6 | /// Use this object to customize the display of the checkmark in the view. 7 | public struct ProgressSuccessIndicator: ProgressIndicator { 8 | 9 | public typealias View = ProgressSuccessIndicatorView 10 | 11 | /// The default configuration. 12 | public static let `default` = Self() 13 | 14 | /// The color used to stroke the shape’s path. 15 | public let color: UIColor 16 | 17 | /// Additional indents to expand the container. 18 | public let insets: UIEdgeInsets 19 | 20 | public var logDescription: String? { 21 | ".success" 22 | } 23 | 24 | /// Creates new indicator content. 25 | /// - Parameters: 26 | /// - color: The color used to stroke the shape’s path. 27 | /// - insets: Additional indents to expand the container. 28 | public init( 29 | color: UIColor = .lightGray, 30 | insets: UIEdgeInsets = UIEdgeInsets( 31 | top: 24.0, 32 | left: 24.0, 33 | bottom: 24.0, 34 | right: 24.0 35 | ) 36 | ) { 37 | self.color = color 38 | self.insets = insets 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/Addons/MediaPicker/MediaPickerCameraSettings.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) && os(iOS) 2 | import UIKit 3 | 4 | /// The object that defines the settings for the camera. 5 | public struct MediaPickerCameraSettings: Sendable { 6 | 7 | /// Default settings 8 | public static let `default` = Self() 9 | 10 | /// The camera used by the image picker controller. 11 | public let device: UIImagePickerController.CameraDevice 12 | 13 | /// The capture mode used by the camera. 14 | public let captureMode: UIImagePickerController.CameraCaptureMode 15 | 16 | /// The flash mode used by the active camera. 17 | public let flashMode: UIImagePickerController.CameraFlashMode 18 | 19 | /// - Parameters: 20 | /// - device: The camera used by the image picker controller. 21 | /// - captureMode: The capture mode used by the camera. 22 | /// - flashMode: The flash mode used by the active camera. 23 | public init( 24 | device: UIImagePickerController.CameraDevice = .rear, 25 | captureMode: UIImagePickerController.CameraCaptureMode = .photo, 26 | flashMode: UIImagePickerController.CameraFlashMode = .auto 27 | ) { 28 | self.device = device 29 | self.captureMode = captureMode 30 | self.flashMode = flashMode 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Indicator/Activity/ProgressActivityIndicator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// A progress indicator that displays an activity indicator. 5 | /// 6 | /// Use this object to customize the display of the activity indicator in the view. 7 | public struct ProgressActivityIndicator: ProgressIndicator { 8 | 9 | public typealias View = ProgressActivityIndicatorView 10 | 11 | /// The default configuration. 12 | public static let `default` = Self() 13 | 14 | /// The color of the activity indicator. 15 | public let color: UIColor 16 | 17 | /// Outset (or expanded) by the specified amount. 18 | public let insets: UIEdgeInsets 19 | 20 | public var logDescription: String? { 21 | ".activity" 22 | } 23 | 24 | /// Creates new indicator content. 25 | /// - Parameters: 26 | /// - color: The color of the activity indicator. 27 | /// - insets: Outset (or expanded) by the specified amount. 28 | public init( 29 | color: UIColor = .lightGray, 30 | insets: UIEdgeInsets = UIEdgeInsets( 31 | top: 25.5, 32 | left: 25.5, 33 | bottom: 25.5, 34 | right: 25.5 35 | ) 36 | ) { 37 | self.color = color 38 | self.insets = insets 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Window/ScreenMakeKeyAndVisibleAction.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Shows the window container and makes it the key window. 5 | public struct ScreenMakeKeyAndVisibleAction: ScreenAction { 6 | 7 | public typealias Output = Void 8 | 9 | public init() { } 10 | 11 | public func perform( 12 | container: Container, 13 | navigator: ScreenNavigator, 14 | completion: @escaping Completion 15 | ) { 16 | container.makeKeyAndVisible() 17 | 18 | completion(.success) 19 | } 20 | } 21 | 22 | extension ScreenThenable where Current: UIWindow { 23 | 24 | /// Shows the window container and makes it the key window. 25 | /// 26 | /// Usage examples 27 | /// ============== 28 | /// 29 | /// - Shows the window container of the current container and makes it the key window: 30 | /// 31 | /// ``` swift 32 | /// navigator.navigate(from: container) { route in 33 | /// route 34 | /// .window 35 | /// .makeKeyAndVisible() 36 | /// } 37 | /// ``` 38 | /// 39 | /// - Returns: An instance containing the new action. 40 | public func makeKeyAndVisible() -> Self { 41 | then(ScreenMakeKeyAndVisibleAction()) 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Sources/Screen/Errors/ScreenContainerTypeMismatchError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// The type of the container does not match the expected type. 4 | /// 5 | /// This error occurs whenever an action fails to cast the container to the expected type. 6 | public struct ScreenContainerTypeMismatchError: ScreenError { 7 | 8 | public let description: String 9 | 10 | /// Creates an error. 11 | /// 12 | /// - Parameters: 13 | /// - container: Container that does not match the expected type. 14 | /// - type: Expected container type. 15 | /// - trigger: The action that caused the error. 16 | public init(container: ScreenContainer, type: Any.Type, for trigger: Any) { 17 | description = """ 18 | The type of the container \(container) does not match the expected type \(type) for: 19 | \(trigger) 20 | """ 21 | } 22 | } 23 | 24 | extension Result where Failure == Error { 25 | 26 | internal static func containerTypeMismatch( 27 | _ container: ScreenContainer, 28 | type: Any.Type, 29 | for trigger: Any 30 | ) -> Self { 31 | .failure( 32 | ScreenContainerTypeMismatchError( 33 | container: container, 34 | type: type, 35 | for: trigger 36 | ) 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UIScrollView+Extenions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UIScrollView { 5 | 6 | internal var isScrolledVertically: Bool { 7 | contentOffset.y >= -adjustedContentInset.top + UIScreen.main.pixelSize 8 | } 9 | 10 | internal var canScrollVertically: Bool { 11 | if alwaysBounceVertical { 12 | return true 13 | } 14 | 15 | return contentSize.height >= frame.height 16 | - adjustedContentInset.vertical 17 | + UIScreen.main.pixelSize 18 | } 19 | 20 | internal var canScrollHorizontally: Bool { 21 | if alwaysBounceHorizontal { 22 | return true 23 | } 24 | 25 | return contentSize.width >= frame.width 26 | - adjustedContentInset.horizontal 27 | + UIScreen.main.pixelSize 28 | } 29 | 30 | internal func resetVerticalScroll(finishing: Bool) { 31 | setContentOffset( 32 | CGPoint( 33 | x: contentOffset.x, 34 | y: -adjustedContentInset.top 35 | ), 36 | animated: false 37 | ) 38 | 39 | if finishing, panGestureRecognizer.isEnabled { 40 | panGestureRecognizer.isEnabled = false 41 | panGestureRecognizer.isEnabled = true 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /docs/theme-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": {}, 3 | "theme": { 4 | "colors": { 5 | "text": "", 6 | "text-background": "", 7 | "grid": "", 8 | "article-background": "", 9 | "generic-modal-background": "", 10 | "secondary-label": "", 11 | "header-text": "", 12 | "not-found": { 13 | "input-border": "" 14 | }, 15 | "runtime-preview": { 16 | "text": "" 17 | }, 18 | "tabnav-item": { 19 | "border-color": "" 20 | }, 21 | "svg-icon": { 22 | "fill-light": "", 23 | "fill-dark": "" 24 | }, 25 | "loading-placeholder": { 26 | "background": "" 27 | }, 28 | "button": { 29 | "text": "", 30 | "light": { 31 | "background": "", 32 | "backgroundHover": "", 33 | "backgroundActive": "" 34 | }, 35 | "dark": { 36 | "background": "", 37 | "backgroundHover": "", 38 | "backgroundActive": "" 39 | } 40 | }, 41 | "link": null 42 | }, 43 | "style": { 44 | "button": { 45 | "borderRadius": null 46 | } 47 | }, 48 | "typography": { 49 | "html-font": "" 50 | } 51 | }, 52 | "features": { 53 | "docs": { 54 | "summary": { 55 | "hide": false 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Tabs/SelectTab/Animations/ScreenTabTransitionAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct ScreenTabTransitionAnimation: ScreenTabCustomAnimation { 5 | 6 | public let duration: TimeInterval 7 | public let options: UIView.AnimationOptions 8 | 9 | public init( 10 | duration: TimeInterval, 11 | options: UIView.AnimationOptions 12 | ) { 13 | self.duration = duration 14 | self.options = options 15 | } 16 | 17 | public func animate( 18 | container: UITabBarController, 19 | from selectedTab: UIViewController, 20 | to newSelectedTab: UIViewController, 21 | completion: @escaping () -> Void 22 | ) { 23 | UIView.transition( 24 | from: selectedTab.view, 25 | to: newSelectedTab.view, 26 | duration: duration, 27 | options: options 28 | ) { _ in 29 | completion() 30 | } 31 | } 32 | } 33 | 34 | extension ScreenTabTransitionAnimation { 35 | 36 | public static let crossDissolve = Self( 37 | duration: 0.3, 38 | options: .transitionCrossDissolve 39 | ) 40 | } 41 | 42 | extension ScreenTabAnimation { 43 | 44 | public static let crossDissolve = Self.custom( 45 | ScreenTabTransitionAnimation.crossDissolve 46 | ) 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/Tools/Extensions/UIKit/UITabBarController+Extensions.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | extension UITabBarController { 5 | 6 | /// The container associated with the currently selected tab item. 7 | /// 8 | /// This container is the one whose custom view is currently displayed by the tab bar interface. 9 | /// The specified container must be in the `viewControllers` array. 10 | /// Assigning a new container to this property changes the currently displayed view 11 | /// and also selects an appropriate tab in the tab bar. 12 | /// Changing the container also updates the `selectedIndex` property accordingly. 13 | /// The default value of this property is `nil`. 14 | /// 15 | /// You can use this property to select any of the containers in the `viewControllers` property. 16 | /// This includes containers that are managed by the More navigation controller 17 | /// and whose tab bar items are not visible in the tab bar. 18 | /// You can also use it to select the More navigation controller itself, 19 | /// which is available from the `moreNavigationController` property. 20 | /// 21 | /// - SeeAlso: `selectedViewController` 22 | public var selectedTab: UIViewController? { 23 | get { selectedViewController } 24 | set { selectedViewController = newValue } 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Example/NivelirExample/Screens/Chat/ChatViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Nivelir 3 | 4 | final class ChatViewController: UIViewController, ScreenKeyedContainer { 5 | 6 | let roomID: Int 7 | let chatID: Int 8 | 9 | let screenKey: ScreenKey 10 | let screenNavigator: ScreenNavigator 11 | 12 | init( 13 | roomID: Int, 14 | chatID: Int, 15 | screenKey: ScreenKey, 16 | screenNavigator: ScreenNavigator 17 | ) { 18 | self.roomID = roomID 19 | self.chatID = chatID 20 | 21 | self.screenKey = screenKey 22 | self.screenNavigator = screenNavigator 23 | 24 | super.init(nibName: nil, bundle: nil) 25 | 26 | #if os(iOS) 27 | hidesBottomBarWhenPushed = true 28 | #endif 29 | } 30 | 31 | @available(*, unavailable) 32 | required init?(coder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | override func loadView() { 37 | let chatEmptyView = ChatEmptyView() 38 | 39 | chatEmptyView.title = "Chat \(chatID) in room #\(roomID)" 40 | 41 | view = chatEmptyView 42 | } 43 | } 44 | 45 | extension ChatViewController: ScreenRefreshableContainer { 46 | 47 | func refresh(completion: @escaping () -> Void) { 48 | print("Refreshed") 49 | 50 | completion() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Stack/SetStack/Animations/ScreenStackTransitionAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct ScreenStackTransitionAnimation: ScreenStackCustomAnimation, Equatable, Sendable { 5 | 6 | public let duration: TimeInterval 7 | public let options: UIView.AnimationOptions 8 | 9 | public init( 10 | duration: TimeInterval, 11 | options: UIView.AnimationOptions 12 | ) { 13 | self.duration = duration 14 | self.options = options 15 | } 16 | 17 | public func animate( 18 | container: UINavigationController, 19 | stack: [UIViewController], 20 | completion: @escaping () -> Void 21 | ) { 22 | UIView.transition( 23 | with: container.view, 24 | duration: duration, 25 | options: options, 26 | animations: { }, 27 | completion: { _ in 28 | completion() 29 | } 30 | ) 31 | } 32 | } 33 | 34 | extension ScreenStackTransitionAnimation { 35 | 36 | public static let crossDissolve = Self( 37 | duration: 0.3, 38 | options: .transitionCrossDissolve 39 | ) 40 | } 41 | 42 | extension ScreenStackAnimation { 43 | 44 | public static let crossDissolve = Self.custom( 45 | ScreenStackTransitionAnimation.crossDissolve 46 | ) 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Window/SetRoot/Animations/ScreenRootTransitionAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct ScreenRootTransitionAnimation: ScreenRootCustomAnimation { 5 | 6 | public let duration: TimeInterval 7 | public let options: UIView.AnimationOptions 8 | 9 | public init( 10 | duration: TimeInterval, 11 | options: UIView.AnimationOptions 12 | ) { 13 | self.duration = duration 14 | self.options = options 15 | } 16 | 17 | public func animate( 18 | container: UIWindow, 19 | from root: UIViewController?, 20 | to newRoot: UIViewController, 21 | completion: @escaping () -> Void 22 | ) { 23 | UIView.transition( 24 | with: container, 25 | duration: duration, 26 | options: options, 27 | animations: { }, 28 | completion: { _ in 29 | completion() 30 | } 31 | ) 32 | } 33 | } 34 | 35 | extension ScreenRootTransitionAnimation { 36 | 37 | public static let crossDissolve = Self( 38 | duration: 0.3, 39 | options: .transitionCrossDissolve 40 | ) 41 | } 42 | 43 | extension ScreenRootAnimation { 44 | 45 | public static let crossDissolve = Self.custom( 46 | ScreenRootTransitionAnimation.crossDissolve 47 | ) 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/Screen/Decorators/Modal/ScreenTabBarItemDecorator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// Sets the `tabBarItem` property for a container with type `UIViewController`. 5 | public struct ScreenTabBarItemDecorator: ScreenDecorator { 6 | 7 | public let item: UITabBarItem 8 | 9 | public var payload: Any? { 10 | nil 11 | } 12 | 13 | public let description: String 14 | 15 | public init(item: UITabBarItem) { 16 | self.item = item 17 | description = "TabBarItemDecorator" 18 | } 19 | 20 | public func build( 21 | screen: Wrapped, 22 | navigator: ScreenNavigator 23 | ) -> Container where Wrapped.Container == Container { 24 | let container = screen.build(navigator: navigator) 25 | 26 | container.tabBarItem = item 27 | 28 | return container 29 | } 30 | } 31 | 32 | extension Screen where Container: UIViewController { 33 | 34 | /// Sets the `tabBarItem` property for a container with type `UIViewController`. 35 | /// - Parameter item: `item` to set. 36 | /// - Returns: New `Screen` with `UITabBarItem` set to `tabBarItem`. 37 | @MainActor 38 | public func withTabBarItem(_ item: UITabBarItem) -> AnyScreen { 39 | decorated(by: ScreenTabBarItemDecorator(item: item)) 40 | } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Interaction/Interactions/BottomSheetDismissedInteraction.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | internal final class BottomSheetDismissedInteraction: BottomSheetInteraction { 5 | 6 | internal var gestureInitialValue: CGFloat { 7 | .zero 8 | } 9 | 10 | internal var currentDetentDelta: CGFloat { 11 | .zero 12 | } 13 | 14 | internal var canRecognizeGestures: Bool { 15 | false 16 | } 17 | 18 | internal func start( 19 | presentationController: BottomSheetPresentationController, 20 | gestureRecognizer: UIPanGestureRecognizer, 21 | simultaneousScrollView: UIScrollView? 22 | ) { } 23 | 24 | internal func update( 25 | presentationController: BottomSheetPresentationController, 26 | gestureRecognizer: UIPanGestureRecognizer, 27 | simultaneousScrollView: UIScrollView? 28 | ) { } 29 | 30 | internal func finish( 31 | presentationController: BottomSheetPresentationController, 32 | gestureRecognizer: UIPanGestureRecognizer, 33 | simultaneousScrollView: UIScrollView? 34 | ) { } 35 | 36 | internal func cancel( 37 | presentationController: BottomSheetPresentationController, 38 | gestureRecognizer: UIPanGestureRecognizer, 39 | simultaneousScrollView: UIScrollView? 40 | ) { } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /Example/NivelirExample/Helpers/DI/ServiceContainer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class ServiceContainer { 4 | 5 | private var services: [ServiceKey: ServiceStorage] = [:] 6 | private let lock = NSRecursiveLock() 7 | 8 | func resolve( 9 | name: String = #function, 10 | traits: [AnyHashable] = [], 11 | using factory: () -> Service, 12 | at scope: ServiceScope 13 | ) -> Service { 14 | lock.lock() 15 | 16 | defer { 17 | lock.unlock() 18 | } 19 | 20 | let serviceKey = ServiceKey( 21 | type: Service.self, 22 | name: name, 23 | traits: traits 24 | ) 25 | 26 | if let service = services[serviceKey]?.service.flatMap({ $0 as? Service }) { 27 | return service 28 | } 29 | 30 | let service = factory() 31 | let storage = scope.storage(for: service) 32 | 33 | services[serviceKey] = storage 34 | 35 | return service 36 | } 37 | 38 | func shared( 39 | name: String = #function, 40 | traits: [AnyHashable] = [], 41 | using factory: () -> Service 42 | ) -> Service { 43 | resolve( 44 | name: name, 45 | traits: traits, 46 | using: factory, 47 | at: ServiceSharedScope.default 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Animation/ProgressAnimation.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// The type of progress view animation of the ``HUD``. 5 | /// 6 | /// An animation is used to update and show the `header`, `indicator` and `footer` parts of the progress view. 7 | @MainActor 8 | @frozen 9 | public enum ProgressAnimation { 10 | 11 | /// Custom animation for showing progress view of the HUD. 12 | /// 13 | /// Implement ``ProgressCustomAnimation`` to configure a custom animation 14 | /// and pass an instance to the associated value. 15 | case custom(ProgressCustomAnimation) 16 | 17 | /// Animates updates of parts of the progress view. 18 | /// - Parameters: 19 | /// - view: View of the `header`, `indicator` or `footer` part. 20 | /// - previousView: Previous view of the `header`, `indicator` or `footer` part. 21 | /// - containerView: Container view of the `header`, `indicator` or `footer` part. 22 | public func animateView( 23 | _ view: UIView, 24 | previousView: UIView, 25 | containerView: UIView 26 | ) { 27 | switch self { 28 | case let .custom(animation): 29 | animation.animateView( 30 | view, 31 | previousView: previousView, 32 | containerView: containerView 33 | ) 34 | } 35 | } 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/Addons/BottomSheet/Decorators/ScreenBottomSheetDecorator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | public struct ScreenBottomSheetDecorator: ScreenDecorator { 5 | 6 | private let bottomSheetController: BottomSheetController 7 | 8 | public let bottomSheet: BottomSheet 9 | 10 | public var payload: Any? { 11 | bottomSheetController 12 | } 13 | 14 | public let description: String 15 | 16 | public init(bottomSheet: BottomSheet) { 17 | self.bottomSheet = bottomSheet 18 | self.bottomSheetController = BottomSheetController(bottomSheet: bottomSheet) 19 | description = "BottomSheetDecorator" 20 | } 21 | 22 | public func build( 23 | screen: Wrapped, 24 | navigator: ScreenNavigator 25 | ) -> Container where Wrapped.Container == Container { 26 | let container = screen.build(navigator: navigator) 27 | 28 | container.modalPresentationStyle = .custom 29 | container.transitioningDelegate = bottomSheetController 30 | 31 | return container 32 | } 33 | } 34 | 35 | extension Screen where Container: UIViewController { 36 | 37 | @MainActor 38 | public func withBottomSheet(_ bottomSheet: BottomSheet) -> AnyScreen { 39 | decorated(by: ScreenBottomSheetDecorator(bottomSheet: bottomSheet)) 40 | } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /Sources/Addons/HUD/Progress/Indicator/Image/ProgressImageIndicator.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | 4 | /// A progress indicator displaying the image. 5 | /// 6 | /// Use this object to configure how the image is displayed in the view. 7 | public struct ProgressImageIndicator: ProgressIndicator { 8 | 9 | public typealias View = ProgressImageIndicatorView 10 | 11 | /// The default configuration. 12 | /// - Parameter image: The image displayed in the image view. 13 | public static func `default`(image: UIImage) -> Self { 14 | Self(image: image) 15 | } 16 | 17 | /// The image displayed in the image view. 18 | public let image: UIImage 19 | 20 | /// Additional indentation to expand the container. 21 | public let insets: UIEdgeInsets 22 | 23 | public var logDescription: String? { 24 | ".image" 25 | } 26 | 27 | /// Creates new indicator content. 28 | /// - Parameters: 29 | /// - image: The image displayed in the image view. 30 | /// - insets: Additional indents to expand the container. 31 | public init( 32 | image: UIImage, 33 | insets: UIEdgeInsets = UIEdgeInsets( 34 | top: 20.0, 35 | left: 20.0, 36 | bottom: 20.0, 37 | right: 20.0 38 | ) 39 | ) { 40 | self.image = image 41 | self.insets = insets 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /Scripts/Bootstrap/xcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while [[ "$#" -gt 0 ]]; do 6 | case $1 in 7 | --sudo-password) sudo_password="${2}"; shift ;; 8 | *) echo "Unknown parameter passed: $1"; exit 1 ;; 9 | esac 10 | shift 11 | done 12 | 13 | if [ -n "${sudo_password}" ]; then 14 | echo "${sudo_password}" | sudo -S -E "$0" "$@" 15 | exit $? 16 | fi 17 | 18 | readonly script_path="$( cd "$( dirname "$0" )" && pwd )" 19 | 20 | source "${script_path}/common.sh" 21 | 22 | echo "Checking ${xcode_style}Xcode${default_style} installation:" 23 | 24 | if [[ "$(uname -m)" == "arm64" ]]; then 25 | eval "$(/opt/homebrew/bin/brew shellenv)" 26 | fi 27 | 28 | eval "$(rbenv init -)" 29 | 30 | readonly xcode_required_version=$(cat "${root_path}"/.xcode-version) 31 | readonly xcode_version=($(bundle exec xcversion selected 2> /dev/null | sed -n 's/Xcode \(.*\)/\1/p')) 32 | 33 | if [[ "$xcode_version" == "$xcode_required_version" ]]; then 34 | echo " Required Xcode version ($xcode_required_version) already installed." 35 | else 36 | echo " Required Xcode version ($xcode_required_version) not found. Installing..." 37 | 38 | bundle exec xcversion update 39 | bundle exec xcversion install ${xcode_required_version} 40 | 41 | echo " Selecting Xcode version..." 42 | 43 | bundle exec xcversion select "${xcode_required_version}" 44 | sudo xcodebuild -license accept 45 | fi 46 | 47 | echo "" 48 | -------------------------------------------------------------------------------- /Sources/Screen/Actions/Any/AnyScreenActionBox.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal final class AnyScreenActionBox< 4 | Wrapped: ScreenAction, 5 | Output 6 | >: AnyScreenActionBaseBox { 7 | 8 | internal typealias Mapper = (_ result: Result) -> Result 9 | 10 | private let wrapped: Wrapped 11 | private let mapper: Mapper 12 | 13 | private let _description: String 14 | internal override var description: String { 15 | _description 16 | } 17 | 18 | internal init(_ wrapped: Wrapped, mapper: @escaping Mapper) { 19 | self.wrapped = wrapped 20 | self.mapper = mapper 21 | _description = "\(wrapped)" 22 | } 23 | 24 | internal override func cast(to type: Action.Type) -> Action? { 25 | wrapped.cast(to: type) 26 | } 27 | 28 | internal override func combine( 29 | with other: Action 30 | ) -> AnyScreenAction? { 31 | wrapped.combine(with: other) 32 | } 33 | 34 | internal override func perform( 35 | container: Container, 36 | navigator: ScreenNavigator, 37 | completion: @escaping Completion 38 | ) { 39 | wrapped.perform(container: container, navigator: navigator) { result in 40 | completion(self.mapper(result)) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Deeplink/DeeplinkInterceptor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Interceptor for additional actions before performing navigation for ``Deeplink``. 4 | /// 5 | /// The interceptor can be useful, for example, 6 | /// for sending analytics events or additional API requests when performing ``Deeplink``. 7 | public protocol DeeplinkInterceptor { 8 | 9 | /// Closure with the result of a ``Deeplink`` interception. 10 | typealias Completion = (Result) -> Void 11 | 12 | /// ``Deeplink`` interception. 13 | /// 14 | /// The method is called before every deeplink performance. 15 | /// It is **required** to handle the result of the intercept via the `completion` parameter. 16 | /// A deeplink can be canceled if the interceptor returns an error in `completion`. 17 | /// - Parameters: 18 | /// - deeplink: Erased type of ``Deeplink``. Cast or check the type if needed. 19 | /// - type: ``DeeplinkType`` of the intercepted ``Deeplink``. 20 | /// - navigator: Navigator for performing navigation actions. 21 | /// - completion: Interceptor result. If `.success`, the intercepted ``Deeplink`` will be performed, 22 | /// otherwise cancelled. 23 | @MainActor 24 | func interceptDeeplink( 25 | _ deeplink: AnyDeeplink, 26 | of type: DeeplinkType, 27 | navigator: ScreenNavigator, 28 | completion: @escaping Completion 29 | ) 30 | } 31 | --------------------------------------------------------------------------------